Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg (revision 25115)
+++ ps/trunk/binaries/data/config/default.cfg (revision 25116)
@@ -1,547 +1,548 @@
; 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
; Allows to force GL version for SDL
forceglversion = false
forceglprofile = "compatibility" ; Possible values: compatibility, core, es
forceglmajorversion = 3
forceglminorversion = 3
; Big screenshot tiles
screenshot.tiles = 4
screenshot.tilewidth = 480
screenshot.tileheight = 270
; 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
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
shadowsfixed = false ; When enabled shadows are rendered only on the
shadowsfixeddistance = 300.0 ; fixed distance and without swimming effect.
vsync = false
particles = true
fog = true
silhouettes = true
showsky = true
; Uses a synchonized call to a GL driver to get an error state. Useful
; for a debugging of a system without GL_KHR_debug.
gl.checkerrorafterswap = false
novbo = false
; Disable hardware cursors
nohwcursor = false
; 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
; Use anti-aliasing techniques.
antialiasing = "disabled"
; Use sharpening techniques.
sharpening = "disabled"
sharpness = 0.3
; 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 = 60 ; Throttle FPS in menus only.
[profiler2]
server = "127.0.0.1"
server.port = "8000" ; Use a free port on your machine.
server.threads = "6" ; Enough for the browser's parallel connection limit
[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", "Alt+F4" ; Exit to desktop
cancel = Escape ; Close or cancel the current dialog box/popup
confirm = Return ; Confirm the current command
pause = Pause, "Shift+Space" ; 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
; > 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
timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter
ceasefirecounter.toggle = "" ; Toggle ceasefire counter
; > 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 = "" ; Focus the camera on the rally point of the selected building
zoom.in = Plus, 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]
cancel = Esc ; Un-select all units and cancel building placement
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
idleworker = Period, NumDecimal ; Select next idle worker
idlewarrior = Slash, NumDivide ; Select next idle warrior
idleunit = BackSlash ; Select next idle unit
offscreen = Alt ; Include offscreen units in selection
[hotkey.selection.group.add]
0 = "Shift+0", "Shift+Num0"
1 = "Shift+1", "Shift+Num1"
2 = "Shift+2", "Shift+Num2"
3 = "Shift+3", "Shift+Num3"
4 = "Shift+4", "Shift+Num4"
5 = "Shift+5", "Shift+Num5"
6 = "Shift+6", "Shift+Num6"
7 = "Shift+7", "Shift+Num7"
8 = "Shift+8", "Shift+Num8"
9 = "Shift+9", "Shift+Num9"
[hotkey.selection.group.save]
0 = "Ctrl+0", "Ctrl+Num0"
1 = "Ctrl+1", "Ctrl+Num1"
2 = "Ctrl+2", "Ctrl+Num2"
3 = "Ctrl+3", "Ctrl+Num3"
4 = "Ctrl+4", "Ctrl+Num4"
5 = "Ctrl+5", "Ctrl+Num5"
6 = "Ctrl+6", "Ctrl+Num6"
7 = "Ctrl+7", "Ctrl+Num7"
8 = "Ctrl+8", "Ctrl+Num8"
9 = "Ctrl+9", "Ctrl+Num9"
[hotkey.selection.group.select]
0 = 0, Num0
1 = 1, Num1
2 = 2, Num2
3 = 3, Num3
4 = 4, Num4
5 = 5, Num5
6 = 6, Num6
7 = 7, Num7
8 = 8, Num8
9 = 9, Num9
[hotkey.gamesetup]
mapbrowser.open = "M"
[hotkey.session]
kill = Delete, Backspace ; 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 = "" ; 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
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
pushorderfront = "" ; Modifier to push unit orders to the front 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
snaptoedges = Ctrl ; Modifier to align new structures with nearby existing structure
toggledefaultformation = "" ; Switch between null default formation and the last default formation used (defaults to "box")
; Overlays
showstatusbars = Tab ; Toggle display of status bars
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
highlightguarding = PageDown ; Toggle highlight of guarding units
highlightguarded = PageUp ; Toggle highlight of guarded units
diplomacycolors = "Alt+X" ; Toggle diplomacy colors
toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures
toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures
togglehealrange = "Alt+B" ; Toggle display of heal range overlays of selected units
[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, Backspace ; 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 = "Shift+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.loadingscreen]
progressdescription = false ; Whether to display the progress percent or a textual description
[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)
snaptoedges = "disabled" ; Possible values: disabled, enabled.
snaptoedgesdistancethreshold = 15 ; On which distance we don't snap to edges
disjointcontrolgroups = "true" ; Whether control groups are disjoint sets or entities can be in multiple control groups at the same time.
defaultformation = "special/formations/box" ; For walking orders, automatically put units into this formation if they don't have one already.
formationwalkonly = "true" ; Formations are disabled when giving gather/attack/... orders.
+howtoshownames = 0 ; Whether the specific names are show as default, as opposed to the generic names. And whether the secondary names are shown. (0 - show both; specific names primary, 1 - show both; generic names primary, 2 - show only specific names, 3 - show only generic names)
[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
[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 = "arena25" ; 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 = "wfgbot25" ; Name of the server-side XMPP-account that manage games
echelon = "echelon25" ; 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 = "RWRcbM/EwV7bucTiQVCcRBhCkYkXmJEO7s4ktyufkB+gW/NxHhOZ38xh" ; 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
[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
[rlinterface]
address = "127.0.0.1:6000"
[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/common/tooltips.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/tooltips.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/common/tooltips.js (revision 25116)
@@ -1,1147 +1,1180 @@
var g_TooltipTextFormats = {
"unit": { "font": "sans-10", "color": "orange" },
"header": { "font": "sans-bold-13" },
"body": { "font": "sans-13" },
"comma": { "font": "sans-12" },
- "nameSpecificBig": { "font": "sans-bold-16" },
- "nameSpecificSmall": { "font": "sans-bold-12" },
- "nameGeneric": { "font": "sans-bold-16" }
+ "namePrimaryBig": { "font": "sans-bold-16" },
+ "namePrimarySmall": { "font": "sans-bold-12" },
+ "nameSecondary": { "font": "sans-bold-16" }
};
+var g_SpecificNamesPrimary = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 2;
+var g_ShowSecondaryNames = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 1;
+
+function initDisplayedNames()
+{
+ registerConfigChangeHandler(changes => {
+ if (changes.has("gui.session.howtoshownames"))
+ updateDisplayedNames();
+ });
+}
+
/**
* String of four spaces to be used as indentation in gui strings.
*/
var g_Indent = " ";
var g_DamageTypesMetadata = new DamageTypesMetadata();
var g_StatusEffectsMetadata = new StatusEffectsMetadata();
/**
* If true, always shows whether the splash damage deals friendly fire.
* Otherwise display the friendly fire tooltip only if it does.
*/
var g_AlwaysDisplayFriendlyFire = false;
function getCostTypes()
{
return g_ResourceData.GetCodes().concat(["population", "time"]);
}
function resourceIcon(resource)
{
return '[icon="icon_' + resource + '"]';
}
function resourceNameFirstWord(type)
{
return translateWithContext("firstWord", g_ResourceData.GetNames()[type]);
}
function resourceNameWithinSentence(type)
{
return translateWithContext("withinSentence", g_ResourceData.GetNames()[type]);
}
/**
* Format resource amounts to proper english and translate (for example: "200 food, 100 wood and 300 metal").
*/
function getLocalizedResourceAmounts(resources)
{
let amounts = g_ResourceData.GetCodes()
.filter(type => !!resources[type])
.map(type => sprintf(translate("%(amount)s %(resourceType)s"), {
"amount": resources[type],
"resourceType": resourceNameWithinSentence(type)
}));
if (amounts.length < 2)
return amounts.join();
let lastAmount = amounts.pop();
return sprintf(translate("%(previousAmounts)s and %(lastAmount)s"), {
// Translation: This comma is used for separating first to penultimate elements in an enumeration.
"previousAmounts": amounts.join(translate(", ")),
"lastAmount": lastAmount
});
}
function bodyFont(text)
{
return setStringTags(text, g_TooltipTextFormats.body);
}
function headerFont(text)
{
return setStringTags(text, g_TooltipTextFormats.header);
}
function unitFont(text)
{
return setStringTags(text, g_TooltipTextFormats.unit);
}
function commaFont(text)
{
return setStringTags(text, g_TooltipTextFormats.comma);
}
function getSecondsString(seconds)
{
return sprintf(translatePlural("%(time)s %(second)s", "%(time)s %(second)s", seconds), {
"time": seconds,
"second": unitFont(translatePlural("second", "seconds", seconds))
});
}
/**
* Entity templates have a `Tooltip` tag in the Identity component.
* (The contents of which are copied to a `tooltip` attribute in globalscripts.)
*
* Technologies have a `tooltip` attribute.
*/
function getEntityTooltip(template)
{
if (!template.tooltip)
return "";
return bodyFont(template.tooltip);
}
/**
* Technologies have a `description` attribute, and Auras have an `auraDescription`
* attribute, which becomes `description`.
*
* (For technologies, this happens in globalscripts.)
*
* (For auras, this happens either in the Auras component (for session gui) or
* reference/common/load.js (for Reference Suite gui))
*/
function getDescriptionTooltip(template)
{
if (!template.description)
return "";
return bodyFont(template.description);
}
/**
* Entity templates have a `History` tag in the Identity component.
* (The contents of which are copied to a `history` attribute in globalscripts.)
*/
function getHistoryTooltip(template)
{
if (!template.history)
return "";
return bodyFont(template.history);
}
function getHealthTooltip(template)
{
if (!template.health)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Health:")),
"details": template.health
});
}
function getCurrentHealthTooltip(entState, label)
{
if (!entState.maxHitpoints)
return "";
return sprintf(translate("%(healthLabel)s %(current)s / %(max)s"), {
"healthLabel": headerFont(label || translate("Health:")),
"current": Math.round(entState.hitpoints),
"max": Math.round(entState.maxHitpoints)
});
}
function getCurrentCaptureTooltip(entState, label)
{
if (!entState.maxCapturePoints)
return "";
return sprintf(translate("%(captureLabel)s %(current)s / %(max)s"), {
"captureLabel": headerFont(label || translate("Capture points:")),
"current": Math.round(entState.capturePoints[entState.player]),
"max": Math.round(entState.maxCapturePoints)
});
}
/**
* Converts an resistance level into the actual reduction percentage.
*/
function resistanceLevelToPercentageString(level)
{
return sprintf(translate("%(percentage)s%%"), {
"percentage": (100 - Math.round(Math.pow(0.9, level) * 100))
});
}
function getResistanceTooltip(template)
{
if (!template.resistance)
return "";
let details = [];
if (template.resistance.Damage)
details.push(getDamageResistanceTooltip(template.resistance.Damage));
if (template.resistance.Capture)
details.push(getCaptureResistanceTooltip(template.resistance.Capture));
if (template.resistance.ApplyStatus)
details.push(getStatusEffectsResistanceTooltip(template.resistance.ApplyStatus));
return details.length ? sprintf(translate("%(label)s\n%(details)s"), {
"label": headerFont(translate("Resistance:")),
"details": g_Indent + details.join("\n" + g_Indent)
}) : "";
}
function getDamageResistanceTooltip(resistanceTypeTemplate)
{
if (!resistanceTypeTemplate)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Damage:")),
"details":
g_DamageTypesMetadata.sort(Object.keys(resistanceTypeTemplate)).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s %(resistancePercentage)s"), {
"damage": resistanceTypeTemplate[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType))),
"resistancePercentage":
'[font="sans-10"]' +
sprintf(translate("(%(resistancePercentage)s)"), {
"resistancePercentage": resistanceLevelToPercentageString(resistanceTypeTemplate[dmgType])
}) + '[/font]'
})
).join(commaFont(translate(", ")))
});
}
function getCaptureResistanceTooltip(resistanceTypeTemplate)
{
if (!resistanceTypeTemplate)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Capture:")),
"details":
sprintf(translate("%(damage)s %(damageType)s %(resistancePercentage)s"), {
"damage": resistanceTypeTemplate.toFixed(1),
"damageType": unitFont(translateWithContext("damage type", "Capture")),
"resistancePercentage":
'[font="sans-10"]' +
sprintf(translate("(%(resistancePercentage)s)"), {
"resistancePercentage": resistanceLevelToPercentageString(resistanceTypeTemplate)
}) + '[/font]'
})
});
}
function getStatusEffectsResistanceTooltip(resistanceTypeTemplate)
{
if (!resistanceTypeTemplate)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Status Effects:")),
"details":
Object.keys(resistanceTypeTemplate).map(
statusEffect => {
if (resistanceTypeTemplate[statusEffect].blockChance == 1)
return sprintf(translate("Blocks %(name)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect)))
});
if (resistanceTypeTemplate[statusEffect].blockChance == 0)
return sprintf(translate("%(name)s %(details)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect))),
"details": sprintf(translate("Duration reduction: %(durationReduction)s%%"), {
"durationReduction": (100 - resistanceTypeTemplate[statusEffect].duration * 100)
})
});
if (resistanceTypeTemplate[statusEffect].duration == 1)
return sprintf(translate("%(name)s %(details)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect))),
"details": sprintf(translate("Blocks: %(blockPercentage)s%%"), {
"blockPercentage": resistanceTypeTemplate[statusEffect].blockChance * 100
})
});
return sprintf(translate("%(name)s %(details)s"), {
"name": unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(statusEffect))),
"details": sprintf(translate("Blocks: %(blockPercentage)s%%, Duration reduction: %(durationReduction)s%%"), {
"blockPercentage": resistanceTypeTemplate[statusEffect].blockChance * 100,
"durationReduction": (100 - resistanceTypeTemplate[statusEffect].duration * 100)
})
});
}
).join(commaFont(translate(", ")))
});
}
function attackRateDetails(interval, projectiles)
{
if (!interval)
return "";
if (projectiles === 0)
return translate("Garrison to fire arrows");
let attackRateString = getSecondsString(interval / 1000);
let header = headerFont(translate("Interval:"));
if (projectiles && +projectiles > 1)
{
header = headerFont(translate("Rate:"));
let projectileString = sprintf(translatePlural("%(projectileCount)s %(projectileName)s", "%(projectileCount)s %(projectileName)s", projectiles), {
"projectileCount": projectiles,
"projectileName": unitFont(translatePlural("arrow", "arrows", projectiles))
});
attackRateString = sprintf(translate("%(projectileString)s / %(attackRateString)s"), {
"projectileString": projectileString,
"attackRateString": attackRateString
});
}
return sprintf(translate("%(label)s %(details)s"), {
"label": header,
"details": attackRateString
});
}
function rangeDetails(attackTypeTemplate)
{
if (!attackTypeTemplate.maxRange)
return "";
let rangeTooltipString = {
"relative": {
// Translation: For example: Range: 2 to 10 (+2) meters
"minRange": translate("%(rangeLabel)s %(minRange)s to %(maxRange)s (%(relativeRange)s) %(rangeUnit)s"),
// Translation: For example: Range: 10 (+2) meters
"no-minRange": translate("%(rangeLabel)s %(maxRange)s (%(relativeRange)s) %(rangeUnit)s"),
},
"non-relative": {
// Translation: For example: Range: 2 to 10 meters
"minRange": translate("%(rangeLabel)s %(minRange)s to %(maxRange)s %(rangeUnit)s"),
// Translation: For example: Range: 10 meters
"no-minRange": translate("%(rangeLabel)s %(maxRange)s %(rangeUnit)s"),
}
};
let minRange = Math.round(attackTypeTemplate.minRange);
let maxRange = Math.round(attackTypeTemplate.maxRange);
let realRange = attackTypeTemplate.elevationAdaptedRange;
let relativeRange = realRange ? Math.round(realRange - maxRange) : 0;
return sprintf(rangeTooltipString[relativeRange ? "relative" : "non-relative"][minRange ? "minRange" : "no-minRange"], {
"rangeLabel": headerFont(translate("Range:")),
"minRange": minRange,
"maxRange": maxRange,
"relativeRange": relativeRange > 0 ? sprintf(translate("+%(number)s"), { "number": relativeRange }) : relativeRange,
"rangeUnit":
unitFont(minRange || relativeRange ?
// Translation: For example "0.5 to 1 meters", "1 (+1) meters" or "1 to 2 (+3) meters"
translate("meters") :
translatePlural("meter", "meters", maxRange))
});
}
function damageDetails(damageTemplate)
{
if (!damageTemplate)
return "";
return g_DamageTypesMetadata.sort(Object.keys(damageTemplate).filter(dmgType => damageTemplate[dmgType])).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s"), {
"damage": (+damageTemplate[dmgType]).toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType)))
})).join(commaFont(translate(", ")));
}
function captureDetails(captureTemplate)
{
if (!captureTemplate)
return "";
return sprintf(translate("%(amount)s %(name)s"), {
"amount": (+captureTemplate).toFixed(1),
"name": unitFont(translateWithContext("damage type", "Capture"))
});
}
function splashDetails(splashTemplate)
{
let splashLabel = sprintf(headerFont(translate("%(splashShape)s Splash")), {
"splashShape": splashTemplate.shape
});
let splashDamageTooltip = sprintf(translate("%(label)s: %(effects)s"), {
"label": splashLabel,
"effects": attackEffectsDetails(splashTemplate)
});
if (g_AlwaysDisplayFriendlyFire || splashTemplate.friendlyFire)
splashDamageTooltip += commaFont(translate(", ")) + sprintf(translate("Friendly Fire: %(enabled)s"), {
"enabled": splashTemplate.friendlyFire ? translate("Yes") : translate("No")
});
return splashDamageTooltip;
}
function applyStatusDetails(applyStatusTemplate)
{
if (!applyStatusTemplate)
return "";
return sprintf(translate("gives %(name)s"), {
"name": Object.keys(applyStatusTemplate).map(x =>
unitFont(translateWithContext("status effect", g_StatusEffectsMetadata.getName(x)))
).join(commaFont(translate(", "))),
});
}
function attackEffectsDetails(attackTypeTemplate)
{
if (!attackTypeTemplate)
return "";
let effects = [
captureDetails(attackTypeTemplate.Capture || undefined),
damageDetails(attackTypeTemplate.Damage || undefined),
applyStatusDetails(attackTypeTemplate.ApplyStatus || undefined)
];
return effects.filter(effect => effect).join(commaFont(translate(", ")));
}
function getAttackTooltip(template)
{
if (!template.attack)
return "";
let tooltips = [];
for (let attackType in template.attack)
{
// Slaughter is used to kill animals, so do not show it.
if (attackType == "Slaughter")
continue;
let attackTypeTemplate = template.attack[attackType];
let attackLabel = sprintf(headerFont(translate("%(attackType)s")), {
"attackType": attackTypeTemplate.attackName.context ?
translateWithContext(attackTypeTemplate.attackName.context || "Name of an attack, usually the weapon.", attackTypeTemplate.attackName.name) :
translate(attackTypeTemplate.attackName.name)
});
let projectiles;
// Use either current rate from simulation or default count if the sim is not running.
// TODO: This ought to be extended to include units which fire multiple projectiles.
if (template.buildingAI)
projectiles = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount;
let splashTemplate = attackTypeTemplate.splash;
// Show the effects of status effects below.
let statusEffectsDetails = [];
if (attackTypeTemplate.ApplyStatus)
for (let status in attackTypeTemplate.ApplyStatus)
statusEffectsDetails.push("\n" + g_Indent + g_Indent + getStatusEffectsTooltip(status, attackTypeTemplate.ApplyStatus[status], true));
statusEffectsDetails = statusEffectsDetails.join("");
tooltips.push(sprintf(translate("%(attackLabel)s: %(effects)s, %(range)s, %(rate)s%(statusEffects)s%(splash)s"), {
"attackLabel": attackLabel,
"effects": attackEffectsDetails(attackTypeTemplate),
"range": rangeDetails(attackTypeTemplate),
"rate": attackRateDetails(attackTypeTemplate.repeatTime, projectiles),
"splash": splashTemplate ? "\n" + g_Indent + g_Indent + splashDetails(splashTemplate) : "",
"statusEffects": statusEffectsDetails
}));
}
return sprintf(translate("%(label)s\n%(details)s"), {
"label": headerFont(translate("Attack:")),
"details": g_Indent + tooltips.join("\n" + g_Indent)
});
}
/**
* @param applier - if true, return the tooltip for the Applier. If false, Receiver is returned.
*/
function getStatusEffectsTooltip(statusCode, template, applier)
{
let tooltipAttributes = [];
let statusData = g_StatusEffectsMetadata.getData(statusCode);
if (template.Damage || template.Capture)
tooltipAttributes.push(attackEffectsDetails(template));
if (template.Interval)
tooltipAttributes.push(attackRateDetails(+template.Interval));
if (template.Duration)
tooltipAttributes.push(getStatusEffectDurationTooltip(template));
if (applier && statusData.applierTooltip)
tooltipAttributes.push(translate(statusData.applierTooltip));
else if (!applier && statusData.receiverTooltip)
tooltipAttributes.push(translate(statusData.receiverTooltip));
if (applier)
return sprintf(translate("%(statusName)s: %(statusInfo)s %(stackability)s"), {
"statusName": headerFont(translateWithContext("status effect", statusData.statusName)),
"statusInfo": tooltipAttributes.join(commaFont(translate(", "))),
"stackability": getStatusEffectStackabilityTooltip(template)
});
return sprintf(translate("%(statusName)s: %(statusInfo)s"), {
"statusName": headerFont(translateWithContext("status effect", statusData.statusName)),
"statusInfo": tooltipAttributes.join(commaFont(translate(", ")))
});
}
function getStatusEffectDurationTooltip(template)
{
if (!template.Duration)
return "";
return sprintf(translate("%(durName)s: %(duration)s"), {
"durName": headerFont(translate("Duration")),
"duration": getSecondsString((template._timeElapsed ?
+template.Duration - template._timeElapsed :
+template.Duration) / 1000)
});
}
function getStatusEffectStackabilityTooltip(template)
{
if (!template.Stackability || template.Stackability == "Ignore")
return "";
let stackabilityString = "";
if (template.Stackability === "Extend")
stackabilityString = translateWithContext("status effect stackability", "(extends)");
else if (template.Stackability === "Replace")
stackabilityString = translateWithContext("status effect stackability", "(replaces)");
else if (template.Stackability === "Stack")
stackabilityString = translateWithContext("status effect stackability", "(stacks)");
return sprintf(translate("%(stackability)s"), {
"stackability": stackabilityString
});
}
function getGarrisonTooltip(template)
{
let tooltips = [];
if (template.garrisonHolder)
{
tooltips.push (
sprintf(translate("%(label)s: %(garrisonLimit)s"), {
"label": headerFont(translate("Garrison Limit")),
"garrisonLimit": template.garrisonHolder.capacity
})
);
if (template.garrisonHolder.buffHeal)
tooltips.push(
sprintf(translate("%(healRateLabel)s %(value)s %(health)s / %(second)s"), {
"healRateLabel": headerFont(translate("Heal:")),
"value": Math.round(template.garrisonHolder.buffHeal),
"health": unitFont(translate("Health")),
"second": unitFont(translate("second")),
})
);
tooltips.join(commaFont(translate(", ")));
}
if (template.garrisonable)
{
let extraSize;
if (template.garrisonHolder)
extraSize = template.garrisonHolder.occupiedSlots;
if (template.garrisonable.size > 1 || extraSize)
tooltips.push (
sprintf(translate("%(label)s: %(garrisonSize)s %(extraSize)s"), {
"label": headerFont(translate("Garrison Size")),
"garrisonSize": template.garrisonable.size,
"extraSize": extraSize ?
translateWithContext("nested garrison", "+ ") + extraSize : ""
})
);
}
return tooltips.join("\n");
}
function getProjectilesTooltip(template)
{
if (!template.garrisonHolder || !template.buildingAI)
return "";
let limit = Math.min(
template.buildingAI.maxArrowCount || Infinity,
template.buildingAI.defaultArrowCount +
Math.round(template.buildingAI.garrisonArrowMultiplier *
template.garrisonHolder.capacity)
);
if (!limit)
return "";
return [
sprintf(translate("%(label)s: %(value)s"), {
"label": headerFont(translate("Projectile Limit")),
"value": limit
}),
sprintf(translate("%(label)s: %(value)s"), {
"label": headerFont(translateWithContext("projectiles", "Default")),
"value": template.buildingAI.defaultArrowCount
}),
sprintf(translate("%(label)s: %(value)s"), {
"label": headerFont(translateWithContext("projectiles", "Per Unit")),
"value": +template.buildingAI.garrisonArrowMultiplier.toFixed(2)
})
].join(commaFont(translate(", ")));
}
function getRepairTimeTooltip(entState)
{
let result = [];
result.push(sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Number of repairers:")),
"details": entState.repairable.numBuilders
}));
if (entState.repairable.numBuilders)
{
result.push(sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Remaining repair time:")),
"details": getSecondsString(Math.floor(entState.repairable.buildTime.timeRemaining))
}));
let timeReduction = Math.round(entState.repairable.buildTime.timeRemaining - entState.repairable.buildTime.timeRemainingNew);
result.push(sprintf(translatePlural(
"Add another worker to speed up the repairs by %(second)s second.",
"Add another worker to speed up the repairs by %(second)s seconds.",
timeReduction),
{
"second": timeReduction
}));
}
else
result.push(sprintf(translatePlural(
"Add a worker to finish the repairs in %(second)s second.",
"Add a worker to finish the repairs in %(second)s seconds.",
Math.round(entState.repairable.buildTime.timeRemainingNew)),
{
"second": Math.round(entState.repairable.buildTime.timeRemainingNew)
}));
return result.join("\n");
}
function getBuildTimeTooltip(entState)
{
let result = [];
result.push(sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Number of builders:")),
"details": entState.foundation.numBuilders
}));
if (entState.foundation.numBuilders)
{
result.push(sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Remaining build time:")),
"details": getSecondsString(Math.floor(entState.foundation.buildTime.timeRemaining))
}));
let timeReduction = Math.round(entState.foundation.buildTime.timeRemaining - entState.foundation.buildTime.timeRemainingNew);
result.push(sprintf(translatePlural(
"Add another worker to speed up the construction by %(second)s second.",
"Add another worker to speed up the construction by %(second)s seconds.",
timeReduction),
{
"second": timeReduction
}));
}
else
result.push(sprintf(translatePlural(
"Add a worker to finish the construction in %(second)s second.",
"Add a worker to finish the construction in %(second)s seconds.",
Math.round(entState.foundation.buildTime.timeRemainingNew)),
{
"second": Math.round(entState.foundation.buildTime.timeRemainingNew)
}));
return result.join("\n");
}
/**
* Multiplies the costs for a template by a given batch size.
*/
function multiplyEntityCosts(template, trainNum)
{
let totalCosts = {};
for (let r of getCostTypes())
if (template.cost[r])
totalCosts[r] = Math.floor(template.cost[r] * trainNum);
return totalCosts;
}
/**
* Helper function for getEntityCostTooltip.
*/
function getEntityCostComponentsTooltipString(template, entity, buildingsCountToTrainFullBatch = 1, fullBatchSize = 1, remainderBatch = 0)
{
if (!template.cost)
return [];
let totalCosts = multiplyEntityCosts(template, buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch);
if (template.cost.time)
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {
"entity": entity,
"batchSize": buildingsCountToTrainFullBatch > 0 ? fullBatchSize : remainderBatch
}) : 1));
let costs = [];
for (let type of getCostTypes())
if (totalCosts[type])
costs.push(sprintf(translate("%(component)s %(cost)s"), {
"component": resourceIcon(type),
"cost": totalCosts[type]
}));
return costs;
}
function getGatherTooltip(template)
{
if (!template.resourceGatherRates)
return "";
let rates = {};
for (let resource of g_ResourceData.GetResources())
{
let types = [resource.code];
for (let subtype in resource.subtypes)
{
// We ignore ruins as those are not that common
if (subtype == "ruins")
continue;
let rate = template.resourceGatherRates[resource.code + "." + subtype];
if (rate > 0)
rates[resource.code + "_" + subtype] = rate;
}
}
if (!Object.keys(rates).length)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Gather Rates:")),
"details":
Object.keys(rates).map(
type => sprintf(translate("%(resourceIcon)s %(rate)s"), {
"resourceIcon": resourceIcon(type),
"rate": rates[type].toFixed(2)
})
).join(" ")
});
}
/**
* Returns the resources this entity supplies in the specified entity's tooltip
*/
function getResourceSupplyTooltip(template)
{
if (!template.supply)
return "";
let supply = template.supply;
// Translation: Label in tooltip showing the resource type and quantity of a given resource supply.
return sprintf(translate("%(label)s %(component)s %(amount)s"), {
"label": headerFont(translate("Resource Supply:")),
"component": resourceIcon(supply.type[0]),
// Translation: Marks that a resource supply entity has an unending, infinite, supply of its resource.
"amount": Number.isFinite(+supply.amount) ? supply.amount : translate("∞")
});
}
/**
* @param {Object} template - The entity's template.
* @return {string} - The resources this entity rewards to a collecter.
*/
function getTreasureTooltip(template)
{
if (!template.treasure)
return "";
let resources = {};
for (let resource of g_ResourceData.GetResources())
{
let type = resource.code;
if (template.treasure.resources[type])
resources[type] = template.treasure.resources[type];
}
let resourceNames = Object.keys(resources);
if (!resourceNames.length)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Reward:")),
"details":
resourceNames.map(
type => sprintf(translate("%(resourceIcon)s %(reward)s"), {
"resourceIcon": resourceIcon(type),
"reward": resources[type]
})
).join(" ")
});
}
function getResourceTrickleTooltip(template)
{
if (!template.resourceTrickle)
return "";
let resCodes = g_ResourceData.GetCodes().filter(res => !!template.resourceTrickle.rates[res]);
if (!resCodes.length)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Resource Trickle:")),
"details": sprintf(translate("%(resources)s / %(time)s"), {
"resources":
resCodes.map(
res => sprintf(translate("%(resourceIcon)s %(rate)s"), {
"resourceIcon": resourceIcon(res),
"rate": template.resourceTrickle.rates[res]
})
).join(" "),
"time": getSecondsString(template.resourceTrickle.interval / 1000)
})
});
}
/**
* Returns an array of strings for a set of wall pieces. If the pieces share
* resource type requirements, output will be of the form '10 to 30 Stone',
* otherwise output will be, e.g. '10 Stone, 20 Stone, 30 Stone'.
*/
function getWallPieceTooltip(wallTypes)
{
let out = [];
let resourceCount = {};
for (let resource of getCostTypes())
if (wallTypes[0].cost[resource])
resourceCount[resource] = [wallTypes[0].cost[resource]];
let sameTypes = true;
for (let i = 1; i < wallTypes.length; ++i)
{
for (let resource in wallTypes[i].cost)
// Break out of the same-type mode if this wall requires
// resource types that the first didn't.
if (wallTypes[i].cost[resource] && !resourceCount[resource])
{
sameTypes = false;
break;
}
for (let resource in resourceCount)
if (wallTypes[i].cost[resource])
resourceCount[resource].push(wallTypes[i].cost[resource]);
else
{
sameTypes = false;
break;
}
}
if (sameTypes)
for (let resource in resourceCount)
// Translation: This string is part of the resources cost string on
// the tooltip for wall structures.
out.push(sprintf(translate("%(resourceIcon)s %(minimum)s to %(resourceIcon)s %(maximum)s"), {
"resourceIcon": resourceIcon(resource),
"minimum": Math.min.apply(Math, resourceCount[resource]),
"maximum": Math.max.apply(Math, resourceCount[resource])
}));
else
for (let i = 0; i < wallTypes.length; ++i)
out.push(getEntityCostComponentsTooltipString(wallTypes[i]).join(", "));
return out;
}
/**
* Returns the cost information to display in the specified entity's construction button tooltip.
*/
function getEntityCostTooltip(template, player, entity, buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch)
{
// Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of
// their own; the individual wall pieces within it do.
if (template.wallSet)
{
let templateLong = GetTemplateData(template.wallSet.templates.long, player);
let templateMedium = GetTemplateData(template.wallSet.templates.medium, player);
let templateShort = GetTemplateData(template.wallSet.templates.short, player);
let templateTower = GetTemplateData(template.wallSet.templates.tower, player);
let wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
let towerCosts = getEntityCostComponentsTooltipString(templateTower);
return sprintf(translate("Walls: %(costs)s"), { "costs": wallCosts.join(" ") }) + "\n" +
sprintf(translate("Towers: %(costs)s"), { "costs": towerCosts.join(" ") });
}
if (template.cost)
{
let costs = getEntityCostComponentsTooltipString(template, entity, buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch).join(" ");
if (costs)
// Translation: Label in tooltip showing cost of a unit, structure or technology.
return sprintf(translate("%(label)s %(costs)s"), {
"label": headerFont(translate("Cost:")),
"costs": costs
});
}
return "";
}
function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology, civ)
{
if (technologyEnabled)
return "";
return sprintf(translate("Requires %(technology)s"), {
"technology": getEntityNames(GetTechnologyData(requiredTechnology, civ))
});
}
/**
* Returns the population bonus information to display in the specified entity's construction button tooltip.
*/
function getPopulationBonusTooltip(template)
{
if (!template.population || !template.population.bonus)
return "";
return sprintf(translate("%(label)s %(bonus)s"), {
"label": headerFont(translate("Population Bonus:")),
"bonus": template.population.bonus
});
}
/**
* Returns a message with the amount of each resource needed to create an entity.
*/
function getNeededResourcesTooltip(resources)
{
if (!resources)
return "";
let formatted = [];
for (let resource in resources)
formatted.push(sprintf(translate("%(component)s %(cost)s"), {
"component": '[font="sans-12"]' + resourceIcon(resource) + '[/font]',
"cost": resources[resource]
}));
return coloredText(
'[font="sans-bold-13"]' + translate("Insufficient resources:") + '[/font]',
"red") + " " +
formatted.join(" ");
}
function getSpeedTooltip(template)
{
if (!template.speed)
return "";
let walk = template.speed.walk.toFixed(1);
let run = template.speed.run.toFixed(1);
if (walk == 0 && run == 0)
return "";
return sprintf(translate("%(label)s %(speeds)s"), {
"label": headerFont(translate("Speed:")),
"speeds":
sprintf(translate("%(speed)s %(movementType)s"), {
"speed": walk,
"movementType": unitFont(translate("Walk"))
}) +
commaFont(translate(", ")) +
sprintf(translate("%(speed)s %(movementType)s"), {
"speed": run,
"movementType": unitFont(translate("Run"))
})
});
}
function getHealerTooltip(template)
{
if (!template.heal)
return "";
let health = +(template.heal.health.toFixed(1));
let range = +(template.heal.range.toFixed(0));
let interval = +((template.heal.interval / 1000).toFixed(1));
return [
sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", health), {
"label": headerFont(translate("Heal:")),
"val": health,
"unit": unitFont(translatePlural("Health", "Health", health))
}),
sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", range), {
"label": headerFont(translate("Range:")),
"val": range,
"unit": unitFont(translatePlural("meter", "meters", range))
}),
sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", interval), {
"label": headerFont(translate("Interval:")),
"val": interval,
"unit": unitFont(translatePlural("second", "seconds", interval))
})
].join(translate(", "));
}
function getAurasTooltip(template)
{
let auras = template.auras || template.wallSet && GetTemplateData(template.wallSet.templates.long).auras;
if (!auras)
return "";
let tooltips = [];
for (let auraID in auras)
{
let tooltip = sprintf(translate("%(auralabel)s %(aurainfo)s"), {
"auralabel": headerFont(sprintf(translate("%(auraname)s:"), {
"auraname": getEntityNames(auras[auraID])
})),
"aurainfo": bodyFont(translate(auras[auraID].description))
});
let radius = +auras[auraID].radius;
if (radius)
tooltip += " " + sprintf(translatePlural("%(label)s %(val)s %(unit)s", "%(label)s %(val)s %(unit)s", radius), {
"label": translateWithContext("aura", "Range:"),
"val": radius,
"unit": unitFont(translatePlural("meter", "meters", radius))
});
tooltips.push(tooltip);
}
return tooltips.join("\n");
}
function getEntityNames(template)
{
if (!template.name.specific)
return template.name.generic;
if (template.name.specific == template.name.generic)
return template.name.specific;
- return sprintf(translate("%(specificName)s (%(genericName)s)"), {
- "specificName": template.name.specific,
- "genericName": template.name.generic
+ let primaryName = g_SpecificNamesPrimary ? template.name.specific : template.name.generic;
+ let secondaryName;
+ if (g_ShowSecondaryNames)
+ secondaryName = g_SpecificNamesPrimary ? template.name.generic : template.name.specific;
+
+ if (secondaryName)
+ return sprintf(translate("%(primaryName)s (%(secondaryName)s)"), {
+ "primaryName": primaryName,
+ "secondaryName": secondaryName
+ });
+ return sprintf(translate("%(primaryName)s"), {
+ "primaryName": primaryName
});
}
function getEntityNamesFormatted(template)
{
if (!template.name.specific)
- return setStringTags(template.name.generic, g_TooltipTextFormats.nameSpecificBig);
+ return setStringTags(template.name.generic, g_TooltipTextFormats.namePrimaryBig);
+
+ let primaryName = g_SpecificNamesPrimary ? template.name.specific : template.name.generic;
+ let secondaryName;
+ if (g_ShowSecondaryNames)
+ secondaryName = g_SpecificNamesPrimary ? template.name.generic : template.name.specific;
+
+ if (!secondaryName || primaryName == secondaryName)
+ return sprintf(translate("%(primaryName)s"), {
+ "primaryName":
+ setStringTags(primaryName[0], g_TooltipTextFormats.namePrimaryBig) +
+ setStringTags(primaryName.slice(1).toUpperCase(), g_TooltipTextFormats.namePrimarySmall)
+ });
// Translation: Example: "Epibátēs Athēnaîos [font="sans-bold-16"](Athenian Marine)[/font]"
- return sprintf(translate("%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s"), {
- "specificName": getEntitySpecificNameFormatted(template),
- "genericName": template.name.generic,
- "fontStart": '[font="' + g_TooltipTextFormats.nameGeneric.font + '"]',
- "fontEnd": '[/font]'
+ return sprintf(translate("%(primaryName)s (%(secondaryName)s)"), {
+ "primaryName":
+ setStringTags(primaryName[0], g_TooltipTextFormats.namePrimaryBig) +
+ setStringTags(primaryName.slice(1).toUpperCase(), g_TooltipTextFormats.namePrimarySmall),
+ "secondaryName": setStringTags(secondaryName, g_TooltipTextFormats.nameSecondary)
});
}
-function getEntitySpecificNameFormatted(template)
+function getEntityPrimaryNameFormatted(template)
{
- if (!template.name.specific)
- return setStringTags(template.name.generic, g_TooltipTextFormats.nameSpecificBig);
+ let primaryName = g_SpecificNamesPrimary ? template.name.specific : template.name.generic;
+ if (!primaryName)
+ return setStringTags(g_SpecificNamesPrimary ? template.name.generic : template.name.specific, g_TooltipTextFormats.namePrimaryBig);
- return setStringTags(template.name.specific[0], g_TooltipTextFormats.nameSpecificBig) +
- setStringTags(template.name.specific.slice(1).toUpperCase(), g_TooltipTextFormats.nameSpecificSmall);
+ return setStringTags(primaryName[0], g_TooltipTextFormats.namePrimaryBig) +
+ setStringTags(primaryName.slice(1).toUpperCase(), g_TooltipTextFormats.namePrimarySmall);
}
function getVisibleEntityClassesFormatted(template)
{
if (!template.visibleIdentityClasses || !template.visibleIdentityClasses.length)
return "";
return headerFont(translate("Classes:")) + ' ' +
bodyFont(template.visibleIdentityClasses.map(c => translate(c)).join(translate(", ")));
}
function getLootTooltip(template)
{
if (!template.loot && !template.resourceCarrying)
return "";
let resourcesCarried = [];
if (template.resourceCarrying)
resourcesCarried = calculateCarriedResources(
template.resourceCarrying,
template.trader && template.trader.goods
);
let lootLabels = [];
for (let type of g_ResourceData.GetCodes().concat(["xp"]))
{
let loot =
(template.loot && template.loot[type] || 0) +
(resourcesCarried[type] || 0);
if (!loot)
continue;
// Translation: %(component) will be the icon for the loot type and %(loot) will be the value.
lootLabels.push(sprintf(translate("%(component)s %(loot)s"), {
"component": resourceIcon(type),
"loot": loot
}));
}
if (!lootLabels.length)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Loot:")),
"details": lootLabels.join(" ")
});
}
function getResourceDropsiteTooltip(template)
{
if (!template || !template.resourceDropsite || !template.resourceDropsite.types)
return "";
return sprintf(translate("%(label)s %(icons)s"), {
"label": headerFont(translate("Dropsite for:")),
"icons": template.resourceDropsite.types.map(type => resourceIcon(type)).join(" ")
});
}
function showTemplateViewerOnRightClickTooltip()
{
// Translation: Appears in a tooltip to indicate that right-clicking the corresponding GUI element will open the Template Details GUI page.
return translate("Right-click to view more information.");
}
function showTemplateViewerOnClickTooltip()
{
// Translation: Appears in a tooltip to indicate that clicking the corresponding GUI element will open the Template Details GUI page.
return translate("Click to view more information.");
}
Index: ps/trunk/binaries/data/mods/public/gui/options/options.json
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 25116)
@@ -1,610 +1,638 @@
[
{
"label": "General",
"options":
[
{
"type": "string",
"label": "Player name (single-player)",
"tooltip": "How you want to be addressed in single-player matches.",
"config": "playername.singleplayer"
},
{
"type": "string",
"label": "Player name (multiplayer)",
"tooltip": "How you want to be addressed in multiplayer matches (except lobby).",
"config": "playername.multiplayer"
},
{
"type": "boolean",
"label": "Background pause",
"tooltip": "Pause single-player games when window loses focus.",
"config": "pauseonfocusloss"
},
{
"type": "boolean",
"label": "Enable welcome screen",
"tooltip": "If you disable it, the welcome screen will still appear once, each time a new version is available. You can always launch it from the main menu.",
"config": "gui.splashscreen.enable"
},
{
"type": "boolean",
"label": "Network warnings",
"tooltip": "Show which player has a bad connection in multiplayer games.",
"config": "overlay.netwarnings"
},
{
"type": "boolean",
"label": "FPS overlay",
"tooltip": "Show frames per second in top right corner.",
"config": "overlay.fps"
},
{
"type": "boolean",
"label": "Real time overlay",
"tooltip": "Show current system time in top right corner.",
"config": "overlay.realtime"
},
{
"type": "boolean",
"label": "Game time overlay",
"tooltip": "Show current simulation time in top right corner.",
"config": "gui.session.timeelapsedcounter"
},
{
"type": "boolean",
"label": "Ceasefire time overlay",
"tooltip": "Always show the remaining ceasefire time.",
"config": "gui.session.ceasefirecounter"
},
{
"type": "dropdown",
"label": "Late observer joins",
"tooltip": "Allow everybody or buddies only to join the game as observer after it started.",
"config": "network.lateobservers",
"list": [
{ "value": "everyone", "label": "Everyone" },
{ "value": "buddies", "label": "Buddies" },
{ "value": "disabled", "label": "Disabled" }
]
},
{
"type": "number",
"label": "Observer limit",
"tooltip": "Prevent further observers from joining if the limit is reached.",
"config": "network.observerlimit",
"min": 0,
"max": 32
},
{
"type": "boolean",
"label": "Chat timestamp",
"tooltip": "Display the time at which a chat message was posted.",
"config": "chat.timestamp"
+ },
+ {
+ "type": "dropdown",
+ "label": "Naming of entities.",
+ "tooltip": "How to show entity names.",
+ "config": "gui.session.howtoshownames",
+ "list": [
+ {
+ "value": 0,
+ "label": "Specific primary.",
+ "tooltip": "Show specific names as primary and generic names as secondary."
+ },
+ {
+ "value": 1,
+ "label": "Generic primary.",
+ "tooltip": "Show generic names as primary and specific names as secondary."
+ },
+ {
+ "value": 2,
+ "label": "Only Specific.",
+ "tooltip": "Show only specific names for units."
+ },
+ {
+ "value": 3,
+ "label": "Only Generic.",
+ "tooltip": "Show only generic names for units."
+ }
+ ]
}
]
},
{
"label": "Graphics",
"tooltip": "Set the balance between performance and visual appearance.",
"options":
[
{
"type": "boolean",
"label": "Windowed mode",
"tooltip": "Start 0 A.D. in a window.",
"config": "windowed"
},
{
"type": "boolean",
"label": "Prefer GLSL",
"tooltip": "Use OpenGL 2.0 shaders (recommended).",
"config": "preferglsl"
},
{
"type": "boolean",
"label": "Fog",
"tooltip": "Enable fog.",
"dependencies": ["preferglsl"],
"config": "fog"
},
{
"type": "boolean",
"label": "Post-processing",
"tooltip": "Use screen-space post-processing filters (HDR, Bloom, DOF, etc).",
"config": "postproc"
},
{
"type": "dropdown",
"label": "Antialiasing",
"tooltip": "Reduce aliasing effect on edges.",
"dependencies": ["postproc", "preferglsl"],
"config": "antialiasing",
"list": [
{ "value": "disabled", "label": "Disabled", "tooltip": "Do not use antialiasing." },
{ "value": "fxaa", "label": "FXAA", "tooltip": "Fast, but simple antialiasing." },
{ "value": "msaa2", "label": "MSAA (2×)", "tooltip": "Slow, but high-quality antialiasing, uses two samples per pixel. Supported for GL3.3+." },
{ "value": "msaa4", "label": "MSAA (4×)", "tooltip": "Slow, but high-quality antialiasing, uses four samples per pixel. Supported for GL3.3+." },
{ "value": "msaa8", "label": "MSAA (8×)", "tooltip": "Slow, but high-quality antialiasing, uses eight samples per pixel. Supported for GL3.3+." },
{ "value": "msaa16", "label": "MSAA (16×)", "tooltip": "Slow, but high-quality antialiasing, uses sixteen samples per pixel. Supported for GL3.3+." }
]
},
{
"type": "dropdown",
"label": "Sharpening",
"tooltip": "Reduce blurry effects.",
"dependencies": ["postproc", "preferglsl"],
"config": "sharpening",
"list": [
{ "value": "disabled", "label": "Disabled", "tooltip": "Do not use sharpening." },
{ "value": "cas", "label": "FidelityFX CAS", "tooltip": "Contrast adaptive sharpening, a fast, contrast based sharpening pass." }
]
},
{
"type": "slider",
"label": "Sharpness factor",
"tooltip": "The sharpness of the choosen pass.",
"dependencies": ["postproc", "preferglsl"],
"config": "sharpness",
"min": 0,
"max": 1
},
{
"type": "slider",
"label": "Shader effects",
"tooltip": "Number of shader effects. REQUIRES GAME RESTART",
"config": "materialmgr.quality",
"min": 0,
"max": 10
},
{
"type": "boolean",
"label": "Shadows",
"tooltip": "Enable shadows.",
"config": "shadows"
},
{
"type": "dropdown",
"label": "Shadow quality",
"tooltip": "Shadow map resolution. High values can crash the game when using a graphics card with low memory!",
"dependencies": ["shadows"],
"config": "shadowquality",
"list": [
{ "value": -2, "label": "Very Low" },
{ "value": -1, "label": "Low" },
{ "value": 0, "label": "Medium" },
{ "value": 1, "label": "High" },
{ "value": 2, "label": "Very High" }
]
},
{
"type": "boolean",
"label": "Shadow filtering",
"tooltip": "Smooth shadows.",
"dependencies": ["shadows"],
"config": "shadowpcf"
},
{
"type": "boolean",
"label": "Unit silhouettes",
"tooltip": "Show outlines of units behind structures.",
"config": "silhouettes"
},
{
"type": "boolean",
"label": "Particles",
"tooltip": "Enable particles.",
"config": "particles"
},
{
"type": "boolean",
"label": "Water effects",
"tooltip": "When OFF, use the lowest settings possible to render water. This makes other settings irrelevant.",
"config": "watereffects"
},
{
"type": "boolean",
"label": "High-quality water effects",
"tooltip": "Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails.",
"dependencies": ["watereffects"],
"config": "waterfancyeffects"
},
{
"type": "boolean",
"label": "Water reflections",
"tooltip": "Allow water to reflect a mirror image.",
"dependencies": ["watereffects"],
"config": "waterreflection"
},
{
"type": "boolean",
"label": "Water refraction",
"tooltip": "Use a real water refraction map and not transparency.",
"dependencies": ["watereffects"],
"config": "waterrefraction"
},
{
"type": "boolean",
"label": "Real water depth",
"tooltip": "Use actual water depth in rendering calculations.",
"dependencies": ["watereffects", "waterrefraction"],
"config": "waterrealdepth"
},
{
"type": "boolean",
"label": "Smooth vision",
"tooltip": "Lift darkness and fog of war smoothly.",
"config": "smoothlos"
},
{
"type": "boolean",
"label": "VSync",
"tooltip": "Run vertical sync to fix screen tearing. REQUIRES GAME RESTART",
"config": "vsync"
},
{
"type": "slider",
"label": "FPS throttling in menus",
"tooltip": "To save CPU workload, throttle render frequency in all menus. Set to maximum to disable throttling.",
"config": "adaptivefps.menu",
"min": 20,
"max": 100
},
{
"type": "slider",
"label": "FPS throttling in games",
"tooltip": "To save CPU workload, throttle render frequency in running games. Set to maximum to disable throttling.",
"config": "adaptivefps.session",
"min": 20,
"max": 100
}
]
},
{
"label": "Sound",
"options":
[
{
"type": "slider",
"label": "Master volume",
"tooltip": "Master audio gain.",
"config": "sound.mastergain",
"function": "SetMasterGain",
"min": 0,
"max": 2
},
{
"type": "slider",
"label": "Music volume",
"tooltip": "In game music gain.",
"config": "sound.musicgain",
"function": "SetMusicGain",
"min": 0,
"max": 2
},
{
"type": "slider",
"label": "Ambient volume",
"tooltip": "In game ambient sound gain.",
"config": "sound.ambientgain",
"function": "SetAmbientGain",
"min": 0,
"max": 2
},
{
"type": "slider",
"label": "Action volume",
"tooltip": "In game unit action sound gain.",
"config": "sound.actiongain",
"function": "SetActionGain",
"min": 0,
"max": 2
},
{
"type": "slider",
"label": "UI volume",
"tooltip": "UI sound gain.",
"config": "sound.uigain",
"function": "SetUIGain",
"min": 0,
"max": 2
},
{
"type": "boolean",
"label": "Nick notification",
"tooltip": "Receive audio notification when someone types your nick.",
"config": "sound.notify.nick"
},
{
"type": "boolean",
"label": "New player notification in game setup",
"tooltip": "Receive audio notification when a new client joins the game setup.",
"config": "sound.notify.gamesetup.join"
}
]
},
{
"label": "Game Setup",
"options":
[
{
"type": "boolean",
"label": "Enable game setting tips",
"tooltip": "Show tips when setting up a game.",
"config": "gui.gamesetup.enabletips"
},
{
"type": "boolean",
"label": "Enable settings panel slide",
"tooltip": "Slide the settings panel when opening, closing or resizing.",
"config": "gui.gamesetup.settingsslide"
},
{
"type": "boolean",
"label": "Persist match settings",
"tooltip": "Save and restore match settings for quick reuse when hosting another game.",
"config": "persistmatchsettings"
},
{
"type": "dropdown",
"label": "Default AI difficulty",
"tooltip": "Default difficulty of the AI.",
"config": "gui.gamesetup.aidifficulty",
"list": [
{ "value": 0, "label": "Sandbox" },
{ "value": 1, "label": "Very Easy" },
{ "value": 2, "label": "Easy" },
{ "value": 3, "label": "Medium" },
{ "value": 4, "label": "Hard" },
{ "value": 5, "label": "Very Hard" }
]
},
{
"type": "dropdown",
"label": "Default AI behavior",
"tooltip": "Default behavior of the AI.",
"config": "gui.gamesetup.aibehavior",
"list": [
{ "value": "random", "label": "Random" },
{ "value": "balanced", "label": "Balanced" },
{ "value": "aggressive", "label": "Aggressive" },
{ "value": "defensive", "label": "Defensive" }
]
},
{
"type": "dropdown",
"label": "Assign players",
"tooltip": "Automatically assign joining clients to free player slots during the match setup.",
"config": "gui.gamesetup.assignplayers",
"list": [
{
"value": "everyone",
"label": "Everyone",
"tooltip": "Players joining the match will be assigned if there is a free slot."
},
{
"value": "buddies",
"label": "Buddies",
"tooltip": "Players joining the match will only be assigned if they are a buddy of the host and if there is a free slot."
},
{
"value": "disabled",
"label": "Disabled",
"tooltip": "Players only receive a slot when the host assigns them explicitly."
}
]
}
]
},
{
"label": "Lobby",
"tooltip": "These settings only affect the multiplayer.",
"options":
[
{
"type": "boolean",
"label": "TLS encryption",
"tooltip": "Protect login and data exchanged with the lobby server using TLS encryption.",
"config": "lobby.tls"
},
{
"type": "number",
"label": "Chat backlog",
"tooltip": "Number of backlogged messages to load when joining the lobby.",
"config": "lobby.history",
"min": "0"
},
{
"type": "boolean",
"label": "Game rating column",
"tooltip": "Show the average rating of the participating players in a column of the gamelist.",
"config": "lobby.columns.gamerating"
}
]
},
{
"label": "Game Session",
"tooltip": "Change options regarding the in-game settings.",
"options":
[
{
"type": "slider",
"label": "Wounded unit health",
"tooltip": "The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number.",
"config": "gui.session.woundedunithotkeythreshold",
"min": 0,
"max": 100
},
{
"type": "number",
"label": "Batch training size",
"tooltip": "Number of units trained per batch by default.",
"config": "gui.session.batchtrainingsize",
"min": 1,
"max": 20
},
{
"type": "slider",
"label": "Scroll batch increment ratio",
"tooltip": "Number of times you have to scroll to increase/decrease the batchsize by 1.",
"config": "gui.session.scrollbatchratio",
"min": 0.1,
"max": 30
},
{
"type": "boolean",
"label": "Chat notification attack",
"tooltip": "Show a chat notification if you are attacked by another player.",
"config": "gui.session.notifications.attack"
},
{
"type": "boolean",
"label": "Chat notification tribute",
"tooltip": "Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode.",
"config": "gui.session.notifications.tribute"
},
{
"type": "boolean",
"label": "Chat notification barter",
"tooltip": "Show a chat notification to observers when a player bartered resources.",
"config": "gui.session.notifications.barter"
},
{
"type": "dropdown",
"label": "Chat notification phase",
"tooltip": "Show a chat notification if you or an ally have started, aborted or completed a new phase, and phases of all players in observer mode.",
"config": "gui.session.notifications.phase",
"list": [
{ "value": "none", "label": "Disable" },
{ "value": "completed", "label": "Completed" },
{ "value": "all", "label": "All displayed" }
]
},
{
"type": "boolean",
"label": "Attack range visualization",
"tooltip": "Display the attack range of selected defensive structures. (It can also be toggled with the hotkey during a game).",
"config": "gui.session.attackrange"
},
{
"type": "boolean",
"label": "Aura range visualization",
"tooltip": "Display the range of auras of selected units and structures. (It can also be toggled with the hotkey during a game).",
"config": "gui.session.aurasrange"
},
{
"type": "boolean",
"label": "Heal range visualization",
"tooltip": "Display the healing range of selected units. (It can also be toggled with the hotkey during a game).",
"config": "gui.session.healrange"
},
{
"type": "boolean",
"label": "Rank icon above status bar",
"tooltip": "Show rank icons above status bars.",
"config": "gui.session.rankabovestatusbar"
},
{
"type": "boolean",
"label": "Experience status bar",
"tooltip": "Show an experience status bar above each selected unit.",
"config": "gui.session.experiencestatusbar"
},
{
"type": "boolean",
"label": "Detailed tooltips",
"tooltip": "Show detailed tooltips for trainable units in unit-producing structures.",
"config": "showdetailedtooltips"
},
{
"type": "dropdown",
"label": "Sort resources and population tooltip",
"tooltip": "Dynamically sort players in the resources and population tooltip by value.",
"config": "gui.session.respoptooltipsort",
"list": [
{ "value": 0, "label": "Unordered" },
{ "value": -1, "label": "Ascending" },
{ "value": 1, "label": "Descending" }
]
},
{
"type": "color",
"label": "Diplomacy colors: self",
"tooltip": "Color of your units when diplomacy colors are enabled.",
"config": "gui.session.diplomacycolors.self"
},
{
"type": "color",
"label": "Diplomacy colors: ally",
"tooltip": "Color of allies when diplomacy colors are enabled.",
"config": "gui.session.diplomacycolors.ally"
},
{
"type": "color",
"label": "Diplomacy colors: neutral",
"tooltip": "Color of neutral players when diplomacy colors are enabled.",
"config": "gui.session.diplomacycolors.neutral"
},
{
"type": "color",
"label": "Diplomacy colors: enemy",
"tooltip": "Color of enemies when diplomacy colors are enabled.",
"config": "gui.session.diplomacycolors.enemy"
},
{
"type": "dropdown",
"label": "Snap to edges",
"tooltip": "This option allows to align new structures with nearby structures.",
"config": "gui.session.snaptoedges",
"list": [
{
"value": "disabled",
"label": "Hotkey to enable snapping",
"tooltip": "New structures are aligned with nearby structures while pressing the hotkey."
},
{
"value": "enabled",
"label": "Hotkey to disable snapping",
"tooltip": "New structures are aligned with nearby structures unless the hotkey is pressed."
}
]
},
{
"type": "dropdown",
"label": "Control group membership",
"tooltip": "Decide whether units can be part of multiple control groups.",
"config": "gui.session.disjointcontrolgroups",
"list": [
{
"value": "true",
"label": "Single",
"tooltip": "When adding a Unit or Structure to a control group, they are removed from other control groups. Use this choice if you want control groups to refer to distinct armies."
},
{
"value": "false",
"label": "Multiple",
"tooltip": "Units and Structures can be part of multiple control groups. This is useful to keep control groups for distinct armies and a control group for the entire army simultaneously."
}
]
},
{
"type": "dropdown",
"label": "Formation control",
"tooltip": "Decide whether formations are enabled for all orders or only 'Walk' and 'Patrol'.",
"config": "gui.session.formationwalkonly",
"list": [
{
"value": "true",
"label": "Walk/Patrol Only",
"tooltip": "Other orders will disband existing formations."
},
{
"value": "false",
"label": "No Override",
"tooltip": "Units in formations stay in formations."
}
]
}
]
}
]
Index: ps/trunk/binaries/data/mods/public/gui/reference/catafalque/Emblem.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/catafalque/Emblem.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/reference/catafalque/Emblem.js (revision 25116)
@@ -1,41 +1,41 @@
class Emblem
{
constructor(page, emblemNum)
{
this.page = page;
this.Emblem = Engine.GetGUIObjectByName("emblem[" + emblemNum + "]");
this.EmblemImage = this.Emblem.children[0];
this.EmblemCaption = this.Emblem.children[1];
let size = this.Emblem.size;
this.cx = (size.right - size.left) / 2;
this.cy = (size.bottom - size.top) / 2;
}
setCiv(civCode, civData)
{
let template = this.page.TemplateParser.getEntity(this.CatafalqueTemplateMethod(civCode), civCode);
if (!template)
return false;
this.EmblemImage.sprite = "stretched:" + civData.Emblem;
this.EmblemImage.tooltip = getAurasTooltip(template);
- this.EmblemCaption.caption = getEntitySpecificNameFormatted(template);
+ this.EmblemCaption.caption = getEntityPrimaryNameFormatted(template);
this.Emblem.hidden = false;
return true;
}
setPosition(x, y)
{
let size = this.Emblem.size;
size.left = x - this.cx;
size.right = x + this.cx;
size.top = y - this.cy;
size.bottom = y + this.cy;
this.Emblem.size = size;
}
}
Emblem.prototype.CatafalqueTemplateMethod =
civCode => "units/" + civCode + "/catafalque";
Index: ps/trunk/binaries/data/mods/public/gui/reference/structree/Boxes/EntityBox.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/structree/Boxes/EntityBox.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/reference/structree/Boxes/EntityBox.js (revision 25116)
@@ -1,83 +1,85 @@
/**
* Class inherited by StructureBox and TrainerBox classes.
*/
class EntityBox
{
constructor(page)
{
this.page = page;
}
static setViewerOnPress(guiObject, templateName, civCode)
{
let viewerFunc = () => {
Engine.PushGuiPage("page_viewer.xml", {
"templateName": templateName,
"civ": civCode
});
};
guiObject.onPress = viewerFunc;
guiObject.onPressRight = viewerFunc;
}
draw(templateName, civCode)
{
this.template = this.page.TemplateParser.getEntity(templateName, civCode);
this.gui.hidden = false;
let caption = this.gui.children[0];
- caption.caption = translate(this.template.name.specific);
+ caption.caption = g_SpecificNamesPrimary ?
+ translate(this.template.name.specific) :
+ translate(this.template.name.generic);
let icon = this.gui.children[1];
icon.sprite = "stretched:" + this.page.IconPath + this.template.icon;
icon.tooltip = this.constructor.compileTooltip(this.template);
this.constructor.setViewerOnPress(icon, this.template.name.internal, civCode);
}
captionWidth()
{
// We make the assumption that the caption's padding is equal on both sides
let caption = this.gui.children[0];
return Engine.GetTextWidth(caption.font, caption.caption) + (caption.size.left + caption.buffer_zone) * 2;
}
static compileTooltip(template)
{
return ReferencePage.buildText(template, this.prototype.TooltipFunctions) + "\n" + showTemplateViewerOnClickTooltip();
}
/**
* Returns the height between the top of the EntityBox, and the top of the production rows.
*
* Used within the TreeSection class to position the production rows,
* and used with the PhaseIdent class to position grey bars under them.
*/
static IconAndCaptionHeight()
{
let height = Engine.GetGUIObjectByName("structure[0]_icon").size.bottom + this.prototype.IconPadding;
// Replace function so the above is only run once.
this.IconAndCaptionHeight = () => height;
return height;
}
}
/**
* Minimum width of the boxes, the margins between them (Horizontally and Vertically),
* and the padding between the main icon and the production row(s) beneath it.
*/
EntityBox.prototype.MinWidth = 96;
EntityBox.prototype.HMargin = 8;
EntityBox.prototype.VMargin = 12;
EntityBox.prototype.IconPadding = 8;
/**
* Functions used to collate the contents of a tooltip.
*/
EntityBox.prototype.TooltipFunctions = [
getEntityNamesFormatted,
getEntityCostTooltip,
getEntityTooltip,
getAurasTooltip
].concat(ReferencePage.prototype.StatsFunctions);
Index: ps/trunk/binaries/data/mods/public/gui/reference/structree/Sections/Trainer/TrainerSection.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/structree/Sections/Trainer/TrainerSection.xml (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/reference/structree/Sections/Trainer/TrainerSection.xml (revision 25116)
@@ -1,28 +1,28 @@
Index: ps/trunk/binaries/data/mods/public/gui/reference/structree/Sections/Tree/TreeSection.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/structree/Sections/Tree/TreeSection.xml (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/reference/structree/Sections/Tree/TreeSection.xml (revision 25116)
@@ -1,35 +1,35 @@
-
+
Index: ps/trunk/binaries/data/mods/public/gui/reference/structree/styles.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/structree/styles.xml (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/reference/structree/styles.xml (revision 25116)
@@ -1,39 +1,39 @@
-
Index: ps/trunk/binaries/data/mods/public/gui/reference/viewer/viewer.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/viewer/viewer.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/reference/viewer/viewer.js (revision 25116)
@@ -1,19 +1,19 @@
/**
- * Override style so we can get a bigger specific name.
+ * Override style so we can get a bigger primary name.
*/
-g_TooltipTextFormats.nameSpecificBig.font = "sans-bold-20";
-g_TooltipTextFormats.nameSpecificSmall.font = "sans-bold-16";
-g_TooltipTextFormats.nameGeneric.font = "sans-bold-16";
+g_TooltipTextFormats.namePrimaryBig.font = "sans-bold-20";
+g_TooltipTextFormats.namePrimarySmall.font = "sans-bold-16";
+g_TooltipTextFormats.nameSecondary.font = "sans-bold-16";
/**
* Page initialisation. May also eventually pre-draw/arrange objects.
*
* @param {Object} data - Contains the civCode and the name of the template to display.
* @param {string} data.templateName
* @param {string} [data.civ]
*/
function init(data)
{
g_Page = new ViewerPage();
g_Page.selectTemplate(data);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js (revision 25116)
@@ -1,146 +1,146 @@
/**
* This class sets up a shortcut to a specific entity in the GUI panel.
* The button shows the portrait a tooltip with state information and a health and/or capture bar.
* Clicking the button selects and jumps to to the entity.
*/
class PanelEntity
{
constructor(selection, entityID, buttonID, orderKey)
{
this.selection = selection;
this.hitpoints = undefined;
this.capturePoints = undefined;
/**
* Public property
*/
this.entityID = entityID;
/**
* Public property
*/
this.orderKey = orderKey;
this.overlayName = "panelEntityHitOverlay[" + buttonID + "]";
this.panelEntityHealthBar = Engine.GetGUIObjectByName("panelEntityHealthBar[" + buttonID + "]");
this.panelEntityCaptureBar = Engine.GetGUIObjectByName("panelEntityCapture[" + buttonID + "]");
this.panelEntButton = Engine.GetGUIObjectByName("panelEntityButton[" + buttonID + "]");
this.panelEntButton.onPress = this.onPress.bind(this);
this.panelEntButton.onDoublePress = this.onDoublePress.bind(this);
this.panelEntButton.hidden = false;
let entityState = GetEntityState(entityID);
let template = GetTemplateData(entityState.template);
- this.nameTooltip = setStringTags(template.name.specific, this.NameTags) + "\n";
+ this.nameTooltip = setStringTags(g_SpecificNamesPrimary ? template.name.specific : template.name.generic, this.NameTags) + "\n";
Engine.GetGUIObjectByName("panelEntityHealthSection[" + buttonID + "]").hidden = !entityState.hitpoints;
let captureSection = Engine.GetGUIObjectByName("panelEntityCaptureSection[" + buttonID + "]");
captureSection.hidden = !entityState.capturePoints;
if (entityState.capturePoints)
{
this.capturePoints = entityState.capturePoints;
if (!entityState.hitpoints)
captureSection.size = Engine.GetGUIObjectByName("panelEntitySectionPosTop[" + buttonID + "]").size;
}
Engine.GetGUIObjectByName("panelEntityImage[" + buttonID + "]").sprite =
"stretched:" + this.PortraitDirectory + template.icon;
}
destroy()
{
this.panelEntButton.hidden = true;
stopColorFade(this.overlayName);
}
update(i, reposition)
{
// TODO: Instead of instant position changes, animate button movement.
if (reposition)
setPanelObjectPosition(this.panelEntButton, i, Infinity);
let entityState = GetEntityState(this.entityID);
this.updateHitpointsBar(entityState);
this.updateCapturePointsBar(entityState);
this.panelEntButton.tooltip =
this.nameTooltip +
this.Tooltips.map(tooltip => tooltip(entityState)).filter(tip => tip).join("\n");
}
updateHitpointsBar(entityState)
{
if (!entityState.hitpoints)
return;
if (this.hitpoints != entityState.hitpoints)
{
let size = this.panelEntityHealthBar.size;
size.rright = 100 * entityState.hitpoints / entityState.maxHitpoints;
this.panelEntityHealthBar.size = size;
}
if (entityState.hitpoints < this.hitpoints)
this.onAttacked();
this.hitpoints = entityState.hitpoints;
}
updateCapturePointsBar(entityState)
{
if (!entityState.capturePoints)
return;
let playerParts = this.panelEntityCaptureBar.children;
let setCaptureBarPart = function(player, startSize) {
let captureBar = playerParts[player];
let size = captureBar.size;
size.rleft = startSize;
size.rright = startSize + 100 * Math.max(0, Math.min(1, entityState.capturePoints[player] / entityState.maxCapturePoints));
captureBar.size = size;
captureBar.sprite = "color:" + g_DiplomacyColors.getPlayerColor(player, 128);
captureBar.hidden = false;
return size.rright;
};
let size = setCaptureBarPart(entityState.player, 0);
for (let i in entityState.capturePoints)
if (i != entityState.player)
size = setCaptureBarPart(i, size);
if (entityState.capturePoints[entityState.player] < this.capturePoints[entityState.player])
this.onAttacked();
this.capturePoints = entityState.capturePoints;
}
onAttacked()
{
startColorFade(this.overlayName, 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit);
}
onPress()
{
if (!Engine.HotkeyIsPressed("selection.add"))
this.selection.reset();
this.selection.addList([this.entityID]);
}
onDoublePress()
{
this.selection.selectAndMoveTo(getEntityOrHolder(this.entityID));
}
}
PanelEntity.prototype.NameTags = { "font": "sans-bold-16" };
PanelEntity.prototype.PortraitDirectory = "session/portraits/";
PanelEntity.prototype.Tooltips = [
getCurrentHealthTooltip,
getCurrentCaptureTooltip,
getAttackTooltip,
getResistanceTooltip,
getEntityTooltip,
getAurasTooltip
];
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 25116)
@@ -1,564 +1,570 @@
function layoutSelectionSingle()
{
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
function layoutSelectionMultiple()
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Updates the health bar of garrisoned units
function updateGarrisonHealthBar(entState, selection)
{
if (!entState.garrisonHolder)
return;
// Summing up the Health of every single unit
let totalGarrisonHealth = 0;
let maxGarrisonHealth = 0;
for (let selEnt of selection)
{
let selEntState = GetEntityState(selEnt);
if (selEntState.garrisonHolder)
for (let ent of selEntState.garrisonHolder.entities)
{
let state = GetEntityState(ent);
totalGarrisonHealth += state.hitpoints || 0;
maxGarrisonHealth += state.maxHitpoints || 0;
}
}
// Configuring the health bar
let healthGarrison = Engine.GetGUIObjectByName("healthGarrison");
healthGarrison.hidden = totalGarrisonHealth <= 0;
if (totalGarrisonHealth > 0)
{
let healthBarGarrison = Engine.GetGUIObjectByName("healthBarGarrison");
let healthSize = healthBarGarrison.size;
healthSize.rtop = 100 - 100 * Math.max(0, Math.min(1, totalGarrisonHealth / maxGarrisonHealth));
healthBarGarrison.size = healthSize;
healthGarrison.tooltip = getCurrentHealthTooltip({
"hitpoints": totalGarrisonHealth,
"maxHitpoints": maxGarrisonHealth
});
}
}
// Fills out information that most entities have
function displaySingle(entState)
{
- // Get general unit and player data
let template = GetTemplateData(entState.template);
- let specificName = template.name.specific;
- let genericName = template.name.generic;
- // If packed, add that to the generic name (reduces template clutter)
- if (genericName && template.pack && template.pack.state == "packed")
- genericName = sprintf(translate("%(genericName)s — Packed"), { "genericName": genericName });
+
+ let primaryName = g_SpecificNamesPrimary ? template.name.specific : template.name.generic;
+ let secondaryName;
+ if (g_ShowSecondaryNames)
+ secondaryName = g_SpecificNamesPrimary ? template.name.generic : template.name.specific;
+
+ // If packed, add that to the generic name (reduces template clutter).
+ if (template.pack && template.pack.state == "packed")
+ {
+ if (secondaryName && g_ShowSecondaryNames)
+ secondaryName = sprintf(translate("%(secondaryName)s — Packed"), { "secondaryName": secondaryName });
+ else
+ secondaryName = sprintf(translate("Packed"));
+ }
let playerState = g_Players[entState.player];
let civName = g_CivData[playerState.civ].Name;
let civEmblem = g_CivData[playerState.civ].Emblem;
let playerName = playerState.name;
// Indicate disconnected players by prefixing their name
if (g_Players[entState.player].offline)
playerName = sprintf(translate("\\[OFFLINE] %(player)s"), { "player": playerName });
// Rank
if (entState.identity && entState.identity.rank && entState.identity.classes)
{
Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), {
"rank": translateWithContext("Rank", entState.identity.rank)
});
Engine.GetGUIObjectByName("rankIcon").sprite = "stretched:session/icons/ranks/" + entState.identity.rank + ".png";
Engine.GetGUIObjectByName("rankIcon").hidden = false;
}
else
{
Engine.GetGUIObjectByName("rankIcon").hidden = true;
Engine.GetGUIObjectByName("rankIcon").tooltip = "";
}
if (entState.statusEffects)
{
let statusEffectsSection = Engine.GetGUIObjectByName("statusEffectsIcons");
statusEffectsSection.hidden = false;
let statusIcons = statusEffectsSection.children;
let i = 0;
for (let effectCode in entState.statusEffects)
{
let effect = entState.statusEffects[effectCode];
statusIcons[i].hidden = false;
statusIcons[i].sprite = "stretched:session/icons/status_effects/" + g_StatusEffectsMetadata.getIcon(effect.baseCode) + ".png";
statusIcons[i].tooltip = getStatusEffectsTooltip(effect.baseCode, effect, false);
let size = statusIcons[i].size;
size.top = i * 18;
size.bottom = i * 18 + 16;
statusIcons[i].size = size;
if (++i >= statusIcons.length)
break;
}
for (; i < statusIcons.length; ++i)
statusIcons[i].hidden = true;
}
else
Engine.GetGUIObjectByName("statusEffectsIcons").hidden = true;
let showHealth = entState.hitpoints;
let showResource = entState.resourceSupply;
let showCapture = entState.capturePoints;
let healthSection = Engine.GetGUIObjectByName("healthSection");
let captureSection = Engine.GetGUIObjectByName("captureSection");
let resourceSection = Engine.GetGUIObjectByName("resourceSection");
let sectionPosTop = Engine.GetGUIObjectByName("sectionPosTop");
let sectionPosMiddle = Engine.GetGUIObjectByName("sectionPosMiddle");
let sectionPosBottom = Engine.GetGUIObjectByName("sectionPosBottom");
// Hitpoints
healthSection.hidden = !showHealth;
if (showHealth)
{
let unitHealthBar = Engine.GetGUIObjectByName("healthBar");
let healthSize = unitHealthBar.size;
healthSize.rright = 100 * Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
unitHealthBar.size = healthSize;
Engine.GetGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
"hitpoints": Math.ceil(entState.hitpoints),
"maxHitpoints": Math.ceil(entState.maxHitpoints)
});
healthSection.size = sectionPosTop.size;
captureSection.size = showResource ? sectionPosMiddle.size : sectionPosBottom.size;
resourceSection.size = showResource ? sectionPosBottom.size : sectionPosMiddle.size;
}
else if (showResource)
{
captureSection.size = sectionPosBottom.size;
resourceSection.size = sectionPosTop.size;
}
else if (showCapture)
captureSection.size = sectionPosTop.size;
// CapturePoints
captureSection.hidden = !entState.capturePoints;
if (entState.capturePoints)
{
let setCaptureBarPart = function(playerID, startSize) {
let unitCaptureBar = Engine.GetGUIObjectByName("captureBar[" + playerID + "]");
let sizeObj = unitCaptureBar.size;
sizeObj.rleft = startSize;
let size = 100 * Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints));
sizeObj.rright = startSize + size;
unitCaptureBar.size = sizeObj;
unitCaptureBar.sprite = "color:" + g_DiplomacyColors.getPlayerColor(playerID, 128);
unitCaptureBar.hidden = false;
return startSize + size;
};
// first handle the owner's points, to keep those points on the left for clarity
let size = setCaptureBarPart(entState.player, 0);
for (let i in entState.capturePoints)
if (i != entState.player)
size = setCaptureBarPart(i, size);
let captureText = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
"capturePoints": Math.ceil(entState.capturePoints[entState.player]),
"maxCapturePoints": Math.ceil(entState.maxCapturePoints)
});
let showSmallCapture = showResource && showHealth;
Engine.GetGUIObjectByName("captureStats").caption = showSmallCapture ? "" : captureText;
Engine.GetGUIObjectByName("capture").tooltip = showSmallCapture ? captureText : "";
}
// Experience
Engine.GetGUIObjectByName("experience").hidden = !entState.promotion;
if (entState.promotion)
{
let experienceBar = Engine.GetGUIObjectByName("experienceBar");
let experienceSize = experienceBar.size;
experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / +entState.promotion.req)));
experienceBar.size = experienceSize;
if (entState.promotion.curr < entState.promotion.req)
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
"experience": "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
"current": Math.floor(entState.promotion.curr),
"required": entState.promotion.req
});
else
Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
"experience": "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
"current": Math.floor(entState.promotion.curr)
});
}
// Resource stats
resourceSection.hidden = !showResource;
if (entState.resourceSupply)
{
let resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol
sprintf(translate("%(amount)s / %(max)s"), {
"amount": Math.ceil(+entState.resourceSupply.amount),
"max": entState.resourceSupply.max
});
let unitResourceBar = Engine.GetGUIObjectByName("resourceBar");
let resourceSize = unitResourceBar.size;
resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
unitResourceBar.size = resourceSize;
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), {
"resource": resourceNameFirstWord(entState.resourceSupply.type.generic)
});
Engine.GetGUIObjectByName("resourceStats").caption = resources;
}
let resourceCarryingIcon = Engine.GetGUIObjectByName("resourceCarryingIcon");
let resourceCarryingText = Engine.GetGUIObjectByName("resourceCarryingText");
resourceCarryingIcon.hidden = false;
resourceCarryingText.hidden = false;
// Resource carrying
if (entState.resourceCarrying && entState.resourceCarrying.length)
{
// We should only be carrying one resource type at once, so just display the first
let carried = entState.resourceCarrying[0];
resourceCarryingIcon.sprite = "stretched:session/icons/resources/" + carried.type + ".png";
resourceCarryingText.caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
resourceCarryingIcon.tooltip = "";
}
// Use the same indicators for traders
else if (entState.trader && entState.trader.goods.amount)
{
resourceCarryingIcon.sprite = "stretched:session/icons/resources/" + entState.trader.goods.type + ".png";
let totalGain = entState.trader.goods.amount.traderGain;
if (entState.trader.goods.amount.market1Gain)
totalGain += entState.trader.goods.amount.market1Gain;
if (entState.trader.goods.amount.market2Gain)
totalGain += entState.trader.goods.amount.market2Gain;
resourceCarryingText.caption = totalGain;
resourceCarryingIcon.tooltip = sprintf(translate("Gain: %(gain)s"), {
"gain": getTradingTooltip(entState.trader.goods.amount)
});
}
// And for number of workers
else if (entState.foundation)
{
resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
resourceCarryingIcon.tooltip = getBuildTimeTooltip(entState);
resourceCarryingText.caption = entState.foundation.numBuilders ? sprintf(translate("(%(number)s)\n%(time)s"), {
"number": entState.foundation.numBuilders,
"time": Engine.FormatMillisecondsIntoDateStringGMT(entState.foundation.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss"))
}) : "";
}
else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
{
resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
resourceCarryingText.caption = sprintf(translate("%(amount)s / %(max)s"), {
"amount": entState.resourceSupply.numGatherers,
"max": entState.resourceSupply.maxGatherers
});
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
}
else if (entState.repairable && entState.needsRepair)
{
resourceCarryingIcon.sprite = "stretched:session/icons/repair.png";
resourceCarryingIcon.tooltip = getRepairTimeTooltip(entState);
resourceCarryingText.caption = entState.repairable.numBuilders ? sprintf(translate("(%(number)s)\n%(time)s"), {
"number": entState.repairable.numBuilders,
"time": Engine.FormatMillisecondsIntoDateStringGMT(entState.repairable.buildTime.timeRemaining * 1000, translateWithContext("countdown format", "m:ss"))
}) : "";
}
else
{
resourceCarryingIcon.hidden = true;
resourceCarryingText.hidden = true;
}
- Engine.GetGUIObjectByName("specific").caption = specificName;
Engine.GetGUIObjectByName("player").caption = playerName;
Engine.GetGUIObjectByName("playerColorBackground").sprite =
"color:" + g_DiplomacyColors.getPlayerColor(entState.player, 128);
- Engine.GetGUIObjectByName("generic").caption = genericName == specificName ? "" :
- sprintf(translate("(%(genericName)s)"), {
- "genericName": genericName
+ Engine.GetGUIObjectByName("primary").caption = primaryName;
+ Engine.GetGUIObjectByName("secondary").caption = !secondaryName || primaryName == secondaryName ? "" :
+ sprintf(translate("(%(secondaryName)s)"), {
+ "secondaryName": secondaryName
});
let isGaia = playerState.civ == "gaia";
Engine.GetGUIObjectByName("playerCivIcon").sprite = isGaia ? "" : "stretched:grayscale:" + civEmblem;
Engine.GetGUIObjectByName("player").tooltip = isGaia ? "" : civName;
// TODO: we should require all entities to have icons
Engine.GetGUIObjectByName("icon").sprite = template.icon ? ("stretched:session/portraits/" + template.icon) : "BackgroundBlack";
if (template.icon)
Engine.GetGUIObjectByName("iconBorder").onPressRight = () => {
showTemplateDetails(entState.template, playerState.civ);
};
let detailedTooltip = [
getAttackTooltip,
getHealerTooltip,
getResistanceTooltip,
getGatherTooltip,
getSpeedTooltip,
getGarrisonTooltip,
getPopulationBonusTooltip,
getProjectilesTooltip,
getResourceTrickleTooltip,
getLootTooltip
].map(func => func(entState)).filter(tip => tip).join("\n");
if (detailedTooltip)
{
Engine.GetGUIObjectByName("attackAndResistanceStats").hidden = false;
Engine.GetGUIObjectByName("attackAndResistanceStats").tooltip = detailedTooltip;
}
else
Engine.GetGUIObjectByName("attackAndResistanceStats").hidden = true;
let iconTooltips = [];
- if (genericName)
- iconTooltips.push("[font=\"sans-bold-16\"]" + genericName + "[/font]");
-
+ iconTooltips.push(setStringTags(primaryName, g_TooltipTextFormats.namePrimaryBig));
iconTooltips = iconTooltips.concat([
getVisibleEntityClassesFormatted,
getAurasTooltip,
getEntityTooltip,
getTreasureTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template)));
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltips.filter(tip => tip).join("\n");
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
// Fills out information for multiple entities
function displayMultiple(entStates)
{
let averageHealth = 0;
let maxHealth = 0;
let maxCapturePoints = 0;
let capturePoints = (new Array(g_MaxPlayers + 1)).fill(0);
let playerID = 0;
let totalCarrying = {};
let totalLoot = {};
let garrisonSize = 0;
for (let entState of entStates)
{
playerID = entState.player; // trust that all selected entities have the same owner
if (entState.hitpoints)
{
averageHealth += entState.hitpoints;
maxHealth += entState.maxHitpoints;
}
if (entState.capturePoints)
{
maxCapturePoints += entState.maxCapturePoints;
capturePoints = entState.capturePoints.map((v, i) => v + capturePoints[i]);
}
let carrying = calculateCarriedResources(
entState.resourceCarrying || null,
entState.trader && entState.trader.goods
);
if (entState.loot)
for (let type in entState.loot)
totalLoot[type] = (totalLoot[type] || 0) + entState.loot[type];
for (let type in carrying)
{
totalCarrying[type] = (totalCarrying[type] || 0) + carrying[type];
totalLoot[type] = (totalLoot[type] || 0) + carrying[type];
}
if (entState.garrisonable)
garrisonSize += entState.garrisonable.size;
if (entState.garrisonHolder)
garrisonSize += entState.garrisonHolder.occupiedSlots;
}
Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
if (averageHealth > 0)
{
let unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple");
let healthSize = unitHealthBar.size;
healthSize.rtop = 100 - 100 * Math.max(0, Math.min(1, averageHealth / maxHealth));
unitHealthBar.size = healthSize;
Engine.GetGUIObjectByName("healthMultiple").tooltip = getCurrentHealthTooltip({
"hitpoints": averageHealth,
"maxHitpoints": maxHealth
});
}
Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0;
if (maxCapturePoints > 0)
{
let setCaptureBarPart = function(pID, startSize)
{
let unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple[" + pID + "]");
let sizeObj = unitCaptureBar.size;
sizeObj.rtop = startSize;
let size = 100 * Math.max(0, Math.min(1, capturePoints[pID] / maxCapturePoints));
sizeObj.rbottom = startSize + size;
unitCaptureBar.size = sizeObj;
unitCaptureBar.sprite = "color:" + g_DiplomacyColors.getPlayerColor(pID, 128);
unitCaptureBar.hidden = false;
return startSize + size;
};
let size = 0;
for (let i in capturePoints)
if (i != playerID)
size = setCaptureBarPart(i, size);
// last handle the owner's points, to keep those points on the bottom for clarity
setCaptureBarPart(playerID, size);
Engine.GetGUIObjectByName("captureMultiple").tooltip = getCurrentHealthTooltip(
{
"hitpoints": capturePoints[playerID],
"maxHitpoints": maxCapturePoints
},
translate("Capture Points:"));
}
let numberOfUnits = Engine.GetGUIObjectByName("numberOfUnits");
numberOfUnits.caption = entStates.length;
numberOfUnits.tooltip = "";
if (garrisonSize)
numberOfUnits.tooltip = sprintf(translate("%(label)s: %(details)s\n"), {
"label": headerFont(translate("Garrison Size")),
"details": bodyFont(garrisonSize)
});
if (Object.keys(totalCarrying).length)
numberOfUnits.tooltip = sprintf(translate("%(label)s %(details)s\n"), {
"label": headerFont(translate("Carrying:")),
"details": bodyFont(Object.keys(totalCarrying).filter(
res => totalCarrying[res] != 0).map(
res => sprintf(translate("%(type)s %(amount)s"),
{ "type": resourceIcon(res), "amount": totalCarrying[res] })).join(" "))
});
if (Object.keys(totalLoot).length)
numberOfUnits.tooltip += sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Loot:")),
"details": bodyFont(Object.keys(totalLoot).filter(
res => totalLoot[res] != 0).map(
res => sprintf(translate("%(type)s %(amount)s"),
{ "type": resourceIcon(res), "amount": totalLoot[res] })).join(" "))
});
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Updates middle entity Selection Details Panel and left Unit Commands Panel
function updateSelectionDetails()
{
let supplementalDetailsPanel = Engine.GetGUIObjectByName("supplementalSelectionDetails");
let detailsPanel = Engine.GetGUIObjectByName("selectionDetails");
let commandsPanel = Engine.GetGUIObjectByName("unitCommands");
let entStates = [];
for (let sel of g_Selection.toList())
{
let entState = GetEntityState(sel);
if (!entState)
continue;
entStates.push(entState);
}
if (entStates.length == 0)
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
hideUnitCommands();
supplementalDetailsPanel.hidden = true;
detailsPanel.hidden = true;
commandsPanel.hidden = true;
return;
}
// Fill out general info and display it
if (entStates.length == 1)
displaySingle(entStates[0]);
else
displayMultiple(entStates);
// Show basic details.
detailsPanel.hidden = false;
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entStates, supplementalDetailsPanel, commandsPanel);
// Show health bar for garrisoned units if the garrison panel is visible
if (Engine.GetGUIObjectByName("unitGarrisonPanel") && !Engine.GetGUIObjectByName("unitGarrisonPanel").hidden)
updateGarrisonHealthBar(entStates[0], g_Selection.toList());
}
function tradingGainString(gain, owner)
{
// Translation: Used in the trading gain tooltip
return sprintf(translate("%(gain)s (%(player)s)"), {
"gain": gain,
"player": GetSimState().players[owner].name
});
}
/**
* Returns a message with the details of the trade gain.
*/
function getTradingTooltip(gain)
{
if (!gain)
return "";
let markets = [
{ "gain": gain.market1Gain, "owner": gain.market1Owner },
{ "gain": gain.market2Gain, "owner": gain.market2Owner }
];
let primaryGain = gain.traderGain;
for (let market of markets)
if (market.gain && market.owner == gain.traderOwner)
// Translation: Used in the trading gain tooltip to concatenate profits of different players
primaryGain += translate("+") + market.gain;
let tooltip = tradingGainString(primaryGain, gain.traderOwner);
for (let market of markets)
if (market.gain && market.owner != gain.traderOwner)
tooltip +=
translateWithContext("Separation mark in an enumeration", ", ") +
tradingGainString(market.gain, market.owner);
return tooltip;
}
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js (revision 25116)
@@ -1,1206 +1,1228 @@
/**
* Contains the layout and button settings per selection panel
*
* getItems returns a list of basic items used to fill the panel.
* This method is obligated. If the items list is empty, the panel
* won't be rendered.
*
* Then there's a loop over all items provided. In the loop,
* the item and some other standard data is added to a data object.
*
* The standard data is
* {
* "i": index
* "item": item coming from the getItems function
* "playerState": playerState
* "unitEntStates": states of the selected entities
* "rowLength": rowLength
* "numberOfItems": number of items that will be processed
* "button": gui Button object
* "icon": gui Icon object
* "guiSelection": gui button Selection overlay
* "countDisplay": gui caption space
* }
*
* Then for every data object, the setupButton function is called which
* sets the view and handlers of the button.
*/
// Cache some formation info
// Available formations per player
var g_AvailableFormations = new Map();
var g_FormationsInfo = new Map();
var g_SelectionPanels = {};
var g_SelectionPanelBarterButtonManager;
g_SelectionPanels.Alert = {
"getMaxNumberOfItems": function()
{
return 2;
},
"getItems": function(unitEntStates)
{
return unitEntStates.some(state => !!state.alertRaiser) ? ["raise", "end"] : [];
},
"setupButton": function(data)
{
data.button.onPress = function() {
switch (data.item)
{
case "raise":
raiseAlert();
return;
case "end":
endOfAlert();
return;
}
};
switch (data.item)
{
case "raise":
data.icon.sprite = "stretched:session/icons/bell_level1.png";
data.button.tooltip = translate("Raise an alert!");
break;
case "end":
data.button.tooltip = translate("End of alert.");
data.icon.sprite = "stretched:session/icons/bell_level0.png";
break;
}
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, this.getMaxNumberOfItems() - data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Barter = {
"getMaxNumberOfItems": function()
{
return 5;
},
"rowLength": 5,
"conflictsWith": ["Garrison"],
"getItems": function(unitEntStates)
{
// If more than `rowLength` resources, don't display icons.
if (unitEntStates.every(state => !state.isBarterMarket) || g_ResourceData.GetBarterableCodes().length > this.rowLength)
return [];
return g_ResourceData.GetBarterableCodes();
},
"setupButton": function(data)
{
if (g_SelectionPanelBarterButtonManager)
{
g_SelectionPanelBarterButtonManager.setViewedPlayer(data.player);
g_SelectionPanelBarterButtonManager.update();
}
return true;
}
};
g_SelectionPanels.Command = {
"getMaxNumberOfItems": function()
{
return 6;
},
"getItems": function(unitEntStates)
{
let commands = [];
for (let command in g_EntityCommands)
{
let info = getCommandInfo(command, unitEntStates);
if (info)
{
info.name = command;
commands.push(info);
}
}
return commands;
},
"setupButton": function(data)
{
data.button.tooltip = data.item.tooltip;
data.button.onPress = function() {
if (data.item.callback)
data.item.callback(data.item);
else
performCommand(data.unitEntStates, data.item.name);
};
data.countDisplay.caption = data.item.count || "";
data.button.enabled = data.item.enabled == true;
data.icon.sprite = "stretched:session/icons/" + data.item.icon;
let size = data.button.size;
// relative to the center ( = 50%)
size.rleft = 50;
size.rright = 50;
// offset from the center calculation, count on square buttons, so size.bottom is the width too
size.left = (data.i - data.numberOfItems / 2) * (size.bottom + 1);
size.right = size.left + size.bottom;
data.button.size = size;
return true;
}
};
g_SelectionPanels.Construction = {
"getMaxNumberOfItems": function()
{
return 40 - getNumberOfRightPanelButtons();
},
"rowLength": 10,
"getItems": function()
{
return getAllBuildableEntitiesFromSelection();
},
"setupButton": function(data)
{
let template = GetTemplateData(data.item, data.player);
if (!template)
return false;
let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": template.requiredTechnology,
"player": data.player
});
let neededResources;
if (template.cost)
neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(template, 1),
"player": data.player
});
data.button.onPress = function() { startBuildingPlacement(data.item, data.playerState); };
let showTemplateFunc = () => { showTemplateDetails(data.item, data.playerState.civ); };
data.button.onPressRight = showTemplateFunc;
data.button.onPressRightDisabled = showTemplateFunc;
let tooltips = [
getEntityNamesFormatted,
getVisibleEntityClassesFormatted,
getAurasTooltip,
getEntityTooltip
].map(func => func(template));
tooltips.push(
getEntityCostTooltip(template, data.player),
getResourceDropsiteTooltip(template),
getGarrisonTooltip(template),
getPopulationBonusTooltip(template),
showTemplateViewerOnRightClickTooltip(template)
);
let limits = getEntityLimitAndCount(data.playerState, data.item);
tooltips.push(
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
let modifier = "";
if (!technologyEnabled || limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
}
else if (neededResources)
{
data.button.enabled = false;
modifier += resourcesToAlphaMask(neededResources) + ":";
}
else
data.button.enabled = controlsPlayer(data.player);
if (template.icon)
data.icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
g_SelectionPanels.Formation = {
"getMaxNumberOfItems": function()
{
return 15;
},
"rowLength": 5,
"conflictsWith": ["Garrison"],
"getItems": function(unitEntStates)
{
if (unitEntStates.some(state => !hasClass(state, "Unit")))
return [];
if (unitEntStates.every(state => !state.identity || !state.identity.hasSomeFormation))
return [];
if (!g_AvailableFormations.has(unitEntStates[0].player))
g_AvailableFormations.set(unitEntStates[0].player, Engine.GuiInterfaceCall("GetAvailableFormations", unitEntStates[0].player));
return g_AvailableFormations.get(unitEntStates[0].player).filter(formation => unitEntStates.some(state => !!state.identity && state.identity.formations.indexOf(formation) != -1));
},
"setupButton": function(data)
{
if (!g_FormationsInfo.has(data.item))
g_FormationsInfo.set(data.item, Engine.GuiInterfaceCall("GetFormationInfoFromTemplate", { "templateName": data.item }));
let formationOk = canMoveSelectionIntoFormation(data.item);
let unitIds = data.unitEntStates.map(state => state.id);
let formationSelected = Engine.GuiInterfaceCall("IsFormationSelected", {
"ents": unitIds,
"formationTemplate": data.item
});
data.button.onPress = function() {
performFormation(unitIds, data.item);
};
data.button.onMouseRightPress = () => g_AutoFormation.setDefault(data.item);
let formationInfo = g_FormationsInfo.get(data.item);
let tooltip = translate(formationInfo.name);
let isDefaultFormation = g_AutoFormation.isDefault(data.item);
if (data.item === NULL_FORMATION)
tooltip += "\n" + (isDefaultFormation ?
translate("Default formation is disabled.") :
translate("Right-click to disable the default formation feature."));
else
tooltip += "\n" + (isDefaultFormation ?
translate("This is the default formation, used for movement orders.") :
translate("Right-click to set this as the default formation."));
if (!formationOk && formationInfo.tooltip)
tooltip += "\n" + coloredText(translate(formationInfo.tooltip), "red");
data.button.tooltip = tooltip;
data.button.enabled = formationOk && controlsPlayer(data.player);
let grayscale = formationOk ? "" : "grayscale:";
data.guiSelection.hidden = !formationSelected;
data.countDisplay.hidden = !isDefaultFormation;
data.icon.sprite = "stretched:" + grayscale + "session/icons/" + formationInfo.icon;
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Garrison = {
"getMaxNumberOfItems": function()
{
return 12;
},
"rowLength": 4,
"conflictsWith": ["Barter"],
"getItems": function(unitEntStates)
{
if (unitEntStates.every(state => !state.garrisonHolder))
return [];
let groups = new EntityGroups();
for (let state of unitEntStates)
if (state.garrisonHolder)
groups.add(state.garrisonHolder.entities);
return groups.getEntsGrouped();
},
"setupButton": function(data)
{
let entState = GetEntityState(data.item.ents[0]);
let template = GetTemplateData(entState.template);
if (!template)
return false;
data.button.onPress = function() {
unloadTemplate(template.selectionGroupName || entState.template, entState.player);
};
data.countDisplay.caption = data.item.ents.length || "";
let canUngarrison = controlsPlayer(data.player) || controlsPlayer(entState.player);
data.button.enabled = canUngarrison;
data.button.tooltip = (canUngarrison ?
sprintf(translate("Unload %(name)s"), { "name": getEntityNames(template) }) + "\n" +
translate("Single-click to unload 1. Shift-click to unload all of this type.") :
getEntityNames(template)) + "\n" +
sprintf(translate("Player: %(playername)s"), {
"playername": g_Players[entState.player].name
});
data.guiSelection.sprite = "color:" + g_DiplomacyColors.getPlayerColor(entState.player, 160);
data.button.sprite_disabled = data.button.sprite;
// Selection panel buttons only appear disabled if they
// also appear disabled to the owner of the structure.
data.icon.sprite =
(canUngarrison || g_IsObserver ? "" : "grayscale:") +
"stretched:session/portraits/" + template.icon;
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Gate = {
"getMaxNumberOfItems": function()
{
return 40 - getNumberOfRightPanelButtons();
},
"rowLength": 10,
"getItems": function(unitEntStates)
{
let hideLocked = unitEntStates.every(state => !state.gate || !state.gate.locked);
let hideUnlocked = unitEntStates.every(state => !state.gate || state.gate.locked);
if (hideLocked && hideUnlocked)
return [];
return [
{
"hidden": hideLocked,
"tooltip": translate("Lock Gate"),
"icon": "session/icons/lock_locked.png",
"locked": true
},
{
"hidden": hideUnlocked,
"tooltip": translate("Unlock Gate"),
"icon": "session/icons/lock_unlocked.png",
"locked": false
}
];
},
"setupButton": function(data)
{
data.button.onPress = function() { lockGate(data.item.locked); };
data.button.tooltip = data.item.tooltip;
data.button.enabled = controlsPlayer(data.player);
data.guiSelection.hidden = data.item.hidden;
data.icon.sprite = "stretched:" + data.item.icon;
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
g_SelectionPanels.Pack = {
"getMaxNumberOfItems": function()
{
return 40 - getNumberOfRightPanelButtons();
},
"rowLength": 10,
"getItems": function(unitEntStates)
{
let checks = {};
for (let state of unitEntStates)
{
if (!state.pack)
continue;
if (state.pack.progress == 0)
{
if (state.pack.packed)
checks.unpackButton = true;
else
checks.packButton = true;
}
else if (state.pack.packed)
checks.unpackCancelButton = true;
else
checks.packCancelButton = true;
}
let items = [];
if (checks.packButton)
items.push({
"packing": false,
"packed": false,
"tooltip": translate("Pack"),
"callback": function() { packUnit(true); }
});
if (checks.unpackButton)
items.push({
"packing": false,
"packed": true,
"tooltip": translate("Unpack"),
"callback": function() { packUnit(false); }
});
if (checks.packCancelButton)
items.push({
"packing": true,
"packed": false,
"tooltip": translate("Cancel Packing"),
"callback": function() { cancelPackUnit(true); }
});
if (checks.unpackCancelButton)
items.push({
"packing": true,
"packed": true,
"tooltip": translate("Cancel Unpacking"),
"callback": function() { cancelPackUnit(false); }
});
return items;
},
"setupButton": function(data)
{
data.button.onPress = function() {data.item.callback(data.item); };
data.button.tooltip = data.item.tooltip;
if (data.item.packing)
data.icon.sprite = "stretched:session/icons/cancel.png";
else if (data.item.packed)
data.icon.sprite = "stretched:session/icons/unpack.png";
else
data.icon.sprite = "stretched:session/icons/pack.png";
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
g_SelectionPanels.Queue = {
"getMaxNumberOfItems": function()
{
return 16;
},
/**
* Returns a list of all items in the productionqueue of the selection
* The first entry of every entity's production queue will come before
* the second entry of every entity's production queue
*/
"getItems": function(unitEntStates)
{
let queue = [];
let foundNew = true;
for (let i = 0; foundNew; ++i)
{
foundNew = false;
for (let state of unitEntStates)
{
if (!state.production || !state.production.queue[i])
continue;
queue.push({
"producingEnt": state.id,
"queuedItem": state.production.queue[i]
});
foundNew = true;
}
}
return queue;
},
"resizePanel": function(numberOfItems, rowLength)
{
let numRows = Math.ceil(numberOfItems / rowLength);
let panel = Engine.GetGUIObjectByName("unitQueuePanel");
let size = panel.size;
let buttonSize = Engine.GetGUIObjectByName("unitQueueButton[0]").size.bottom;
let margin = 4;
size.top = size.bottom - numRows * buttonSize - (numRows + 2) * margin;
panel.size = size;
},
"setupButton": function(data)
{
let queuedItem = data.item.queuedItem;
// Differentiate between units and techs
let template;
if (queuedItem.unitTemplate)
template = GetTemplateData(queuedItem.unitTemplate);
else if (queuedItem.technologyTemplate)
template = GetTechnologyData(queuedItem.technologyTemplate, GetSimState().players[data.player].civ);
else
{
warning("Unknown production queue template " + uneval(queuedItem));
return false;
}
data.button.onPress = function() { removeFromProductionQueue(data.item.producingEnt, queuedItem.id); };
let tooltip = getEntityNames(template);
if (queuedItem.neededSlots)
{
tooltip += "\n" + coloredText(translate("Insufficient population capacity:"), "red");
tooltip += "\n" + sprintf(translate("%(population)s %(neededSlots)s"), {
"population": resourceIcon("population"),
"neededSlots": queuedItem.neededSlots
});
}
data.button.tooltip = tooltip;
data.countDisplay.caption = queuedItem.count > 1 ? queuedItem.count : "";
// Show the time remaining to finish the first item
if (data.i == 0)
Engine.GetGUIObjectByName("queueTimeRemaining").caption =
Engine.FormatMillisecondsIntoDateStringGMT(queuedItem.timeRemaining, translateWithContext("countdown format", "m:ss"));
let guiObject = Engine.GetGUIObjectByName("unitQueueProgressSlider[" + data.i + "]");
let size = guiObject.size;
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
size.top = size.left + Math.round(queuedItem.progress * (size.right - size.left));
guiObject.size = size;
if (template.icon)
data.icon.sprite = "stretched:session/portraits/" + template.icon;
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Research = {
"getMaxNumberOfItems": function()
{
return 10;
},
"rowLength": 10,
"getItems": function(unitEntStates)
{
let ret = [];
if (unitEntStates.length == 1)
return !unitEntStates[0].production || !unitEntStates[0].production.technologies ? ret :
unitEntStates[0].production.technologies.map(tech => ({
"tech": tech,
"techCostMultiplier": unitEntStates[0].production.techCostMultiplier,
"researchFacilityId": unitEntStates[0].id,
"isUpgrading": !!unitEntStates[0].upgrade && unitEntStates[0].upgrade.isUpgrading
}));
let sortedEntStates = unitEntStates.sort((a, b) =>
(!b.upgrade || !b.upgrade.isUpgrading) - (!a.upgrade || !a.upgrade.isUpgrading));
for (let state of sortedEntStates)
{
if (!state.production || !state.production.technologies)
continue;
// Remove the techs we already have in ret (with the same name and techCostMultiplier)
let filteredTechs = state.production.technologies.filter(
tech => tech != null && !ret.some(
item =>
(item.tech == tech ||
item.tech.pair &&
tech.pair &&
item.tech.bottom == tech.bottom &&
item.tech.top == tech.top) &&
Object.keys(item.techCostMultiplier).every(
k => item.techCostMultiplier[k] == state.production.techCostMultiplier[k])
));
if (filteredTechs.length + ret.length <= this.getMaxNumberOfItems() &&
getNumberOfRightPanelButtons() <= this.getMaxNumberOfItems() * (filteredTechs.some(tech => !!tech.pair) ? 1 : 2))
ret = ret.concat(filteredTechs.map(tech => ({
"tech": tech,
"techCostMultiplier": state.production.techCostMultiplier,
"researchFacilityId": state.id,
"isUpgrading": !!state.upgrade && state.upgrade.isUpgrading
})));
}
return ret;
},
"hideItem": function(i, rowLength) // Called when no item is found
{
Engine.GetGUIObjectByName("unitResearchButton[" + i + "]").hidden = true;
// We also remove the paired tech and the pair symbol
Engine.GetGUIObjectByName("unitResearchButton[" + (i + rowLength) + "]").hidden = true;
Engine.GetGUIObjectByName("unitResearchPair[" + i + "]").hidden = true;
},
"setupButton": function(data)
{
if (!data.item.tech)
{
g_SelectionPanels.Research.hideItem(data.i, data.rowLength);
return false;
}
// Start position (start at the bottom)
let position = data.i + data.rowLength;
// Only show the top button for pairs
if (!data.item.tech.pair)
Engine.GetGUIObjectByName("unitResearchButton[" + data.i + "]").hidden = true;
// Set up the tech connector
let pair = Engine.GetGUIObjectByName("unitResearchPair[" + data.i + "]");
pair.hidden = data.item.tech.pair == null;
setPanelObjectPosition(pair, data.i, data.rowLength);
// Handle one or two techs (tech pair)
let player = data.player;
let playerState = GetSimState().players[player];
for (let tech of data.item.tech.pair ? [data.item.tech.bottom, data.item.tech.top] : [data.item.tech])
{
// Don't change the object returned by GetTechnologyData
let template = clone(GetTechnologyData(tech, playerState.civ));
if (!template)
return false;
// Not allowed by civ.
if (!template.reqs)
{
// One of the pair may still be researchable by the current civ,
// hence don't hide everything.
Engine.GetGUIObjectByName("unitResearchButton[" + data.i + "]").hidden = true;
pair.hidden = true;
continue;
}
for (let res in template.cost)
template.cost[res] *= data.item.techCostMultiplier[res];
let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": template.cost,
"player": player
});
let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
"tech": tech,
"player": player
});
let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]");
let icon = Engine.GetGUIObjectByName("unitResearchIcon[" + position + "]");
let tooltips = [
getEntityNamesFormatted,
getEntityTooltip,
getEntityCostTooltip,
showTemplateViewerOnRightClickTooltip
].map(func => func(template));
if (!requirementsPassed)
{
let tip = template.requirementsTooltip;
let reqs = template.reqs;
for (let req of reqs)
{
if (!req.entities)
continue;
let entityCounts = [];
for (let entity of req.entities)
{
let current = 0;
switch (entity.check)
{
case "count":
current = playerState.classCounts[entity.class] || 0;
break;
case "variants":
current = playerState.typeCountsByClass[entity.class] ?
Object.keys(playerState.typeCountsByClass[entity.class]).length : 0;
break;
}
let remaining = entity.number - current;
if (remaining < 1)
continue;
entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
"number": remaining,
"class": entity.class
}));
}
tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
"entityCounts": entityCounts.join(translateWithContext("Separator for a list of entity counts", ", "))
});
}
tooltips.push(tip);
}
tooltips.push(getNeededResourcesTooltip(neededResources));
button.tooltip = tooltips.filter(tip => tip).join("\n");
button.onPress = (t => function() {
addResearchToQueue(data.item.researchFacilityId, t);
})(tech);
let showTemplateFunc = (t => function() {
showTemplateDetails(
t,
GetTemplateData(data.unitEntStates.find(state => state.id == data.item.researchFacilityId).template).nativeCiv);
});
button.onPressRight = showTemplateFunc(tech);
button.onPressRightDisabled = showTemplateFunc(tech);
if (data.item.tech.pair)
{
// On mouse enter, show a cross over the other icon
let unchosenIcon = Engine.GetGUIObjectByName("unitResearchUnchosenIcon[" + (position + data.rowLength) % (2 * data.rowLength) + "]");
button.onMouseEnter = function() {
unchosenIcon.hidden = false;
};
button.onMouseLeave = function() {
unchosenIcon.hidden = true;
};
}
button.hidden = false;
let modifier = "";
if (!requirementsPassed)
{
button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
}
else if (neededResources)
{
button.enabled = false;
modifier += resourcesToAlphaMask(neededResources) + ":";
}
else
button.enabled = controlsPlayer(data.player);
if (data.item.isUpgrading)
{
button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
button.tooltip += "\n" + coloredText(translate("Cannot research while upgrading."), "red");
}
if (template.icon)
icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
setPanelObjectPosition(button, position, data.rowLength);
// Prepare to handle the top button (if any)
position -= data.rowLength;
}
return true;
}
};
g_SelectionPanels.Selection = {
"getMaxNumberOfItems": function()
{
return 16;
},
"rowLength": 4,
"getItems": function(unitEntStates)
{
if (unitEntStates.length < 2)
return [];
return g_Selection.groups.getEntsGrouped();
},
"setupButton": function(data)
{
let entState = GetEntityState(data.item.ents[0]);
let template = GetTemplateData(entState.template);
if (!template)
return false;
for (let ent of data.item.ents)
{
let state = GetEntityState(ent);
if (state.resourceCarrying && state.resourceCarrying.length !== 0)
{
if (!data.carried)
data.carried = {};
let carrying = state.resourceCarrying[0];
if (data.carried[carrying.type])
data.carried[carrying.type] += carrying.amount;
else
data.carried[carrying.type] = carrying.amount;
}
if (state.trader && state.trader.goods && state.trader.goods.amount)
{
if (!data.carried)
data.carried = {};
let amount = state.trader.goods.amount;
let type = state.trader.goods.type;
let totalGain = amount.traderGain;
if (amount.market1Gain)
totalGain += amount.market1Gain;
if (amount.market2Gain)
totalGain += amount.market2Gain;
if (data.carried[type])
data.carried[type] += totalGain;
else
data.carried[type] = totalGain;
}
}
let unitOwner = GetEntityState(data.item.ents[0]).player;
let tooltip = getEntityNames(template);
if (data.carried)
tooltip += "\n" + Object.keys(data.carried).map(res =>
resourceIcon(res) + data.carried[res]
).join(" ");
if (g_IsObserver)
tooltip += "\n" + sprintf(translate("Player: %(playername)s"), {
"playername": g_Players[unitOwner].name
});
data.button.tooltip = tooltip;
data.guiSelection.sprite = "color:" + g_DiplomacyColors.getPlayerColor(unitOwner, 160);
data.guiSelection.hidden = !g_IsObserver;
data.countDisplay.caption = data.item.ents.length || "";
data.button.onPress = function() { changePrimarySelectionGroup(data.item.key, false); };
data.button.onPressRight = function() { changePrimarySelectionGroup(data.item.key, true); };
if (template.icon)
data.icon.sprite = "stretched:session/portraits/" + template.icon;
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Stance = {
"getMaxNumberOfItems": function()
{
return 5;
},
"getItems": function(unitEntStates)
{
if (unitEntStates.some(state => !state.unitAI || !hasClass(state, "Unit") || hasClass(state, "Animal")))
return [];
return unitEntStates[0].unitAI.selectableStances;
},
"setupButton": function(data)
{
let unitIds = data.unitEntStates.map(state => state.id);
data.button.onPress = function() { performStance(unitIds, data.item); };
data.button.tooltip = getStanceDisplayName(data.item) + "\n" +
"[font=\"sans-13\"]" + getStanceTooltip(data.item) + "[/font]";
data.guiSelection.hidden = !Engine.GuiInterfaceCall("IsStanceSelected", {
"ents": unitIds,
"stance": data.item
});
data.icon.sprite = "stretched:session/icons/stances/" + data.item + ".png";
data.button.enabled = controlsPlayer(data.player);
setPanelObjectPosition(data.button, data.i, data.rowLength);
return true;
}
};
g_SelectionPanels.Training = {
"getMaxNumberOfItems": function()
{
return 40 - getNumberOfRightPanelButtons();
},
"rowLength": 10,
"getItems": function()
{
return getAllTrainableEntitiesFromSelection();
},
"setupButton": function(data)
{
let template = GetTemplateData(data.item, data.player);
if (!template)
return false;
let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": template.requiredTechnology,
"player": data.player
});
let unitIds = data.unitEntStates.map(status => status.id);
let [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] =
getTrainingStatus(unitIds, data.item, data.playerState);
let trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
let neededResources;
if (template.cost)
neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(template, trainNum),
"player": data.player
});
data.button.onPress = function() {
if (!neededResources)
addTrainingToQueue(unitIds, data.item, data.playerState);
};
let showTemplateFunc = () => { showTemplateDetails(data.item, data.playerState.civ); };
data.button.onPressRight = showTemplateFunc;
data.button.onPressRightDisabled = showTemplateFunc;
data.countDisplay.caption = trainNum > 1 ? trainNum : "";
let tooltips = [
"[font=\"sans-bold-16\"]" +
colorizeHotkey("%(hotkey)s", "session.queueunit." + (data.i + 1)) +
"[/font]" + " " + getEntityNamesFormatted(template),
getVisibleEntityClassesFormatted(template),
getAurasTooltip(template),
getEntityTooltip(template),
getEntityCostTooltip(template, data.player, unitIds[0], buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch)
];
let limits = getEntityLimitAndCount(data.playerState, data.item);
tooltips.push(formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type));
if (Engine.ConfigDB_GetValue("user", "showdetailedtooltips") === "true")
tooltips = tooltips.concat([
getHealthTooltip,
getAttackTooltip,
getHealerTooltip,
getResistanceTooltip,
getGarrisonTooltip,
getProjectilesTooltip,
getSpeedTooltip,
getResourceDropsiteTooltip
].map(func => func(template)));
tooltips.push(showTemplateViewerOnRightClickTooltip());
tooltips.push(
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch),
getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
let modifier = "";
if (!technologyEnabled || limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
}
else
{
data.button.enabled = controlsPlayer(data.player);
if (neededResources)
modifier = resourcesToAlphaMask(neededResources) + ":";
}
if (data.unitEntStates.every(state => state.upgrade && state.upgrade.isUpgrading))
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
data.button.tooltip += "\n" + coloredText(translate("Cannot train while upgrading."), "red");
}
if (template.icon)
data.icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
let index = data.i + getNumberOfRightPanelButtons();
setPanelObjectPosition(data.button, index, data.rowLength);
return true;
}
};
g_SelectionPanels.Upgrade = {
"getMaxNumberOfItems": function()
{
return 40 - getNumberOfRightPanelButtons();
},
"rowLength": 10,
"getItems": function(unitEntStates)
{
// Interface becomes complicated with multiple different units and this is meant per-entity, so prevent it if the selection has multiple different units.
if (unitEntStates.some(state => state.template != unitEntStates[0].template))
return false;
return unitEntStates[0].upgrade && unitEntStates[0].upgrade.upgrades;
},
"setupButton": function(data)
{
let template = GetTemplateData(data.item.entity);
if (!template)
return false;
let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
progressOverlay.hidden = true;
let technologyEnabled = true;
if (data.item.requiredTechnology)
technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
"tech": data.item.requiredTechnology,
"player": data.player
});
let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
let upgradingEntStates = data.unitEntStates.filter(state => state.upgrade.template == data.item.entity);
let upgradableEntStates = data.unitEntStates.filter(state =>
!state.upgrade.progress &&
(!state.production || !state.production.queue || !state.production.queue.length));
let neededResources = data.item.cost && Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(data.item, upgradableEntStates.length),
"player": data.player
});
let tooltip;
let modifier = "";
if (!upgradingEntStates.length && upgradableEntStates.length)
{
+ let primaryName = g_SpecificNamesPrimary ? template.name.specific : template.name.generic;
+ let secondaryName;
+ if (g_ShowSecondaryNames)
+ secondaryName = g_SpecificNamesPrimary ? template.name.generic : template.name.specific;
+
let tooltips = [];
- if (data.item.tooltip)
- tooltips.push(sprintf(translate("Upgrade to %(name)s. %(tooltip)s"), {
- "name": template.name.generic,
- "tooltip": translate(data.item.tooltip)
- }));
+ if (g_ShowSecondaryNames)
+ {
+ if (data.item.tooltip)
+ tooltips.push(sprintf(translate("Upgrade to a %(primaryName)s (%(secondaryName)s). %(tooltip)s"), {
+ "primaryName": primaryName,
+ "secondaryName": secondaryName,
+ "tooltip": translate(data.item.tooltip)
+ }));
+ else
+ tooltips.push(sprintf(translate("Upgrade to a %(primaryName)s (%(secondaryName)s)."), {
+ "primaryName": primaryName,
+ "secondaryName": secondaryName
+ }));
+ }
else
- tooltips.push(sprintf(translate("Upgrade to %(name)s."), {
- "name": template.name.generic
- }));
+ {
+ if (data.item.tooltip)
+ tooltips.push(sprintf(translate("Upgrade to a %(primaryName)s. %(tooltip)s"), {
+ "primaryName": primaryName,
+ "tooltip": translate(data.item.tooltip)
+ }));
+ else
+ tooltips.push(sprintf(translate("Upgrade to a %(primaryName)s."), {
+ "primaryName": primaryName
+ }));
+ }
tooltips.push(
getEntityCostTooltip(data.item, undefined, undefined, data.unitEntStates.length),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources),
showTemplateViewerOnRightClickTooltip());
tooltip = tooltips.filter(tip => tip).join("\n");
data.button.onPress = function() {
upgradeEntity(
data.item.entity,
upgradableEntStates.map(state => state.id));
};
if (!technologyEnabled || limits.canBeAddedCount == 0 &&
!upgradableEntStates.some(state => hasSameRestrictionCategory(data.item.entity, state.template)))
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
}
else if (neededResources)
{
data.button.enabled = false;
modifier = resourcesToAlphaMask(neededResources) + ":";
}
data.countDisplay.caption = upgradableEntStates.length > 1 ? upgradableEntStates.length : "";
}
else if (upgradingEntStates.length)
{
tooltip = translate("Cancel Upgrading");
data.button.onPress = function() { cancelUpgradeEntity(); };
data.countDisplay.caption = upgradingEntStates.length > 1 ? upgradingEntStates.length : "";
let progress = 0;
for (let state of upgradingEntStates)
progress = Math.max(progress, state.upgrade.progress || 1);
let progressOverlaySize = progressOverlay.size;
// TODO This is bad: we assume the progressOverlay is square
progressOverlaySize.top = progressOverlaySize.bottom + Math.round((1 - progress) * (progressOverlaySize.left - progressOverlaySize.right));
progressOverlay.size = progressOverlaySize;
progressOverlay.hidden = false;
}
else
{
tooltip = coloredText(translatePlural(
"Cannot upgrade when the entity is training, researching or already upgrading.",
"Cannot upgrade when all entities are training, researching or already upgrading.",
data.unitEntStates.length), "red");
data.button.onPress = function() {};
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
}
data.button.enabled = controlsPlayer(data.player);
data.button.tooltip = tooltip;
let showTemplateFunc = () => { showTemplateDetails(data.item.entity, data.playerState.civ); };
data.button.onPressRight = showTemplateFunc;
data.button.onPressRightDisabled = showTemplateFunc;
data.icon.sprite = modifier + "stretched:session/" +
(data.item.icon || "portraits/" + template.icon);
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}
};
function initSelectionPanels()
{
let unitBarterPanel = Engine.GetGUIObjectByName("unitBarterPanel");
if (BarterButtonManager.IsAvailable(unitBarterPanel))
g_SelectionPanelBarterButtonManager = new BarterButtonManager(unitBarterPanel);
}
/**
* Pauses game and opens the template details viewer for a selected entity or technology.
*
* Technologies don't have a set civ, so we pass along the native civ of
* the template of the entity that's researching it.
*
* @param {string} [civCode] - The template name of the entity that researches the selected technology.
*/
function showTemplateDetails(templateName, civCode)
{
g_PauseControl.implicitPause();
Engine.PushGuiPage(
"page_viewer.xml",
{
"templateName": templateName,
"civ": civCode
},
resumeGame);
}
/**
* If two panels need the same space, so they collide,
* the one appearing first in the order is rendered.
*
* Note that the panel needs to appear in the list to get rendered.
*/
let g_PanelsOrder = [
// LEFT PANE
"Barter", // Must always be visible on markets
"Garrison", // More important than Formation, as you want to see the garrisoned units in ships
"Alert",
"Formation",
"Stance", // Normal together with formation
// RIGHT PANE
"Gate", // Must always be shown on gates
"Pack", // Must always be shown on packable entities
"Upgrade", // Must always be shown on upgradable entities
"Training",
"Construction",
"Research", // Normal together with training
// UNIQUE PANES (importance doesn't matter)
"Command",
"Queue",
"Selection",
];
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml (revision 25116)
@@ -1,120 +1,120 @@
HealthCapture pointsAttack and ResistanceExperienceRank
-
+
-
-
+
+
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 25116)
@@ -1,832 +1,842 @@
const g_IsReplay = Engine.IsVisualReplay();
const g_CivData = loadCivData(false, true);
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_WorldPopulationCapacities = prepareForDropdown(g_Settings && g_Settings.WorldPopulationCapacities);
const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions;
var g_Ambient;
var g_AutoFormation;
var g_Chat;
var g_Cheats;
var g_DeveloperOverlay;
var g_DiplomacyColors;
var g_DiplomacyDialog;
var g_GameSpeedControl;
var g_Menu;
var g_MiniMapPanel;
var g_NetworkStatusOverlay;
var g_ObjectivesDialog;
var g_OutOfSyncNetwork;
var g_OutOfSyncReplay;
var g_PanelEntityManager;
var g_PauseControl;
var g_PauseOverlay;
var g_PlayerViewControl;
var g_QuitConfirmationDefeat;
var g_QuitConfirmationReplay;
var g_RangeOverlayManager;
var g_ResearchProgress;
var g_TimeNotificationOverlay;
var g_TopPanel;
var g_TradeDialog;
/**
* Map, player and match settings set in game setup.
*/
const g_InitAttributes = deepfreeze(Engine.GuiInterfaceCall("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.IsNetController();
/**
* 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;
/**
* 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 single-player.
*/
var g_PlayerAssignments;
/**
* 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;
/**
* 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();
/**
* These handlers are called each time a new turn was simulated.
* Use this as sparely as possible.
*/
var g_SimulationUpdateHandlers = new Set();
/**
* These handlers are called after the player states have been initialized.
*/
var g_PlayersInitHandlers = new Set();
/**
* These handlers are called when a player has been defeated or won the game.
*/
var g_PlayerFinishedHandlers = new Set();
/**
* These events are fired whenever the player added or removed entities from the selection.
*/
var g_EntitySelectionChangeHandlers = new Set();
/**
* These events are fired when the user has performed a hotkey assignment change.
* Currently only fired on init, but to be fired from any hotkey editor dialog.
*/
var g_HotkeyChangeHandlers = new Set();
/**
* List of additional entities to highlight.
*/
var g_ShowGuarding = false;
var g_ShowGuarded = false;
var g_AdditionalHighlight = [];
/**
* 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", "Citizen"];
/**
* 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];
}
/**
* Returns template data calling GetTemplateData defined in GuiInterface.js
* and deepfreezing returned object.
* @param {string} templateName - Data of this template will be returned.
* @param {number|undefined} player - Modifications of this player will be applied to the template.
* If undefined, id of player calling this method will be used.
*/
function GetTemplateData(templateName, player)
{
if (!(templateName in g_TemplateData))
{
let template = Engine.GuiInterfaceCall("GetTemplateData", { "templateName": templateName, "player": player });
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);
}
if (g_InitAttributes.campaignData)
g_CampaignSession = new CampaignSession(g_InitAttributes.campaignData);
let mapCache = new MapCache();
g_Cheats = new Cheats();
g_DiplomacyColors = new DiplomacyColors();
g_PlayerViewControl = new PlayerViewControl();
g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(updateGUIObjects);
g_PauseControl = new PauseControl();
g_PlayerViewControl.registerPreViewedPlayerChangeHandler(removeStatusBarDisplay);
g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates);
g_Ambient = new Ambient();
g_AutoFormation = new AutoFormation();
g_Chat = new Chat(g_PlayerViewControl, g_Cheats);
g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl, g_Selection);
g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors);
g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl);
g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat);
g_MiniMapPanel = new MiniMapPanel(g_PlayerViewControl, g_DiplomacyColors, g_WorkerTypes);
g_NetworkStatusOverlay = new NetworkStatusOverlay();
g_ObjectivesDialog = new ObjectivesDialog(g_PlayerViewControl, mapCache);
g_OutOfSyncNetwork = new OutOfSyncNetwork();
g_OutOfSyncReplay = new OutOfSyncReplay();
g_PanelEntityManager = new PanelEntityManager(g_PlayerViewControl, g_Selection, g_PanelEntityOrder);
g_PauseOverlay = new PauseOverlay(g_PauseControl);
g_QuitConfirmationDefeat = new QuitConfirmationDefeat();
g_QuitConfirmationReplay = new QuitConfirmationReplay();
g_RangeOverlayManager = new RangeOverlayManager(g_Selection);
g_ResearchProgress = new ResearchProgress(g_PlayerViewControl, g_Selection);
g_TradeDialog = new TradeDialog(g_PlayerViewControl);
g_TopPanel = new TopPanel(g_PlayerViewControl, g_DiplomacyDialog, g_TradeDialog, g_ObjectivesDialog, g_GameSpeedControl);
g_TimeNotificationOverlay = new TimeNotificationOverlay(g_PlayerViewControl);
initBatchTrain();
+ initDisplayedNames();
initSelectionPanels();
LoadModificationTemplates();
updatePlayerData();
initializeMusic(); // before changing the perspective
Engine.SetBoundingBoxDebugOverlay(false);
for (let handler of g_PlayersInitHandlers)
handler();
for (let handler of g_HotkeyChangeHandlers)
handler();
if (hotloadData)
{
g_Selection.selected = hotloadData.selection;
g_PlayerAssignments = hotloadData.playerAssignments;
g_Players = hotloadData.player;
}
// TODO: use event instead
onSimulationUpdate();
setTimeout(displayGamestateNotifications, 1000);
}
function registerPlayersInitHandler(handler)
{
g_PlayersInitHandlers.add(handler);
}
function registerPlayersFinishedHandler(handler)
{
g_PlayerFinishedHandlers.add(handler);
}
function registerSimulationUpdateHandler(handler)
{
g_SimulationUpdateHandlers.add(handler);
}
function unregisterSimulationUpdateHandler(handler)
{
g_SimulationUpdateHandlers.delete(handler);
}
function registerEntitySelectionChangeHandler(handler)
{
g_EntitySelectionChangeHandlers.add(handler);
}
function unregisterEntitySelectionChangeHandler(handler)
{
g_EntitySelectionChangeHandlers.delete(handler);
}
function registerHotkeyChangeHandler(handler)
{
g_HotkeyChangeHandlers.add(handler);
}
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;
}
/**
* @param {number} ent - The entity to get its ID for.
* @return {number} - The entity ID of the entity or of its garrisonHolder.
*/
function getEntityOrHolder(ent)
{
let entState = GetEntityState(ent);
if (entState && !entState.position && entState.garrisonable && entState.garrisonable.holder != INVALID_ENTITY)
return getEntityOrHolder(entState.garrisonable.holder);
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);
}
function resetTemplates()
{
// Update GUI and clear player-dependent cache
g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified");
// TODO: do this more selectively
onSimulationUpdate();
}
/**
* 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": "playerstate",
"message": victoryString,
"players": players
});
updatePlayerData();
// TODO: The other calls in this function should move too
for (let handler of g_PlayerFinishedHandlers)
handler(players, won);
if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning())
return;
global.music.setState(
won ?
global.music.states.VICTORY :
global.music.states.DEFEAT
);
}
function resumeGame()
{
g_PauseControl.implicitResume();
}
function closeOpenDialogs()
{
g_Menu.close();
g_Chat.closePage();
g_DiplomacyDialog.close();
g_ObjectivesDialog.close();
g_TradeDialog.close();
}
function endGame()
{
// Before ending the game
let replayDirectory = Engine.GetCurrentReplayDirectory();
let simData = Engine.GuiInterfaceCall("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();
let summaryData = {
"sim": simData,
"gui": {
"dialog": false,
"assignedPlayer": playerID,
"disconnected": g_Disconnected,
"isReplay": g_IsReplay,
"replayDirectory": !g_HasRejoined && replayDirectory,
"replaySelectionData": g_ReplaySelectionData
}
};
if (g_InitAttributes.campaignData)
{
let menu = g_CampaignSession.getMenu();
if (g_InitAttributes.campaignData.skipSummary)
{
Engine.SwitchGuiPage(menu);
return;
}
summaryData.campaignData = { "filename": g_InitAttributes.campaignData.run };
summaryData.nextPage = menu;
}
Engine.SwitchGuiPage("page_summary.xml", summaryData);
}
// Return some data that we'll use when hotloading this file after changes
function getHotloadData()
{
return {
"selection": g_Selection.selected,
"playerAssignments": g_PlayerAssignments,
"player": g_Players,
};
}
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]));
for (let handler of g_EntitySelectionChangeHandlers)
handler();
updateGUIObjects();
// Display rally points for selected structures.
if (Engine.GetPlayerID() != -1)
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
}
else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength)
recalculateStatusBarDisplay();
updateTimers();
Engine.GuiInterfaceCall("ClearRenamedEntities");
}
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;
// Some changes may require re-rendering the selection.
if (Engine.GuiInterfaceCall("IsSelectionDirty"))
{
g_Selection.onChange();
Engine.GuiInterfaceCall("ResetSelectionDirty");
}
if (!GetSimState())
return;
GetMultipleEntityStates(g_Selection.toList());
for (let handler of g_SimulationUpdateHandlers)
handler();
// TODO: Move to handlers
updateCinemaPath();
handleNotifications();
updateGUIObjects();
}
function toggleGUI()
{
g_ShowGUI = !g_ShowGUI;
updateCinemaPath();
}
function updateCinemaPath()
{
let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected;
Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath;
Engine.ConfigDB_CreateValue("user", "silhouettes", !isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true" ? "true" : "false");
}
// TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead
function updateGUIObjects()
{
g_Selection.update();
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay();
if (g_ShowGuarding || g_ShowGuarded)
updateAdditionalHighlight();
updateGroups();
updateSelectionDetails();
updateBuildingPlacementPreview();
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]);
}
}
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);
}
}
/**
* 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"
});
}
function removeStatusBarDisplay()
{
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay(true);
}
/**
+ * Updates the primary/secondary names in the simulation and GUI.
+ */
+function updateDisplayedNames()
+{
+ g_SpecificNamesPrimary = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 2;
+ g_ShowSecondaryNames = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 1;
+}
+
+/**
* 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;
}
// 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;
}
Index: ps/trunk/binaries/data/mods/public/gui/session/styles.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/styles.xml (revision 25115)
+++ ps/trunk/binaries/data/mods/public/gui/session/styles.xml (revision 25116)
@@ -1,265 +1,265 @@
-
-