2014-06-14 08:35:13 -07:00
/ * *
* Contains the layout and button settings per selection panel
*
2014-06-16 11:34:27 -07:00
* 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 .
2016-01-09 15:01:07 -08:00
*
* Then there ' s a loop over all items provided . In the loop ,
2014-06-16 11:34:27 -07:00
* the item and some other standard data is added to a data object .
2014-06-14 08:35:13 -07:00
*
2016-01-09 15:01:07 -08:00
* The standard data is
2016-05-28 07:46:20 -07:00
* {
2014-06-16 11:34:27 -07:00
* "i" : index
* "item" : item coming from the getItems function
* "playerState" : playerState
2016-09-25 12:38:10 -07:00
* "unitEntStates" : states of the selected entities
2014-06-16 11:34:27 -07:00
* "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
2016-05-28 07:46:20 -07:00
* }
2014-06-16 11:34:27 -07:00
*
2016-05-28 07:46:20 -07:00
* Then for every data object , the setupButton function is called which
* sets the view and handlers of the button .
2014-06-14 08:35:13 -07:00
* /
2016-05-28 07:46:20 -07:00
// Cache some formation info
// Available formations per player
2019-10-12 20:43:42 -07:00
var g _AvailableFormations = new Map ( ) ;
var g _FormationsInfo = new Map ( ) ;
2015-03-23 11:38:27 -07:00
2019-10-12 20:43:42 -07:00
var g _SelectionPanels = { } ;
var g _SelectionPanelBarterButtonManager ;
2014-06-14 08:35:13 -07:00
2015-05-24 06:51:02 -07:00
g _SelectionPanels . Alert = {
"getMaxNumberOfItems" : function ( )
{
2018-02-23 20:35:26 -08:00
return 2 ;
2015-05-24 06:51:02 -07:00
} ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2015-05-24 06:51:02 -07:00
{
2018-02-23 20:35:26 -08:00
return unitEntStates . some ( state => ! ! state . alertRaiser ) ? [ "raise" , "end" ] : [ ] ;
2015-05-24 06:51:02 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2015-05-24 06:51:02 -07:00
{
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
2016-09-25 12:38:10 -07:00
switch ( data . item )
{
case "raise" :
raiseAlert ( ) ;
return ;
case "end" :
2015-05-24 06:51:02 -07:00
endOfAlert ( ) ;
2016-09-25 12:38:10 -07:00
return ;
2025-05-12 12:26:08 -07:00
default :
error ( "Unknown value for alert action: " + data . item ) ;
2016-09-25 12:38:10 -07:00
}
2015-05-24 06:51:02 -07:00
} ;
2016-05-28 07:46:20 -07:00
2016-09-25 12:38:10 -07:00
switch ( data . item )
2015-05-24 06:51:02 -07:00
{
2016-09-25 12:38:10 -07:00
case "raise" :
data . icon . sprite = "stretched:session/icons/bell_level1.png" ;
data . button . tooltip = translate ( "Raise an alert!" ) ;
2025-06-10 11:00:31 -07:00
if ( data . unitEntStates . every ( state => MatchesClassList ( [ "Civilian" ] , state . alertRaiser ? . classes ) ) )
data . button . tooltip += "\n" + bodyFont ( translate ( "Alert nearby Civilians to seek refuge." ) ) ;
2023-10-13 02:34:51 -07:00
else if ( data . unitEntStates . every ( state => MatchesClassList ( [ "Trader" ] , state . alertRaiser ? . classes ) ) )
data . button . tooltip += "\n" + bodyFont ( translate ( "Alert nearby Traders to seek refuge." ) ) ;
else
data . button . tooltip += "\n" + bodyFont ( translate ( "Alert nearby vulnerable units to seek refuge." ) ) ;
2016-09-25 12:38:10 -07:00
break ;
case "end" :
2015-05-24 06:51:02 -07:00
data . icon . sprite = "stretched:session/icons/bell_level0.png" ;
2023-10-13 02:34:51 -07:00
data . button . tooltip = translate ( "End the alert." ) ;
2025-06-10 11:00:31 -07:00
if ( data . unitEntStates . every ( state => MatchesClassList ( [ "Civilian" ] , state . alertRaiser ? . classes ) ) )
data . button . tooltip += "\n" + bodyFont ( translate ( "Unload nearby Civilians." ) ) ;
2023-10-13 02:34:51 -07:00
else if ( data . unitEntStates . every ( state => MatchesClassList ( [ "Trader" ] , state . alertRaiser ? . classes ) ) )
data . button . tooltip += "\n" + bodyFont ( translate ( "Unload nearby Traders." ) ) ;
else
data . button . tooltip += "\n" + bodyFont ( translate ( "Unload nearby vulnerable units." ) ) ;
2016-09-25 12:38:10 -07:00
break ;
2025-05-12 12:26:08 -07:00
default :
error ( "Unknown value for alert action: " + data . item ) ;
2015-05-24 06:51:02 -07:00
}
2016-09-25 12:38:10 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2016-05-28 07:46:20 -07:00
2018-02-23 20:35:26 -08:00
setPanelObjectPosition ( data . button , this . getMaxNumberOfItems ( ) - data . i , data . rowLength ) ;
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2015-05-24 06:51:02 -07:00
} ;
2014-06-16 11:34:27 -07:00
g _SelectionPanels . Barter = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 5 ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 5 ,
2018-02-23 20:35:26 -08:00
"conflictsWith" : [ "Garrison" ] ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2017-03-27 19:34:32 -07:00
// If more than `rowLength` resources, don't display icons.
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
if ( unitEntStates . every ( state => ! state . enablesBartering ) || g _ResourceData . GetBarterableCodes ( ) . length > this . rowLength )
2014-06-16 11:34:27 -07:00
return [ ] ;
2019-09-22 07:53:47 -07:00
return g _ResourceData . GetBarterableCodes ( ) ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-16 11:34:27 -07:00
{
2019-10-12 20:43:42 -07:00
if ( g _SelectionPanelBarterButtonManager )
{
g _SelectionPanelBarterButtonManager . setViewedPlayer ( data . player ) ;
g _SelectionPanelBarterButtonManager . update ( ) ;
}
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
} ;
2014-06-16 11:34:27 -07:00
2014-06-14 08:35:13 -07:00
g _SelectionPanels . Command = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
return 6 ;
} ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2025-05-10 07:21:01 -07:00
const commands = [ ] ;
2016-03-13 09:44:21 -07:00
2025-05-10 07:21:01 -07:00
for ( const command in g _EntityCommands )
2014-06-18 08:27:28 -07:00
{
2025-05-10 07:21:01 -07:00
const info = getCommandInfo ( command , unitEntStates ) ;
Only load command buttons that will be displayed
There are currently 19 entity commands in total, but only the first 6
possible (depends on the active selection, e.g. patrolling isn't
possible if no units are selected) ones are ever displayed as buttons
(defined by g_SelectionPanels.Command.getMaxNumberOfItems) However,
when updating (once per turn or whenever the selection changes),
g_SelectionPanels.Command.getItems always called `getInfo` on all of
them, even though all data computed after the first 6 wasn't read or
used anywhere later. So, stopping immediately after the 6th and never
returning an array longer than 6 saves all of the dead time without
affecting the outcome in any way.
It's important to mention, that this issue isn't exclusive to the
'Command' selection panel: the getItems methods of the other panels can
also return an array longer than their getMaxNumbertOfItems value
(that's why they specify it in the first place). However, for the
command panel this happens for many common selections and seemingly to
by far the largest extent. For the other panels it happens much more
rarely, only for especially large and obscure selections, and even then
does not have nearly as big of an impact. So, modifying the other
getItems methods as well (to never return too many items) is probably
not worth it, and the more robust solution is to instead keep the
safeguard system of getMaxNumbertOfItems.
2025-10-21 12:21:18 -07:00
if ( ! info )
continue ;
info . name = command ;
if ( commands . push ( info ) >= this . getMaxNumberOfItems ( ) )
break ;
2014-06-18 08:27:28 -07:00
}
return commands ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2014-11-12 17:26:36 -08:00
data . button . tooltip = data . item . tooltip ;
2016-05-28 07:46:20 -07:00
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
2016-01-09 15:01:07 -08:00
if ( data . item . callback )
data . item . callback ( data . item ) ;
else
2017-05-08 11:04:43 -07:00
performCommand ( data . unitEntStates , data . item . name ) ;
2016-01-09 15:01:07 -08:00
} ;
2016-05-28 07:46:20 -07:00
2014-06-16 11:34:27 -07:00
data . countDisplay . caption = data . item . count || "" ;
2016-05-28 07:46:20 -07:00
2021-01-01 23:19:17 -08:00
data . button . enabled = data . item . enabled == true ;
2016-06-09 08:32:41 -07:00
data . icon . sprite = "stretched:session/icons/" + data . item . icon ;
2016-05-28 07:46:20 -07:00
2025-05-29 15:23:46 -07:00
const left = ( data . i - data . numberOfItems / 2 ) * ( data . button . size . bottom + 1 ) ;
Object . assign ( data . button . size , {
// relative to the center ( = 50%)
"rleft" : 50 ,
"rright" : 50 ,
// offset from the center calculation, count on square buttons, so size.bottom is the width too
"left" : left ,
"right" : left + data . button . size . bottom
} ) ;
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Construction = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 40 - getNumberOfRightPanelButtons ( ) ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 10 ,
2014-06-16 11:34:27 -07:00
"getItems" : function ( )
{
return getAllBuildableEntitiesFromSelection ( ) ;
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2025-05-10 07:21:01 -07:00
const template = GetTemplateData ( data . item , data . player ) ;
2016-05-28 07:46:20 -07:00
if ( ! template )
2014-06-14 08:35:13 -07:00
return false ;
2016-01-09 14:01:08 -08:00
2022-11-24 03:20:11 -08:00
const requirementsMet = Engine . GuiInterfaceCall ( "AreRequirementsMet" , {
"requirements" : template . requirements ,
2016-09-25 12:38:10 -07:00
"player" : data . player
2016-01-09 14:01:08 -08:00
} ) ;
2016-05-28 07:46:20 -07:00
let neededResources ;
if ( template . cost )
neededResources = Engine . GuiInterfaceCall ( "GetNeededResources" , {
"cost" : multiplyEntityCosts ( template , 1 ) ,
2016-09-25 12:38:10 -07:00
"player" : data . player
2016-01-09 14:01:08 -08:00
} ) ;
2017-10-21 10:31:05 -07:00
data . button . onPress = function ( ) { startBuildingPlacement ( data . item , data . playerState ) ; } ;
2025-05-10 07:21:01 -07:00
const showTemplateFunc = ( ) => { showTemplateDetails ( data . item , data . playerState . civ ) ; } ;
2020-11-14 10:16:24 -08:00
data . button . onPressRight = showTemplateFunc ;
data . button . onPressRightDisabled = showTemplateFunc ;
2014-06-14 08:35:13 -07:00
2025-05-10 07:21:01 -07:00
const tooltips = [
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
getEntityNamesFormatted ,
getVisibleEntityClassesFormatted ,
getAurasTooltip ,
2019-12-14 12:10:32 -08:00
getEntityTooltip
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
] . map ( func => func ( template ) ) ;
2019-12-14 12:10:32 -08:00
tooltips . push (
getEntityCostTooltip ( template , data . player ) ,
2020-09-16 08:28:44 -07:00
getResourceDropsiteTooltip ( template ) ,
2019-12-14 12:10:32 -08:00
getGarrisonTooltip ( template ) ,
2021-03-26 03:18:30 -07:00
getTurretsTooltip ( template ) ,
2019-12-14 12:10:32 -08:00
getPopulationBonusTooltip ( template ) ,
2024-09-02 04:22:18 -07:00
getTemplateViewerOnRightClickTooltip ( template )
2019-12-14 12:10:32 -08:00
) ;
2014-06-14 08:35:13 -07:00
2025-05-10 07:21:01 -07:00
const limits = getEntityLimitAndCount ( data . playerState , data . item ) ;
2016-10-26 16:19:19 -07:00
tooltips . push (
formatLimitString ( limits . entLimit , limits . entCount , limits . entLimitChangers ) ,
2020-12-29 03:00:54 -08:00
formatMatchLimitString ( limits . matchLimit , limits . matchCount , limits . type ) ,
2022-11-24 03:20:11 -08:00
getRequirementsTooltip ( requirementsMet , template . requirements , GetSimState ( ) . players [ data . player ] . civ ) ,
2016-10-26 16:19:19 -07:00
getNeededResourcesTooltip ( neededResources ) ) ;
2016-01-09 15:01:07 -08:00
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
data . button . tooltip = tooltips . filter ( tip => tip ) . join ( "\n" ) ;
2016-05-28 07:46:20 -07:00
let modifier = "" ;
2022-11-24 03:20:11 -08:00
if ( ! requirementsMet || limits . canBeAddedCount == 0 )
2014-06-14 08:35:13 -07:00
{
data . button . enabled = false ;
2016-05-28 07:46:20 -07:00
modifier += "color:0 0 0 127:grayscale:" ;
2014-06-14 08:35:13 -07:00
}
2016-05-28 07:46:20 -07:00
else if ( neededResources )
2014-06-14 08:35:13 -07:00
{
data . button . enabled = false ;
2017-10-21 10:31:05 -07:00
modifier += resourcesToAlphaMask ( neededResources ) + ":" ;
2014-06-14 08:35:13 -07:00
}
2016-01-09 14:01:08 -08:00
else
2016-09-25 12:38:10 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2015-12-13 08:03:17 -08:00
2016-05-28 07:46:20 -07:00
if ( template . icon )
data . icon . sprite = modifier + "stretched:session/portraits/" + template . icon ;
2018-03-13 14:02:13 -07:00
setPanelObjectPosition ( data . button , data . i + getNumberOfRightPanelButtons ( ) , data . rowLength ) ;
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Formation = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 15 ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 5 ,
2014-06-16 11:34:27 -07:00
"conflictsWith" : [ "Garrison" ] ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2017-03-17 15:46:42 -07:00
if ( unitEntStates . some ( state => ! hasClass ( state , "Unit" ) ) )
2014-06-16 11:34:27 -07:00
return [ ] ;
2017-03-17 15:46:42 -07:00
2022-01-22 23:23:44 -08:00
if ( unitEntStates . every ( state => ! state . unitAI || ! state . unitAI . formations . length ) )
2020-01-27 09:49:06 -08:00
return [ ] ;
2016-09-25 12:38:10 -07:00
if ( ! g _AvailableFormations . has ( unitEntStates [ 0 ] . player ) )
g _AvailableFormations . set ( unitEntStates [ 0 ] . player , Engine . GuiInterfaceCall ( "GetAvailableFormations" , unitEntStates [ 0 ] . player ) ) ;
2017-03-17 15:46:42 -07:00
2022-01-22 23:23:44 -08:00
return g _AvailableFormations . get ( unitEntStates [ 0 ] . player ) . filter ( formation => unitEntStates . some ( state => ! ! state . unitAI && state . unitAI . formations . includes ( formation ) ) ) ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2016-05-28 07:46:20 -07:00
if ( ! g _FormationsInfo . has ( data . item ) )
g _FormationsInfo . set ( data . item , Engine . GuiInterfaceCall ( "GetFormationInfoFromTemplate" , { "templateName" : data . item } ) ) ;
2025-05-10 07:21:01 -07:00
const formationOk = canMoveSelectionIntoFormation ( data . item ) ;
const unitIds = data . unitEntStates . map ( state => state . id ) ;
const formationSelected = Engine . GuiInterfaceCall ( "IsFormationSelected" , {
2018-03-13 14:02:13 -07:00
"ents" : unitIds ,
2014-06-14 08:35:13 -07:00
"formationTemplate" : data . item
} ) ;
2016-05-28 07:46:20 -07:00
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
2018-03-13 14:02:13 -07:00
performFormation ( unitIds , data . item ) ;
2016-09-25 12:38:10 -07:00
} ;
2016-05-28 07:46:20 -07:00
Allow picking a default formation for walk (and walk-like) orders.
This allows choosing a "default formation", which is activated
automatically for units given walk orders (and attack-walk etc.).
Conversely, units in formation that are given a gather/build/... order
are taken out of formation and given the order individually.
The default formation can be selected by right-clicking on any formation
icon.
This leverages formations for walking, where they are quite efficient
(in fact, perhaps too efficient), while circumventing issues with
various orders.
Choosing the "null formation" as the default formation de-activates the
behaviour entirely, and plays out exactly like SVN.
This makes it possible to queue a formation-order then a
noformation-order (i.e. walk then repair), though the behaviour isn't
very flexible.
For modders, it should be relatively easy to change the setup for each
order, and/or to force deactivating/activating formations in general.
Tested by: Freagarach, Angen
Refs #3479, #1791.
Makes #3478 mostly invalid.
Differential Revision: https://code.wildfiregames.com/D2764
This was SVN commit r24480.
2020-12-31 02:04:58 -08:00
data . button . onMouseRightPress = ( ) => g _AutoFormation . setDefault ( data . item ) ;
2025-05-10 07:21:01 -07:00
const formationInfo = g _FormationsInfo . get ( data . item ) ;
2016-05-28 07:46:20 -07:00
let tooltip = translate ( formationInfo . name ) ;
2023-07-22 07:14:03 -07:00
if ( formationInfo . tooltip )
tooltip += "\n" + bodyFont ( translate ( formationInfo . tooltip ) ) ;
Allow picking a default formation for walk (and walk-like) orders.
This allows choosing a "default formation", which is activated
automatically for units given walk orders (and attack-walk etc.).
Conversely, units in formation that are given a gather/build/... order
are taken out of formation and given the order individually.
The default formation can be selected by right-clicking on any formation
icon.
This leverages formations for walking, where they are quite efficient
(in fact, perhaps too efficient), while circumventing issues with
various orders.
Choosing the "null formation" as the default formation de-activates the
behaviour entirely, and plays out exactly like SVN.
This makes it possible to queue a formation-order then a
noformation-order (i.e. walk then repair), though the behaviour isn't
very flexible.
For modders, it should be relatively easy to change the setup for each
order, and/or to force deactivating/activating formations in general.
Tested by: Freagarach, Angen
Refs #3479, #1791.
Makes #3478 mostly invalid.
Differential Revision: https://code.wildfiregames.com/D2764
This was SVN commit r24480.
2020-12-31 02:04:58 -08:00
2025-05-10 07:21:01 -07:00
const isDefaultFormation = g _AutoFormation . isDefault ( data . item ) ;
Allow picking a default formation for walk (and walk-like) orders.
This allows choosing a "default formation", which is activated
automatically for units given walk orders (and attack-walk etc.).
Conversely, units in formation that are given a gather/build/... order
are taken out of formation and given the order individually.
The default formation can be selected by right-clicking on any formation
icon.
This leverages formations for walking, where they are quite efficient
(in fact, perhaps too efficient), while circumventing issues with
various orders.
Choosing the "null formation" as the default formation de-activates the
behaviour entirely, and plays out exactly like SVN.
This makes it possible to queue a formation-order then a
noformation-order (i.e. walk then repair), though the behaviour isn't
very flexible.
For modders, it should be relatively easy to change the setup for each
order, and/or to force deactivating/activating formations in general.
Tested by: Freagarach, Angen
Refs #3479, #1791.
Makes #3478 mostly invalid.
Differential Revision: https://code.wildfiregames.com/D2764
This was SVN commit r24480.
2020-12-31 02:04:58 -08:00
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." ) ) ;
2023-07-22 07:14:03 -07:00
if ( ! formationOk && formationInfo . disabledTooltip )
tooltip += "\n" + objectionFont ( translate ( formationInfo . disabledTooltip ) ) ;
2014-06-14 08:35:13 -07:00
data . button . tooltip = tooltip ;
2016-05-28 07:46:20 -07:00
2016-09-25 12:38:10 -07:00
data . button . enabled = formationOk && controlsPlayer ( data . player ) ;
2025-05-10 07:21:01 -07:00
const grayscale = formationOk ? "" : "grayscale:" ;
2016-05-28 07:46:20 -07:00
data . guiSelection . hidden = ! formationSelected ;
Allow picking a default formation for walk (and walk-like) orders.
This allows choosing a "default formation", which is activated
automatically for units given walk orders (and attack-walk etc.).
Conversely, units in formation that are given a gather/build/... order
are taken out of formation and given the order individually.
The default formation can be selected by right-clicking on any formation
icon.
This leverages formations for walking, where they are quite efficient
(in fact, perhaps too efficient), while circumventing issues with
various orders.
Choosing the "null formation" as the default formation de-activates the
behaviour entirely, and plays out exactly like SVN.
This makes it possible to queue a formation-order then a
noformation-order (i.e. walk then repair), though the behaviour isn't
very flexible.
For modders, it should be relatively easy to change the setup for each
order, and/or to force deactivating/activating formations in general.
Tested by: Freagarach, Angen
Refs #3479, #1791.
Makes #3478 mostly invalid.
Differential Revision: https://code.wildfiregames.com/D2764
This was SVN commit r24480.
2020-12-31 02:04:58 -08:00
data . countDisplay . hidden = ! isDefaultFormation ;
2016-05-28 07:46:20 -07:00
data . icon . sprite = "stretched:" + grayscale + "session/icons/" + formationInfo . icon ;
setPanelObjectPosition ( data . button , data . i , data . rowLength ) ;
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Garrison = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
return 12 ;
} ,
2014-06-14 08:35:13 -07:00
"rowLength" : 4 ,
2016-09-25 12:38:10 -07:00
"conflictsWith" : [ "Barter" ] ,
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2016-09-25 12:38:10 -07:00
if ( unitEntStates . every ( state => ! state . garrisonHolder ) )
2014-06-16 11:34:27 -07:00
return [ ] ;
2016-06-09 08:32:41 -07:00
2025-05-10 07:21:01 -07:00
const groups = new EntityGroups ( ) ;
2016-06-09 08:32:41 -07:00
2025-05-10 07:21:01 -07:00
for ( const state of unitEntStates )
2014-06-16 11:34:27 -07:00
if ( state . garrisonHolder )
2015-09-19 05:55:58 -07:00
groups . add ( state . garrisonHolder . entities ) ;
2016-06-09 08:32:41 -07:00
2014-06-16 11:34:27 -07:00
return groups . getEntsGrouped ( ) ;
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2025-05-10 07:21:01 -07:00
const entState = GetEntityState ( data . item . ents [ 0 ] ) ;
2017-02-06 14:17:21 -08:00
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
const template = GetTemplateData ( entState . templateName , entState . player ) ;
2016-05-28 07:46:20 -07:00
if ( ! template )
2014-06-14 08:35:13 -07:00
return false ;
2016-05-28 07:46:20 -07:00
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
unloadTemplate ( template . selectionGroupName || entState . templateName , entState . player ) ;
2016-06-09 08:32:41 -07:00
} ;
2016-05-28 07:46:20 -07:00
2023-08-16 02:56:57 -07:00
data . countDisplay . caption = data . item . ents . length > 1 ? data . item . ents . length : "" ;
2016-05-28 07:46:20 -07:00
2025-05-10 07:21:01 -07:00
const canUngarrison = controlsPlayer ( data . player ) || controlsPlayer ( entState . player ) ;
2016-06-11 08:02:34 -07:00
2021-01-03 22:31:29 -08:00
data . button . enabled = canUngarrison ;
2016-06-11 08:02:34 -07:00
2021-01-03 22:31:29 -08:00
data . button . tooltip = ( canUngarrison ?
2017-10-21 10:31:05 -07:00
sprintf ( translate ( "Unload %(name)s" ) , { "name" : getEntityNames ( template ) } ) + "\n" +
2016-06-11 08:02:34 -07:00
translate ( "Single-click to unload 1. Shift-click to unload all of this type." ) :
2018-03-13 14:02:13 -07:00
getEntityNames ( template ) ) + "\n" +
sprintf ( translate ( "Player: %(playername)s" ) , {
"playername" : g _Players [ entState . player ] . name
} ) ;
2016-06-11 08:02:34 -07:00
2019-10-11 05:28:12 -07:00
data . guiSelection . sprite = "color:" + g _DiplomacyColors . getPlayerColor ( entState . player , 160 ) ;
2016-06-11 08:02:34 -07:00
data . button . sprite _disabled = data . button . sprite ;
2016-01-09 14:01:08 -08:00
2016-06-11 08:02:34 -07:00
// Selection panel buttons only appear disabled if they
2020-01-11 02:58:43 -08:00
// also appear disabled to the owner of the structure.
2016-06-11 08:02:34 -07:00
data . icon . sprite =
( canUngarrison || g _IsObserver ? "" : "grayscale:" ) +
"stretched:session/portraits/" + template . icon ;
2016-05-28 07:46:20 -07:00
setPanelObjectPosition ( data . button , data . i , data . rowLength ) ;
2016-06-11 08:02:34 -07:00
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Gate = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 40 - getNumberOfRightPanelButtons ( ) ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 10 ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2025-05-10 07:21:01 -07:00
const hideLocked = unitEntStates . every ( state => ! state . gate || ! state . gate . locked ) ;
const hideUnlocked = unitEntStates . every ( state => ! state . gate || state . gate . locked ) ;
2017-11-05 08:59:09 -08:00
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
}
] ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-16 11:34:27 -07:00
{
2017-11-05 08:59:09 -08:00
data . button . onPress = function ( ) { lockGate ( data . item . locked ) ; } ;
2016-07-01 12:43:26 -07:00
data . button . tooltip = data . item . tooltip ;
2016-09-25 12:38:10 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2017-11-05 08:59:09 -08:00
data . guiSelection . hidden = data . item . hidden ;
data . icon . sprite = "stretched:" + data . item . icon ;
2016-05-28 07:46:20 -07:00
2018-03-13 14:02:13 -07:00
setPanelObjectPosition ( data . button , data . i + getNumberOfRightPanelButtons ( ) , data . rowLength ) ;
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Pack = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 40 - getNumberOfRightPanelButtons ( ) ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 10 ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2025-05-10 07:21:01 -07:00
const checks = { } ;
for ( const state of unitEntStates )
2014-06-16 11:34:27 -07:00
{
if ( ! state . pack )
continue ;
2016-07-02 18:50:55 -07:00
2014-06-16 11:34:27 -07:00
if ( state . pack . progress == 0 )
{
2016-07-02 18:50:55 -07:00
if ( state . pack . packed )
2014-06-16 11:34:27 -07:00
checks . unpackButton = true ;
2016-07-02 18:50:55 -07:00
else
checks . packButton = true ;
2014-06-16 11:34:27 -07:00
}
2017-10-21 10:31:05 -07:00
else if ( state . pack . packed )
checks . unpackCancelButton = true ;
2014-06-16 11:34:27 -07:00
else
2017-10-21 10:31:05 -07:00
checks . packCancelButton = true ;
2014-06-16 11:34:27 -07:00
}
2016-07-02 18:50:55 -07:00
2025-05-10 07:21:01 -07:00
const items = [ ] ;
2014-06-16 11:34:27 -07:00
if ( checks . packButton )
2016-07-01 12:43:26 -07:00
items . push ( {
"packing" : false ,
"packed" : false ,
"tooltip" : translate ( "Pack" ) ,
"callback" : function ( ) { packUnit ( true ) ; }
} ) ;
2016-07-02 18:50:55 -07:00
2014-06-16 11:34:27 -07:00
if ( checks . unpackButton )
2016-07-01 12:43:26 -07:00
items . push ( {
"packing" : false ,
"packed" : true ,
"tooltip" : translate ( "Unpack" ) ,
"callback" : function ( ) { packUnit ( false ) ; }
} ) ;
2016-07-02 18:50:55 -07:00
2014-06-16 11:34:27 -07:00
if ( checks . packCancelButton )
2016-07-01 12:43:26 -07:00
items . push ( {
"packing" : true ,
"packed" : false ,
"tooltip" : translate ( "Cancel Packing" ) ,
"callback" : function ( ) { cancelPackUnit ( true ) ; }
} ) ;
2016-07-02 18:50:55 -07:00
2014-06-16 11:34:27 -07:00
if ( checks . unpackCancelButton )
2016-07-01 12:43:26 -07:00
items . push ( {
"packing" : true ,
"packed" : true ,
"tooltip" : translate ( "Cancel Unpacking" ) ,
"callback" : function ( ) { cancelPackUnit ( false ) ; }
} ) ;
2016-07-02 18:50:55 -07:00
2014-06-16 11:34:27 -07:00
return items ;
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-16 11:34:27 -07:00
{
data . button . onPress = function ( ) { data . item . callback ( data . item ) ; } ;
2016-05-28 07:46:20 -07:00
2014-06-14 08:35:13 -07:00
data . button . tooltip = data . item . tooltip ;
2016-05-28 07:46:20 -07:00
2014-06-14 08:35:13 -07:00
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" ;
2016-01-09 14:01:08 -08:00
2016-09-25 12:38:10 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2016-05-28 07:46:20 -07:00
2018-03-13 14:02:13 -07:00
setPanelObjectPosition ( data . button , data . i + getNumberOfRightPanelButtons ( ) , data . rowLength ) ;
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Queue = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
return 16 ;
} ,
2016-09-25 12:38:10 -07:00
/ * *
* 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 )
2014-06-16 11:34:27 -07:00
{
2021-06-12 02:43:57 -07:00
const queue = [ ] ;
2016-09-25 12:38:10 -07:00
let foundNew = true ;
for ( let i = 0 ; foundNew ; ++ i )
{
foundNew = false ;
2021-06-12 02:43:57 -07:00
for ( const state of unitEntStates )
2016-09-25 12:38:10 -07:00
{
if ( ! state . production || ! state . production . queue [ i ] )
continue ;
2017-08-26 12:01:44 -07:00
queue . push ( {
"producingEnt" : state . id ,
2021-06-12 02:43:57 -07:00
"queuedItem" : state . production . queue [ i ] ,
"autoqueue" : state . production . autoqueue && state . production . queue [ i ] . unitTemplate ,
2017-08-26 12:01:44 -07:00
} ) ;
2016-09-25 12:38:10 -07:00
foundNew = true ;
}
}
2021-06-12 02:43:57 -07:00
if ( ! queue . length )
return queue ;
// Add 'ghost' items to show autoqueues.
const repeat = [ ] ;
for ( const item of queue )
if ( item . autoqueue )
{
const ghostItem = clone ( item ) ;
ghostItem . ghost = true ;
repeat . push ( ghostItem ) ;
}
if ( repeat . length )
for ( let i = 0 ; queue . length < g _SelectionPanels . Queue . getMaxNumberOfItems ( ) ; ++ i )
queue . push ( repeat [ i % repeat . length ] ) ;
2016-09-25 12:38:10 -07:00
return queue ;
2014-06-16 11:34:27 -07:00
} ,
"resizePanel" : function ( numberOfItems , rowLength )
{
2025-05-10 07:21:01 -07:00
const numRows = Math . ceil ( numberOfItems / rowLength ) ;
const panel = Engine . GetGUIObjectByName ( "unitQueuePanel" ) ;
const buttonSize = Engine . GetGUIObjectByName ( "unitQueueButton[0]" ) . size . bottom ;
const margin = 4 ;
2025-05-29 15:23:46 -07:00
panel . size . top = panel . size . bottom - numRows * buttonSize - ( numRows + 2 ) * margin ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2025-05-10 07:21:01 -07:00
const queuedItem = data . item . queuedItem ;
2017-08-26 12:01:44 -07:00
2016-05-28 07:46:20 -07:00
// Differentiate between units and techs
let template ;
2017-08-26 12:01:44 -07:00
if ( queuedItem . unitTemplate )
2026-02-21 08:35:27 -08:00
template = GetTemplateData ( queuedItem . unitTemplate , data . player ) ;
2017-08-26 12:01:44 -07:00
else if ( queuedItem . technologyTemplate )
2018-01-02 09:49:20 -08:00
template = GetTechnologyData ( queuedItem . technologyTemplate , GetSimState ( ) . players [ data . player ] . civ ) ;
2017-08-26 12:01:44 -07:00
else
{
warning ( "Unknown production queue template " + uneval ( queuedItem ) ) ;
2016-05-28 07:46:20 -07:00
return false ;
2017-08-26 12:01:44 -07:00
}
data . button . onPress = function ( ) { removeFromProductionQueue ( data . item . producingEnt , queuedItem . id ) ; } ;
2016-05-28 07:46:20 -07:00
2021-05-21 00:11:05 -07:00
const tooltips = [ getEntityNames ( template ) ] ;
2021-06-12 02:43:57 -07:00
if ( data . item . ghost )
tooltips . push ( translate ( "The auto-queue will try to train this item later." ) ) ;
2017-08-26 12:01:44 -07:00
if ( queuedItem . neededSlots )
2014-06-14 08:35:13 -07:00
{
2023-01-24 00:03:24 -08:00
tooltips . push ( objectionFont ( translate ( "Insufficient population capacity:" ) ) ) ;
2021-05-21 00:11:05 -07:00
tooltips . push ( sprintf ( translate ( "%(population)s %(neededSlots)s" ) , {
2016-11-30 07:35:06 -08:00
"population" : resourceIcon ( "population" ) ,
2017-08-26 12:01:44 -07:00
"neededSlots" : queuedItem . neededSlots
2021-05-21 00:11:05 -07:00
} ) ) ;
2014-06-14 08:35:13 -07:00
}
2024-09-02 04:22:18 -07:00
tooltips . push ( getTemplateViewerOnRightClickTooltip ( template ) ) ;
2021-05-21 00:11:05 -07:00
data . button . tooltip = tooltips . join ( "\n" ) ;
2016-05-28 07:46:20 -07:00
2017-08-26 12:01:44 -07:00
data . countDisplay . caption = queuedItem . count > 1 ? queuedItem . count : "" ;
2016-05-28 07:46:20 -07:00
2022-04-08 22:34:43 -07:00
const progressSlider = Engine . GetGUIObjectByName ( "unitQueueProgressSlider[" + data . i + "]" ) ;
2021-06-12 02:43:57 -07:00
if ( data . item . ghost )
{
data . button . enabled = false ;
2025-05-29 15:23:46 -07:00
progressSlider . sprite = "color:0 150 250 50" ;
2022-04-08 22:34:43 -07:00
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
2025-05-29 15:23:46 -07:00
progressSlider . size . top = progressSlider . size . left ;
2021-06-12 02:43:57 -07:00
}
else
{
// 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" ) ) ;
2022-04-08 22:34:43 -07:00
progressSlider . sprite = "queueProgressSlider" ;
2014-06-14 08:35:13 -07:00
2021-06-12 02:43:57 -07:00
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
2025-05-29 15:23:46 -07:00
progressSlider . size . top = progressSlider . size . left + Math . round ( queuedItem . progress * ( progressSlider . size . right - progressSlider . size . left ) ) ;
2014-06-14 08:35:13 -07:00
2021-06-12 02:43:57 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2021-10-10 12:07:42 -07:00
Engine . GetGUIObjectByName ( "unitQueuePausedIcon[" + data . i + "]" ) . hidden = ! queuedItem . paused ;
if ( queuedItem . paused )
// Translation: String displayed when the research is paused. E.g. by being garrisoned or when not the first item in the queue.
2022-03-31 22:22:49 -07:00
data . button . tooltip += "\n" + translate ( "This item is paused." ) ;
2021-06-12 02:43:57 -07:00
}
2016-05-28 07:46:20 -07:00
if ( template . icon )
2021-10-10 12:07:42 -07:00
{
let modifier = "stretched:" ;
if ( queuedItem . paused )
modifier += "color:0 0 0 127:grayscale:" ;
else if ( data . item . ghost )
modifier += "grayscale:" ;
data . icon . sprite = modifier + "session/portraits/" + template . icon ;
}
2016-01-09 14:01:08 -08:00
2016-05-28 07:46:20 -07:00
2021-05-21 00:11:05 -07:00
const showTemplateFunc = ( ) => { showTemplateDetails ( data . item . queuedItem . unitTemplate || data . item . queuedItem . technologyTemplate , data . playerState . civ ) ; } ;
data . button . onPressRight = showTemplateFunc ;
data . button . onPressRightDisabled = showTemplateFunc ;
2016-05-28 07:46:20 -07:00
setPanelObjectPosition ( data . button , data . i , data . rowLength ) ;
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Research = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 10 ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 10 ,
2025-09-24 07:52:59 -07:00
"init" : function ( )
{
const updateAffectsIconVisibility = ( ) =>
{
this . helper . showAffectsIcons = Engine . ConfigDB _GetValue ( "user" , "gui.session.techarrows" ) === "true" ;
} ;
registerConfigChangeHandler ( changes =>
{
if ( changes . has ( "gui.session.techarrows" ) )
updateAffectsIconVisibility ( ) ;
// They will be rerendered with the new visibility next frame.
} ) ;
updateAffectsIconVisibility ( ) ;
} ,
"reset" : function ( )
{
this . helper . occupiedPositions = new Set ( ) ;
this . helper . bottomRowButtonCount = 0 ;
} ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2025-09-24 07:52:59 -07:00
if ( getNumberOfRightPanelButtons ( ) >= this . rowLength * 2 )
return [ ] ;
2016-09-25 12:38:10 -07:00
let ret = [ ] ;
if ( unitEntStates . length == 1 )
2022-01-10 22:34:07 -08:00
{
const entState = unitEntStates [ 0 ] ;
if ( ! entState ? . researcher ? . technologies )
return ret ;
if ( ! entState . production )
warn ( "Researcher without ProductionQueue found: " + entState . id + "." ) ;
return entState . researcher . technologies . map ( tech => ( {
"tech" : tech ,
"techCostMultiplier" : entState . researcher . techCostMultiplier ,
"researchFacilityId" : entState . id ,
"isUpgrading" : ! ! entState . upgrade && entState . upgrade . isUpgrading
} ) ) ;
}
2016-05-17 12:05:18 -07:00
2025-05-10 07:21:01 -07:00
const sortedEntStates = unitEntStates . sort ( ( a , b ) =>
2021-04-08 22:55:05 -07:00
( ! b . upgrade || ! b . upgrade . isUpgrading ) - ( ! a . upgrade || ! a . upgrade . isUpgrading ) ||
2025-05-13 06:47:44 -07:00
( ! a . production ? 0 : a . production . queue . length ) - ( ! b . production ? 0 : b . production . queue . length )
2025-05-30 12:33:49 -07:00
) ;
2020-10-04 03:20:20 -07:00
2025-05-10 07:21:01 -07:00
for ( const state of sortedEntStates )
2014-06-18 04:23:22 -07:00
{
2021-11-15 23:08:39 -08:00
if ( ! state . researcher || ! state . researcher . technologies )
2016-09-25 12:38:10 -07:00
continue ;
2022-01-10 22:34:07 -08:00
if ( ! state . production )
warn ( "Researcher without ProductionQueue found: " + state . id + "." ) ;
2020-10-04 03:20:20 -07:00
2016-09-25 12:38:10 -07:00
// Remove the techs we already have in ret (with the same name and techCostMultiplier)
2021-11-15 23:08:39 -08:00
const filteredTechs = state . researcher . technologies . filter (
2016-09-25 12:38:10 -07:00
tech => tech != null && ! ret . some (
2016-09-29 03:25:48 -07:00
item =>
( item . tech == tech ||
item . tech . pair &&
tech . pair &&
2026-05-23 14:27:31 -07:00
item . tech . pair ? . [ 0 ] == tech . pair ? . [ 0 ] &&
item . tech . pair ? . [ 1 ] == tech . pair ? . [ 1 ] ) &&
2016-09-29 03:25:48 -07:00
Object . keys ( item . techCostMultiplier ) . every (
2021-11-15 23:08:39 -08:00
k => item . techCostMultiplier [ k ] == state . researcher . techCostMultiplier [ k ] )
2017-10-21 10:31:05 -07:00
) ) ;
2016-09-25 12:38:10 -07:00
2025-09-24 07:52:59 -07:00
if ( filteredTechs . length + ret . length <= this . getMaxNumberOfItems ( ) )
2016-09-25 12:38:10 -07:00
ret = ret . concat ( filteredTechs . map ( tech => ( {
2016-05-26 12:56:14 -07:00
"tech" : tech ,
2021-11-15 23:08:39 -08:00
"techCostMultiplier" : state . researcher . techCostMultiplier ,
2020-10-04 03:20:20 -07:00
"researchFacilityId" : state . id ,
"isUpgrading" : ! ! state . upgrade && state . upgrade . isUpgrading
2016-09-25 12:38:10 -07:00
} ) ) ) ;
2014-06-18 04:23:22 -07:00
}
2016-09-25 12:38:10 -07:00
return ret ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"hideItem" : function ( i , rowLength ) // Called when no item is found
2014-06-14 08:35:13 -07:00
{
2016-05-28 07:46:20 -07:00
Engine . GetGUIObjectByName ( "unitResearchButton[" + i + "]" ) . hidden = true ;
2025-09-24 07:52:59 -07:00
// Remove the button it would have been paired with as well.
Engine . GetGUIObjectByName ( "unitResearchButton[" + ( i + this . getMaxNumberOfItems ( ) ) + "]" ) . hidden = true ;
2014-06-14 08:35:13 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2016-05-27 01:37:44 -07:00
if ( ! data . item . tech )
{
2025-09-24 07:52:59 -07:00
this . hideItem ( data . i , data . rowLength ) ;
return false ;
}
// There are twice as many button objects than this.getMaxNumberOfItems()
// This is because each item could be a tech pair and need a second one in addition to the one at data.i
data . j = data . i + this . getMaxNumberOfItems ( ) ;
const playerState = GetSimState ( ) . players [ data . player ] ;
if ( data . item . tech . pair )
{
const firstTemplate = GetTechnologyData ( data . item . tech . pair [ 0 ] , playerState . civ ) ;
const secondTemplate = GetTechnologyData ( data . item . tech . pair [ 1 ] , playerState . civ ) ;
// template.reqs is false if the tech isn't researchable by the current civ.
const firstResearchable = ! ! firstTemplate ? . reqs ;
const secondResearchable = ! ! secondTemplate ? . reqs ;
if ( firstResearchable && secondResearchable )
// Ideal/expected case: Display both techs in a pair.
return this . helper . setupButtonPair ( data , data . item . tech . pair [ 0 ] , data . item . tech . pair [ 1 ] , firstTemplate ,
secondTemplate , playerState ) ;
// At least one of the two is not valid or researchable. If the other one is, display it as a single tech
// on its own.
if ( firstResearchable && ! secondResearchable )
return this . helper . setupSingleButton ( data , data . item . tech . pair [ 0 ] , firstTemplate , playerState ) ;
if ( ! firstResearchable && secondResearchable )
return this . helper . setupSingleButton ( data , data . item . tech . pair [ 1 ] , secondTemplate , playerState ) ;
// Neither of the two are valid and researchable.
this . hideItem ( data . i , data . rowLength ) ;
2016-05-27 01:37:44 -07:00
return false ;
}
2016-05-25 23:28:23 -07:00
2025-09-24 07:52:59 -07:00
const template = GetTechnologyData ( data . item . tech , playerState . civ ) ;
// template.reqs is false if the tech isn't researchable by the current civ.
if ( template ? . reqs )
return this . helper . setupSingleButton ( data , data . item . tech , template , playerState ) ;
this . hideItem ( data . i , data . rowLength ) ;
return false ;
} ,
"helper" : {
// Techs can optionally define a placeBelow property that specifies a unit whose training button they want to be placed below.
// It can be:
// - "{UnlockedUnit}": the first unit whose requirements (of the Identity component) contain the tech.
// - "{AffectedUnit}": the first unit whose stats are modified (receives buffs or debuffs) by the tech.
// - class combination: the first unit whose identity classes match that combination.
"findTargetTrainingButton" : function ( data , techName , template )
{
// Also check whether the other right panel buttons (training, constructing, upgrading) reach the second row.
// In that case, we want to place all techs in the bottom row. Research buttons should never be placed in the
// same row as these.
if ( ! template . placeBelow || getNumberOfRightPanelButtons ( ) > data . rowLength )
return - 1 ;
const indices = [ ] ;
if ( template . placeBelow === "{UnlockedUnit}" )
{
getAllTrainableEntitiesFromSelection ( ) . forEach ( ( trainableTemplate , i ) =>
{
if ( GetTemplateData ( trainableTemplate , data . player ) ? . requirements ? . Techs ? . _string . split ( /\s+/ ) . includes ( techName ) )
indices . push ( i ) ;
} ) ;
}
else
{
let targetClassList ;
if ( template . placeBelow === "{AffectedUnit}" )
{
const affectsList = ( template . affects || [ ] ) ;
for ( const mod of template . modifications )
if ( mod . affects )
affectsList . push ( mod . affects ) ;
targetClassList = affectsList . map ( classes => classes . split ( /\s+/ ) ) ;
}
else
targetClassList = [ template . placeBelow . split ( /\s+/ ) ] ;
getAllTrainableEntitiesFromSelection ( ) . forEach ( ( trainableTemplate , i ) =>
{
if ( MatchesClassList ( GetTemplateData ( trainableTemplate , data . player ) . visibleIdentityClasses , targetClassList ) )
indices . push ( i ) ;
} ) ;
}
// Only choose a training button if it's the only matching one.
if ( indices . length !== 1 )
return - 1 ;
// Make sure to account for the other buttons placed before the unit training ones.
return indices [ 0 ] + [ "Construction" , "Pack" , "Gate" , "Upgrade" ] . reduce ( ( total , panel ) =>
total + g _unitPanelButtons [ panel ] , 0
) ;
} ,
"setupSingleButton" : function ( data , techName , template , playerState )
{
// The item is not a tech pair. So hide the button that data.button would have been paired with.
Engine . GetGUIObjectByName ( "unitResearchButton[" + data . j + "]" ) . hidden = true ;
// Note: The GUI object container of the research buttons (unlike the one of the training buttons) only reaches up to the second row.
// This means that, for example, a research button with position 5 is located directly one row under a training button with position 5.
let position = this . findTargetTrainingButton ( data , techName , template ) ;
2016-05-25 23:28:23 -07:00
2025-09-24 07:52:59 -07:00
let placeInBottomRow = position == - 1 ;
if ( ! placeInBottomRow && this . occupiedPositions . has ( position ) )
{
// Try to fall back to the third (second-to-bottom) row.
position += data . rowLength ;
if ( this . occupiedPositions . has ( position ) )
// Both positions below the target unit are already used by other techs.
// Note: Ideally this should never occur. Two techs per unit should be the limit. This here is just edge case handling.
placeInBottomRow = true ;
}
if ( placeInBottomRow )
{
// Try to move it to the fourth (bottom) row.
if ( this . bottomRowButtonCount >= data . rowLength )
return false ; // Bottom row is full, we can't display it.
position = this . bottomRowButtonCount + data . rowLength * 2 ;
}
2014-06-14 08:35:13 -07:00
2025-09-24 07:52:59 -07:00
Engine . GetGUIObjectByName ( "unitResearchVerticalPairIcon[" + data . i + "]" ) . hidden = true ;
Engine . GetGUIObjectByName ( "unitResearchHorizontalPairIcon[" + data . i + "]" ) . hidden = true ;
2014-06-14 08:35:13 -07:00
2025-09-24 07:52:59 -07:00
// When it's not "active", it's grayed out.
const buttonActive = this . buildButton ( data , techName , template , position , playerState , data . button , data . icon ) ;
this . buildAffectsIcon ( data . i , ! placeInBottomRow , buttonActive ) ;
return true ;
} ,
"setupButtonPair" : function ( data , firstTechName , secondTechName , firstTemplate , secondTemplate , playerState )
2016-05-28 07:46:20 -07:00
{
2025-09-24 07:52:59 -07:00
// Note: The GUI object container of the research buttons (unlike the one of the training buttons) only
// reaches up to the second row. This means that, for example, a research button with position 5 is located
// directly one row under a training button with position 5.
let firstPosition = this . findTargetTrainingButton ( data , firstTechName , firstTemplate ) ;
let secondPosition = this . findTargetTrainingButton ( data , secondTechName , secondTemplate ) ;
// Possible placements of tech pair with descending preference:
// - Vertically below a single unit.
// - Horizontally below two adjacent units.
// - Horizontally adjacent below no unit in the bottom row.
// Only ever place either below a unit, if the other can be too and below the same or an adjacent one.
let placeInBottomRow = firstPosition == - 1 || secondPosition == - 1 || Math . abs ( firstPosition - secondPosition ) > 1 ;
let placeHorizontally = true ;
if ( ! placeInBottomRow && firstPosition === secondPosition )
{
// Both want to be placed under the same unit.
// Try to place the pair vertically by moving the second one down to the third (second-to-bottom) row,
// below the first one.
secondPosition += data . rowLength ;
if ( this . occupiedPositions . has ( firstPosition ) || this . occupiedPositions . has ( secondPosition ) )
placeInBottomRow = true ;
else
placeHorizontally = false ;
}
else if ( ! placeInBottomRow && ( this . occupiedPositions . has ( firstPosition ) || this . occupiedPositions . has ( secondPosition ) ) )
{
// At least one of the two respective positions in the second (third-to-bottom) row is occupied.
// So try move both to the third.
firstPosition += data . rowLength ;
secondPosition += data . rowLength ;
if ( this . occupiedPositions . has ( firstPosition ) || this . occupiedPositions . has ( secondPosition ) )
// Neither the two positions in the second row nor the third row below the target training buttons
// are available.
placeInBottomRow = true ;
}
2014-06-15 00:56:40 -07:00
2025-09-24 07:52:59 -07:00
if ( placeInBottomRow )
2021-01-15 00:55:01 -08:00
{
2025-09-24 07:52:59 -07:00
// Try to move both to the bottom row.
if ( this . bottomRowButtonCount >= data . rowLength - 1 )
// Not enough space in the bottom row for both of them. We can't display them.
return false ;
firstPosition = this . bottomRowButtonCount + data . rowLength * 2 ;
secondPosition = firstPosition + 1 ;
2021-01-15 00:55:01 -08:00
}
2025-09-24 07:52:59 -07:00
// Note: the button indices here aren't related to positioning at all.
const firstButtonIndex = data . i ;
const secondButtonIndex = data . j ;
const firstButton = data . button ;
const secondButton = Engine . GetGUIObjectByName ( "unitResearchButton[" + secondButtonIndex + "]" ) ;
const firstIcon = data . icon ;
const secondIcon = Engine . GetGUIObjectByName ( "unitResearchIcon[" + secondButtonIndex + "]" ) ;
// When it's not "active", it's grayed out.
const firstButtonActive = this . buildButton ( data , firstTechName , firstTemplate , firstPosition , playerState ,
firstButton , firstIcon ) ;
this . buildAffectsIcon ( firstButtonIndex , ! placeInBottomRow , firstButtonActive ) ;
// When it's not "active", it's grayed out.
const secondButtonActive = this . buildButton ( data , secondTechName , secondTemplate , secondPosition , playerState ,
secondButton , secondIcon ) ;
this . buildAffectsIcon ( secondButtonIndex , ! placeInBottomRow && placeHorizontally , secondButtonActive ) ;
this . buildPairIcon ( false , firstButtonIndex , placeHorizontally && secondPosition > firstPosition , firstButtonActive ) ;
this . buildPairIcon ( false , secondButtonIndex , placeHorizontally && secondPosition < firstPosition , secondButtonActive ) ;
this . buildPairIcon ( true , firstButtonIndex , ! placeHorizontally , firstButtonActive ) ;
this . buildPairIcon ( true , secondButtonIndex , false , secondButtonActive ) ;
// While hovering over either button, show a cross over the other one.
// TODO: The following lines have to be executed only once, technically, and not every this function is called.
const firstUnchosenIcon = Engine . GetGUIObjectByName ( "unitResearchUnchosenIcon[" + firstButtonIndex + "]" ) ;
const secondUnchosenIcon = Engine . GetGUIObjectByName ( "unitResearchUnchosenIcon[" + secondButtonIndex + "]" ) ;
firstButton . onMouseEnter = ( ) => { secondUnchosenIcon . hidden = false ; } ;
firstButton . onMouseLeave = ( ) => { secondUnchosenIcon . hidden = true ; } ;
secondButton . onMouseEnter = ( ) => { firstUnchosenIcon . hidden = false ; } ;
secondButton . onMouseLeave = ( ) => { firstUnchosenIcon . hidden = true ; } ;
return true ;
} ,
"buildAffectsIcon" : function ( i , show , enable )
{
const icon = Engine . GetGUIObjectByName ( "unitResearchAffectsIcon[" + i + "]" ) ;
const hidden = ! show || ! this . showAffectsIcons ;
icon . hidden = hidden ;
if ( ! hidden )
icon . sprite = "stretched:session/icons/" + ( enable ? "tech_affects.png" : "tech_affects_disabled.png" ) ;
} ,
"buildPairIcon" : function ( vertical , i , show , enable )
{
const icon = Engine . GetGUIObjectByName ( "unitResearch" + ( vertical ? "Vertical" : "Horizontal" ) + "PairIcon[" + i + "]" ) ;
icon . hidden = ! show ;
if ( show )
icon . sprite = "stretched:session/icons/" +
( vertical ?
enable ? "vertical_tech_pair.png" : "vertical_tech_pair_disabled.png" :
enable ? "horizontal_tech_pair.png" : "horizontal_tech_pair_disabled.png" ) ;
} ,
"buildButton" : function ( baseData , techName , template , position , playerState , button , icon )
{
// Make sure to not modify the original template.
const adaptedTemplate = clone ( template ) ;
for ( const res in adaptedTemplate . cost )
adaptedTemplate . cost [ res ] *=
baseData . item . techCostMultiplier [ res ] !== undefined ? baseData . item . techCostMultiplier [ res ] : 1 ;
2014-06-14 08:35:13 -07:00
2025-05-10 07:21:01 -07:00
const neededResources = Engine . GuiInterfaceCall ( "GetNeededResources" , {
2025-09-24 07:52:59 -07:00
"cost" : adaptedTemplate . cost ,
"player" : baseData . player
2016-05-28 07:46:20 -07:00
} ) ;
2014-06-14 08:35:13 -07:00
2025-05-10 07:21:01 -07:00
const requirementsPassed = Engine . GuiInterfaceCall ( "CheckTechnologyRequirements" , {
2025-09-24 07:52:59 -07:00
"tech" : techName ,
"player" : baseData . player
2016-05-28 07:46:20 -07:00
} ) ;
2014-06-14 08:35:13 -07:00
2025-05-10 07:21:01 -07:00
const tooltips = [
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
getEntityNamesFormatted ,
getEntityTooltip ,
2018-02-21 13:39:00 -08:00
getEntityCostTooltip ,
2024-09-02 04:22:18 -07:00
getTemplateViewerOnRightClickTooltip
2025-09-24 07:52:59 -07:00
] . map ( func => func ( adaptedTemplate ) ) ;
2014-06-14 08:35:13 -07:00
2016-05-28 07:46:20 -07:00
if ( ! requirementsPassed )
2014-06-14 08:35:13 -07:00
{
2025-09-24 07:52:59 -07:00
let tip = adaptedTemplate . requirementsTooltip ;
const reqs = adaptedTemplate . reqs ;
2025-05-10 07:21:01 -07:00
for ( const req of reqs )
2014-06-14 08:35:13 -07:00
{
2017-01-08 06:00:20 -08:00
if ( ! req . entities )
continue ;
2025-05-10 07:21:01 -07:00
const entityCounts = [ ] ;
for ( const entity of req . entities )
2017-01-08 06:00:20 -08:00
{
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 ;
2025-05-12 12:26:08 -07:00
default :
error ( "Unknow value in entity requirement check: " + entity . check ) ;
2017-01-08 06:00:20 -08:00
}
2025-05-10 07:21:01 -07:00
const remaining = entity . number - current ;
2017-01-08 06:00:20 -08:00
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 ,
2021-04-22 00:41:56 -07:00
"class" : translate ( entity . class )
2017-01-08 06:00:20 -08:00
} ) ) ;
}
tip += " " + sprintf ( translate ( "Remaining: %(entityCounts)s" ) , {
2017-06-03 09:30:18 -07:00
"entityCounts" : entityCounts . join ( translateWithContext ( "Separator for a list of entity counts" , ", " ) )
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
} ) ;
2014-06-14 08:35:13 -07:00
}
2023-01-24 00:03:24 -08:00
tooltips . push ( objectionFont ( tip ) ) ;
2014-06-14 08:35:13 -07:00
}
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
tooltips . push ( getNeededResourcesTooltip ( neededResources ) ) ;
button . tooltip = tooltips . filter ( tip => tip ) . join ( "\n" ) ;
2016-05-28 07:46:20 -07:00
2025-12-30 00:57:37 -08:00
button . onPress = ( t => function ( )
{
2025-09-24 07:52:59 -07:00
addResearchToQueue ( baseData . item . researchFacilityId , t ) ;
} ) ( techName ) ;
2016-05-28 07:46:20 -07:00
2025-12-30 00:57:37 -08:00
const showTemplateFunc = ( t => function ( )
{
2018-03-13 14:02:13 -07:00
showTemplateDetails (
2018-03-20 15:56:00 -07:00
t ,
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
GetTemplateData ( baseData . unitEntStates . find ( state => state . id == baseData . item . researchFacilityId ) . templateName , state . player ) . nativeCiv
2026-02-21 08:35:27 -08:00
) ;
2020-11-14 10:16:24 -08:00
} ) ;
2025-09-24 07:52:59 -07:00
button . onPressRight = showTemplateFunc ( techName ) ;
button . onPressRightDisabled = showTemplateFunc ( techName ) ;
2016-05-28 07:46:20 -07:00
2014-06-15 00:56:40 -07:00
button . hidden = false ;
2016-05-28 07:46:20 -07:00
let modifier = "" ;
2025-09-24 07:52:59 -07:00
let isActive = true ;
2016-05-28 07:46:20 -07:00
if ( ! requirementsPassed )
2014-06-14 08:35:13 -07:00
{
2014-06-15 00:56:40 -07:00
button . enabled = false ;
2016-05-28 07:46:20 -07:00
modifier += "color:0 0 0 127:grayscale:" ;
2025-09-24 07:52:59 -07:00
isActive = false ;
2014-06-14 08:35:13 -07:00
}
2016-05-28 07:46:20 -07:00
else if ( neededResources )
2014-06-14 08:35:13 -07:00
{
2014-06-15 00:56:40 -07:00
button . enabled = false ;
2016-05-28 07:46:20 -07:00
modifier += resourcesToAlphaMask ( neededResources ) + ":" ;
2014-06-14 08:35:13 -07:00
}
else
2025-09-24 07:52:59 -07:00
button . enabled = controlsPlayer ( baseData . player ) ;
2015-12-13 08:03:17 -08:00
2025-09-24 07:52:59 -07:00
if ( baseData . item . isUpgrading )
2020-10-04 03:20:20 -07:00
{
button . enabled = false ;
modifier += "color:0 0 0 127:grayscale:" ;
2025-09-24 07:52:59 -07:00
isActive = false ;
2023-01-24 00:03:24 -08:00
button . tooltip += "\n" + objectionFont ( translate ( "Cannot research while upgrading." ) ) ;
2020-10-04 03:20:20 -07:00
}
2025-09-24 07:52:59 -07:00
if ( adaptedTemplate . icon )
icon . sprite = modifier + "stretched:session/portraits/" + adaptedTemplate . icon ;
2016-05-28 07:46:20 -07:00
2025-09-24 07:52:59 -07:00
this . occupiedPositions . add ( position ) ;
if ( position >= 2 * baseData . rowLength )
this . bottomRowButtonCount ++ ;
2016-05-28 07:46:20 -07:00
2025-09-24 07:52:59 -07:00
// The panel is a bit higher than 4 * baseData.rowLength, which allows us to visibility anchor the buttons
// in the bottom row to the bottom by moving them down those few pixels. Else the gap would be at the bottom.
// This creates a small spatial separation between the "generic" techs in the bottom row and the "specific"
// techs above them.
const vOffset = position >= baseData . rowLength * 2 ? 6 : 0 ;
setPanelObjectPosition ( button , position , baseData . rowLength , 1 , 1 , vOffset ) ;
2016-05-28 07:46:20 -07:00
2025-09-24 07:52:59 -07:00
return isActive ;
}
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Selection = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
return 16 ;
} ,
2014-06-14 08:35:13 -07:00
"rowLength" : 4 ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2016-09-25 12:38:10 -07:00
if ( unitEntStates . length < 2 )
2014-06-16 11:34:27 -07:00
return [ ] ;
2016-11-21 13:35:26 -08:00
return g _Selection . groups . getEntsGrouped ( ) ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2025-05-10 07:21:01 -07:00
const entState = GetEntityState ( data . item . ents [ 0 ] ) ;
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
const template = GetTemplateData ( entState . templateName , entState . player ) ;
2016-05-28 07:46:20 -07:00
if ( ! template )
2014-06-14 08:35:13 -07:00
return false ;
2014-07-31 05:46:33 -07:00
2025-05-10 07:21:01 -07:00
for ( const ent of data . item . ents )
2014-07-31 05:46:33 -07:00
{
2025-05-10 07:21:01 -07:00
const state = GetEntityState ( ent ) ;
2014-07-31 05:46:33 -07:00
if ( state . resourceCarrying && state . resourceCarrying . length !== 0 )
{
if ( ! data . carried )
data . carried = { } ;
2025-05-10 07:21:01 -07:00
const carrying = state . resourceCarrying [ 0 ] ;
2014-07-31 05:46:33 -07:00
if ( data . carried [ carrying . type ] )
data . carried [ carrying . type ] += carrying . amount ;
else
data . carried [ carrying . type ] = carrying . amount ;
}
2015-01-12 13:39:31 -08:00
if ( state . trader && state . trader . goods && state . trader . goods . amount )
{
if ( ! data . carried )
data . carried = { } ;
2025-05-10 07:21:01 -07:00
const amount = state . trader . goods . amount ;
const type = state . trader . goods . type ;
2016-05-28 07:46:20 -07:00
let totalGain = amount . traderGain ;
2015-01-12 13:39:31 -08:00
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 ;
}
2014-07-31 05:46:33 -07:00
}
2016-05-28 07:46:20 -07:00
2025-05-10 07:21:01 -07:00
const unitOwner = GetEntityState ( data . item . ents [ 0 ] ) . player ;
2016-05-28 07:46:20 -07:00
let tooltip = getEntityNames ( template ) ;
2014-07-31 05:46:33 -07:00
if ( data . carried )
2016-05-10 17:07:38 -07:00
tooltip += "\n" + Object . keys ( data . carried ) . map ( res =>
2016-11-30 07:35:06 -08:00
resourceIcon ( res ) + data . carried [ res ]
2016-05-10 17:07:38 -07:00
) . join ( " " ) ;
2016-11-21 13:35:26 -08:00
if ( g _IsObserver )
tooltip += "\n" + sprintf ( translate ( "Player: %(playername)s" ) , {
"playername" : g _Players [ unitOwner ] . name
} ) ;
2016-05-10 17:07:38 -07:00
data . button . tooltip = tooltip ;
2017-01-20 03:16:20 -08:00
2019-10-11 05:28:12 -07:00
data . guiSelection . sprite = "color:" + g _DiplomacyColors . getPlayerColor ( unitOwner , 160 ) ;
2017-01-20 03:16:20 -08:00
data . guiSelection . hidden = ! g _IsObserver ;
2016-05-28 07:46:20 -07:00
2023-08-16 02:56:57 -07:00
data . countDisplay . caption = data . item . ents . length > 1 ? data . item . ents . length : "" ;
2016-05-28 07:46:20 -07:00
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
2021-10-02 23:09:01 -07:00
if ( Engine . HotkeyIsPressed ( "session.deselectgroup" ) )
removeFromSelectionGroup ( data . item . key ) ;
else
makePrimarySelectionGroup ( data . item . key ) ;
} ;
data . button . onPressRight = function ( ) { removeFromSelectionGroup ( data . item . key ) ; } ;
2016-05-28 07:46:20 -07:00
if ( template . icon )
data . icon . sprite = "stretched:session/portraits/" + template . icon ;
setPanelObjectPosition ( data . button , data . i , data . rowLength ) ;
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Stance = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
return 5 ;
} ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2014-06-16 11:34:27 -07:00
{
2026-08-07 01:47:30 -07:00
if ( unitEntStates . some ( state => ! state . unitAI || ! hasClass ( state , "Unit" ) || hasClass ( state , "Animal" ) ) ||
unitEntStates . every ( state => state . turretable && state . turretable . holder !== INVALID _ENTITY ) )
2014-06-16 11:34:27 -07:00
return [ ] ;
2016-09-25 12:38:10 -07:00
2026-08-07 01:47:30 -07:00
return Object . keys ( this . stancesData ) ;
2014-06-16 11:34:27 -07:00
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2025-05-10 07:21:01 -07:00
const unitIds = data . unitEntStates . map ( state => state . id ) ;
2018-03-13 14:02:13 -07:00
data . button . onPress = function ( ) { performStance ( unitIds , data . item ) ; } ;
2016-05-28 07:46:20 -07:00
2026-08-07 01:47:30 -07:00
data . button . tooltip = this . stancesData [ data . item ] . Name + "\n" + bodyFont ( this . stancesData [ data . item ] . Tooltip ) ;
2016-05-28 07:46:20 -07:00
data . guiSelection . hidden = ! Engine . GuiInterfaceCall ( "IsStanceSelected" , {
2018-03-13 14:02:13 -07:00
"ents" : unitIds ,
2014-06-14 08:35:13 -07:00
"stance" : data . item
} ) ;
2016-05-28 07:46:20 -07:00
data . icon . sprite = "stretched:session/icons/stances/" + data . item + ".png" ;
2016-09-25 12:38:10 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2016-05-28 07:46:20 -07:00
setPanelObjectPosition ( data . button , data . i , data . rowLength ) ;
return true ;
2026-08-07 01:47:30 -07:00
} ,
"stancesData" : {
"violent" : {
"Name" : translateWithContext ( "stance" , "Violent" ) ,
"Tooltip" : translateWithContext ( "stance" , "Attack nearby opponents, focus on attackers and chase while visible" )
} ,
"aggressive" : {
"Name" : translateWithContext ( "stance" , "Aggressive" ) ,
"Tooltip" : translateWithContext ( "stance" , "Attack nearby opponents" )
} ,
"defensive" : {
"Name" : translateWithContext ( "stance" , "Defensive" ) ,
"Tooltip" : translateWithContext ( "stance" , "Attack nearby opponents, chase a short distance and return to the original location" )
} ,
"passive" : {
"Name" : translateWithContext ( "stance" , "Passive" ) ,
"Tooltip" : translateWithContext ( "stance" , "Flee if attacked" )
} ,
"standground" : {
"Name" : translateWithContext ( "stance" , "Standground" ) ,
"Tooltip" : translateWithContext ( "stance" , "Attack opponents in range, but don't move" )
}
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
g _SelectionPanels . Training = {
2014-06-18 04:23:22 -07:00
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 40 - getNumberOfRightPanelButtons ( ) ;
2014-06-18 04:23:22 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 10 ,
2014-06-16 11:34:27 -07:00
"getItems" : function ( )
{
return getAllTrainableEntitiesFromSelection ( ) ;
} ,
2016-05-28 07:46:20 -07:00
"setupButton" : function ( data )
2014-06-14 08:35:13 -07:00
{
2025-05-10 07:21:01 -07:00
const template = GetTemplateData ( data . item , data . player ) ;
2016-05-28 07:46:20 -07:00
if ( ! template )
2014-06-14 08:35:13 -07:00
return false ;
2016-01-09 14:01:08 -08:00
2022-11-24 03:20:11 -08:00
const requirementsMet = Engine . GuiInterfaceCall ( "AreRequirementsMet" , {
"requirements" : template . requirements ,
2016-09-25 12:38:10 -07:00
"player" : data . player
2016-01-09 14:01:08 -08:00
} ) ;
2014-06-14 08:35:13 -07:00
2025-05-10 07:21:01 -07:00
const unitIds = data . unitEntStates . map ( status => status . id ) ;
const [ buildingsCountToTrainFullBatch , fullBatchSize , remainderBatch ] =
2018-03-13 14:02:13 -07:00
getTrainingStatus ( unitIds , data . item , data . playerState ) ;
2016-01-09 15:01:07 -08:00
2025-05-10 07:21:01 -07:00
const trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch ;
2014-06-14 08:35:13 -07:00
2016-05-28 07:46:20 -07:00
let neededResources ;
if ( template . cost )
neededResources = Engine . GuiInterfaceCall ( "GetNeededResources" , {
"cost" : multiplyEntityCosts ( template , trainNum ) ,
2016-09-25 12:38:10 -07:00
"player" : data . player
2016-01-09 14:01:08 -08:00
} ) ;
2014-06-14 08:35:13 -07:00
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
2026-03-18 15:05:12 -07:00
addTrainingToQueue ( unitIds , data . item , data . playerState ) ;
2016-09-25 12:38:10 -07:00
} ;
2020-11-14 10:16:24 -08:00
2025-05-10 07:21:01 -07:00
const showTemplateFunc = ( ) => { showTemplateDetails ( data . item , data . playerState . civ ) ; } ;
2020-11-14 10:16:24 -08:00
data . button . onPressRight = showTemplateFunc ;
data . button . onPressRightDisabled = showTemplateFunc ;
2016-05-28 07:46:20 -07:00
data . countDisplay . caption = trainNum > 1 ? trainNum : "" ;
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
let tooltips = [
"[font=\"sans-bold-16\"]" +
colorizeHotkey ( "%(hotkey)s" , "session.queueunit." + ( data . i + 1 ) ) +
"[/font]" + " " + getEntityNamesFormatted ( template ) ,
getVisibleEntityClassesFormatted ( template ) ,
getAurasTooltip ( template ) ,
getEntityTooltip ( template ) ,
2019-12-14 12:10:32 -08:00
getEntityCostTooltip ( template , data . player , unitIds [ 0 ] , buildingsCountToTrainFullBatch , fullBatchSize , remainderBatch )
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
] ;
2025-05-10 07:21:01 -07:00
const limits = getEntityLimitAndCount ( data . playerState , data . item ) ;
2020-12-29 03:00:54 -08:00
tooltips . push ( formatLimitString ( limits . entLimit , limits . entCount , limits . entLimitChangers ) ,
formatMatchLimitString ( limits . matchLimit , limits . matchCount , limits . type ) ) ;
2014-06-14 08:35:13 -07:00
if ( Engine . ConfigDB _GetValue ( "user" , "showdetailedtooltips" ) === "true" )
2016-07-02 21:08:52 -07:00
tooltips = tooltips . concat ( [
getHealthTooltip ,
getAttackTooltip ,
getHealerTooltip ,
2020-08-27 03:24:59 -07:00
getResistanceTooltip ,
2016-07-02 21:08:52 -07:00
getGarrisonTooltip ,
2021-03-26 03:18:30 -07:00
getTurretsTooltip ,
2016-07-02 21:08:52 -07:00
getProjectilesTooltip ,
2020-09-16 08:28:44 -07:00
getSpeedTooltip ,
getResourceDropsiteTooltip
2016-07-02 21:08:52 -07:00
] . map ( func => func ( template ) ) ) ;
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
2024-09-02 04:22:18 -07:00
tooltips . push ( getTemplateViewerOnRightClickTooltip ( ) ) ;
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
tooltips . push (
2017-12-04 09:29:26 -08:00
formatBatchTrainingString ( buildingsCountToTrainFullBatch , fullBatchSize , remainderBatch ) ,
2022-11-24 03:20:11 -08:00
getRequirementsTooltip ( requirementsMet , template . requirements , GetSimState ( ) . players [ data . player ] . civ ) ,
2016-10-26 16:19:19 -07:00
getNeededResourcesTooltip ( neededResources ) ) ;
2014-06-14 08:35:13 -07:00
Tooltip overhaul.
Also contains a patch by fatherbushido for attackRateDetails and
getAttackTooltip to
not show something broken for buildingAI units that can capture, fixes
#4061 (see #4000).
Make tooltip functions uniform.
Pass template everywhere
instead of template.armour in getArmorTooltip
rate in getRepairRateTooltip and getBuildRateTooltip and
entState in attackRateDetails.
Add an early return for every tooltip function.
Use empty string instead of "Armor: (None)" for trees etc..
Don't prefix tooltip return values with "\n", but let the user of that
function add them.
Thus make tooltip concatenation much nicer (f.e. draw.js).
Use a loop instead of duplicating per damage type in damageTypesToText.
Add font functions to avoid duplicating tag code.
Merge sprintf's and inline variables.
Add few TODOs.
Fix some strings:
Use "%(specificName)s %(fontStart)s(%(genericName)s)%(fontEnd)s")
instead of "(" + foo + ")" ...
Use existing "%(percentage)s%%" instead of foo + "%" in
armorLevelToPercentageString.
Remove
duplication by calling/introducing shared functions (getEntityTooltip,
getHealthTooltip, getGatherTooltip, getVisibleEntityClassesFormatted),
unused function damageTypeDetails which was also a duplicate of
damageTypesToText,
unused function damageValues,
some warns that are equivalent to errors they attempt to cover up
(getAttackTypeLabel, getCostComponentDisplayIcon, getEntityNames,
getEntityNamesFormatted),
some unused variables,
"???" and translate("???").
Don't fix translate("Foo:") strings to avoid a lot of translation work.
This was SVN commit r18454.
2016-06-29 18:16:09 -07:00
data . button . tooltip = tooltips . filter ( tip => tip ) . join ( "\n" ) ;
2016-05-28 07:46:20 -07:00
let modifier = "" ;
2022-11-24 03:20:11 -08:00
if ( ! requirementsMet || limits . canBeAddedCount == 0 )
2016-05-28 07:46:20 -07:00
{
data . button . enabled = false ;
modifier = "color:0 0 0 127:grayscale:" ;
}
else
2018-03-10 12:15:51 -08:00
{
2016-09-25 12:38:10 -07:00
data . button . enabled = controlsPlayer ( data . player ) ;
2018-03-10 12:15:51 -08:00
if ( neededResources )
modifier = resourcesToAlphaMask ( neededResources ) + ":" ;
}
2016-05-28 07:46:20 -07:00
2020-10-04 03:20:20 -07:00
if ( data . unitEntStates . every ( state => state . upgrade && state . upgrade . isUpgrading ) )
{
data . button . enabled = false ;
modifier = "color:0 0 0 127:grayscale:" ;
2023-01-24 00:03:24 -08:00
data . button . tooltip += "\n" + objectionFont ( translate ( "Cannot train while upgrading." ) ) ;
2020-10-04 03:20:20 -07:00
}
2016-05-28 07:46:20 -07:00
if ( template . icon )
2017-10-21 10:31:05 -07:00
data . icon . sprite = modifier + "stretched:session/portraits/" + template . icon ;
2016-05-28 07:46:20 -07:00
2025-05-10 07:21:01 -07:00
const index = data . i + getNumberOfRightPanelButtons ( ) ;
2014-06-18 04:23:22 -07:00
setPanelObjectPosition ( data . button , index , data . rowLength ) ;
2016-05-28 07:46:20 -07:00
return true ;
2016-01-09 15:01:07 -08:00
}
2014-06-14 08:35:13 -07:00
} ;
2016-07-01 12:43:26 -07:00
g _SelectionPanels . Upgrade = {
"getMaxNumberOfItems" : function ( )
{
2020-09-07 10:36:44 -07:00
return 40 - getNumberOfRightPanelButtons ( ) ;
2016-07-01 12:43:26 -07:00
} ,
2020-09-07 10:36:44 -07:00
"rowLength" : 10 ,
2016-09-25 12:38:10 -07:00
"getItems" : function ( unitEntStates )
2016-07-01 12:43:26 -07:00
{
2016-09-25 12:38:10 -07:00
// Interface becomes complicated with multiple different units and this is meant per-entity, so prevent it if the selection has multiple different units.
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
if ( unitEntStates . some ( state => state . templateName != unitEntStates [ 0 ] . templateName ) )
2016-07-01 12:43:26 -07:00
return false ;
2016-08-27 08:33:22 -07:00
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
return unitEntStates [ 0 ] . upgrade ? . options ;
2016-07-01 12:43:26 -07:00
} ,
2017-10-21 10:31:05 -07:00
"setupButton" : function ( data )
2016-07-01 12:43:26 -07:00
{
2026-02-21 08:35:27 -08:00
const template = GetTemplateData ( data . item . entity , data . player ) ;
2016-07-01 12:43:26 -07:00
if ( ! template )
return false ;
2025-05-10 07:21:01 -07:00
const progressOverlay = Engine . GetGUIObjectByName ( "unitUpgradeProgressSlider[" + data . i + "]" ) ;
2020-10-04 03:20:20 -07:00
progressOverlay . hidden = true ;
2022-11-24 03:20:11 -08:00
const requirementsMet = ! data . item . requirements ||
Engine . GuiInterfaceCall ( "AreRequirementsMet" , {
"requirements" : data . item . requirements ,
2016-09-25 12:38:10 -07:00
"player" : data . player
2016-07-01 12:43:26 -07:00
} ) ;
2025-05-10 07:21:01 -07:00
const limits = getEntityLimitAndCount ( data . playerState , data . item . entity ) ;
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
const upgradingEntStates = data . unitEntStates . filter ( state => state . upgrade . templateName == data . item . entity ) ;
2020-10-04 03:20:20 -07:00
2025-05-10 07:21:01 -07:00
const upgradableEntStates = data . unitEntStates . filter ( state =>
2020-10-04 03:20:20 -07:00
! state . upgrade . progress &&
( ! state . production || ! state . production . queue || ! state . production . queue . length ) ) ;
2025-05-10 07:21:01 -07:00
const neededResources = data . item . cost && Engine . GuiInterfaceCall ( "GetNeededResources" , {
2020-10-04 03:20:20 -07:00
"cost" : multiplyEntityCosts ( data . item , upgradableEntStates . length ) ,
2017-08-26 12:34:23 -07:00
"player" : data . player
} ) ;
2016-07-01 12:43:26 -07:00
let tooltip ;
2020-10-04 03:20:20 -07:00
let modifier = "" ;
if ( ! upgradingEntStates . length && upgradableEntStates . length )
2016-07-01 12:43:26 -07:00
{
2025-05-10 07:21:01 -07:00
const primaryName = g _SpecificNamesPrimary ? template . name . specific : template . name . generic ;
2021-03-24 23:58:47 -07:00
let secondaryName ;
if ( g _ShowSecondaryNames )
secondaryName = g _SpecificNamesPrimary ? template . name . generic : template . name . specific ;
2025-05-10 07:21:01 -07:00
const tooltips = [ ] ;
2021-03-24 23:58:47 -07:00
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
} ) ) ;
}
2016-07-01 12:43:26 -07:00
else
2021-03-24 23:58:47 -07:00
{
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
} ) ) ;
}
2016-07-01 12:43:26 -07:00
2016-10-26 16:19:19 -07:00
tooltips . push (
2020-12-22 14:27:10 -08:00
getEntityCostTooltip ( data . item , undefined , undefined , data . unitEntStates . length ) ,
2016-10-26 16:19:19 -07:00
formatLimitString ( limits . entLimit , limits . entCount , limits . entLimitChangers ) ,
2020-12-29 03:00:54 -08:00
formatMatchLimitString ( limits . matchLimit , limits . matchCount , limits . type ) ,
2022-11-24 03:20:11 -08:00
getRequirementsTooltip ( requirementsMet , data . item . requirements , GetSimState ( ) . players [ data . player ] . civ ) ,
2018-02-21 13:39:00 -08:00
getNeededResourcesTooltip ( neededResources ) ,
2024-09-02 04:22:18 -07:00
getTemplateViewerOnRightClickTooltip ( )
) ;
2016-07-02 18:50:55 -07:00
2016-10-26 16:19:19 -07:00
tooltip = tooltips . filter ( tip => tip ) . join ( "\n" ) ;
2016-07-01 12:43:26 -07:00
2025-12-30 00:57:37 -08:00
data . button . onPress = function ( )
{
2020-10-04 03:20:20 -07:00
upgradeEntity (
2025-05-30 12:33:49 -07:00
data . item . entity ,
upgradableEntStates . map ( state => state . id ) ) ;
2020-10-04 03:20:20 -07:00
} ;
2022-11-24 03:20:11 -08:00
if ( ! requirementsMet || limits . canBeAddedCount == 0 &&
Optimise GetEntityState
GetEntityState is a performance-critical function in the GUI when a
large number of units are selected. The goal of this patch is to
increase the efficiency of it without modifying the returned states
visible to rest of the GUI in any way (it renames a few properties of
entities states or moves them around, but the information they contain
and the way to access it remain the complete same)
As explained the comments, certain parts (the template data) of an
entity state are "predictable" and can be reused between entities with
the same template.
What one has to account for, however, is that values of the template
data can be modified. This happens on two different levels:
Firstly, player-wide modifications, which apply to all entities owned
by that player. This includes stuff like bonuses from researched techs,
civs bonuses, team bonuses. And secondly, entity-local modifications,
which apply to individual entities. This includes buffs or debuffs from
status effects or auras (of other entities).
So we can construct a whole entity state by first just computing the
dynamic state (stuff like current hitpoints, which differs from entity
to entity) and then adding the (potentially already cached) template
data to it, which can be reused between entities with the same template
and owning player. And then overwrite the values affected by
entity-local modifications, which are usually just a few and even they
can be cached and reused for entities with the same owning player,
template, and entity-local modifications (identified by the
"modifications ID").
This saves the effort of retrieving/computing a ton of data each turn
and also saves the time it takes the engine to clone the data from the
simulation to the GUI (as the return value of the GUI interface call),
which previously took just as long as retrieving the entity states
themselves.
Since only the dynamic state is read from the components, a number of
small getter methods have become unused; they are kept (for now at
least) since they might be useful again in the future or for mods.
2026-02-12 06:52:21 -08:00
! upgradableEntStates . some ( state => hasSameRestrictionCategory ( data . item . entity , state . templateName , state . player ) ) )
2020-10-04 03:20:20 -07:00
{
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 : "" ;
2016-07-01 12:43:26 -07:00
}
2020-10-04 03:20:20 -07:00
else if ( upgradingEntStates . length )
2016-07-01 12:43:26 -07:00
{
tooltip = translate ( "Cancel Upgrading" ) ;
data . button . onPress = function ( ) { cancelUpgradeEntity ( ) ; } ;
2020-10-04 03:20:20 -07:00
data . countDisplay . caption = upgradingEntStates . length > 1 ? upgradingEntStates . length : "" ;
let progress = 0 ;
2025-05-10 07:21:01 -07:00
for ( const state of upgradingEntStates )
2020-10-04 03:20:20 -07:00
progress = Math . max ( progress , state . upgrade . progress || 1 ) ;
2025-05-29 15:23:46 -07:00
2020-10-04 03:20:20 -07:00
// TODO This is bad: we assume the progressOverlay is square
2025-05-29 15:23:46 -07:00
progressOverlay . size . top = progressOverlay . size . bottom + Math . round ( ( 1 - progress ) * ( progressOverlay . size . left - progressOverlay . size . right ) ) ;
2020-10-04 03:20:20 -07:00
progressOverlay . hidden = false ;
2016-07-01 12:43:26 -07:00
}
else
{
2023-01-24 00:03:24 -08:00
tooltip = objectionFont ( translatePlural (
2020-10-04 03:20:20 -07:00
"Cannot upgrade when the entity is training, researching or already upgrading." ,
"Cannot upgrade when all entities are training, researching or already upgrading." ,
2023-01-24 00:03:24 -08:00
data . unitEntStates . length ) ) ;
2020-10-04 03:20:20 -07:00
2016-07-01 12:43:26 -07:00
data . button . onPress = function ( ) { } ;
2020-10-04 03:20:20 -07:00
data . button . enabled = false ;
modifier = "color:0 0 0 127:grayscale:" ;
2016-07-01 12:43:26 -07:00
}
2016-11-13 14:31:04 -08:00
data . button . enabled = controlsPlayer ( data . player ) ;
2016-07-01 12:43:26 -07:00
data . button . tooltip = tooltip ;
2025-05-10 07:21:01 -07:00
const showTemplateFunc = ( ) => { showTemplateDetails ( data . item . entity , data . playerState . civ ) ; } ;
2020-11-14 10:16:24 -08:00
data . button . onPressRight = showTemplateFunc ;
data . button . onPressRightDisabled = showTemplateFunc ;
2018-02-21 13:39:00 -08:00
2016-08-27 08:33:22 -07:00
data . icon . sprite = modifier + "stretched:session/" +
2016-07-01 12:43:26 -07:00
( data . item . icon || "portraits/" + template . icon ) ;
2018-03-13 14:02:13 -07:00
setPanelObjectPosition ( data . button , data . i + getNumberOfRightPanelButtons ( ) , data . rowLength ) ;
2016-07-01 12:43:26 -07:00
return true ;
}
} ;
2019-10-12 20:43:42 -07:00
function initSelectionPanels ( )
{
2025-05-10 07:21:01 -07:00
const unitBarterPanel = Engine . GetGUIObjectByName ( "unitBarterPanel" ) ;
2019-10-12 20:43:42 -07:00
if ( BarterButtonManager . IsAvailable ( unitBarterPanel ) )
g _SelectionPanelBarterButtonManager = new BarterButtonManager ( unitBarterPanel ) ;
2025-09-24 07:52:59 -07:00
for ( const panel in g _SelectionPanels )
g _SelectionPanels [ panel ] . init ? . ( ) ;
2019-10-12 20:43:42 -07:00
}
2018-02-21 13:39:00 -08:00
/ * *
* 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 .
* /
2024-07-08 12:07:04 -07:00
async function showTemplateDetails ( templateName , civCode )
2018-02-21 13:39:00 -08:00
{
2021-06-06 12:00:04 -07:00
if ( inputState != INPUT _NORMAL )
return ;
Implement session event subscription system and rewrite TopPanel, PlayerViewControl, GameSpeed, Pausing, ObjectivesDialog to use object orientation, refs #5387.
New controller classes: PlayerViewControl, PauseControl,
GameSpeedControl
New viewer classes: ObjectivesDialog, PauseOverlay, FollowPlayer,
TopPanel (BuildLabel, CivIcon, CounterManager, CounterPopulation,
CounterResource refs 7e14a33411/D1113, GameSpeedButton,
ObjectivesDialogButton)
New events: SimulationUpdate, EntitySelectionChange, ViewedPlayerChange,
PreViewedPlayerChangeHandler, PlayerIDChange, PlayersInit,
PlayersFinished, Pause, DiplomacyColorsChange, HotkeyChange, refs #2604
Improves GUI onSimuationUpdate performance without selected entities by
allegedly 30%.
Delete misleading dead code resign command from leaveGame and rename to
endGame. The command is not sent via network (see fa85527baf) nor
processed in simulation, because the Game instance is deleted
immediately thereafter, introduced in fcedcae052, refs a3e1c68b9a,
39ffb0a6bd, 9f796068f8.
Remove explicitResume 0 value from e57c99c6f6 and 8ae67ed15f which
should have been a false if defined, and is equivalent to the default.
Restore fast forwarding option from cd571035bb/D595 for developers
changing the perspective to observer or player following 56308ec1ad.
Add pausing for the delete dialog missing following 7a7ebaa983.
Differential Revision: https://code.wildfiregames.com/D2378
This was SVN commit r23076.
2019-10-17 08:08:56 -07:00
g _PauseControl . implicitPause ( ) ;
2018-02-21 13:39:00 -08:00
2024-10-30 10:56:33 -07:00
await Engine . OpenChildPage (
2019-08-16 11:46:04 -07:00
"page_viewer.xml" ,
{
"templateName" : templateName ,
"civ" : civCode
2024-07-08 12:07:04 -07:00
} ) ;
resumeGame ( ) ;
2018-02-21 13:39:00 -08:00
}
2014-06-16 11:34:27 -07:00
/ * *
* 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 .
* /
2025-05-10 07:21:01 -07:00
const g _PanelsOrder = [
2014-06-16 11:34:27 -07:00
// LEFT PANE
2016-05-28 07:46:20 -07:00
"Barter" , // Must always be visible on markets
"Garrison" , // More important than Formation, as you want to see the garrisoned units in ships
2015-05-24 06:51:02 -07:00
"Alert" ,
2014-06-16 11:34:27 -07:00
"Formation" ,
2016-05-28 07:46:20 -07:00
"Stance" , // Normal together with formation
2014-06-16 11:34:27 -07:00
// RIGHT PANE
2016-05-28 07:46:20 -07:00
"Gate" , // Must always be shown on gates
"Pack" , // Must always be shown on packable entities
2016-07-01 12:43:26 -07:00
"Upgrade" , // Must always be shown on upgradable entities
2014-06-18 04:23:22 -07:00
"Training" ,
2014-06-16 11:34:27 -07:00
"Construction" ,
2016-05-28 07:46:20 -07:00
"Research" , // Normal together with training
2014-06-16 11:34:27 -07:00
// UNIQUE PANES (importance doesn't matter)
"Command" ,
"Queue" ,
"Selection" ,
] ;