diff --git a/.gitignore b/.gitignore index 9491a2fda28342ab358eaf234e1afe0c07a53d62..afeec89c9bf1ece1bca5688d3a46b4ba23cd8321 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +#Zip files +*.zip + # User-specific files *.rsuser *.suo diff --git a/CityBannerManager.lua.cuc b/CityBannerManager.lua.cuc new file mode 100644 index 0000000000000000000000000000000000000000..41e1049b6af71af42a6fa060740f2d9189eba0b9 --- /dev/null +++ b/CityBannerManager.lua.cuc @@ -0,0 +1,1241 @@ +--========================================================== +-- CityBannerManager +-- Re-written by bc1 using Notepad++ +-- code is common using gk_mode and bnw_mode switches +--========================================================== + +Events.SequenceGameInitComplete.Add(function() + +include "GameInfoCache" -- warning! booleans are true, not 1, and use iterator ONLY with table field conditions, NOT string SQL query +local GameInfo = GameInfoCache + +include "IconHookup" +local IconHookup = IconHookup +local CivIconHookup = CivIconHookup +local Color = Color +local PrimaryColors = PrimaryColors +local BackgroundColors = BackgroundColors +local ColorGreen = Color( 0, 1, 0, 1 ) +local ColorYellow = Color( 1, 1, 0, 1 ) +local ColorRed = Color( 1, 0, 0, 1 ) +local ColorCulture = Color( 1, 0, 1, 1 ) + +include "CityStateStatusHelper" +local GetCityStateStatusRow = GetCityStateStatusRow +local GetActiveQuestText = GetActiveQuestText + +--========================================================== +-- Minor lua optimizations +--========================================================== + +local ipairs = ipairs +local floor = math.floor +local max = math.max +local min = math.min +local pairs = pairs +local print = print +local insert = table.insert +local remove = table.remove +local format = string.format + +local ButtonPopupTypes = ButtonPopupTypes +local CityUpdateTypes = CityUpdateTypes +local ContextPtr = ContextPtr +local Controls = Controls +local Events = Events +local EventsClearHexHighlightStyle = Events.ClearHexHighlightStyle.Call +local EventsRequestYieldDisplay = Events.RequestYieldDisplay.Call +local EventsSerialEventHexHighlight = Events.SerialEventHexHighlight.Call +local Game = Game +local GridToWorld = GridToWorld +local InStrategicView = InStrategicView +local InterfaceModeTypes = InterfaceModeTypes +local L = Locale.ConvertTextKey +local ToUpper = Locale.ToUpper +local GetPlot = Map.GetPlot +local GetPlotByIndex = Map.GetPlotByIndex +local MinorCivQuestTypes = MinorCivQuestTypes +local Mouse = Mouse +local SendUpdateCityCitizens = Network.SendUpdateCityCitizens +local IsCivilianYields = OptionsManager.IsCivilianYields +local Players = Players +local Teams = Teams +local ToGridFromHex = ToGridFromHex +local ToHexFromGrid = ToHexFromGrid +local UI = UI +local GetUnitPortraitIcon = UI.GetUnitPortraitIcon +local UnitMoving = UnitMoving +local YieldDisplayTypes = YieldDisplayTypes +local MAX_CITY_HIT_POINTS = GameDefines.MAX_CITY_HIT_POINTS +local CITY_PLOTS_RADIUS = GameDefines.CITY_PLOTS_RADIUS + +--========================================================== +-- Globals +--========================================================== + +local RefreshCityBanner + +local gk_mode = Game.GetReligionName ~= nil +local bnw_mode = Game.GetActiveLeague ~= nil + +local g_activePlayerID = Game.GetActivePlayer() +local g_activePlayer = Players[ g_activePlayerID ] +local g_activeTeamID = Game.GetActiveTeam() +local g_activeTeam = Teams[ g_activeTeamID ] + +local g_cityBanners = {} +--local g_outpostBanners = {} +--local g_stationBanners = {} +local g_svStrikeButtons = {} + +local g_scrapTeamBanners = {} +local g_scrapOtherBanners = {} +local g_scrapSVStrikeButtons = {} + +local g_WorldPositionOffsetZ = InStrategicView and 35 or 55 + + +local g_cityHexHighlight + +--local IsCivBE = Game.GetAvailableBeliefs ~= nil +--local g_CovertOpsBannerContainer = IsCivBE and ContextPtr:LookUpControl( "../CovertOpsBannerContainer" ) +--local g_CovertOpsIntelReportContainer = IsCivBE and ContextPtr:LookUpControl( "../CovertOpsIntelReportContainer" ) + +local g_cityFocusIcons = { +--[CityAIFocusTypes.NO_CITY_AI_FOCUS_TYPE or -1] = "", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FOOD or -1] = "[ICON_FOOD]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_PRODUCTION or -1] = "[ICON_PRODUCTION]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GOLD or -1] = "[ICON_GOLD]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_SCIENCE or -1] = "[ICON_RESEARCH]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_CULTURE or -1] = "[ICON_CULTURE]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GREAT_PEOPLE or -1] = "[ICON_GREAT_PEOPLE]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FAITH or -1] = "[ICON_PEACE]", +} g_cityFocusIcons[-1] = nil + +local g_cityFocusTooltips = { +[CityAIFocusTypes.NO_CITY_AI_FOCUS_TYPE or -1] = L"TXT_KEY_CITYVIEW_FOCUS_BALANCED_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FOOD or -1] = L"TXT_KEY_CITYVIEW_FOCUS_FOOD_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_PRODUCTION or -1] = L"TXT_KEY_CITYVIEW_FOCUS_PROD_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GOLD or -1] = L"TXT_KEY_CITYVIEW_FOCUS_GOLD_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_SCIENCE or -1] = L"TXT_KEY_CITYVIEW_FOCUS_RESEARCH_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_CULTURE or -1] = L"TXT_KEY_CITYVIEW_FOCUS_CULTURE_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GREAT_PEOPLE or -1] = L"TXT_KEY_CITYVIEW_FOCUS_GREAT_PERSON_TEXT", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FAITH or -1] = L"TXT_KEY_CITYVIEW_FOCUS_FAITH_TEXT", +} g_cityFocusTooltips[-1] = nil + +local function IsTurnActive( player ) + return player and player:IsTurnActive() and not Game.IsProcessingMessages() +end + +local function BannerError( where, arg ) + if Game.IsDebugMode() then + local txt = "" + if arg and arg.PlotIndex then + txt = "city banner" + arg = arg and GetPlotByIndex(arg.PlotIndex) + end + if arg and arg.GetPlotCity then + txt = "plot " .. (arg:IsCity() and "with" or "without") .. " city" + arg = arg:GetPlotCity() + end + if arg and arg.GetCityPlotIndex then + txt = "city " .. arg:GetName() + end + print( "glitch", where, txt, debug and debug.traceback and debug.traceback() ) + end +end + +--========================================================== +-- Clear Hex Highlighting +--========================================================== + +local function ClearHexHighlights() + EventsClearHexHighlightStyle( "HexContour" ) + EventsClearHexHighlightStyle( "WorkedFill" ) + EventsClearHexHighlightStyle( "WorkedOutline" ) + EventsClearHexHighlightStyle( "UnlockedFill" ) + EventsClearHexHighlightStyle( "UnlockedOutline" ) + EventsClearHexHighlightStyle( "OwnedFill") + EventsClearHexHighlightStyle( "OwnedOutline" ) + EventsClearHexHighlightStyle( "CityLimits" ) + EventsClearHexHighlightStyle( "EnemyFill" ) + EventsClearHexHighlightStyle( "EnemyOutline" ) + g_cityHexHighlight = false +end + +--========================================================== +-- Show/hide the garrison frame icon +--========================================================== + +local function HideGarrisonFrame( instance, isHide ) + -- Only the active team has a Garrison ring + if instance and instance[1] then + instance.GarrisonFrame:SetHide( isHide ) + end +end + +--========================================================== +-- Show/hide the range strike icon +--========================================================== + +local function UpdateRangeIcons( plotIndex, city, instance ) + + if city and instance then + + local hideRangeStrikeButton = city:GetOwner() ~= g_activePlayerID or not city:CanRangeStrikeNow() + if instance.CityRangeStrikeButton then + instance.CityRangeStrikeButton:SetHide( hideRangeStrikeButton ) + end + + instance = g_svStrikeButtons[ plotIndex ] + if instance then + instance.CityRangeStrikeButton:SetHide( hideRangeStrikeButton ) + end + end +end + +--========================================================== +-- Refresh the City Damage bar +--========================================================== + +local function RefreshCityDamage( city, instance, cityDamage ) + + if instance then + + local maxCityHitPoints = gk_mode and city and city:GetMaxHitPoints() or MAX_CITY_HIT_POINTS + local iHealthPercent = 1 - cityDamage / maxCityHitPoints + + instance.CityBannerHealthBar:SetPercent(iHealthPercent) + instance.CityBannerHealthBar:SetToolTipString( format("%g / %g", maxCityHitPoints - cityDamage, maxCityHitPoints) ) + + ---- Health bar color based on amount of damage + local barColor = {} + + if iHealthPercent > 0.66 then + barColor = ColorGreen + elseif iHealthPercent > 0.33 then + barColor = ColorYellow + else + barColor = ColorRed + end + instance.CityBannerHealthBar:SetFGColor( barColor ) + + -- Show or hide the Health Bar as necessary + instance.CityBannerHealthBarBase:SetHide( cityDamage == 0 ) + end +end + +--========================================================== +-- Click On City State Quest Info +--========================================================== + +local questKillCamp = MinorCivQuestTypes.MINOR_CIV_QUEST_KILL_CAMP +local IsActiveQuestKillCamp +if bnw_mode then + IsActiveQuestKillCamp = function( minorPlayer ) + return minorPlayer and minorPlayer:IsMinorCivDisplayedQuestForPlayer( g_activePlayerID, questKillCamp ) + end +elseif gk_mode then + IsActiveQuestKillCamp = function( minorPlayer ) + return minorPlayer and minorPlayer:IsMinorCivActiveQuestForPlayer( g_activePlayerID, questKillCamp ) + end +else + IsActiveQuestKillCamp = function( minorPlayer ) + return minorPlayer and minorPlayer:GetActiveQuestForPlayer( g_activePlayerID ) == questKillCamp + end +end + +local function OnQuestInfoClicked( plotIndex ) + local plot = GetPlotByIndex( plotIndex ) + local city = plot and plot:GetPlotCity() + local cityOwner = city and Players[ city:GetOwner() ] + if cityOwner and cityOwner:IsMinorCiv() and IsActiveQuestKillCamp( cityOwner ) then + local questData1 = cityOwner:GetQuestData1( g_activePlayerID, questKillCamp ) + local questData2 = cityOwner:GetQuestData2( g_activePlayerID, questKillCamp ) + local plot = GetPlot( questData1, questData2 ) + if plot then + UI.LookAt( plot ) + local hex = ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() } + Events.GameplayFX( hex.x, hex.y, -1 ) + end + end +end + +local function AnnexPopup( plotIndex ) + local plot = GetPlotByIndex( plotIndex ) + local city = plot and plot:GetPlotCity() + if city and city:GetOwner() == g_activePlayerID and not( bnw_mode and g_activePlayer:MayNotAnnex() ) then + Events.SerialEventGameMessagePopup{ + Type = ButtonPopupTypes.BUTTONPOPUP_ANNEX_CITY, + Data1 = city:GetID(), + Data2 = -1, + Data3 = -1, + Option1 = false, + Option2 = false + } + end +end + +local function EspionagePopup( plotIndex ) + local plot = GetPlotByIndex( plotIndex ) + local city = plot and plot:GetPlotCity() + if city and not Players[city:GetOwner()]:IsMinorCiv() then + ClearHexHighlights() + UI.SetInterfaceMode( InterfaceModeTypes.INTERFACEMODE_SELECTION ) + UI.DoSelectCityAtPlot( plot ) + else + Events.SerialEventGameMessagePopup{ Type = ButtonPopupTypes.BUTTONPOPUP_ESPIONAGE_OVERVIEW } + end +end + +--========================================================== +-- Click on City Range Strike Button +--========================================================== + +local function OnCityRangeStrikeButtonClick( plotIndex ) + local plot = GetPlotByIndex( plotIndex ) + local city = plot and plot:GetPlotCity() + + if city and city:GetOwner() == g_activePlayerID then + UI.ClearSelectionList() + UI.SelectCity( city ) + UI.SetInterfaceMode( InterfaceModeTypes.INTERFACEMODE_CITY_RANGE_ATTACK ) +-- Events.InitCityRangeStrike( city:GetOwner(), city:GetID() ) + end +end + +--========================================================== +-- Left Click on city banner +--========================================================== + +local function OnBannerClick( plotIndex ) + local plot = GetPlotByIndex( plotIndex ) + local city = plot and plot:GetPlotCity() + if city then + UI.SetInterfaceMode( InterfaceModeTypes.INTERFACEMODE_SELECTION ) + local cityOwnerID = city:GetOwner() + local cityOwner = Players[ cityOwnerID ] + + -- Active player city + if cityOwnerID == g_activePlayerID then + + -- always open city screen, puppets are not that special + ClearHexHighlights() + UI.DoSelectCityAtPlot( plot ) + + -- Observers get to look at anything + elseif Game.IsDebugMode() or g_activePlayer:IsObserver() then + UI.SelectCity( city ) + UI.LookAt( plot ) + UI.SetCityScreenUp( true ) + + -- Other player, which has been met + elseif g_activeTeam:IsHasMet( city:GetTeam() ) then + + if cityOwner:IsMinorCiv() then + UI.DoSelectCityAtPlot( plot ) + elseif IsTurnActive( g_activePlayer ) then + if cityOwner:IsHuman() then + Events.OpenPlayerDealScreenEvent( cityOwnerID ) + elseif not cityOwner:IsBarbarian() then + UI.SetRepeatActionPlayer( cityOwnerID ) + UI.ChangeStartDiploRepeatCount(1) + cityOwner:DoBeginDiploWithHuman() + end + end + end + else + BannerError( "OnBannerClick", plot ) + end +end + +--========================================================== +-- Destroy City Banner +--========================================================== + +local function DestroyCityBanner( plotIndex, instance ) + + -- Release city banner + if instance then + insert( instance[1] and g_scrapTeamBanners or g_scrapOtherBanners, instance ) + g_cityBanners[ plotIndex or -1 ] = nil + instance.Anchor:ChangeParent( Controls.Scrap ) + end + + -- Release sv strike button + instance = g_svStrikeButtons[ plotIndex ] + if instance then + instance.Anchor:ChangeParent( Controls.Scrap ) + insert( g_scrapSVStrikeButtons, instance ) + g_svStrikeButtons[ plotIndex ] = nil + end +end + +--========================================================== +-- City banner mouse over +--========================================================== + +local function OnBannerMouseExit() + if not UI.IsCityScreenUp() then + + ClearHexHighlights() + -- duplicate code from InGame.lua function RequestYieldDisplay() + + local isDisplayCivilianYields = IsCivilianYields() + local unit = UI.GetHeadSelectedUnit() + + if isDisplayCivilianYields and UI.CanSelectionListWork() and not( unit and (GameInfo.Units[unit:GetUnitType()] or {}).DontShowYields ) then + EventsRequestYieldDisplay( YieldDisplayTypes.EMPIRE ) + + elseif isDisplayCivilianYields and UI.CanSelectionListFound() and unit then + EventsRequestYieldDisplay( YieldDisplayTypes.AREA, 2, unit:GetX(), unit:GetY() ) + else + EventsRequestYieldDisplay( YieldDisplayTypes.AREA, 0 ) + end + end +end + +local function OnBannerMouseEnter( plotIndex ) + local plot = GetPlotByIndex( plotIndex ) + if plot then + local city = plot:GetPlotCity() + g_cityHexHighlight = plotIndex + if city and city:GetOwner() == g_activePlayerID and not( Game.IsNetworkMultiPlayer() and g_activePlayer:HasReceivedNetTurnComplete() ) then -- required to prevent turn interrupt + SendUpdateCityCitizens( city:GetID() ) + end + return RefreshCityBanner( city ) + end +end + +local CityTooltip = LuaEvents.CityToolTips.Call +local TeamCityTooltips = { + CityBannerButton = "EUI_ItemTooltip", + CityBannerRightBackground = "EUI_ItemTooltip", + BuildGrowth = "EUI_ItemTooltip", + CityGrowth = "EUI_ItemTooltip", +-- BorderGrowth = "EUI_ItemTooltip", + CityReligion = "EUI_ItemTooltip", + CityFocus = "EUI_ItemTooltip", + CityQuests = "EUI_ItemTooltip", + CityIsPuppet = "EUI_ItemTooltip", + CityIsRazing = "EUI_ItemTooltip", + CityIsResistance = "EUI_ItemTooltip", + CityIsConnected = "EUI_ItemTooltip", + CityIsBlockaded = "EUI_ItemTooltip", + CityIsOccupied = "EUI_ItemTooltip", + CityIsCapital = "EUI_ItemTooltip", + CityIsOriginalCapital = "EUI_ItemTooltip", + CivIndicator = "EUI_ItemTooltip", + CityProductionBG = "EUI_CityProductionTooltip", + CityPopulation = "EUI_CityGrowthTooltip", +} +local OtherCityTooltips = { + CityBannerButton = "EUI_ItemTooltip", + CityBannerRightBackground = "EUI_ItemTooltip", +-- BuildGrowth = "EUI_ItemTooltip", +-- CityGrowth = "EUI_ItemTooltip", +-- BorderGrowth = "EUI_ItemTooltip", + CityReligion = "EUI_ItemTooltip", +-- CityFocus = "EUI_ItemTooltip", + CityQuests = "EUI_ItemTooltip", + CityIsPuppet = "EUI_ItemTooltip", + CityIsRazing = "EUI_ItemTooltip", + CityIsResistance = "EUI_ItemTooltip", +-- CityIsConnected = "EUI_ItemTooltip", + CityIsBlockaded = "EUI_ItemTooltip", + CityIsOccupied = "EUI_ItemTooltip", + CityIsCapital = "EUI_ItemTooltip", + CityIsOriginalCapital = "EUI_ItemTooltip", + CivIndicator = "EUI_ItemTooltip", +-- CityProductionBG = "EUI_CityProductionTooltip", +-- CityPopulation = "EUI_CityGrowthTooltip", +} +local function InitBannerCallbacks( instance, tooltips ) + local button = instance.CityBannerButton + button:RegisterCallback( Mouse.eLClick, OnBannerClick ) + button:RegisterCallback( Mouse.eMouseEnter, OnBannerMouseEnter ) + button:RegisterCallback( Mouse.eMouseExit, OnBannerMouseExit ) +-- instance.CityName:SetColor( Color( 0, 0, 0, 0.5 ), 1 ) -- #1 = shadow color +-- instance.CityName:SetColor( Color( 1, 1, 1, 0.5 ), 2 ) -- #2 = soft color + instance.CityDiplomat:RegisterCallback( Mouse.eLClick, EspionagePopup ) + instance.CitySpy:RegisterCallback( Mouse.eLClick, EspionagePopup ) + -- Setup Tootip Callbacks + for controlID, toolTipType in pairs( tooltips ) do + instance[ controlID ]:SetToolTipCallback( function( control ) + control:SetToolTipCallback( function( control ) return CityTooltip( control, GetPlotByIndex( button:GetVoid1() ):GetPlotCity() ) end ) + control:SetToolTipType( toolTipType ) + end) + end +end + +--========================================================== +-- Update banners to reflect latest city info +--========================================================== + +function RefreshCityBanner( city ) + + if city then + + local isDebug = Game.IsDebugMode() or g_activePlayer:IsObserver() + local plot = city:Plot() + local plotIndex = plot:GetPlotIndex() + local instance = g_cityBanners[ plotIndex ] + local cityOwnerID = city:GetOwner() + local cityOwner = Players[ cityOwnerID ] + local isActiveType = isDebug or city:GetTeam() == g_activeTeamID + local isActivePlayerCity = cityOwnerID == g_activePlayerID + + -- Incompatible banner type ? Destroy ! + if instance and isActiveType ~= instance[1] then + DestroyCityBanner( plotIndex, instance ) + instance = nil + end + + --------------------- + -- Create City Banner + if not instance then + local worldX, worldY, worldZ = GridToWorld( plot:GetX(), plot:GetY() ) + if isActiveType then + -- create a strike button for stategic view + instance = remove( g_scrapSVStrikeButtons ) + if instance then + instance.Anchor:ChangeParent( Controls.StrategicViewStrikeButtons ) + else + instance = {} + ContextPtr:BuildInstanceForControl( "SVRangeStrikeButton", instance, Controls.StrategicViewStrikeButtons ) + instance.CityRangeStrikeButton:RegisterCallback( Mouse.eLClick, OnCityRangeStrikeButtonClick ) + end + instance.Anchor:SetWorldPositionVal( worldX, worldY, worldZ ) + instance.CityRangeStrikeButton:SetVoid1( plotIndex ) + g_svStrikeButtons[ plotIndex ] = instance + + -- create a team type city banner + instance = remove( g_scrapTeamBanners ) + if instance then + instance.Anchor:ChangeParent( Controls.CityBanners ) + else + instance = {} + ContextPtr:BuildInstanceForControl( "TeamCityBanner", instance, Controls.CityBanners ) + instance.CityRangeStrikeButton:RegisterCallback( Mouse.eLClick, OnCityRangeStrikeButtonClick ) + instance.CityIsPuppet:RegisterCallback( Mouse.eLClick, AnnexPopup ) + InitBannerCallbacks( instance, TeamCityTooltips ) + end + instance.CityIsPuppet:SetVoid1( plotIndex ) + instance.CityRangeStrikeButton:SetVoid1( plotIndex ) + else + -- create a foreign type city banner + instance = remove( g_scrapOtherBanners ) + if instance then + instance.Anchor:ChangeParent( Controls.CityBanners ) + else + instance = {} + ContextPtr:BuildInstanceForControl( "OtherCityBanner", instance, Controls.CityBanners ) + instance.CityQuests:RegisterCallback( Mouse.eLClick, OnQuestInfoClicked ) + InitBannerCallbacks( instance, OtherCityTooltips ) + end + instance.CityQuests:SetVoid1( plotIndex ) + end + + instance.CityBannerButton:SetVoid1( plotIndex ) + instance.Anchor:SetWorldPositionVal( worldX, worldY, worldZ + g_WorldPositionOffsetZ ) + + instance[1] = isActiveType + g_cityBanners[ plotIndex ] = instance + end + -- /Create City Banner + --------------------- + + -- Refresh the damage bar + RefreshCityDamage( city, instance, city:GetDamage() ) + + -- Colors + local color = PrimaryColors[ cityOwnerID ] + local backgroundColor = BackgroundColors[ cityOwnerID ] + + -- Update name + local cityName = city:GetName() + local upperCaseCityName = ToUpper( cityName ) + + local originalCityOwnerID = city:GetOriginalOwner() + local originalCityOwner = Players[ originalCityOwnerID ] + local otherCivID, otherCivAlpha + local isRazing = city:IsRazing() + local isResistance = city:IsResistance() + local isPuppet = city:IsPuppet() + + -- Update capital icon + instance.CityIsCapital:SetHide( not city:IsCapital() or cityOwner:IsMinorCiv() ) + instance.CityIsOriginalCapital:SetHide( city:IsCapital() or not city:IsOriginalCapital() ) + + instance.CityName:SetText( upperCaseCityName ) + instance.CityName:SetColor( color, 0 ) -- #0 = main color + + -- Update strength + instance.CityStrength:SetText(floor(city:GetStrengthValue() / 100)) + + -- Update population + instance.CityPopulationValue:SetText( city:GetPopulation() ) + + -- Being Razed ? + instance.CityIsRazing:SetHide( not isRazing ) + + -- In Resistance ? + instance.CityIsResistance:SetHide( not isResistance ) + + -- Puppet ? + instance.CityIsPuppet:SetHide( not isPuppet ) + + -- Occupied ? + instance.CityIsOccupied:SetHide( not city:IsOccupied() or city:IsNoOccupiedUnhappiness() ) + + -- Blockaded ? + instance.CityIsBlockaded:SetHide( not city:IsBlockaded() ) + + -- Garrisoned ? + instance.GarrisonFrame:SetHide( not ( plot:IsVisible( g_activeTeamID, true ) and city:GetGarrisonedUnit() ) ) + + instance.CityBannerBackground:SetColor( backgroundColor ) + instance.CityBannerRightBackground:SetColor( backgroundColor ) + instance.CityBannerLeftBackground:SetColor( backgroundColor ) + + if isActiveType then + + instance.CityBannerBGLeftHL:SetColor( backgroundColor ) + instance.CityBannerBGRightHL:SetColor( backgroundColor ) + instance.CityBannerBackgroundHL:SetColor( backgroundColor ) + + -- Update Growth + local foodStored100 = city:GetFoodTimes100() + local foodThreshold100 = city:GrowthThreshold() * 100 + local foodPerTurn100 = city:FoodDifferenceTimes100( true ) + local foodStoredPercent = 0 + local foodStoredNextTurnPercent = 0 + if foodThreshold100 > 0 then + foodStoredPercent = foodStored100 / foodThreshold100 + foodStoredNextTurnPercent = ( foodStored100 + foodPerTurn100 ) / foodThreshold100 + if foodPerTurn100 < 0 then + foodStoredPercent, foodStoredNextTurnPercent = foodStoredNextTurnPercent, foodStoredPercent + end + end + + -- Update Growth Meter + instance.GrowthBar:SetPercent( max(min( foodStoredPercent, 1),0)) + instance.GrowthBarShadow:SetPercent( max(min( foodStoredNextTurnPercent, 1),0)) + instance.GrowthBarStarve:SetHide( foodPerTurn100 >= 0 ) + + -- Update Growth Time + local turnsToCityGrowth = city:GetFoodTurnsLeft() + local growthText + + if foodPerTurn100 < 0 then + turnsToCityGrowth = floor( foodStored100 / -foodPerTurn100 ) + 1 + growthText = "[COLOR_WARNING_TEXT]" .. turnsToCityGrowth .. "[ENDCOLOR]" + elseif city:IsForcedAvoidGrowth() then + growthText = "[ICON_LOCKED]" + elseif foodPerTurn100 == 0 then + growthText = "-" + else + growthText = min(turnsToCityGrowth,99) + end + + instance.CityGrowth:SetText( growthText ) + + local productionPerTurn100 = city:GetCurrentProductionDifferenceTimes100(false, false) -- food = false, overflow = false + local productionStored100 = city:GetProductionTimes100() + city:GetCurrentProductionDifferenceTimes100(false, true) - productionPerTurn100 + local productionNeeded100 = city:GetProductionNeeded() * 100 + local productionStoredPercent = 0 + local productionStoredNextTurnPercent = 0 + + if productionNeeded100 > 0 then + productionStoredPercent = productionStored100 / productionNeeded100 + productionStoredNextTurnPercent = (productionStored100 + productionPerTurn100) / productionNeeded100 + end + + instance.ProductionBar:SetPercent( max(min( productionStoredPercent, 1),0)) + instance.ProductionBarShadow:SetPercent( max(min( productionStoredNextTurnPercent, 1),0)) + + -- Update Production Time + if city:IsProduction() + and not city:IsProductionProcess() + and productionPerTurn100 > 0 + then + instance.BuildGrowth:SetText( city:GetProductionTurnsLeft() ) + else + instance.BuildGrowth:SetText( "-" ) + end + + -- Update Production icon + local unitProductionID = city:GetProductionUnit() + local buildingProductionID = city:GetProductionBuilding() + local projectProductionID = city:GetProductionProject() + local processProductionID = city:GetProductionProcess() + local portraitIndex, portraitAtlas + local item = nil + + if unitProductionID ~= -1 then + item = GameInfo.Units[unitProductionID] + portraitIndex, portraitAtlas = GetUnitPortraitIcon( (item or {}).ID or -1, cityOwnerID ) + elseif buildingProductionID ~= -1 then + item = GameInfo.Buildings[buildingProductionID] + elseif projectProductionID ~= -1 then + item = GameInfo.Projects[projectProductionID] + elseif processProductionID ~= -1 then + item = GameInfo.Processes[processProductionID] + end + -- really should have an error texture + + instance.CityProduction:SetHide( not( item and + IconHookup( portraitIndex or item.PortraitIndex, 45, portraitAtlas or item.IconAtlas, instance.CityProduction ))) + + -- Focus? + if isRazing or isResistance or isPuppet then + instance.CityFocus:SetHide( true ) + else + instance.CityFocus:SetText( g_cityFocusIcons[city:GetFocusType()] ) + instance.CityFocus:SetHide( false ) + end + + -- Connected to capital? + instance.CityIsConnected:SetHide( city:IsCapital() or not cityOwner:IsCapitalConnectedToCity( city ) ) + + -- Demand resource / King day ? + local resource = GameInfo.Resources[ city:GetResourceDemanded() ] + local weLoveTheKingDayCounter = city:GetWeLoveTheKingDayCounter() + -- We love the king + if weLoveTheKingDayCounter > 0 then + instance.CityQuests:SetText( "[ICON_HAPPINESS_1]" ) + instance.CityQuests:SetHide( false ) + + elseif resource then + instance.CityQuests:SetText( resource.IconString ) + instance.CityQuests:SetHide( false ) + else + instance.CityQuests:SetHide( true ) + end + + -- update range strike button (if it is the active player's city) + + UpdateRangeIcons( plotIndex, city, instance ) + + -- not active team city + else + local isMinorCiv = cityOwner:IsMinorCiv() + if isMinorCiv then + -- Update Quests + instance.CityQuests:SetText( GetActiveQuestText( g_activePlayerID, cityOwnerID ) ) + local info = GetCityStateStatusRow( g_activePlayerID, cityOwnerID ) + instance.StatusIconBG:SetTexture( info and info.StatusIcon ) + instance.StatusIcon:SetTexture( (GameInfo.MinorCivTraits[ (GameInfo.MinorCivilizations[ cityOwner:GetMinorCivType() ] or {}).MinorCivTrait ] or {}).TraitIcon ) + -- Update Pledge + if gk_mode then + local pledge = g_activePlayer:IsProtectingMinor( cityOwnerID ) + local free = pledge and cityOwner:CanMajorWithdrawProtection( g_activePlayerID ) + instance.Pledge1:SetHide( not pledge or free ) + instance.Pledge2:SetHide( not free ) + end + -- Update Allies + local allyID = cityOwner:GetAlly() + local ally = Players[ allyID ] + if ally then + -- Set left banner icon to ally flag + otherCivAlpha = 1 + otherCivID = g_activeTeam:IsHasMet( ally:GetTeam() ) and allyID or -1 + end + else + CivIconHookup( cityOwnerID, 45, instance.OwnerIcon, instance.OwnerIconBG, instance.OwnerIconShadow, false, true ) + end + instance.CityQuests:SetHide( not isMinorCiv ) + instance.StatusIconBG:SetHide( not isMinorCiv ) + instance.OwnerIconBG:SetHide( isMinorCiv ) + end + if not otherCivID and originalCityOwner and (originalCityOwnerID ~= cityOwnerID) then + -- Set left banner icon to city state flag + if originalCityOwner:IsMinorCiv() then + otherCivAlpha = 4 --hack + instance.MinorTraitIcon:SetTexture( (GameInfo.MinorCivTraits[ (GameInfo.MinorCivilizations[ originalCityOwner:GetMinorCivType() ] or {}).MinorCivTrait ] or {}).TraitIcon ) + instance.CityIsOriginalCapital:SetHide( true ) + else + otherCivAlpha = 0.5 + otherCivID = originalCityOwnerID + end + end + if otherCivID then + CivIconHookup( otherCivID, 32, instance.CivIcon, instance.CivIconBG, instance.CivIconShadow, false, true ) + instance.CivIndicator:SetAlpha( otherCivAlpha ) + end + instance.MinorCivIndicator:SetHide( otherCivAlpha ~= 4 ) -- hack + instance.CivIndicator:SetHide( not otherCivID ) + + -- Spy & Religion + if gk_mode then + local spy + local x = city:GetX() + local y = city:GetY() + + for _, s in ipairs( g_activePlayer:GetEspionageSpies() ) do + if s.CityX == x and s.CityY == y then + spy = s + break + end + end + + if spy then + if spy.IsDiplomat then + instance.CityDiplomat:SetVoid1( spy.EstablishedSurveillance and plotIndex or -1 ) + instance.CityDiplomat:LocalizeAndSetToolTip( "TXT_KEY_CITY_DIPLOMAT_OTHER_CIV_TT", spy.Rank, spy.Name, cityName, spy.Rank, spy.Name, spy.Rank, spy.Name ) + instance.CityDiplomat:SetHide( false ) + instance.CitySpy:SetHide( true ) + else + instance.CitySpy:SetHide( false ) + instance.CitySpy:SetVoid1( spy.EstablishedSurveillance and plotIndex or -1 ) + if isActivePlayerCity then + instance.CitySpy:LocalizeAndSetToolTip( "TXT_KEY_CITY_SPY_YOUR_CITY_TT", spy.Rank, spy.Name, cityName, spy.Rank, spy.Name ) + elseif cityOwner:IsMinorCiv() then + instance.CitySpy:LocalizeAndSetToolTip( "TXT_KEY_CITY_SPY_CITY_STATE_TT", spy.Rank, spy.Name, cityName, spy.Rank, spy.Name) + else + instance.CitySpy:LocalizeAndSetToolTip( "TXT_KEY_CITY_SPY_OTHER_CIV_TT", spy.Rank, spy.Name, cityName, spy.Rank, spy.Name, spy.Rank, spy.Name) + end + instance.CityDiplomat:SetHide( true ) + end + else + instance.CitySpy:SetHide( true ) + instance.CityDiplomat:SetHide( true ) + end + + local religion = GameInfo.Religions[city:GetReligiousMajority()] + if religion then + IconHookup( religion.PortraitIndex, 32, religion.IconAtlas, instance.CityReligion ) + IconHookup( religion.PortraitIndex, 32, religion.IconAtlas, instance.ReligiousIconShadow ) + instance.ReligiousIconContainer:SetHide( false ) + else + instance.ReligiousIconContainer:SetHide( true ) + end + end + + -- Change the width of the banner so it looks good with the length of the city name + + instance.NameStack:CalculateSize() + local bannerWidth = instance.NameStack:GetSizeX() - 64 + instance.CityBannerButton:SetSizeX( bannerWidth + 64 ) + instance.CityBannerBackground:SetSizeX( bannerWidth ) + instance.CityBannerBackgroundHL:SetSizeX( bannerWidth ) + if isActiveType then + instance.CityBannerBackgroundIcon:SetSizeX( bannerWidth ) + instance.CityBannerButtonGlow:SetSizeX( bannerWidth ) + instance.CityBannerButtonBase:SetSizeX( bannerWidth ) + else + instance.CityBannerBaseFrame:SetSizeX( bannerWidth ) + instance.CityAtWar:SetSizeX( bannerWidth ) + instance.CityAtWar:SetHide( not g_activeTeam:IsAtWar( city:GetTeam() ) ) + end + + instance.CityBannerButton:ReprocessAnchoring() + instance.NameStack:ReprocessAnchoring() + instance.IconsStack:CalculateSize() + instance.IconsStack:ReprocessAnchoring() + + if g_cityHexHighlight == plotIndex then + + if not (InStrategicView and InStrategicView()) then + local cityID = city:GetID() + local cityTeamID = cityOwner:GetTeam() + local plot = GetPlot(0,0) + local hexPos + local checkFunc = plot.GetCityPurchaseID or plot.GetWorkingCity + local checkVal = plot.GetCityPurchaseID and cityID or city + for i = 0, city:GetNumCityPlots()-1 do + plot = city:GetCityIndexPlot( i ) + if plot then + hexPos = ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() } + -- Show city limits + EventsSerialEventHexHighlight( hexPos, true, nil, "CityLimits" ) + if plot:GetOwner() == cityOwnerID then + local isImproved = true + if plot:GetImprovementType()>0 then + isImproved = not plot:IsImprovementPillaged() -- CanHaveImprovement, GetImprovementType, GetRevealedImprovementType, IsImprovementPillaged + else + for improvement in GameInfo.Improvements() do + if plot:CanHaveImprovement( improvement.ID, cityTeamID ) then + isImproved = false + break + end + end + end + if isActiveType and city:IsWorkingPlot( plot ) then + -- worked city plots + if plot:IsCity() or city:IsForcedWorkingPlot( plot ) then + if isImproved then + EventsSerialEventHexHighlight( hexPos , true, nil, "WorkedFill" ) + end + EventsSerialEventHexHighlight( hexPos , true, nil, "WorkedOutline" ) + else + if isImproved then + EventsSerialEventHexHighlight( hexPos , true, nil, "UnlockedFill" ) + end + EventsSerialEventHexHighlight( hexPos , true, nil, "UnlockedOutline" ) + end + elseif not city:CanWork( plot ) and ( plot:IsWater() and city:IsPlotBlockaded( plot ) or plot:IsVisibleEnemyUnit( cityOwnerID ) ) then + -- Blockaded water plot or Enemy Unit standing here + EventsSerialEventHexHighlight( hexPos , true, nil, "EnemyFill" ) + EventsSerialEventHexHighlight( hexPos , true, nil, "EnemyOutline" ) + elseif checkFunc( plot ) == checkVal then + -- city plots that are owned but not worked + if isImproved then + EventsSerialEventHexHighlight( hexPos , true, nil, "OwnedFill" ) + end + EventsSerialEventHexHighlight( hexPos , true, nil, "OwnedOutline" ) + end + end + end + end + end + if isActiveType then + -- Show plots that will be acquired by culture + local purchasablePlots = {city:GetBuyablePlotList()} + for i = 1, #purchasablePlots do + local plot = purchasablePlots[i] + EventsSerialEventHexHighlight( ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() }, true, ColorCulture, "HexContour" ) + end + EventsRequestYieldDisplay( YieldDisplayTypes.AREA, CITY_PLOTS_RADIUS, city:GetX(), city:GetY() ) + else + EventsRequestYieldDisplay( YieldDisplayTypes.CITY_OWNED, city:GetX(), city:GetY() ) + end + end + end +end + +local function RefreshCityBannerAtPlot( plot ) + return plot and RefreshCityBanner( plot:GetPlotCity() ) +end + +--========================================================== +-- Register Events +--========================================================== + +------------------ +-- On City Created +Events.SerialEventCityCreated.Add( function( hexPos, cityOwnerID, cityID, cultureType, eraType, continent, populationSize, size, fowState ) + -- fowState 0 is invisible + if fowState ~= 0 then + return RefreshCityBannerAtPlot( GetPlot( ToGridFromHex( hexPos.x, hexPos.y ) ) ) + end +end) + +------------------ +-- On City Updated +Events.SerialEventCityInfoDirty.Add( function() + -- Don't know which city, so update all visible city banners + for plotIndex in pairs( g_cityBanners ) do + RefreshCityBannerAtPlot( GetPlotByIndex( plotIndex ) ) + end +end) + +-------------------- +-- On City Destroyed +Events.SerialEventCityDestroyed.Add( +function( hexPos ) --, cityOwnerID, cityID, newPlayerID ) + local plot = GetPlot( ToGridFromHex( hexPos.x, hexPos.y ) ) + if plot then + local plotIndex = plot:GetPlotIndex() + return DestroyCityBanner( plotIndex, g_cityBanners[ plotIndex ] ) + end +end) + +--------------------- +-- On City Set Damage +Events.SerialEventCitySetDamage.Add( function( cityOwnerID, cityID, cityDamage, previousDamage ) + local cityOwner = Players[ cityOwnerID ] + if cityOwner then + local city = cityOwner:GetCityByID( cityID ) + if city then + return RefreshCityDamage( city, g_cityBanners[ city:Plot():GetPlotIndex() ], cityDamage ) + end + end +end) + +--------------------------- +-- On Specific City changed +Events.SpecificCityInfoDirty.Add( function( cityOwnerID, cityID, updateType ) + local cityOwner = Players[ cityOwnerID ] + if cityOwner then + local city = cityOwner:GetCityByID( cityID ) + if city then + local plotIndex = city:Plot():GetPlotIndex() + local instance = g_cityBanners[ plotIndex ] + if instance then + if updateType == CityUpdateTypes.CITY_UPDATE_TYPE_ENEMY_IN_RANGE then + return UpdateRangeIcons( plotIndex, city, instance ) + elseif updateType == CityUpdateTypes.CITY_UPDATE_TYPE_BANNER or updateType == CityUpdateTypes.CITY_UPDATE_TYPE_GARRISON then + return RefreshCityBanner( city ) + end + end + end + end +end) + +------------------------- +-- On Improvement Created +Events.SerialEventImprovementCreated.Add( function( hexX, hexY, cultureID, continentID, playerID )--, improvementID, rawResourceID, improvementEra, improvementState ) + if playerID == g_activePlayerID then + local plot = GetPlot( ToGridFromHex( hexX, hexY ) ) + if plot then + return RefreshCityBanner( plot:GetWorkingCity() ) + end + end +end) + +--------------------------- +-- On Road/Railroad Created +Events.SerialEventRoadCreated.Add( function( hexX, hexY, playerID, roadID ) + if playerID == g_activePlayerID then + for city in g_activePlayer:Cities() do + RefreshCityBanner( city ) + end + end +end) + +--[[ +----------------------- +-- On city range strike +Events.InitCityRangeStrike.Add( function( cityOwnerID, cityID ) + if cityOwnerID == g_activePlayerID then + local city = g_activePlayer:GetCityByID( cityID ) + if city and city == UI.GetHeadSelectedCity() then + UI.SetInterfaceMode( InterfaceModeTypes.INTERFACEMODE_CITY_RANGE_ATTACK ) + end + end +end) +--]] + +------------------- +-- On Unit Garrison +Events.UnitGarrison.Add( function( unitOwnerID, unitID, isGarrisoned ) + if isGarrisoned then + local unitOwner = Players[ unitOwnerID ] + if unitOwner then + local unit = unitOwner:GetUnitByID( unitID ) + if unit then + local city = unit:GetGarrisonedCity() + if city then + return HideGarrisonFrame( g_cityBanners[ city:Plot():GetPlotIndex() ], UnitMoving( unitOwnerID, unitID ) ) + end + end + end + end +end) + +----------------------------- +-- On Unit Move Queue Changed +Events.UnitMoveQueueChanged.Add( function( unitOwnerID, unitID, hasRemainingMoves ) + local unitOwner = Players[ unitOwnerID ] + if unitOwner then + local unit = unitOwner:GetUnitByID( unitID ) + if unit then + local city = unit:GetGarrisonedCity() + if city then + return HideGarrisonFrame( g_cityBanners[ city:Plot():GetPlotIndex() ], not hasRemainingMoves ) + end + end + end +end) + +--[[ +--------------------------- +-- On interface mode change +Events.InterfaceModeChanged.Add( function( oldInterfaceMode, newInterfaceMode ) + local disableBanners = newInterfaceMode ~= InterfaceModeTypes.INTERFACEMODE_SELECTION + for _, instance in pairs( g_cityBanners ) do + instance.CityBannerButton:SetDisabled( disableBanners ) + instance.CityBannerButton:EnableToolTip( not disableBanners ) + end +end) +--]] + +--------------------------- +-- On strategic view change +Events.StrategicViewStateChanged.Add( function(isStrategicView, showCityBanners) + local showBanners = showCityBanners or not isStrategicView + Controls.CityBanners:SetHide( not showBanners ) + return Controls.StrategicViewStrikeButtons:SetHide( showBanners ) +end) + +----------------------- +-- On fog of war change +Events.HexFOWStateChanged.Add( function( hexPos, fowType, isWholeMap ) + if isWholeMap then + -- fowState 0 is invisible + if fowType == 0 then + for plotIndex, instance in pairs( g_cityBanners ) do + DestroyCityBanner( plotIndex, instance ) + end + else + for playerID = 0, #Players do + local player = Players[ playerID ] + if player and player:IsAlive() then + for city in player:Cities() do + RefreshCityBanner( city ) + end + end + end + end + else + local plot = GetPlot( ToGridFromHex( hexPos.x, hexPos.y ) ) + if plot then + -- fowType 0 is invisible + if fowType == 0 then + local plotIndex = plot:GetPlotIndex() + return DestroyCityBanner( plotIndex, g_cityBanners[ plotIndex ] ) + else + return RefreshCityBannerAtPlot( plot ) + end + end + end +end) + +--------------------------- +-- On War Declared +Events.WarStateChanged.Add( +function( teamID1, teamID2, isAtWar ) + if teamID1 == g_activeTeamID then + teamID1 = teamID2 + elseif teamID2 ~= g_activeTeamID then + return + end + for playerID = 0, #Players do + local player = Players[playerID] + if player and player:IsAlive() and player:GetTeam() == teamID1 then + for city in player:Cities() do + if city:Plot():IsRevealed( g_activeTeamID, true ) then + RefreshCityBanner( city ) + end + end + end + end +end) + +--========================================================== +-- 'Active' (local human) player has changed: +-- Check for City Banner Active Type change +--========================================================== +Events.GameplaySetActivePlayer.Add( function( activePlayerID, previousActivePlayerID ) + -- update globals + + g_activePlayerID = Game.GetActivePlayer() + g_activePlayer = Players[ g_activePlayerID ] + g_activeTeamID = Game.GetActiveTeam() + g_activeTeam = Teams[ g_activeTeamID ] + ClearHexHighlights() + local isDebug = Game.IsDebugMode() or g_activePlayer:IsObserver() + -- Update all city banners + for playerID = 0, #Players do + local player = Players[ playerID ] + if player and player:IsAlive() then + for city in player:Cities() do + local plot = city:Plot() + local plotIndex = plot:GetPlotIndex() + local instance = g_cityBanners[ plotIndex ] + + if plot:IsRevealed( g_activeTeamID, isDebug ) then + RefreshCityBanner( city ) + -- If city banner is hidden, destroy the banner + elseif instance then + DestroyCityBanner( plotIndex, instance ) + end + end + end + end +end) + +--========================================================== +-- Hide Garrisson Ring during Animated Combat +--========================================================== +if gk_mode then + + local function HideGarrisonRing( x, y, hideGarrisonRing ) + + local plot = GetPlot( x, y ) + local city = plot and plot:GetPlotCity() + local instance = city and g_cityBanners[ plot:GetPlotIndex() ] + return instance and HideGarrisonFrame( instance, hideGarrisonRing or not city:GetGarrisonedUnit() ) + end + + Events.RunCombatSim.Add( function( + attackerPlayerID, + attackerUnitID, + attackerUnitDamage, + attackerFinalUnitDamage, + attackerMaxHitPoints, + defenderPlayerID, + defenderUnitID, + defenderUnitDamage, + defenderFinalUnitDamage, + defenderMaxHitPoints, + attackerX, + attackerY, + defenderX, + defenderY, + bContinuation) +--print( "CityBanner CombatBegin", attackerX, attackerY, defenderX, defenderY ) + + HideGarrisonRing(attackerX, attackerY, true) + HideGarrisonRing(defenderX, defenderY, true) + end) + + Events.EndCombatSim.Add( function( + attackerPlayerID, + attackerUnitID, + attackerUnitDamage, + attackerFinalUnitDamage, + attackerMaxHitPoints, + defenderPlayerID, + defenderUnitID, + defenderUnitDamage, + defenderFinalUnitDamage, + defenderMaxHitPoints, + attackerX, + attackerY, + defenderX, + defenderY ) + +--print( "CityBanner CombatEnd", attackerX, attackerY, defenderX, defenderY ) + + HideGarrisonRing(attackerX, attackerY, false) + HideGarrisonRing(defenderX, defenderY, false) + end) + +end -- gk_mode + +--========================================================== +-- The active player's turn has begun, make sure their range strike icons are correct +--========================================================== +Events.ActivePlayerTurnStart.Add( function() + for plotIndex, instance in pairs( g_cityBanners ) do + UpdateRangeIcons( plotIndex, GetPlotByIndex( plotIndex ):GetPlotCity(), instance ) + end +end) + +Events.SerialEventUnitDestroyed.Add( function( unitOwnerID, unitID ) + local unitOwner = Players[ unitOwnerID ] + if unitOwner and g_activeTeam:IsAtWar( unitOwner:GetTeam() ) then + for city in g_activePlayer:Cities() do + local plotIndex = city:Plot():GetPlotIndex() + UpdateRangeIcons( plotIndex, city, g_cityBanners[ plotIndex ] ) + end + end +end) + +--========================================================== +-- Initialize all Visible City Banners +--========================================================== +for playerID = 0, #Players do + local player = Players[ playerID ] + if player and player:IsEverAlive() then + for city in player:Cities() do + if city:Plot():IsRevealed( g_activeTeamID, true ) then + RefreshCityBanner( city ) + end + end + end +end + +end) diff --git a/CityView.lua.cuc b/CityView.lua.cuc new file mode 100644 index 0000000000000000000000000000000000000000..9411c02ae8d3c98a32a3c44d4891f18fcde2a453 --- /dev/null +++ b/CityView.lua.cuc @@ -0,0 +1,2246 @@ +--========================================================== +-- City View +-- Re-written by bc1 using Notepad++ +-- code is common using switches +-- compatible with Gazebo's City-State Diplomacy Mod (CSD) for Brave New World v21 +-- compatible with JFD's Piety & Prestige for Brave New World +-- compatible with GameInfo.Yields() iterator broken by Communitas +--todo: upper left corner +--todo: selection list with all buildable items +--todo: mod case where several buildings are allowed +--========================================================== + +Events.SequenceGameInitComplete.Add(function() + +local IsCiv5 = InStrategicView ~= nil +local IsCivBE = not IsCiv5 +local IsCiv5vanilla = IsCiv5 and not Game.GetReligionName +local IsCiv5BNW = IsCiv5 and Game.GetActiveLeague ~= nil + +include "UserInterfaceSettings" +local UserInterfaceSettings = UserInterfaceSettings + +include "GameInfoCache" -- warning! booleans are true, not 1, and use iterator ONLY with table field conditions, NOT string SQL query +local GameInfo = GameInfoCache + +include "IconHookup" +local IconHookup = IconHookup +local CivIconHookup = CivIconHookup +local ColorCulture = Color( 1, 0, 1, 1 ) + +include "StackInstanceManager" +local StackInstanceManager = StackInstanceManager + +include "ShowProgress" +local ShowProgress = ShowProgress + +include "SupportFunctions" +local TruncateString = TruncateString + +if IsCiv5BNW then + include "GreatPeopleIcons" +end +local GreatPeopleIcons = GreatPeopleIcons + +if IsCivBE then + include "IntrigueHelper" +end + +--========================================================== +-- Minor lua optimizations +--========================================================== + +local ipairs = ipairs +local abs = math.abs +local ceil = math.ceil +local floor = math.floor +local max = math.max +local min = math.min +local sqrt = math.sqrt +local pairs = pairs +local tonumber = tonumber +local tostring = tostring +local unpack = unpack +local concat = table.concat +local insert = table.insert +local sort = table.sort + +local ButtonPopupTypes = ButtonPopupTypes +local CityAIFocusTypes = CityAIFocusTypes +local CityUpdateTypes = CityUpdateTypes +local ContextPtr = ContextPtr +local Controls = Controls +local Events = Events +local EventsClearHexHighlightStyle = Events.ClearHexHighlightStyle.Call +local EventsRequestYieldDisplay = Events.RequestYieldDisplay.Call +local EventsSerialEventHexHighlight = Events.SerialEventHexHighlight.Call +local Game = Game +local GameDefines = GameDefines +local GameInfoTypes = GameInfoTypes +local GameMessageTypes = GameMessageTypes +local GameOptionTypes = GameOptionTypes +local HexToWorld = HexToWorld +local InStrategicView = InStrategicView or function() return false end +local KeyEvents = KeyEvents +local Keys = Keys +local L = Locale.ConvertTextKey +local Locale = Locale +local eLClick = Mouse.eLClick +local eRClick = Mouse.eRClick +local eMouseEnter = Mouse.eMouseEnter +local Network = Network +local NotificationTypes = NotificationTypes +local OptionsManager = OptionsManager +local Players = Players +local PopupPriority = PopupPriority +local TaskTypes = TaskTypes +local ToHexFromGrid = ToHexFromGrid +local UI = UI +local GetHeadSelectedCity = UI.GetHeadSelectedCity +local GetUnitPortraitIcon = UI.GetUnitPortraitIcon +local YieldDisplayTypesAREA = YieldDisplayTypes.AREA +local YieldTypes = YieldTypes + +local ORDER_TRAIN = OrderTypes.ORDER_TRAIN +local ORDER_CONSTRUCT = OrderTypes.ORDER_CONSTRUCT +local ORDER_CREATE = OrderTypes.ORDER_CREATE +local ORDER_MAINTAIN = OrderTypes.ORDER_MAINTAIN + +--========================================================== +-- Globals +--========================================================== + +local g_currencyIcon = IsCiv5 and "[ICON_GOLD]" or "[ICON_ENERGY]" +local g_maintenanceCurrency = IsCiv5 and "GoldMaintenance" or "EnergyMaintenance" +local g_yieldCurrency = IsCiv5 and YieldTypes.YIELD_GOLD or YieldTypes.YIELD_ENERGY + +local g_isAdvisor = true + +local g_activePlayerID = Game.GetActivePlayer() +local g_activePlayer = Players[ g_activePlayerID ] +local g_finishedItems = {} + +local g_workerHeadingOpen = OptionsManager.IsNoCitizenWarning() + +local g_worldPositionOffset = { x = 0, y = 0, z = 30 } +local g_worldPositionOffset2 = { x = 0, y = 35, z = 0 } +local g_portraitSize = Controls.PQportrait:GetSizeX() +local g_screenHeight = select(2, UIManager.GetScreenSizeVal() ) +local g_leftStackHeigth = g_screenHeight - 40 - Controls.CityInfoBG:GetOffsetY() - Controls.CityInfoBG:GetSizeY() + +local g_PlotButtonIM = StackInstanceManager( "PlotButtonInstance", "PlotButtonAnchor", Controls.PlotButtonContainer ) +local g_BuyPlotButtonIM = StackInstanceManager( "BuyPlotButtonInstance", "BuyPlotButtonAnchor", Controls.PlotButtonContainer ) +local g_GreatWorksIM = StackInstanceManager( "Work", "Button", false ) +local g_SpecialistsIM = StackInstanceManager( "Slot", "Button", false ) + +local g_ProdQueueIM, g_SpecialBuildingsIM, g_GreatWorkIM, g_WondersIM, g_BuildingsIM, g_GreatPeopleIM, g_SlackerIM, g_UnitSelectIM, g_BuildingSelectIM, g_WonderSelectIM, g_ProcessSelectIM, g_FocusSelectIM +local g_queuedItemNumber, g_isDebugMode, g_BuyPlotMode, g_previousCity, g_isButtonPopupChooseProduction, g_isScreenAutoClose, g_isResetCityPlotPurchase + +local g_citySpecialists = {} + +local g_isViewingMode = true + +local g_slotTexture = { + SPECIALIST_CITIZEN = "CitizenUnemployed.dds", + SPECIALIST_SCIENTIST = "CitizenScientist.dds", + SPECIALIST_MERCHANT = "CitizenMerchant.dds", + SPECIALIST_ARTIST = "CitizenArtist.dds", + SPECIALIST_MUSICIAN = "CitizenArtist.dds", + SPECIALIST_WRITER = "CitizenArtist.dds", + SPECIALIST_ENGINEER = "CitizenEngineer.dds", + SPECIALIST_CIVIL_SERVANT = "CitizenCivilServant.dds", -- Compatibility with Gazebo's City-State Diplomacy Mod (CSD) for Brave New World + SPECIALIST_JFD_MONK = "CitizenMonk.dds", -- Compatibility with JFD's Piety & Prestige for Brave New World + SPECIALIST_PMMM_ENTERTAINER = "PMMMEntertainmentSpecialist.dds", --Compatibility with Vicevirtuoso's Madoka Magica: Wish for the World for Brave New World +} +for specialist in GameInfo.Specialists() do + if specialist.SlotTexture then + g_slotTexture[ specialist.Type ] = specialist.SlotTexture + end +end + +local g_slackerTexture = IsCivBE and "UnemployedIndicator.dds" or g_slotTexture[ (GameInfo.Specialists[GameDefines.DEFAULT_SPECIALIST] or {}).Type ] + +local g_gameInfo = { +[ORDER_TRAIN] = GameInfo.Units, +[ORDER_CONSTRUCT] = GameInfo.Buildings, +[ORDER_CREATE] = GameInfo.Projects, +[ORDER_MAINTAIN] = GameInfo.Processes, +} +local g_avisorRecommended = { +[ORDER_TRAIN] = Game.IsUnitRecommended, +[ORDER_CONSTRUCT] = Game.IsBuildingRecommended, +[ORDER_CREATE] = Game.IsProjectRecommended, +} +local g_advisors = { +[AdvisorTypes.ADVISOR_ECONOMIC] = "EconomicRecommendation", +[AdvisorTypes.ADVISOR_MILITARY] = "MilitaryRecommendation", +[AdvisorTypes.ADVISOR_SCIENCE] = "ScienceRecommendation", +[AdvisorTypes.ADVISOR_FOREIGN] = "ForeignRecommendation", +} + +local function GetSelectedCity() + return ( not Game.IsNetworkMultiPlayer() or g_activePlayer:IsTurnActive() ) and GetHeadSelectedCity() +end + +local function GetSelectedModifiableCity() + return not g_isViewingMode and GetSelectedCity() +end + +---------------- +-- Citizen Focus +local g_cityFocusButtons = { + a = Controls.AvoidGrowthButton, + b = Controls.ResetButton, + c = Controls.BoxOSlackers, + d = Controls.EditButton, +} +do + local g_cityFocusControls = { + [ Controls.BalancedFocusButton or -1 ] = CityAIFocusTypes.NO_CITY_AI_FOCUS_TYPE or false, + [ Controls.FoodFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FOOD or false, + [ Controls.ProductionFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_PRODUCTION or false, + [ Controls.GoldFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GOLD or CityAIFocusTypes.CITY_AI_FOCUS_TYPE_ENERGY or false, + [ Controls.ResearchFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_SCIENCE or false, + [ Controls.CultureFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_CULTURE or false, + [ Controls.GPFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GREAT_PEOPLE or false, + [ Controls.FaithFocusButton or -1 ] = CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FAITH or false, + } g_cityFocusControls[-1] = nil + local function FocusButtonBehavior( focus ) + local city = GetSelectedModifiableCity() + if city then + Network.SendSetCityAIFocus( city:GetID(), focus ) + return Network.SendUpdateCityCitizens( city:GetID() ) + end + end + for control, focus in pairs( g_cityFocusControls ) do + if focus then + g_cityFocusButtons[ focus ] = control + control:SetVoid1( focus ) + control:RegisterCallback( eLClick, FocusButtonBehavior ) + else + control:SetHide( true ) + end + end +end + +local function HexRadius( a ) + return ( sqrt( 1 + 4*a/3 ) - 1 ) / 2 +end + +local function SetupCallbacks( controls, toolTips, tootTipType, callBacks ) + local control + -- Setup Tootips + for name, callback in pairs( toolTips ) do + control = controls[name] + if control then + control:SetToolTipCallback( callback ) + control:SetToolTipType( tootTipType ) + end + end + -- Setup Callbacks + for name, eventCallbacks in pairs( callBacks ) do + control = controls[name] + if control then + for event, callback in pairs( eventCallbacks ) do + control:RegisterCallback( event, callback ) + end + end + end +end + +local function ResizeProdQueue() + local selectionPanelHeight = 0 + local queuePanelHeight = min( 190, Controls.QueueStack:IsHidden() and 0 or Controls.QueueStack:GetSizeY() ) -- 190 = 5 x 38=instance height + if not Controls.SelectionScrollPanel:IsHidden() then + Controls.SelectionStacks:CalculateSize() + selectionPanelHeight = max( min( g_leftStackHeigth - queuePanelHeight, Controls.SelectionStacks:GetSizeY() ), 64 ) +-- Controls.SelectionBackground:SetSizeY( selectionPanelHeight + 85 ) + Controls.SelectionScrollPanel:SetSizeY( selectionPanelHeight ) + Controls.SelectionScrollPanel:CalculateInternalSize() + Controls.SelectionScrollPanel:ReprocessAnchoring() + end + Controls.QueueSlider:SetSizeY( queuePanelHeight + 38 ) -- 38 = Controls.PQbox:GetSizeY() + Controls.QueueScrollPanel:SetSizeY( queuePanelHeight ) + Controls.QueueScrollPanel:CalculateInternalSize() + Controls.QueueBackground:SetSizeY( queuePanelHeight + selectionPanelHeight + 152 ) -- 125 = 38=Controls.PQbox:GetSizeY() + 87 + 27 + return Controls.QueueBackground:ReprocessAnchoring() +end + +local function ResizeRightStack() + Controls.BoxOSlackers:SetHide( Controls.SlackerStack:IsHidden() ) + Controls.BoxOSlackers:SetSizeY( Controls.SlackerStack:GetSizeY() ) + Controls.WorkerManagementBox:CalculateSize() + Controls.WorkerManagementBox:ReprocessAnchoring() + Controls.RightStack:CalculateSize() + local rightStackHeight = Controls.RightStack:GetSizeY() + 85 + Controls.BuildingListBackground:SetSizeY( max( min( g_screenHeight + 48, rightStackHeight ), 160 ) ) + Controls.RightScrollPanel:SetSizeY( min( g_screenHeight - 38, rightStackHeight ) ) + Controls.RightScrollPanel:CalculateInternalSize() + return Controls.RightScrollPanel:ReprocessAnchoring() +end + +local cityIsCanPurchase +if IsCiv5vanilla then + function cityIsCanPurchase( city, bTestPurchaseCost, bTestTrainable, unitID, buildingID, projectID, yieldID ) + if yieldID == g_yieldCurrency then + return city:IsCanPurchase( not bTestPurchaseCost, unitID, buildingID, projectID ) + -- bOnlyTestVisible + else + return false + end + end +else + function cityIsCanPurchase( city, ... ) + return city:IsCanPurchase( ... ) + end +end + +--========================================================== +-- Clear out the UI so that when a player changes +-- the next update doesn't show the previous player's +-- values for a frame +--========================================================== +local function ClearCityUIInfo() + g_ProdQueueIM:ResetInstances() + g_ProdQueueIM.Commit() + Controls.PQremove:SetHide( true ) + Controls.PQrank:SetText() + Controls.PQname:SetText() + Controls.PQturns:SetText() + return Controls.ProductionPortraitButton:SetHide(true) +end + +--========================================================== +-- Selling Buildings +--========================================================== +local function SellBuilding( buildingID ) + + local city = GetSelectedModifiableCity() + local building = GameInfo.Buildings[ buildingID ] + -- Can this building be sold? + if building and city and city:IsBuildingSellable( buildingID ) then + Controls.YesButton:SetVoids( city:GetID(), buildingID ) + Controls.SellBuildingTitle:SetText( building._Name:upper() ) + Controls.SellBuildingText:LocalizeAndSetText( "TXT_KEY_SELL_BUILDING_INFO", city:GetSellBuildingRefund(buildingID), building[g_maintenanceCurrency] or 0 ) + Controls.SellBuildingImage:SetHide( not IconHookup( building.PortraitIndex, 256, building.IconAtlas, Controls.SellBuildingImage ) ) + Controls.SellBuildingStack:CalculateSize() + Controls.SellBuildingFrame:DoAutoSize() +--todo energy + return Controls.SellBuildingConfirm:SetHide( false ) + end +end + +local function CancelBuildingSale() + Controls.SellBuildingConfirm:SetHide(true) + return Controls.YesButton:SetVoids( -1, -1 ) +end + +local function CleanupCityScreen() + -- clear any rogue leftover tooltip + g_isButtonPopupChooseProduction = false + Controls.RightScrollPanel:SetScrollValue(0) + return CancelBuildingSale() +end + +local function GotoNextCity() + if not g_isViewingMode then + CleanupCityScreen() + return Game.DoControl( GameInfoTypes.CONTROL_NEXTCITY ) + end +end + +local function GotoPrevCity() + if not g_isViewingMode then + CleanupCityScreen() + return Game.DoControl( GameInfoTypes.CONTROL_PREVCITY ) + end +end + +local function ExitCityScreen() +--print("request exit city screen") +-- CleanupCityScreen() + return Events.SerialEventExitCityScreen() +end + +--========================================================== +-- Key Down Processing +--========================================================== +do + local VK_RETURN = Keys.VK_RETURN + local VK_ESCAPE = Keys.VK_ESCAPE + local VK_LEFT = Keys.VK_LEFT + local VK_RIGHT = Keys.VK_RIGHT + local KeyDown = KeyEvents.KeyDown + ContextPtr:SetInputHandler( function ( uiMsg, wParam ) + if uiMsg == KeyDown then + if wParam == VK_ESCAPE or wParam == VK_RETURN then + if Controls.SellBuildingConfirm:IsHidden() then + ExitCityScreen() + else + CancelBuildingSale() + end + return true + elseif wParam == VK_LEFT then + GotoPrevCity() + return true + elseif wParam == VK_RIGHT then + GotoNextCity() + return true + end + end + end) +end + +--========================================================== +-- Pedia +--========================================================== +local SearchForPediaEntry = Events.SearchForPediaEntry.Call +local function Pedia( row ) + return SearchForPediaEntry( row and row._Name ) +end + +local function UnitClassPedia( unitClassID ) + return Pedia( GameInfo.UnitClasses[ unitClassID ] ) +end + +local function BuildingPedia( buildingID ) + return Pedia( GameInfo.Buildings[ buildingID ] ) +end + +local function SpecialistPedia( buildingID ) + return Pedia( GameInfo.Specialists[ GameInfoTypes[(GameInfo.Buildings[buildingID]or{}).SpecialistType] or GameDefines.DEFAULT_SPECIALIST ] ) +end + +local function SelectionPedia( orderID, itemID ) + return Pedia( (g_gameInfo[ orderID ]or{})[ itemID ] ) +end + +local function ProductionPedia( queuedItemNumber ) + local city = GetHeadSelectedCity() + if city and queuedItemNumber then + return SelectionPedia( city:GetOrderFromQueue( queuedItemNumber ) ) + end +end + +--========================================================== +-- Tooltips +--========================================================== + +local GreatPeopleIcon = GreatPeopleIcons and function (k) + return GreatPeopleIcons[k] +end or function() + return "[ICON_GREAT_PEOPLE]" +end + +local function GetSpecialistYields( city, specialist ) + local yieldTips = {} + if city and specialist then + local specialistYield, specialistYieldModifier, yieldInfo + local specialistID = specialist.ID + local cityOwner = Players[ city:GetOwner() ] + -- Culture + local cultureFromSpecialist = city:GetCultureFromSpecialist( specialistID ) + local specialistCultureModifier = city:GetCultureRateModifier() + ( cityOwner and ( cityOwner:GetCultureCityModifier() + ( city:GetNumWorldWonders() > 0 and cityOwner:GetCultureWonderMultiplier() or 0 ) or 0 ) ) + -- Yield + for yieldID = 0, YieldTypes.NUM_YIELD_TYPES-1 do + yieldInfo = GameInfo.Yields[yieldID] + if yieldInfo then + specialistYield = city:GetSpecialistYield( specialistID, yieldID ) + specialistYieldModifier = city:GetBaseYieldRateModifier( yieldID ) + if yieldID == YieldTypes.YIELD_CULTURE then + specialistYield = specialistYield + cultureFromSpecialist + specialistYieldModifier = specialistYieldModifier + specialistCultureModifier + cultureFromSpecialist = 0 + end + if specialistYield ~= 0 then + insert( yieldTips, specialistYield * specialistYieldModifier / 100 .. yieldInfo.IconString ) + end + end + end + if cultureFromSpecialist ~= 0 then + insert( yieldTips, cultureFromSpecialist .. "[ICON_CULTURE]" ) + end + if IsCiv5 and (specialist.GreatPeopleRateChange or 0) ~= 0 then + insert( yieldTips, specialist.GreatPeopleRateChange .. GreatPeopleIcon( specialist.Type ) ) + end + end + return concat( yieldTips, " " ) +end + +local ShowTextToolTipAndPicture = LuaEvents.ShowTextToolTipAndPicture.Call +local BuildingToolTip = LuaEvents.CityViewBuildingToolTip.Call +local CityOrderItemTooltip = LuaEvents.CityOrderItemTooltip.Call + +local function SpecialistTooltip( control ) + local buildingID = control:GetVoid1() + local building = GameInfo.Buildings[ buildingID ] + local specialistType = building and building.SpecialistType + local specialistID = specialistType and GameInfoTypes[specialistType] or GameDefines.DEFAULT_SPECIALIST + local specialist = GameInfo.Specialists[ specialistID ] + local tip = specialist._Name .. " " .. GetSpecialistYields( GetHeadSelectedCity(), specialist ) + local slotTable = building and g_citySpecialists[buildingID] + if slotTable and not slotTable[control:GetVoid2()] then + tip = L"TXT_KEY_CITYVIEW_EMPTY_SLOT".."[NEWLINE]("..tip..")" + end + return ShowTextToolTipAndPicture( tip, specialist.PortraitIndex, specialist.IconAtlas ) +end + +local function ProductionToolTip( control ) + local city = GetHeadSelectedCity() + local queuedItemNumber = control:GetVoid1() + if city and not Controls.QueueSlider:IsTrackingLeftMouseButton() then + return CityOrderItemTooltip( city, false, false, city:GetOrderFromQueue( queuedItemNumber ) ) + end +end + +--========================================================== +-- Specialist Managemeent +--========================================================== + +local function OnSlackersSelected( buildingID, slotID ) + local city = GetSelectedModifiableCity() + if city then + for _=1, slotID<=0 and city:GetSpecialistCount( GameDefines.DEFAULT_SPECIALIST ) or 1 do + Network.SendDoTask( city:GetID(), TaskTypes.TASK_REMOVE_SLACKER, 0, -1, false ) + end + end +end + +local function ToggleSpecialist( buildingID, slotID ) + local city = buildingID and slotID and GetSelectedModifiableCity() + if city then + + -- If Specialists are automated then you can't change things with them + if IsCiv5 and not city:IsNoAutoAssignSpecialists() then + Game.SelectedCitiesGameNetMessage(GameMessageTypes.GAMEMESSAGE_DO_TASK, TaskTypes.TASK_NO_AUTO_ASSIGN_SPECIALISTS, -1, -1, true) + Controls.NoAutoSpecialistCheckbox:SetCheck(true) + if IsCiv5BNW then + Controls.NoAutoSpecialistCheckbox2:SetCheck(true) + end + end + + local specialistID = GameInfoTypes[(GameInfo.Buildings[ buildingID ] or {}).SpecialistType] or -1 + local specialistTable = g_citySpecialists[buildingID] + if specialistTable[slotID] then + if city:GetNumSpecialistsInBuilding(buildingID) > 0 then + specialistTable[slotID] = false + specialistTable.n = specialistTable.n - 1 + return Game.SelectedCitiesGameNetMessage( GameMessageTypes.GAMEMESSAGE_DO_TASK, TaskTypes.TASK_REMOVE_SPECIALIST, specialistID, buildingID ) + end + elseif city:IsCanAddSpecialistToBuilding(buildingID) then + specialistTable[slotID] = true + specialistTable.n = specialistTable.n + 1 + return Game.SelectedCitiesGameNetMessage( GameMessageTypes.GAMEMESSAGE_DO_TASK, TaskTypes.TASK_ADD_SPECIALIST, specialistID, buildingID ) + end + end +end + +--========================================================== +-- Great Work Managemeent +--========================================================== + +local function GreatWorkPopup( greatWorkID ) + local greatWork = GameInfo.GreatWorks[ Game.GetGreatWorkType( greatWorkID or -1 ) or -1 ] + + if greatWork and greatWork.GreatWorkClassType ~= "GREAT_WORK_ARTIFACT" then + return Events.SerialEventGameMessagePopup{ + Type = ButtonPopupTypes.BUTTONPOPUP_GREAT_WORK_COMPLETED_ACTIVE_PLAYER, + Data1 = greatWorkID, + Priority = PopupPriority.Current + } + end +end + +local function YourCulturePopup( greatWorkID ) + return Events.SerialEventGameMessagePopup{ + Type = ButtonPopupTypes.BUTTONPOPUP_CULTURE_OVERVIEW, + Data1 = 1, + Data2 = 1, + } +end + +local function ThemingTooltip( buildingClassID, _, control ) + control:SetToolTipString( GetHeadSelectedCity():GetThemingTooltip( buildingClassID ) ) +end + +local function GreatWorkTooltip( greatWorkID, greatWorkSlotID, slot ) + if greatWorkID >= 0 then + return slot:SetToolTipString( Game.GetGreatWorkTooltip( greatWorkID, GetHeadSelectedCity():GetOwner() ) ) + else + return slot:LocalizeAndSetToolTip( tostring(( GameInfo.GreatWorkSlots[ greatWorkSlotID ] or {}).EmptyToolTipText) ) + end +end + +--========================================================== +-- City Buildings List +--========================================================== + +local function sortBuildings(a,b) + if a and b then + if a[4] ~= b[4] then + return a[4] < b[4] + elseif a[3] ~= b[3] then + return a[3] > b[3] + end + return a[2] < b[2] + end +end + +local function SetupBuildingList( city, buildings, buildingIM ) + buildingIM:ResetInstances() + sort( buildings, sortBuildings ) + local cityOwnerID = city:GetOwner() + local cityOwner = Players[ cityOwnerID ] + local isNotResistance = not city:IsResistance() + -- Get the active perk types for civ BE + local cityOwnerPerks = IsCivBE and cityOwner:GetAllActivePlayerPerkTypes() + local building, buildingID, buildingClassID, buildingName, greatWorkCount, greatWorkID, slotStack, slot, new, instance, buildingButton, sellButton, textButton + + for i = 1, #buildings do + + building, buildingName, greatWorkCount = unpack(buildings[i]) + buildingID = building.ID + buildingClassID = GameInfoTypes[ building.BuildingClass ] or -1 + instance, new = buildingIM:GetInstance() + slotStack = instance.SlotStack + buildingButton = instance.Button + sellButton = instance.SellButton + textButton = instance.TextButton + textButton:SetHide( true ) + + if new then + buildingButton:RegisterCallback( eRClick, BuildingPedia ) + buildingButton:SetToolTipCallback( BuildingToolTip ) + sellButton:RegisterCallback( eLClick, SellBuilding ) + textButton:RegisterCallback( eLClick, YourCulturePopup ) + textButton:RegisterCallback( eMouseEnter, ThemingTooltip ) + end + buildingButton:SetVoid1( buildingID ) + + -- Can we sell this building? + if not g_isViewingMode and city:IsBuildingSellable( buildingID ) then + sellButton:SetText( city:GetSellBuildingRefund( buildingID ) .. g_currencyIcon ) + sellButton:SetHide( false ) + sellButton:SetVoid1( buildingID ) + else + sellButton:SetHide( true ) + end + + +--!!!BE portrait size is bigger + + instance.Portrait:SetHide( not IconHookup( building.PortraitIndex, 64, building.IconAtlas, instance.Portrait ) ) + + ------------------- + -- Great Work Slots + if greatWorkCount > 0 then + local buildingGreatWorkSlotType = building.GreatWorkSlotType + if buildingGreatWorkSlotType then + local buildingGreatWorkSlot = GameInfo.GreatWorkSlots[ buildingGreatWorkSlotType ] + if city:IsThemingBonusPossible( buildingClassID ) then + textButton:SetText( " +" .. city:GetThemingBonus( buildingClassID ) ) + textButton:SetVoid1( buildingClassID ) + textButton:SetHide( false ) + end + + for i = 0, greatWorkCount - 1 do + slot, new = g_GreatWorksIM:GetInstance( slotStack ) + slot = slot.Button + if new then + slot:RegisterCallback( eLClick, YourCulturePopup ) + slot:RegisterCallback( eMouseEnter, GreatWorkTooltip ) + end + greatWorkID = city:GetBuildingGreatWork( buildingClassID, i ) + slot:SetVoids( greatWorkID, buildingGreatWorkSlot.ID ) + if greatWorkID >= 0 then + slot:SetTexture( buildingGreatWorkSlot.FilledIcon ) + slot:RegisterCallback( eRClick, GreatWorkPopup ) + else + slot:SetTexture( buildingGreatWorkSlot.EmptyIcon ) + slot:ClearCallback( eRClick ) + end + end + end + end + + ------------------- + -- Specialist Slots + local numSpecialistsInBuilding = city:GetNumSpecialistsInBuilding( buildingID ) + local specialistTable = g_citySpecialists[buildingID] or {} + if specialistTable.n ~= numSpecialistsInBuilding then + specialistTable = { n = numSpecialistsInBuilding } + for i = 1, numSpecialistsInBuilding do + specialistTable[i] = true + end + g_citySpecialists[buildingID] = specialistTable + end + local specialistType = building.SpecialistType + local specialist = GameInfo.Specialists[specialistType] + if specialist then + for slotID = 1, city:GetNumSpecialistsAllowedByBuilding( buildingID ) do + slot, new = g_SpecialistsIM:GetInstance( slotStack ) + slot = slot.Button + if new then + slot:RegisterCallback( eRClick, SpecialistPedia ) + slot:SetToolTipCallback( SpecialistTooltip ) + end + if IsCiv5 then + slot:SetTexture( specialistTable[ slotID ] and g_slotTexture[ specialistType ] or "CitizenEmpty.dds" ) + else + -- todo (does not look right) + IconHookup( specialist.PortraitIndex, 45, specialist.IconAtlas, slot ) + slot:SetHide( not specialistTable[ slotID ] ) + end + slot:SetVoids( buildingID, slotID ) + if g_isViewingMode then + slot:ClearCallback( eLClick ) + else + slot:RegisterCallback( eLClick, ToggleSpecialist ) + end + end -- Specialist Slots + end + + -- Building stats/bonuses + local maintenanceCost = tonumber(building[g_maintenanceCurrency]) or 0 + local defenseChange = tonumber(building.Defense) or 0 + local hitPointChange = tonumber(building.ExtraCityHitPoints) or 0 + local buildingCultureRate = (IsCiv5vanilla and tonumber(building.Culture) or 0) + (specialist and city:GetCultureFromSpecialist( specialist.ID ) or 0) * numSpecialistsInBuilding + local buildingCultureModifier = tonumber(building.CultureRateModifier) or 0 + local cityCultureRateModifier = cityOwner:GetCultureCityModifier() + city:GetCultureRateModifier() + (city:GetNumWorldWonders() > 0 and cityOwner and cityOwner:GetCultureWonderMultiplier() or 0) + local cityCultureRate + local population = city:GetPopulation() + local tips = {} + local thisBuildingAndYieldTypes = { BuildingType = building.Type } + if IsCiv5 then + cityCultureRate = city:GetBaseJONSCulturePerTurn() + -- Happiness + local happinessChange = (tonumber(building.Happiness) or 0) + (tonumber(building.UnmoddedHappiness) or 0) + + cityOwner:GetExtraBuildingHappinessFromPolicies( buildingID ) + + (cityOwner:IsHalfSpecialistUnhappiness() and GameDefines.UNHAPPINESS_PER_POPULATION * numSpecialistsInBuilding * ((city:IsCapital() and cityOwner:GetCapitalUnhappinessMod() or 0)+100) * (cityOwner:GetUnhappinessMod() + 100) * (cityOwner:GetTraitPopUnhappinessMod() + 100) / 2e6 or 0) -- missing getHandicapInfo().getPopulationUnhappinessMod() + if happinessChange ~=0 then + insert( tips, happinessChange .. "[ICON_HAPPINESS_1]" ) + end + else -- IsCivBE + cityCultureRate = city:GetBaseCulturePerTurn() + -- Health + local healthChange = (tonumber(building.Health) or 0) + (tonumber(building.UnmoddedHealth) or 0) + cityOwner:GetExtraBuildingHealthFromPolicies( buildingID ) + local healthModifier = tonumber(building.HealthModifier) or 0 + -- Effect of player perks + for _, perkID in ipairs(cityOwnerPerks) do + healthChange = healthChange + Game.GetPlayerPerkBuildingClassPercentHealthChange( perkID, buildingClassID ) + healthModifier = healthModifier + Game.GetPlayerPerkBuildingClassPercentHealthChange( perkID, buildingClassID ) + defenseChange = defenseChange + Game.GetPlayerPerkBuildingClassCityStrengthChange( perkID, buildingClassID ) + hitPointChange = hitPointChange + Game.GetPlayerPerkBuildingClassCityHPChange( perkID, buildingClassID ) + maintenanceCost = maintenanceCost + Game.GetPlayerPerkBuildingClassEnergyMaintenanceChange( perkID, buildingClassID ) + end + if healthChange ~=0 then + insert( tips, healthChange .. "[ICON_HEALTH_1]" ) + end +-- if healthModifier~=0 then insert( tips, L( "TXT_KEY_STAT_POSITIVE_YIELD_MOD", "[ICON_HEALTH_1]", healthModifier ) ) end + end + local buildingYieldRate, buildingYieldPerPop, buildingYieldModifier, cityYieldRate, cityYieldRateModifier, isProducing, yieldInfo + for yieldID = 0, YieldTypes.NUM_YIELD_TYPES-1 do + yieldInfo = GameInfo.Yields[yieldID] + if yieldInfo then + isProducing = isNotResistance + thisBuildingAndYieldTypes.YieldType = yieldInfo.Type or -1 + -- Yield changes from the building + buildingYieldRate = Game.GetBuildingYieldChange( buildingID, yieldID ) + + (not IsCiv5vanilla and cityOwner:GetPlayerBuildingClassYieldChange( buildingClassID, yieldID ) + + city:GetReligionBuildingClassYieldChange( buildingClassID, yieldID ) or 0) + + (IsCiv5BNW and city:GetLeagueBuildingClassYieldChange( buildingClassID, yieldID ) or 0) + -- Yield modifiers from the building + buildingYieldModifier = Game.GetBuildingYieldModifier( buildingID, yieldID ) + + cityOwner:GetPolicyBuildingClassYieldModifier( buildingClassID, yieldID ) + -- Effect of player perks + if IsCivBE then + for _, perkID in ipairs(cityOwnerPerks) do + buildingYieldRate = buildingYieldRate + Game.GetPlayerPerkBuildingClassFlatYieldChange( perkID, buildingClassID, yieldID ) + buildingYieldModifier = buildingYieldModifier + Game.GetPlayerPerkBuildingClassPercentYieldChange( perkID, buildingClassID, yieldID ) + end + end + -- Specialists yield + if specialist then + buildingYieldRate = buildingYieldRate + numSpecialistsInBuilding * city:GetSpecialistYield( specialist.ID, yieldID ) + end + cityYieldRateModifier = city:GetBaseYieldRateModifier( yieldID ) + cityYieldRate = city:GetYieldPerPopTimes100( yieldID ) * population / 100 + city:GetBaseYieldRate( yieldID ) + -- Special culture case + if yieldID == YieldTypes.YIELD_CULTURE then + buildingYieldRate = buildingYieldRate + buildingCultureRate + buildingYieldModifier = buildingYieldModifier + buildingCultureModifier + cityYieldRateModifier = cityYieldRateModifier + cityCultureRateModifier + cityYieldRate = cityYieldRate + cityCultureRate + buildingCultureRate = 0 + buildingCultureModifier = 0 + elseif yieldID == YieldTypes.YIELD_FOOD then + local foodPerPop = GameDefines.FOOD_CONSUMPTION_PER_POPULATION + local foodConsumed = city:FoodConsumption() + buildingYieldRate = buildingYieldRate + (foodConsumed < foodPerPop * population and foodPerPop * numSpecialistsInBuilding / 2 or 0) + buildingYieldModifier = buildingYieldModifier + (tonumber(building.FoodKept) or 0) + cityYieldRate = city:FoodDifferenceTimes100() / 100 -- cityYieldRate - foodConsumed + cityYieldRateModifier = cityYieldRateModifier + city:GetMaxFoodKeptPercent() + isProducing = true + end + -- Population yield + buildingYieldPerPop = 0 + for row in GameInfo.Building_YieldChangesPerPop( thisBuildingAndYieldTypes ) do + buildingYieldPerPop = buildingYieldPerPop + (row.Yield or 0) + end + buildingYieldRate = buildingYieldRate + buildingYieldPerPop * population / 100 + + buildingYieldRate = buildingYieldRate * cityYieldRateModifier + ( cityYieldRate - buildingYieldRate ) * buildingYieldModifier + if isProducing and buildingYieldRate ~= 0 then + insert( tips, buildingYieldRate / 100 .. yieldInfo.IconString ) + end + end + end + + -- Culture leftovers + buildingCultureRate = buildingCultureRate * (100+cityCultureRateModifier) + ( cityCultureRate - buildingCultureRate ) * buildingCultureModifier + if isNotResistance and buildingCultureRate ~=0 then + insert( tips, buildingCultureRate / 100 .. "[ICON_CULTURE]" ) + end + +-- TODO TOURISM + if IsCiv5BNW then + local tourism = ( ( (building.FaithCost or 0) > 0 + and building.UnlockedByBelief + and building.Cost == -1 + and city and city:GetFaithBuildingTourism() + ) or 0 ) +-- local enhancedYieldTechID = GameInfoTypes[ building.EnhancedYieldTech ] + tourism = tourism + (tonumber(building.TechEnhancedTourism) or 0) + if tourism ~= 0 then + insert( tips, tourism.."[ICON_TOURISM]" ) + end + end + + if IsCiv5 and building.IsReligious then + buildingName = L( "TXT_KEY_RELIGIOUS_BUILDING", buildingName, Players[city:GetOwner()]:GetStateReligionKey() ) + end + if city:GetNumFreeBuilding( buildingID ) > 0 then + buildingName = buildingName .. " (" .. L"TXT_KEY_FREE" .. ")" + elseif maintenanceCost ~=0 then + insert( tips, -maintenanceCost .. g_currencyIcon ) + end + instance.Name:SetText( buildingName ) + + if defenseChange ~=0 then + insert( tips, defenseChange / 100 .. "[ICON_STRENGTH]" ) + end + if hitPointChange ~= 0 then + insert( tips, L( "TXT_KEY_PEDIA_DEFENSE_HITPOINTS", hitPointChange ) ) + end + + instance.Label:ChangeParent( instance.Stack ) + instance.Label:SetText( concat( tips, " ") ) + slotStack:CalculateSize() + if slotStack:GetSizeX() + instance.Label:GetSizeX() < 254 then + instance.Label:ChangeParent( slotStack ) + end + instance.Stack:CalculateSize() +-- slotStack:ReprocessAnchoring() +-- instance.Stack:ReprocessAnchoring() + buildingButton:SetSizeY( max(64, instance.Stack:GetSizeY() + 16) ) + end + return buildingIM.Commit() +end + +--========================================================== +-- Production Selection List Management +--========================================================== + +local function SelectionPurchase( orderID, itemID, yieldID, soundKey ) + local city = GetHeadSelectedCity() + if city then + local cityOwnerID = city:GetOwner() + if cityOwnerID == g_activePlayerID + and ( not city:IsPuppet() or ( IsCiv5BNW and g_activePlayer:MayNotAnnex() ) ) + ----------- Venice exception ----------- + then + local cityID = city:GetID() + local isPurchase + if orderID == ORDER_TRAIN then + if cityIsCanPurchase( city, true, true, itemID, -1, -1, yieldID ) then + Game.CityPurchaseUnit( city, itemID, yieldID ) + isPurchase = true + end + elseif orderID == ORDER_CONSTRUCT then + if cityIsCanPurchase( city, true, true, -1, itemID, -1, yieldID ) then + Game.CityPurchaseBuilding( city, itemID, yieldID ) + Network.SendUpdateCityCitizens( cityID ) + isPurchase = true + end + elseif orderID == ORDER_CREATE then + if cityIsCanPurchase( city, true, true, -1, -1, itemID, yieldID ) then + Game.CityPurchaseProject( city, itemID, yieldID ) + isPurchase = true + end + end + if isPurchase then + Events.SpecificCityInfoDirty( cityOwnerID, cityID, CityUpdateTypes.CITY_UPDATE_TYPE_BANNER ) + Events.SpecificCityInfoDirty( cityOwnerID, cityID, CityUpdateTypes.CITY_UPDATE_TYPE_PRODUCTION ) + if soundKey then + Events.AudioPlay2DSound( soundKey ) + end + end + end + end +end + +local function AddSelectionItem( city, item, + selectionList, + orderID, + cityCanProduce, + unitID, buildingID, projectID, + cityGetProductionTurnsLeft, + cityGetGoldCost, + cityGetFaithCost ) + + local itemID = item.ID + local name = item._Name + local turnsLeft = not g_isViewingMode and cityCanProduce( city, itemID, 0, 1 ) -- 0 = /continue, 1 = testvisible, nil = /ignore cost + local canProduce = not g_isViewingMode and cityCanProduce( city, itemID ) -- nil = /continue, nil = /testvisible, nil = /ignore cost + local canBuyWithGold, goldCost, canBuyWithFaith, faithCost + if unitID then + if IsCivBE then + local bestUpgradeInfo = GameInfo.UnitUpgrades[ g_activePlayer:GetBestUnitUpgrade(unitID) ] + name = bestUpgradeInfo and bestUpgradeInfo._Name or name + end + if cityGetGoldCost then + canBuyWithGold = cityIsCanPurchase( city, true, true, unitID, buildingID, projectID, g_yieldCurrency ) + goldCost = cityIsCanPurchase( city, false, false, unitID, buildingID, projectID, g_yieldCurrency ) + and cityGetGoldCost( city, itemID ) + end + if cityGetFaithCost then + canBuyWithFaith = cityIsCanPurchase( city, true, true, unitID, buildingID, projectID, YieldTypes.YIELD_FAITH ) + faithCost = cityIsCanPurchase( city, false, false, unitID, buildingID, projectID, YieldTypes.YIELD_FAITH ) + and cityGetFaithCost( city, itemID, true ) + end + end + if turnsLeft or goldCost or faithCost then --or (g_isDebugMode and not city:IsHasBuilding(buildingID)) then + turnsLeft = turnsLeft and ( cityGetProductionTurnsLeft and cityGetProductionTurnsLeft( city, itemID ) or -1 ) + return insert( selectionList, { item, orderID, name, turnsLeft, canProduce, goldCost, canBuyWithGold, faithCost, canBuyWithFaith } ) + end +end + +local function SortSelectionList(a,b) + return a[3]<b[3] +end + +local g_SelectionListCallBacks = { + Button = { + [eLClick] = function( orderID, itemID ) + local city = GetSelectedModifiableCity() + if city then + local cityOwnerID = city:GetOwner() + if cityOwnerID == g_activePlayerID and not city:IsPuppet() then + -- cityPushOrder( city, orderID, itemID, bAlt, bShift, bCtrl ) + -- cityPushOrder( city, orderID, itemID, repeatBuild, replaceQueue, bottomOfQueue ) + Game.CityPushOrder( city, orderID, itemID, UI.AltKeyDown(), UI.ShiftKeyDown(), not UI.CtrlKeyDown() ) + Events.SpecificCityInfoDirty( cityOwnerID, city:GetID(), CityUpdateTypes.CITY_UPDATE_TYPE_BANNER ) + Events.SpecificCityInfoDirty( cityOwnerID, city:GetID(), CityUpdateTypes.CITY_UPDATE_TYPE_PRODUCTION ) + if g_isButtonPopupChooseProduction then + -- is there another city without production order ? + for cityX in g_activePlayer:Cities() do + if cityX ~= city and not cityX:IsPuppet() and cityX:GetOrderQueueLength() < 1 then + UI.SelectCity( cityX ) + return UI.LookAtSelectionPlot() + end + end + -- all cities are producing... + return ExitCityScreen() + end + end + end + end, + [eRClick] = SelectionPedia, + }, + GoldButton = { + [eLClick] = function( orderID, itemID ) + return SelectionPurchase( orderID, itemID, g_yieldCurrency, "AS2D_INTERFACE_CITY_SCREEN_PURCHASE" ) + end, + }, + FaithButton = { + [eLClick] = function( orderID, itemID ) + return SelectionPurchase( orderID, itemID, YieldTypes.YIELD_FAITH, "AS2D_INTERFACE_FAITH_PURCHASE" ) + end, + }, +} +local g_SelectionListTooltips = { + Button = function( control ) + return CityOrderItemTooltip( GetHeadSelectedCity(), true, false, control:GetVoid1(), control:GetVoid2() ) + end, + GoldButton = function( control ) + return CityOrderItemTooltip( GetHeadSelectedCity(), control:IsDisabled(), g_yieldCurrency, control:GetVoid1(), control:GetVoid2() ) + end, + FaithButton = function( control ) + return CityOrderItemTooltip( GetHeadSelectedCity(), control:IsDisabled(), YieldTypes.YIELD_FAITH, control:GetVoid1(), control:GetVoid2() ) + end, +} + +local function SetupSelectionList( itemList, selectionIM, cityOwnerID, getUnitPortraitIcon ) + sort( itemList, SortSelectionList ) + selectionIM:ResetInstances() + local cash = g_activePlayer:GetGold() + local faith = not IsCiv5vanilla and g_activePlayer:GetFaith() or 0 + for i = 1, #itemList do + local item, orderID, itemDescription, turnsLeft, canProduce, goldCost, canBuyWithGold, faithCost, canBuyWithFaith = unpack( itemList[i] ) + local itemID = item.ID + local avisorRecommended = g_isAdvisor and g_avisorRecommended[ orderID ] + + local instance, new = selectionIM:GetInstance() + if new then + SetupCallbacks( instance, g_SelectionListTooltips, "EUI_ItemTooltip", g_SelectionListCallBacks ) + end + instance.DisabledProduction:SetHide( canProduce or not(canBuyWithGold or canBuyWithFaith) ) + instance.Disabled:SetHide( canProduce or canBuyWithGold or canBuyWithFaith ) + if getUnitPortraitIcon then + local iconIndex, iconAtlas = getUnitPortraitIcon( itemID, cityOwnerID ) + IconHookup( iconIndex, 45, iconAtlas, instance.Portrait ) + else + IconHookup( item.PortraitIndex, 45, item.IconAtlas, instance.Portrait ) + end + instance.Name:SetText( itemDescription ) + if not turnsLeft then + elseif turnsLeft > -1 and turnsLeft <= 999 then + instance.Turns:LocalizeAndSetText( "TXT_KEY_STR_TURNS", turnsLeft ) + else + instance.Turns:LocalizeAndSetText( "TXT_KEY_PRODUCTION_HELP_INFINITE_TURNS" ) + end + instance.Turns:SetHide( not turnsLeft ) + instance.Button:SetVoids( orderID, itemID ) + + instance.GoldButton:SetHide( not goldCost ) + if goldCost then + instance.GoldButton:SetDisabled( not canBuyWithGold ) + instance.GoldButton:SetAlpha( canBuyWithGold and 1 or 0.5 ) + instance.GoldButton:SetVoids( orderID, itemID ) + instance.GoldButton:SetText( (cash>=goldCost and goldCost or "[COLOR_WARNING_TEXT]"..(goldCost-cash).."[ENDCOLOR]") .. g_currencyIcon ) + end + instance.FaithButton:SetHide( not faithCost ) + if faithCost then + instance.FaithButton:SetDisabled( not canBuyWithFaith ) + instance.FaithButton:SetAlpha( canBuyWithFaith and 1 or 0.5 ) + instance.FaithButton:SetVoids( orderID, itemID ) + instance.FaithButton:SetText( (faith>=faithCost and faithCost or "[COLOR_WARNING_TEXT]"..(faithCost-faith).."[ENDCOLOR]") .. "[ICON_PEACE]" ) + end + + for advisorID, advisorName in pairs(g_advisors) do + local advisorControl = instance[ advisorName ] + if advisorControl then + advisorControl:SetHide( not (avisorRecommended and avisorRecommended( itemID, advisorID )) ) + end + end + end + return selectionIM.Commit() +end + +--========================================================== +-- Production Queue Managemeent +--========================================================== +local function RemoveQueueItem( queuedItemNumber ) + local city = GetSelectedModifiableCity() + if city then + local queueLength = city:GetOrderQueueLength() + if city:GetOwner() == g_activePlayerID and queueLength > queuedItemNumber then + Game.SelectedCitiesGameNetMessage( GameMessageTypes.GAMEMESSAGE_POP_ORDER, queuedItemNumber ) + if queueLength < 2 then + local strTooltip = L( "TXT_KEY_NOTIFICATION_NEW_CONSTRUCTION", city:GetNameKey() ) + g_activePlayer:AddNotification( NotificationTypes.NOTIFICATION_PRODUCTION, strTooltip, strTooltip, city:GetX(), city:GetY(), -1, -1 ) + end + end + end +end + +local function SwapQueueItem( queuedItemNumber ) + if g_queuedItemNumber and Controls.QueueSlider:IsTrackingLeftMouseButton() then + local a = g_queuedItemNumber + local b = queuedItemNumber + if a>b then a, b = b, a end + for i=a, b-1 do + Game.SelectedCitiesGameNetMessage( GameMessageTypes.GAMEMESSAGE_SWAP_ORDER, i ) + end + for i=b-2, a, -1 do + Game.SelectedCitiesGameNetMessage( GameMessageTypes.GAMEMESSAGE_SWAP_ORDER, i ) + end + end + g_queuedItemNumber = queuedItemNumber or g_queuedItemNumber +end + +Controls.AutomateProduction:RegisterCheckHandler( function( isChecked ) + Game.SelectedCitiesGameNetMessage( GameMessageTypes.GAMEMESSAGE_DO_TASK, TaskTypes.TASK_SET_AUTOMATED_PRODUCTION, -1, -1, isChecked, false ) +-- Network.SendDoTask( city:GetID(), TaskTypes.TASK_SET_AUTOMATED_PRODUCTION, -1, -1, isChecked, false ) +end) + +--========================================================== +-- Update Production Queue +--========================================================== +local function UpdateCityProductionQueue( city, cityID, cityOwnerID, isVeniceException ) + local queueLength = city:GetOrderQueueLength() + local currentProductionPerTurnTimes100 = city:GetCurrentProductionDifferenceTimes100( false, false ) -- bIgnoreFood, bOverflow + local isGeneratingProduction = ( currentProductionPerTurnTimes100 > 0 ) and not ( city.IsProductionProcess and city:IsProductionProcess() ) + local isMaintain = false + local isQueueEmpty = queueLength < 1 + Controls.ProductionFinished:SetHide( true ) + + -- Progress info for meter + ShowProgress( g_portraitSize, Controls.PQlossMeter, Controls.PQprogressMeter, Controls.PQline1, Controls.PQlabel1, Controls.PQline2, Controls.PQlabel2, + isGeneratingProduction and not isQueueEmpty and city:GetProductionTurnsLeft(), + city:GetProductionNeeded() * 100, + city:GetProductionTimes100() + city:GetCurrentProductionDifferenceTimes100( false, true ) - currentProductionPerTurnTimes100, -- bIgnoreFood, bOverflow + currentProductionPerTurnTimes100 ) + + Controls.ProdPerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_PERTURN_TEXT", currentProductionPerTurnTimes100 / 100 ) + + Controls.ProductionPortraitButton:SetHide( false ) + + g_ProdQueueIM:ResetInstances() + local queueItems = {} + + for queuedItemNumber = 0, max( queueLength-1, 0 ) do + + local orderID, itemID, _, isRepeat, isReallyRepeat + if isQueueEmpty then + local item = g_finishedItems[ cityID ] + if item then + orderID, itemID = unpack( item ) + Controls.ProductionFinished:SetHide( false ) + end + else + orderID, itemID, _, isRepeat = city:GetOrderFromQueue( queuedItemNumber ) + queueItems[ orderID / 64 + itemID ] = true + end + local instance, portraitSize + if queuedItemNumber == 0 then + instance = Controls + portraitSize = g_portraitSize + else + portraitSize = 45 + instance = g_ProdQueueIM:GetInstance() + instance.PQdisabled:SetHide( not isMaintain ) + end + instance.PQbox:SetVoid1( queuedItemNumber ) + instance.PQbox:RegisterCallback( eMouseEnter, SwapQueueItem ) + instance.PQbox:RegisterCallback( eRClick, ProductionPedia ) + instance.PQbox:SetToolTipCallback( ProductionToolTip ) + + instance.PQremove:SetHide( isQueueEmpty or g_isViewingMode ) + instance.PQremove:SetVoid1( queuedItemNumber ) + instance.PQremove:RegisterCallback( eLClick, RemoveQueueItem ) + + local itemInfo, turnsRemaining, portraitOffset, portraitAtlas + + if orderID == ORDER_TRAIN then + itemInfo = GameInfo.Units + turnsRemaining = city:GetUnitProductionTurnsLeft( itemID, queuedItemNumber ) + portraitOffset, portraitAtlas = GetUnitPortraitIcon( itemID, cityOwnerID ) + isReallyRepeat = isRepeat + elseif orderID == ORDER_CONSTRUCT then + itemInfo = GameInfo.Buildings + turnsRemaining = city:GetBuildingProductionTurnsLeft( itemID, queuedItemNumber ) + elseif orderID == ORDER_CREATE then + itemInfo = GameInfo.Projects + turnsRemaining = city:GetProjectProductionTurnsLeft( itemID, queuedItemNumber ) + elseif orderID == ORDER_MAINTAIN then + itemInfo = GameInfo.Processes + isMaintain = true + isReallyRepeat = true + end + if itemInfo then + local item = itemInfo[itemID] + itemInfo = IconHookup( portraitOffset or item.PortraitIndex, portraitSize, portraitAtlas or item.IconAtlas, instance.PQportrait ) + instance.PQname:SetText( item._Name ) + if isMaintain or isQueueEmpty then + elseif isGeneratingProduction then + instance.PQturns:LocalizeAndSetText( "TXT_KEY_PRODUCTION_HELP_NUM_TURNS", turnsRemaining ) + else + instance.PQturns:LocalizeAndSetText( "TXT_KEY_PRODUCTION_HELP_INFINITE_TURNS" ) + end + else + instance.PQname:LocalizeAndSetText( "TXT_KEY_PRODUCTION_NO_PRODUCTION" ) + end + instance.PQturns:SetHide( isMaintain or isQueueEmpty or not itemInfo ) + instance.PQportrait:SetHide( not itemInfo ) + if isReallyRepeat then + isMaintain = true + instance.PQrank:SetText( "[ICON_TURNS_REMAINING]" ) + else + instance.PQrank:SetText( not isMaintain and queueLength > 1 and (queuedItemNumber+1).."." ) + end + end + + g_ProdQueueIM.Commit() + + ------------------------------------------- + -- Update Selection List + ------------------------------------------- + + local isSelectionList = not g_isViewingMode or isVeniceException or g_isDebugMode + Controls.SelectionScrollPanel:SetHide( not isSelectionList ) + if isSelectionList then + local unitSelectList = {} + local buildingSelectList = {} + local wonderSelectList = {} + local processSelectList = {} + + if g_isAdvisor then + Game.SetAdvisorRecommenderCity( city ) + end + -- Buildings & Wonders + local orderID = ORDER_CONSTRUCT + local code = orderID / 64 + for item in GameInfo.Buildings() do + local buildingClass = GameInfo.BuildingClasses[item.BuildingClass] + local isWonder = buildingClass and (buildingClass.MaxGlobalInstances > 0 or buildingClass.MaxPlayerInstances == 1 or buildingClass.MaxTeamInstances > 0) + if not queueItems[ code + item.ID ] then + AddSelectionItem( city, item, + isWonder and wonderSelectList or buildingSelectList, + orderID, + city.CanConstruct, + -1, item.ID, -1, + city.GetBuildingProductionTurnsLeft, + city.GetBuildingPurchaseCost, + city.GetBuildingFaithPurchaseCost ) + end + end + + -- Units + orderID = ORDER_TRAIN + for item in GameInfo.Units() do + AddSelectionItem( city, item, + unitSelectList, + orderID, + city.CanTrain, + item.ID, -1, -1, + city.GetUnitProductionTurnsLeft, + city.GetUnitPurchaseCost, + city.GetUnitFaithPurchaseCost ) + end + -- Projects + orderID = ORDER_CREATE + code = orderID / 64 + for item in GameInfo.Projects() do + if not queueItems[ code + item.ID ] then + AddSelectionItem( city, item, + wonderSelectList, + orderID, + city.CanCreate, + -1, -1, item.ID, + city.GetProjectProductionTurnsLeft, + city.GetProjectPurchaseCost, + city.GetProjectFaithPurchaseCost ) -- nil + end + end + -- Processes + orderID = ORDER_MAINTAIN + code = orderID / 64 + for item in GameInfo.Processes() do + if not queueItems[ code + item.ID ] then + AddSelectionItem( city, item, + processSelectList, + orderID, + city.CanMaintain ) + end + end + + SetupSelectionList( unitSelectList, g_UnitSelectIM, cityOwnerID, GetUnitPortraitIcon ) + SetupSelectionList( buildingSelectList, g_BuildingSelectIM ) + SetupSelectionList( wonderSelectList, g_WonderSelectIM ) + SetupSelectionList( processSelectList, g_ProcessSelectIM ) + + end + return ResizeProdQueue() +end + +--========================================================== +-- City Hex Clicking & Mousing +--========================================================== + +local function PlotButtonClicked( plotIndex ) + local city = GetSelectedModifiableCity() + local plot = city and city:GetCityIndexPlot( plotIndex ) + if plot then + local outside = city ~= plot:GetWorkingCity() + -- calling this with the city center (0 in the third param) causes it to reset all forced tiles + Network.SendDoTask( city:GetID(), TaskTypes.TASK_CHANGE_WORKING_PLOT, plotIndex, -1, false ) + if outside then + return Network.SendUpdateCityCitizens( city:GetID() ) + end + end +end + +local function BuyPlotAnchorButtonClicked( plotIndex ) + local city = GetSelectedModifiableCity() + if city then + local plot = city:GetCityIndexPlot( plotIndex ) + local plotX = plot:GetX() + local plotY = plot:GetY() + Network.SendCityBuyPlot(city:GetID(), plotX, plotY) + Network.SendUpdateCityCitizens( city:GetID() ) + UI.UpdateCityScreen() + Events.AudioPlay2DSound("AS2D_INTERFACE_BUY_TILE") + end + return true +end + +--========================================================== +-- Update City Hexes +--========================================================== + +local function UpdateCityWorkingHexes( city ) + + EventsClearHexHighlightStyle( "HexContour" ) + EventsClearHexHighlightStyle( "WorkedFill" ) + EventsClearHexHighlightStyle( "WorkedOutline" ) + EventsClearHexHighlightStyle( "UnlockedFill" ) + EventsClearHexHighlightStyle( "UnlockedOutline" ) + EventsClearHexHighlightStyle( "OverlapFill" ) + EventsClearHexHighlightStyle( "OverlapOutline" ) + EventsClearHexHighlightStyle( "VacantFill" ) + EventsClearHexHighlightStyle( "VacantOutline" ) + EventsClearHexHighlightStyle( "EnemyFill" ) + EventsClearHexHighlightStyle( "EnemyOutline" ) + EventsClearHexHighlightStyle( "BuyFill" ) + EventsClearHexHighlightStyle( "BuyOutline" ) + + g_PlotButtonIM:ResetInstances() + g_BuyPlotButtonIM:ResetInstances() + + -- Show plots that will be acquired by culture + local purchasablePlots = {city:GetBuyablePlotList()} + for i = 1, #purchasablePlots do + local plot = purchasablePlots[i] + EventsSerialEventHexHighlight( ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() }, true, ColorCulture, "HexContour" ) + purchasablePlots[ plot ] = true + end + + + local cityArea = city:GetNumCityPlots() - 1 + EventsRequestYieldDisplay( YieldDisplayTypesAREA, HexRadius( cityArea ), city:GetX(), city:GetY() ) + + -- display worked plots buttons + local cityOwnerID = city:GetOwner() + local notInStrategicView = not InStrategicView() + local showButtons = g_workerHeadingOpen and not g_isViewingMode + + for cityPlotIndex = 0, cityArea do + local plot = city:GetCityIndexPlot( cityPlotIndex ) + + if plot and plot:GetOwner() == cityOwnerID then + + local hexPos = ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() } + local worldPos = HexToWorld( hexPos ) + local iconID, tipKey + if city:IsWorkingPlot( plot ) then + + -- The city itself + if cityPlotIndex == 0 then + iconID = 11 + tipKey = "TXT_KEY_CITYVIEW_CITY_CENTER" + + -- FORCED worked plot + elseif city:IsForcedWorkingPlot( plot ) then + iconID = 10 + tipKey = "TXT_KEY_CITYVIEW_FORCED_WORK_TILE" + + -- AI-picked worked plot + else + iconID = 0 + tipKey = "TXT_KEY_CITYVIEW_GUVNA_WORK_TILE" + end + if notInStrategicView then + if plot:IsCity() or city:IsForcedWorkingPlot( plot ) then + EventsSerialEventHexHighlight( hexPos , true, nil, "WorkedFill" ) + EventsSerialEventHexHighlight( hexPos , true, nil, "WorkedOutline" ) + else + EventsSerialEventHexHighlight( hexPos , true, nil, "UnlockedFill") + EventsSerialEventHexHighlight( hexPos , true, nil, "UnlockedOutline") + end + end + else + local workingCity = plot:GetWorkingCity() + -- worked by another one of our Cities + if workingCity:IsWorkingPlot( plot ) then + iconID = 12 + tipKey = "TXT_KEY_CITYVIEW_NUTHA_CITY_TILE" + + -- Workable plot + elseif workingCity:CanWork( plot ) then + iconID = 9 + tipKey = "TXT_KEY_CITYVIEW_UNWORKED_CITY_TILE" + + -- Blockaded water plot + elseif plot:IsWater() and city:IsPlotBlockaded( plot ) then + iconID = 13 + tipKey = "TXT_KEY_CITYVIEW_BLOCKADED_CITY_TILE" + cityPlotIndex = nil + + -- Enemy Unit standing here + elseif plot:IsVisibleEnemyUnit( cityOwnerID ) then + iconID = 13 + tipKey = "TXT_KEY_CITYVIEW_ENEMY_UNIT_CITY_TILE" + cityPlotIndex = nil + end + if notInStrategicView then + if workingCity ~= city then + EventsSerialEventHexHighlight( hexPos , true, nil, "OverlapFill" ) + EventsSerialEventHexHighlight( hexPos , true, nil, "OverlapOutline" ) + elseif cityPlotIndex then + EventsSerialEventHexHighlight( hexPos , true, nil, "VacantFill" ) + EventsSerialEventHexHighlight( hexPos , true, nil, "VacantOutline" ) + else + EventsSerialEventHexHighlight( hexPos , true, nil, "EnemyFill" ) + EventsSerialEventHexHighlight( hexPos , true, nil, "EnemyOutline" ) + end + end + end + if iconID and showButtons then + local instance = g_PlotButtonIM:GetInstance() + instance.PlotButtonAnchor:SetWorldPositionVal( worldPos.x + g_worldPositionOffset.x, worldPos.y + g_worldPositionOffset.y, worldPos.z + g_worldPositionOffset.z ) --todo: improve code + instance.PlotButtonImage:LocalizeAndSetToolTip( tipKey ) + IconHookup( iconID, 45, "CITIZEN_ATLAS", instance.PlotButtonImage ) + local button = instance.PlotButtonImage + if not cityPlotIndex or g_isViewingMode then + button:ClearCallback( eLClick ) + else + button:SetVoid1( cityPlotIndex ) + button:RegisterCallback( eLClick, PlotButtonClicked ) + end + end + end + end --loop + + -- display buy plot buttons + if g_BuyPlotMode and not g_isViewingMode then + local cash = g_activePlayer:GetGold() + for cityPlotIndex = 0, cityArea do + local plot = city:GetCityIndexPlot( cityPlotIndex ) + if plot then + local x = plot:GetX() + local y = plot:GetY() + local hexPos = ToHexFromGrid{ x=x, y=y } + local worldPos = HexToWorld( hexPos ) + if city:CanBuyPlotAt( x, y, true ) then + local instance = g_BuyPlotButtonIM:GetInstance() + local button = instance.BuyPlotAnchoredButton + instance.BuyPlotButtonAnchor:SetWorldPositionVal( worldPos.x + g_worldPositionOffset2.x, worldPos.y + g_worldPositionOffset2.y, worldPos.z + g_worldPositionOffset2.z ) --todo: improve code + local plotCost = city:GetBuyPlotCost( x, y ) + local tip, txt, alpha + local canBuy = city:CanBuyPlotAt( x, y, false ) + if canBuy then + tip = L( "TXT_KEY_CITYVIEW_CLAIM_NEW_LAND", plotCost ) + txt = plotCost + alpha = 1 + button:SetVoid1( cityPlotIndex ) + button:RegisterCallback( eLClick, BuyPlotAnchorButtonClicked ) + if notInStrategicView then + EventsSerialEventHexHighlight( hexPos , true, nil, "BuyFill" ) + if not purchasablePlots[ plot ] then + EventsSerialEventHexHighlight( hexPos , true, nil, "BuyOutline" ) + end + end + else + tip = L( "TXT_KEY_CITYVIEW_NEED_MONEY_BUY_TILE", plotCost ) + txt = "[COLOR_WARNING_TEXT]"..(plotCost-cash).."[ENDCOLOR]" + alpha = 0.5 + end + button:SetDisabled( not canBuy ) + instance.BuyPlotButtonAnchor:SetAlpha( alpha ) +--todo + button:SetToolTipString( tip ) + instance.BuyPlotAnchoredButtonLabel:SetText( txt ) + end + end + end --loop + end +end + +local function UpdateWorkingHexes() + local city = GetHeadSelectedCity() + if city and UI.IsCityScreenUp() then + return UpdateCityWorkingHexes( city ) + end +end + +--========================================================== +-- Update City View +--========================================================== + +local function UpdateCityView() + + local city = GetHeadSelectedCity() + + if city and UI.IsCityScreenUp() then + + if g_citySpecialists.city ~= city then + g_citySpecialists = { city = city } + end +--[[ + if g_previousCity ~= city then + g_previousCity = city + EventsClearHexHighlightStyle("CityLimits") + if not InStrategicView() then + for cityPlotIndex = 0, city:GetNumCityPlots() - 1 do + local plot = city:GetCityIndexPlot( cityPlotIndex ) + if plot then + local hexPos = ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() } + EventsSerialEventHexHighlight( hexPos , true, nil, "CityLimits" ) + end + end + end + end +--]] + local cityID = city:GetID() + local cityOwnerID = city:GetOwner() + local cityOwner = Players[cityOwnerID] + local isActivePlayerCity = cityOwnerID == Game.GetActivePlayer() + local isCityCaptureViewingMode = UI.IsPopupTypeOpen(ButtonPopupTypes.BUTTONPOPUP_CITY_CAPTURED) + g_isDebugMode = Game.IsDebugMode() + g_isViewingMode = city:IsPuppet() or not isActivePlayerCity or isCityCaptureViewingMode + + if IsCiv5 then + -- Auto Specialist checkbox + local isNoAutoAssignSpecialists = city:IsNoAutoAssignSpecialists() + Controls.NoAutoSpecialistCheckbox:SetCheck( isNoAutoAssignSpecialists ) + Controls.NoAutoSpecialistCheckbox:SetDisabled( g_isViewingMode ) + if IsCiv5BNW then + Controls.TourismPerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_PERTURN_TEXT", city:GetBaseTourism() ) + Controls.NoAutoSpecialistCheckbox2:SetCheck( isNoAutoAssignSpecialists ) + Controls.NoAutoSpecialistCheckbox2:SetDisabled( g_isViewingMode ) + end + end + Controls.AutomateProduction:SetCheck( city:IsProductionAutomated() ) + Controls.AutomateProduction:SetDisabled( g_isViewingMode ) + + ------------------------------------------- + -- City Banner + ------------------------------------------- + + local isCapital = city:IsCapital() + Controls.CityCapitalIcon:SetHide( not isCapital ) + + Controls.CityIsConnected:SetHide( isCapital or city:IsBlockaded() or not cityOwner:IsCapitalConnectedToCity(city) or city:GetTeam() ~= Game.GetActiveTeam() ) + + Controls.CityIsBlockaded:SetHide( not city:IsBlockaded() ) + + if city:IsRazing() then + Controls.CityIsRazing:SetHide(false) + Controls.CityIsRazing:LocalizeAndSetToolTip("TXT_KEY_CITY_BURNING", city:GetRazingTurns()) + else + Controls.CityIsRazing:SetHide(true) + end + + Controls.CityIsPuppet:SetHide( not city:IsPuppet() ) + + if city:IsResistance() then + Controls.CityIsResistance:SetHide(false) + Controls.CityIsResistance:LocalizeAndSetToolTip("TXT_KEY_CITY_RESISTANCE", city:GetResistanceTurns()) + else + Controls.CityIsResistance:SetHide(true) + end + +--todo BE + Controls.CityIsOccupied:SetHide( not( IsCiv5 and city:IsOccupied() and not city:IsNoOccupiedUnhappiness() ) ) + + local cityName = Locale.ToUpper( city:GetName() ) + + if city:IsRazing() then + cityName = cityName .. " (" .. L"TXT_KEY_BURNING" .. ")" + end + + local size = isCapital and Controls.CityCapitalIcon:GetSizeX() or 0 + Controls.CityNameTitleBarLabel:SetOffsetX( size / 2 ) + TruncateString( Controls.CityNameTitleBarLabel, abs(Controls.NextCityButton:GetOffsetX()) * 2 - Controls.NextCityButton:GetSizeX() - size, cityName ) + + Controls.Defense:SetText( floor( city:GetStrengthValue() / 100 ) ) + + CivIconHookup( cityOwnerID, 64, Controls.CivIcon, Controls.CivIconBG, Controls.CivIconShadow, false, true ) + + ------------------------------------------- + -- City Damage + ------------------------------------------- + + local cityDamage = city:GetDamage() + if cityDamage > 0 then + local cityHealthPercent = 1 - cityDamage / ( not IsCiv5vanilla and city:GetMaxHitPoints() or GameDefines.MAX_CITY_HIT_POINTS ) + + Controls.HealthMeter:SetPercent( cityHealthPercent ) + if cityHealthPercent > 0.66 then + Controls.HealthMeter:SetTexture("CityNamePanelHealthBarGreen.dds") + elseif cityHealthPercent > 0.33 then + Controls.HealthMeter:SetTexture("CityNamePanelHealthBarYellow.dds") + else + Controls.HealthMeter:SetTexture("CityNamePanelHealthBarRed.dds") + end + Controls.HealthFrame:SetHide( false ) + else + Controls.HealthFrame:SetHide( true ) + end + + ------------------------------------------- + -- Growth Meter + ------------------------------------------- + + local cityPopulation = floor( city:GetPopulation() ) + Controls.CityPopulationLabel:SetText( cityPopulation ) + Controls.PeopleMeter:SetPercent( city:GetFood() / city:GrowthThreshold() ) + + --Update suffix to use correct plurality. + Controls.CityPopulationLabelSuffix:LocalizeAndSetText( "TXT_KEY_CITYVIEW_CITIZENS_TEXT", cityPopulation ) + + + ------------------------------------------- + -- Citizen Focus & Slackers + ------------------------------------------- + + Controls.AvoidGrowthButton:SetCheck( city:IsForcedAvoidGrowth() ) + + local slackerCount = city:GetSpecialistCount( GameDefines.DEFAULT_SPECIALIST ) + + local focusButton = g_cityFocusButtons[ city:GetFocusType() ] + if focusButton then + focusButton:SetCheck( true ) + end + + local doHide = city:GetNumForcedWorkingPlots() < 1 and slackerCount < 1 + Controls.ResetButton:SetHide( doHide ) + Controls.ResetFooter:SetHide( doHide ) + + g_SlackerIM:ResetInstances() + for i = 1, slackerCount do + local instance = g_SlackerIM:GetInstance() + local slot = instance.Button + slot:SetVoids( -1, i ) + slot:SetToolTipCallback( SpecialistTooltip ) + slot:SetTexture( g_slackerTexture ) + if g_isViewingMode then + slot:ClearCallback( eLClick ) + else + slot:RegisterCallback( eLClick, OnSlackersSelected ) + end + slot:RegisterCallback( eRClick, SpecialistPedia ) + end + g_SlackerIM.Commit() + + ------------------------------------------- + -- Great Person Meters + ------------------------------------------- + if IsCiv5 then + g_GreatPeopleIM:ResetInstances() + for specialist in GameInfo.Specialists() do + + local gpuClass = specialist.GreatPeopleUnitClass -- nil / UNITCLASS_ARTIST / UNITCLASS_SCIENTIST / UNITCLASS_MERCHANT / UNITCLASS_ENGINEER ... + local unitClass = GameInfo.UnitClasses[ gpuClass or -1 ] + if unitClass then + local gpThreshold = city:GetSpecialistUpgradeThreshold(unitClass.ID) + local gpProgress = city:GetSpecialistGreatPersonProgressTimes100(specialist.ID) / 100 + local gpChange = specialist.GreatPeopleRateChange * city:GetSpecialistCount( specialist.ID ) + for building in GameInfo.Buildings{SpecialistType = specialist.Type} do + if city:IsHasBuilding(building.ID) then + gpChange = gpChange + building.GreatPeopleRateChange + end + end + + local gpChangePlayerMod = cityOwner:GetGreatPeopleRateModifier() + local gpChangeCityMod = city:GetGreatPeopleRateModifier() + local gpChangePolicyMod = 0 + local gpChangeWorldCongressMod = 0 + local gpChangeGoldenAgeMod = 0 + local isGoldenAge = cityOwner:GetGoldenAgeTurns() > 0 + + if IsCiv5BNW then + -- Generic GP mods + + gpChangePolicyMod = cityOwner:GetPolicyGreatPeopleRateModifier() + + local worldCongress = (Game.GetNumActiveLeagues() > 0) and Game.GetActiveLeague() + + -- GP mods by type + if specialist.GreatPeopleUnitClass == "UNITCLASS_WRITER" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatWriterRateModifier() + gpChangePolicyMod = gpChangePolicyMod + cityOwner:GetPolicyGreatWriterRateModifier() + if worldCongress then + gpChangeWorldCongressMod = gpChangeWorldCongressMod + worldCongress:GetArtsyGreatPersonRateModifier() + end + if isGoldenAge and cityOwner:GetGoldenAgeGreatWriterRateModifier() > 0 then + gpChangeGoldenAgeMod = gpChangeGoldenAgeMod + cityOwner:GetGoldenAgeGreatWriterRateModifier() + end + elseif specialist.GreatPeopleUnitClass == "UNITCLASS_ARTIST" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatArtistRateModifier() + gpChangePolicyMod = gpChangePolicyMod + cityOwner:GetPolicyGreatArtistRateModifier() + if worldCongress then + gpChangeWorldCongressMod = gpChangeWorldCongressMod + worldCongress:GetArtsyGreatPersonRateModifier() + end + if isGoldenAge and cityOwner:GetGoldenAgeGreatArtistRateModifier() > 0 then + gpChangeGoldenAgeMod = gpChangeGoldenAgeMod + cityOwner:GetGoldenAgeGreatArtistRateModifier() + end + elseif specialist.GreatPeopleUnitClass == "UNITCLASS_MUSICIAN" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatMusicianRateModifier() + gpChangePolicyMod = gpChangePolicyMod + cityOwner:GetPolicyGreatMusicianRateModifier() + if worldCongress then + gpChangeWorldCongressMod = gpChangeWorldCongressMod + worldCongress:GetArtsyGreatPersonRateModifier() + end + if isGoldenAge and cityOwner:GetGoldenAgeGreatMusicianRateModifier() > 0 then + gpChangeGoldenAgeMod = gpChangeGoldenAgeMod + cityOwner:GetGoldenAgeGreatMusicianRateModifier() + end + elseif specialist.GreatPeopleUnitClass == "UNITCLASS_SCIENTIST" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatScientistRateModifier() + gpChangePolicyMod = gpChangePolicyMod + cityOwner:GetPolicyGreatScientistRateModifier() + if worldCongress then + gpChangeWorldCongressMod = gpChangeWorldCongressMod + worldCongress:GetScienceyGreatPersonRateModifier() + end + elseif specialist.GreatPeopleUnitClass == "UNITCLASS_MERCHANT" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatMerchantRateModifier() + gpChangePolicyMod = gpChangePolicyMod + cityOwner:GetPolicyGreatMerchantRateModifier() + if worldCongress then + gpChangeWorldCongressMod = gpChangeWorldCongressMod + worldCongress:GetScienceyGreatPersonRateModifier() + end + elseif specialist.GreatPeopleUnitClass == "UNITCLASS_ENGINEER" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatEngineerRateModifier() + gpChangePolicyMod = gpChangePolicyMod + cityOwner:GetPolicyGreatEngineerRateModifier() + if worldCongress then + gpChangeWorldCongressMod = gpChangeWorldCongressMod + worldCongress:GetScienceyGreatPersonRateModifier() + end + -- Compatibility with Gazebo's City-State Diplomacy Mod (CSD) for Brave New World + elseif cityOwner.GetGreatDiplomatRateModifier and specialist.GreatPeopleUnitClass == "UNITCLASS_GREAT_DIPLOMAT" then + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetGreatDiplomatRateModifier() + end + + -- Player mod actually includes policy mod and World Congress mod, so separate them for tooltip + + gpChangePlayerMod = gpChangePlayerMod - gpChangePolicyMod - gpChangeWorldCongressMod + + elseif gpuClass == "UNITCLASS_SCIENTIST" then + + gpChangePlayerMod = gpChangePlayerMod + cityOwner:GetTraitGreatScientistRateModifier() + + end + + local gpChangeMod = gpChangePlayerMod + gpChangePolicyMod + gpChangeWorldCongressMod + gpChangeCityMod + gpChangeGoldenAgeMod + gpChange = (gpChangeMod / 100 + 1) * gpChange + + if gpProgress > 0 or gpChange > 0 then + local instance = g_GreatPeopleIM:GetInstance() + local percent = gpProgress / gpThreshold + instance.GPMeter:SetPercent( percent ) + local labelText = unitClass._Name + local icon = GreatPeopleIcon(gpuClass) + local tips = { "[COLOR_YIELD_FOOD]" .. Locale.ToUpper( labelText ) .. "[ENDCOLOR]" .. " " .. gpProgress .. icon .." / " .. gpThreshold .. icon } + -- insert( tips, L( "TXT_KEY_PROGRESS_TOWARDS", "[COLOR_YIELD_FOOD]" .. Locale.ToUpper( labelText ) .. "[ENDCOLOR]" ) ) + if gpChange > 0 then + local gpTurns = ceil( (gpThreshold - gpProgress) / gpChange ) + insert( tips, "[COLOR_YIELD_FOOD]" .. Locale.ToUpper( L( "TXT_KEY_STR_TURNS", gpTurns ) ) .. "[ENDCOLOR] " + .. gpChange .. icon .. " " .. L"TXT_KEY_GOLD_PERTURN_HEADING4_TITLE" ) + labelText = labelText .. ": " .. Locale.ToLower( L( "TXT_KEY_STR_TURNS", gpTurns ) ) + end + instance.GreatPersonLabel:SetText( icon .. labelText ) + if IsCiv5vanilla then + if gpChangeMod ~= 0 then + insert( tips, "[ICON_BULLET] "..gpChangeMod..icon ) + end + else + if gpChangePlayerMod ~= 0 then + insert( tips, L( "TXT_KEY_PLAYER_GP_MOD", gpChangePlayerMod ) ) + end + if gpChangePolicyMod ~= 0 then + insert( tips, L( "TXT_KEY_POLICY_GP_MOD", gpChangePolicyMod ) ) + end + if gpChangeCityMod ~= 0 then + insert( tips, L( "TXT_KEY_CITY_GP_MOD", gpChangeCityMod ) ) + end + if gpChangeGoldenAgeMod ~= 0 then + insert( tips, L( "TXT_KEY_GOLDENAGE_GP_MOD", gpChangeGoldenAgeMod ) ) + end + if gpChangeWorldCongressMod < 0 then + insert( tips, L( "TXT_KEY_WORLD_CONGRESS_NEGATIVE_GP_MOD", gpChangeWorldCongressMod ) ) + elseif gpChangeWorldCongressMod > 0 then + insert( tips, L( "TXT_KEY_WORLD_CONGRESS_POSITIVE_GP_MOD", gpChangeWorldCongressMod ) ) + end + end + instance.GPBox:SetToolTipString( concat( tips, "[NEWLINE]") ) + instance.GPBox:SetVoid1( unitClass.ID ) + instance.GPBox:RegisterCallback( eRClick, UnitClassPedia ) + + local portraitOffset, portraitAtlas = GetUnitPortraitIcon( GameInfoTypes[ unitClass.DefaultUnit ], cityOwnerID ) + instance.GPImage:SetHide(not IconHookup( portraitOffset, 64, portraitAtlas, instance.GPImage ) ) + end + end + end + g_GreatPeopleIM.Commit() + end + + ------------------------------------------- + -- Buildings + ------------------------------------------- + + local greatWorkBuildings = {} + local specialistBuildings = {} + local wonders = {} + local otherBuildings = {} + local noWondersWithSpecialistInThisCity = true + + for building in GameInfo.Buildings() do + local buildingID = building.ID + if city:IsHasBuilding(buildingID) then + local buildingClass = GameInfo.BuildingClasses[ building.BuildingClass ] + local buildings + local greatWorkCount = IsCiv5BNW and building.GreatWorkCount or 0 + local areSpecialistsAllowedByBuilding = city:GetNumSpecialistsAllowedByBuilding(buildingID) > 0 + + if buildingClass + and ( buildingClass.MaxGlobalInstances > 0 + or buildingClass.MaxTeamInstances > 0 + or ( buildingClass.MaxPlayerInstances == 1 and not areSpecialistsAllowedByBuilding ) ) + then + buildings = wonders + if areSpecialistsAllowedByBuilding then + noWondersWithSpecialistInThisCity = false + end + elseif areSpecialistsAllowedByBuilding then + buildings = specialistBuildings + elseif greatWorkCount > 0 then + buildings = greatWorkBuildings + elseif greatWorkCount == 0 then -- compatibility with Firaxis code exploit for invisibility + buildings = otherBuildings + end + if buildings then + insert( buildings, { building, building._Name, greatWorkCount, areSpecialistsAllowedByBuilding and GameInfoTypes[building.SpecialistType] or 999 } ) + end + end + end + local strMaintenanceTT = L( "TXT_KEY_BUILDING_MAINTENANCE_TT", city:GetTotalBaseBuildingMaintenance() ) + Controls.SpecialBuildingsHeader:SetToolTipString(strMaintenanceTT) + Controls.BuildingsHeader:SetToolTipString(strMaintenanceTT) + Controls.GreatWorkHeader:SetToolTipString(strMaintenanceTT) + Controls.SpecialistControlBox:SetHide( #specialistBuildings < 1 ) + Controls.SpecialistControlBox2:SetHide( noWondersWithSpecialistInThisCity ) + g_GreatWorksIM:ResetInstances() + g_SpecialistsIM:ResetInstances() + SetupBuildingList( city, specialistBuildings, g_SpecialBuildingsIM ) + SetupBuildingList( city, wonders, g_WondersIM ) + SetupBuildingList( city, greatWorkBuildings, g_GreatWorkIM ) + SetupBuildingList( city, otherBuildings, g_BuildingsIM ) + ResizeRightStack() + + ------------------------------------------- + -- Buying Plots + ------------------------------------------- +-- szText = L"TXT_KEY_CITYVIEW_BUY_TILE" +-- Controls.BuyPlotButton:LocalizeAndSetToolTip( "TXT_KEY_CITYVIEW_BUY_TILE_TT" ) +-- Controls.BuyPlotText:SetText(szText) +-- Controls.BuyPlotButton:SetDisabled( g_isViewingMode or (GameDefines.BUY_PLOTS_DISABLED ~= 0 and city:CanBuyAnyPlot()) ) + + ------------------------------------------- + -- Resource Demanded + ------------------------------------------- + + if city:GetResourceDemanded(true) ~= -1 then + local resourceInfo = GameInfo.Resources[ city:GetResourceDemanded() ] + local weLoveTheKingDayCounter = city:GetWeLoveTheKingDayCounter() + if weLoveTheKingDayCounter > 0 then + Controls.ResourceDemandedString:LocalizeAndSetText( "TXT_KEY_CITYVIEW_WLTKD_COUNTER", weLoveTheKingDayCounter ) + Controls.ResourceDemandedBox:LocalizeAndSetToolTip( "TXT_KEY_CITYVIEW_RESOURCE_FULFILLED_TT" ) + else + Controls.ResourceDemandedString:LocalizeAndSetText( "TXT_KEY_CITYVIEW_RESOURCE_DEMANDED", (resourceInfo.IconString or"") .. " " .. resourceInfo._Name ) + Controls.ResourceDemandedBox:LocalizeAndSetToolTip( "TXT_KEY_CITYVIEW_RESOURCE_DEMANDED_TT" ) + end + + Controls.ResourceDemandedBox:SetSizeX(Controls.ResourceDemandedString:GetSizeX() + 10) + Controls.ResourceDemandedBox:SetHide(false) + else + Controls.ResourceDemandedBox:SetHide(true) + end + + Controls.IconsStack:CalculateSize() + Controls.IconsStack:ReprocessAnchoring() + + Controls.NotificationStack:CalculateSize() + Controls.NotificationStack:ReprocessAnchoring() + + ------------------------------------------- + -- Raze / Unraze / Annex City Buttons + ------------------------------------------- + + local buttonToolTip, buttonLabel, taskID + if isActivePlayerCity then + + if city:IsRazing() then + + -- We can unraze this city + taskID = TaskTypes.TASK_UNRAZE + buttonLabel = L"TXT_KEY_CITYVIEW_UNRAZE_BUTTON_TEXT" + buttonToolTip = L"TXT_KEY_CITYVIEW_UNRAZE_BUTTON_TT" + + elseif city:IsPuppet() and not(IsCiv5BNW and cityOwner:MayNotAnnex()) then + + -- We can annex this city + taskID = TaskTypes.TASK_ANNEX_PUPPET + buttonLabel = L"TXT_KEY_POPUP_ANNEX_CITY" +-- todo + if IsCiv5 then + buttonToolTip = L( "TXT_KEY_POPUP_CITY_CAPTURE_INFO_ANNEX", cityOwner:GetUnhappinessForecast(city) - cityOwner:GetUnhappiness() ) + end + elseif not g_isViewingMode and cityOwner:CanRaze( city, true ) then + buttonLabel = L"TXT_KEY_CITYVIEW_RAZE_BUTTON_TEXT" + + if cityOwner:CanRaze( city, false ) then + + -- We can actually raze this city + taskID = TaskTypes.TASK_RAZE + buttonToolTip = L"TXT_KEY_CITYVIEW_RAZE_BUTTON_TT" + else + -- We COULD raze this city if it weren't a capital + buttonToolTip = L"TXT_KEY_CITYVIEW_RAZE_BUTTON_DISABLED_BECAUSE_CAPITAL_TT" + end + end + end + local CityTaskButton = Controls.CityTaskButton + CityTaskButton:SetText( buttonLabel ) + CityTaskButton:SetVoids( cityID, taskID or -1 ) + CityTaskButton:SetToolTipString( buttonToolTip ) + CityTaskButton:SetDisabled( not taskID ) + CityTaskButton:SetHide( not buttonLabel ) +--Controls.ReturnToMapButton:SetToolTipString( concat( {"g_isViewingMode:", tostring(g_isViewingMode), "Can raze:", tostring(cityOwner:CanRaze( city, true )), "Can actually raze:", tostring(cityOwner:CanRaze( city, false )), "taskID:", tostring(taskID) }, " " ) ) + + UpdateCityWorkingHexes( city ) + + UpdateCityProductionQueue( city, cityID, cityOwnerID, isActivePlayerCity and not isCityCaptureViewingMode and IsCiv5BNW and cityOwner:MayNotAnnex() and city:IsPuppet() ) + + -- display gold income + local iGoldPerTurn = city:GetYieldRateTimes100( g_yieldCurrency ) / 100 + Controls.GoldPerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_PERTURN_TEXT", iGoldPerTurn ) + + -- display science income + if Game.IsOption(GameOptionTypes.GAMEOPTION_NO_SCIENCE) then + Controls.SciencePerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_OFF" ) + else + local iSciencePerTurn = city:GetYieldRateTimes100(YieldTypes.YIELD_SCIENCE) / 100 + Controls.SciencePerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_PERTURN_TEXT", iSciencePerTurn ) + end + + local culturePerTurn, cultureStored, cultureNext + -- thanks for Firaxis Cleverness ! + if IsCiv5 then + culturePerTurn = city:GetJONSCulturePerTurn() + cultureStored = city:GetJONSCultureStored() + cultureNext = city:GetJONSCultureThreshold() + else + culturePerTurn = city:GetCulturePerTurn() + cultureStored = city:GetCultureStored() + cultureNext = city:GetCultureThreshold() + end + Controls.CulturePerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_PERTURN_TEXT", culturePerTurn ) + local cultureDiff = cultureNext - cultureStored + if culturePerTurn > 0 then + local cultureTurns = max( ceil(cultureDiff / culturePerTurn), 1 ) + Controls.CultureTimeTillGrowthLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_TURNS_TILL_TILE_TEXT", cultureTurns ) + Controls.CultureTimeTillGrowthLabel:SetHide( false ) + else + Controls.CultureTimeTillGrowthLabel:SetHide( true ) + end + local percentComplete = cultureStored / cultureNext + Controls.CultureMeter:SetPercent( percentComplete ) + + if not IsCiv5vanilla then + if Game.IsOption(GameOptionTypes.GAMEOPTION_NO_RELIGION) then + Controls.FaithPerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_OFF" ) + else + Controls.FaithPerTurnLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_PERTURN_TEXT", city:GetFaithPerTurn() ) + end + Controls.FaithFocusButton:SetDisabled( g_isViewingMode ) + end + + local cityGrowth = city:GetFoodTurnsLeft() + local foodPerTurnTimes100 = city:FoodDifferenceTimes100() + if city:IsFoodProduction() or foodPerTurnTimes100 == 0 then + Controls.CityGrowthLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_STAGNATION_TEXT" ) + elseif foodPerTurnTimes100 < 0 then + Controls.CityGrowthLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_STARVATION_TEXT" ) + else + Controls.CityGrowthLabel:LocalizeAndSetText( "TXT_KEY_CITYVIEW_TURNS_TILL_CITIZEN_TEXT", cityGrowth ) + end + + Controls.FoodPerTurnLabel:LocalizeAndSetText( foodPerTurnTimes100 >= 0 and "TXT_KEY_CITYVIEW_PERTURN_TEXT" or "TXT_KEY_CITYVIEW_PERTURN_TEXT_NEGATIVE", foodPerTurnTimes100 / 100 ) + + ------------------------------------------- + -- Disable Buttons as Appropriate + ------------------------------------------- + local bIsLock = g_isViewingMode or (cityOwner:GetNumCities() <= 1) + Controls.PrevCityButton:SetDisabled( bIsLock ) + Controls.NextCityButton:SetDisabled( bIsLock ) + + for _, control in pairs( g_cityFocusButtons ) do + control:SetDisabled( g_isViewingMode ) + end + + end +end + +local function UpdateOptionsAndCityView() + g_isAdvisor = UserInterfaceSettings.CityAdvisor ~= 0 + g_isScreenAutoClose = UserInterfaceSettings.ScreenAutoClose ~= 0 + g_isResetCityPlotPurchase = UserInterfaceSettings.ResetCityPlotPurchase ~= 0 + g_FocusSelectIM.Collapse( not OptionsManager.IsNoCitizenWarning() ) + return UpdateCityView() +end + +g_SpecialBuildingsIM = StackInstanceManager( "BuildingInstance", "Button", Controls.SpecialBuildingsStack, Controls.SpecialBuildingsHeader, ResizeRightStack ) +g_GreatWorkIM = StackInstanceManager( "BuildingInstance", "Button", Controls.GreatWorkStack, Controls.GreatWorkHeader, ResizeRightStack ) +g_WondersIM = StackInstanceManager( "BuildingInstance", "Button", Controls.WondersStack, Controls.WondersHeader, ResizeRightStack ) +g_BuildingsIM = StackInstanceManager( "BuildingInstance", "Button", Controls.BuildingsStack, Controls.BuildingsHeader, ResizeRightStack ) +g_GreatPeopleIM = StackInstanceManager( "GPInstance", "GPBox", Controls.GPStack, Controls.GPHeader, ResizeRightStack ) +g_SlackerIM = StackInstanceManager( "Slot", "Button", Controls.SlackerStack, Controls.SlackerHeader, ResizeRightStack ) +g_ProdQueueIM = StackInstanceManager( "ProductionInstance", "PQbox", Controls.QueueStack, Controls.ProdBox, ResizeProdQueue, true ) +g_UnitSelectIM = StackInstanceManager( "SelectionInstance", "Button", Controls.UnitButtonStack, Controls.UnitButton, ResizeProdQueue ) +g_BuildingSelectIM = StackInstanceManager( "SelectionInstance", "Button", Controls.BuildingButtonStack, Controls.BuildingsButton, ResizeProdQueue ) +g_WonderSelectIM = StackInstanceManager( "SelectionInstance", "Button", Controls.WonderButtonStack, Controls.WondersButton, ResizeProdQueue ) +g_ProcessSelectIM = StackInstanceManager( "SelectionInstance", "Button", Controls.OtherButtonStack, Controls.OtherButton, ResizeProdQueue ) +g_FocusSelectIM = StackInstanceManager( "", "", Controls.WorkerManagementBox, Controls.WorkerHeader, function(collapsed) g_workerHeadingOpen = not collapsed ResizeRightStack() UpdateWorkingHexes() end, true, not g_workerHeadingOpen ) + +-------------- +-- Rename City +local function RenameCity() + local city = GetHeadSelectedCity() + if city then + return Events.SerialEventGameMessagePopup{ + Type = ButtonPopupTypes.BUTTONPOPUP_RENAME_CITY, + Data1 = city:GetID(), + Data2 = -1, + Data3 = -1, + Option1 = false, + Option2 = false + } + end +end + +local NoAutoSpecialistCheckbox = { [eLClick] = function() + return Game.SelectedCitiesGameNetMessage(GameMessageTypes.GAMEMESSAGE_DO_TASK, TaskTypes.TASK_NO_AUTO_ASSIGN_SPECIALISTS, -1, -1, not GetHeadSelectedCity():IsNoAutoAssignSpecialists() ) + end } + +--========================================================== +-- Register Events +--========================================================== + +SetupCallbacks( Controls, +{ + ProdBox = LuaEvents.CityViewToolTips.Call, + FoodBox = LuaEvents.CityViewToolTips.Call, + GoldBox = LuaEvents.CityViewToolTips.Call, + ScienceBox = LuaEvents.CityViewToolTips.Call, + CultureBox = LuaEvents.CityViewToolTips.Call, + FaithBox = LuaEvents.CityViewToolTips.Call, + TourismBox = LuaEvents.CityViewToolTips.Call, + ProductionPortraitButton = LuaEvents.CityViewToolTips.Call, + PopulationBox = LuaEvents.CityViewToolTips.Call, +}, +"EUI_ItemTooltip", +{ + AvoidGrowthButton = { + [eLClick] = function() + local city = GetSelectedModifiableCity() + if city then + Network.SendSetCityAvoidGrowth( city:GetID(), not city:IsForcedAvoidGrowth() ) + return Network.SendUpdateCityCitizens( city:GetID() ) + end + end, + }, + CityTaskButton = { + [eLClick] = function( cityID, taskID, button ) + local city = GetSelectedCity() + if city and city:GetID() == cityID then + return Events.SerialEventGameMessagePopup{ + Type = ButtonPopupTypes.BUTTONPOPUP_CONFIRM_CITY_TASK, + Data1 = cityID, + Data2 = taskID, + Text = button:GetToolTipString() + } + end + end, + }, + YesButton = { + [eLClick] = function( cityID, buildingID ) + Controls.SellBuildingConfirm:SetHide( true ) + if cityID and buildingID and buildingID > 0 and GetSelectedModifiableCity() then + Network.SendSellBuilding( cityID, buildingID ) + Network.SendUpdateCityCitizens( cityID ) + end + return Controls.YesButton:SetVoids( -1, -1 ) + end, + }, + NoButton = { [eLClick] = CancelBuildingSale }, + NextCityButton = { [eLClick] = GotoNextCity }, + PrevCityButton = { [eLClick] = GotoPrevCity }, + ReturnToMapButton = { [eLClick] = ExitCityScreen }, + ProductionPortraitButton = { [eRClick] = ProductionPedia }, + BoxOSlackers = { [eLClick] = OnSlackersSelected }, + ResetButton = { [eLClick] = PlotButtonClicked }, + NoAutoSpecialistCheckbox = NoAutoSpecialistCheckbox, + NoAutoSpecialistCheckbox2 = NoAutoSpecialistCheckbox, + EditButton = { [eLClick] = RenameCity }, + TitlePanel = { [eRClick] = RenameCity }, +}) + +--Controls.ResetButton:SetVoid1( 0 ) -- calling with 0 = city center causes reset of all forced tiles +--Controls.BoxOSlackers:SetVoids(-1,-1) +--Controls.ProductionPortraitButton:SetVoid1( 0 ) + +Controls.FaithBox:SetHide( IsCivBE or IsCiv5vanilla ) +Controls.TourismBox:SetHide( not IsCiv5BNW ) + + +Controls.BuyPlotCheckBox:RegisterCheckHandler( function( isChecked ) -- Void1, Void2, control ) + g_BuyPlotMode = isChecked + return UpdateCityView() +end) + +Events.GameOptionsChanged.Add( UpdateOptionsAndCityView ) +UpdateOptionsAndCityView() + +-------------------------- +-- Enter City Screen Event +Events.SerialEventEnterCityScreen.Add( function() + +--print("enter city screen", GetHeadSelectedCity()) + LuaEvents.TryQueueTutorial("CITY_SCREEN", true) + + Events.SerialEventCityScreenDirty.Add( UpdateCityView ) + Events.SerialEventCityInfoDirty.Add( UpdateCityView ) + Events.SerialEventCityHexHighlightDirty.Add( UpdateWorkingHexes ) + g_queuedItemNumber = nil + g_previousCity = nil + if g_isResetCityPlotPurchase then + g_BuyPlotMode = false + Controls.BuyPlotCheckBox:SetCheck( false ) + end + Controls.RightScrollPanel:SetScrollValue(0) +--TODO other scroll panels + return UpdateCityView() +end) + +------------------------- +-- Exit City Screen Event +Events.SerialEventExitCityScreen.Add( function() +--print("exit city screen") + if UI.IsCityScreenUp() then + Events.SerialEventCityScreenDirty.RemoveAll() + Events.SerialEventCityInfoDirty.RemoveAll() + Events.SerialEventCityHexHighlightDirty.RemoveAll() + local city = UI.GetHeadSelectedCity() + local plot = city and city:Plot() + + CleanupCityScreen() + Events.ClearHexHighlights() + + -- We may get here after a player change, clear the UI if this is not the active player's city + if not city or city:GetOwner() ~= g_activePlayerID then + ClearCityUIInfo() + end + -- required for game engine to display proper hex shading + UI.ClearSelectedCities() + LuaEvents.TryDismissTutorial("CITY_SCREEN") + -- Try and re-select the last unit selected + if not UI.GetHeadSelectedUnit() and UI.GetLastSelectedUnit() then + UI.SelectUnit( UI.GetLastSelectedUnit() ) + end + if plot then + UI.LookAt( plot, 2 ) -- 1 = CAMERALOOKAT_CITY_ZOOM_IN, 2 = CAMERALOOKAT_NORMAL (player zoom) + else + UI.LookAtSelectionPlot() + end + g_isViewingMode = true + end +end) + +--========================================================== +-- Strategic View State Change Event +--========================================================== + +if IsCiv5 then + local NormalWorldPositionOffset = g_worldPositionOffset + local NormalWorldPositionOffset2 = g_worldPositionOffset2 + local StrategicViewWorldPositionOffset = { x = 0, y = 20, z = 0 } + Events.StrategicViewStateChanged.Add( function( bStrategicView ) + if bStrategicView then + g_worldPositionOffset = StrategicViewWorldPositionOffset + g_worldPositionOffset2 = StrategicViewWorldPositionOffset + else + g_worldPositionOffset = NormalWorldPositionOffset + g_worldPositionOffset2 = NormalWorldPositionOffset2 + end + g_previousCity = false + return UpdateCityView() + end) +end +--========================================================== +-- 'Active' (local human) player has changed +--========================================================== + +Events.GameplaySetActivePlayer.Add( function( activePlayerID )--, previousActivePlayerID ) + g_activePlayerID = activePlayerID + g_activePlayer = Players[ g_activePlayerID ] + g_finishedItems = {} + ClearCityUIInfo() + if UI.IsCityScreenUp() then + return ExitCityScreen() + end +end) + +Events.ActivePlayerTurnEnd.Add( function() + g_finishedItems = {} +end) + +AddSerialEventGameMessagePopup( function( popupInfo ) + if popupInfo.Type == ButtonPopupTypes.BUTTONPOPUP_CHOOSEPRODUCTION then + Events.SerialEventGameMessagePopupProcessed.CallImmediate(ButtonPopupTypes.BUTTONPOPUP_CHOOSEPRODUCTION, 0) + Events.SerialEventGameMessagePopupShown( popupInfo ) + + local cityID = popupInfo.Data1 -- city id + local orderID = popupInfo.Data2 -- finished order id + local itemID = popupInfo.Data3 -- finished item id + local city = cityID and g_activePlayer:GetCityByID( cityID ) + + if city and not UI.IsCityScreenUp() then + if orderID >= 0 and itemID >= 0 then + g_finishedItems[ cityID ] = { orderID, itemID } + end + g_isButtonPopupChooseProduction = g_isScreenAutoClose + return UI.DoSelectCityAtPlot( city:Plot() ) -- open city screen + end + end +end, ButtonPopupTypes.BUTTONPOPUP_CHOOSEPRODUCTION ) + +Events.NotificationAdded.Add( function( notificationID, notificationType, toolTip, strSummary, data1, data2, playerID ) + if notificationType == NotificationTypes.NOTIFICATION_PRODUCTION and playerID == g_activePlayerID then + -- Hack to find city + for city in g_activePlayer:Cities() do + if strSummary == L( "TXT_KEY_NOTIFICATION_NEW_CONSTRUCTION", city:GetNameKey() ) then + if data1 >= 0 and data2 >=0 then + g_finishedItems[ city:GetID() ] = { data1, data2 } + end + if city:GetGameTurnFounded() == Game.GetGameTurn() and not UI.IsCityScreenUp() then + return UI.DoSelectCityAtPlot( city:Plot() ) -- open city screen + end + return + end + end + end +end) +end) + +--========================================================== +-- Support for Modded Add-in UI's +--========================================================== +do + g_uiAddins = {} + local Modding = Modding + local uiAddins = g_uiAddins + for addin in Modding.GetActivatedModEntryPoints("CityViewUIAddin") do + local addinFile = Modding.GetEvaluatedFilePath(addin.ModID, addin.Version, addin.File) + if addinFile then + print( "Loading MOD CityViewUIAddin\n", Modding.GetModProperty(addin.ModID, addin.Version, "Name"), addin.File ) + table.insert( uiAddins, ContextPtr:LoadNewContext( addinFile.EvaluatedPath:match("(.*)%..*") ) ) + end + end +end diff --git a/EUI_Converter/Changes.txt b/EUI_Converter/Changes.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a00f91c2ef7929c60919c5d146cd66d4f81f043 --- /dev/null +++ b/EUI_Converter/Changes.txt @@ -0,0 +1,17 @@ +Add UI_bc1 folder to MP_MODSPACK +Replace: + IGE-Mod: IGE_Window.lua --> EUI compatible + EUI: UnitPanel.lua --> mod compatible + EUI: CityBannerManager.lua, CityView.lua & Highlights.xml --> Unlocked recolored +Add "ContextPtr:LoadNewContext"-Parts from old UI files to UI_bc1 in: + CityView.lua + InGame.lua + LeaderHeadRoot.lua + UnitPanel.lua (EvilSpiritsMission, THTanukiMission) +Delete: + Old UI folder + BNW Mass Effect (v 7)\Dummy Building Folder\CivilopediaScreen.lua + BNW Mass Effect (v 7)\Dummy Building Folder\CityView.lua + Touhou - Evil Spirits (v 2)\Lua\UnitPanel.lua + Touhou - Probability Space Hypervessel (v 1)\Lua\TechTree.lua + Touhou - Probability Space Hypervessel (v 1)\Lua\TechButtonInclude.lua \ No newline at end of file diff --git a/EUI_Converter/EUI_Converter.py b/EUI_Converter/EUI_Converter.py new file mode 100644 index 0000000000000000000000000000000000000000..d0f65c779797f54a18bd78b456c269d9bc297f8a --- /dev/null +++ b/EUI_Converter/EUI_Converter.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python + +#Imports +import os +from os.path import join as j +from glob import glob as g +import subprocess +import shutil +import re + +#Change to base DLC directory +os.chdir("../..") + +##Global Values +print("Configuring variables...") +#Names +modpack_folder_name = "MP_MODSPACK" +modded_eui_zip_name = "EUI_CUC.7z" +eui_cu_file_names = ["CityBannerManager.lua", + "CityView.lua", + "Highlights.xml"] +load_tag = "ContextPtr:LoadNewContext" +unit_panel_file_name = "UnitPanel.lua" +ige_compat_file_name = "IGE_Window.lua" +delete_file_names = ["CivilopediaScreen.lua", + "CityView.lua", + "TechTree.lua", + "TechButtonInclude.lua", + unit_panel_file_name] +unit_panel_modcompat_file_names = ["EvilSpiritsMission.lua", + "THTanukiMission.lua"] +#Paths +base_path = os.getcwd() +modsave_path = j(base_path, "zzz_Modsaves") +modpack_path = j(base_path, modpack_folder_name) +vanilla_packs_path = j(base_path, "zz_Vanilla_Versions") +ui_path = j(modpack_path, "UI") +eui_path = j(base_path, "UI_bc1") +szip = r"C:\Program Files\7-Zip\7z.exe" +#Files +mod_files = j(modpack_path, "Mods", "**", "*.lua") +ui_files = j(ui_path, "*.lua") +eui_files = j(eui_path, "*", "*.lua") +base_eui_zip = j(vanilla_packs_path, "0EUI.7z") +modded_eui_zip_path = j(base_path, modded_eui_zip_name) + +#Global Variables +load_tags = {} +unit_panel_modcompat_needed = False +null = open(os.devnull, 'w') + + +#Get modpack zip +while True: + modpack_name = input("\nWhich pack should be converted?\n") + modpack_zips = g(j(vanilla_packs_path, modpack_name + ".*")) + if len(modpack_zips) > 0: + modpack_zip = modpack_zips[0] + break + print("This file doesn't exist, try again.") + + +#Remove previous modpack +print("Removing previous modpack leftovers...") +if os.path.isdir(modpack_path): + shutil.rmtree(modpack_path) +if os.path.isdir(eui_path): + shutil.rmtree(eui_path) + +#Compile EUI with colored unlocked citizens +if not os.path.isfile(modded_eui_zip_path): + print("Creating colored unlocked Citizens EUI...") + subprocess.run([szip, 'x', base_eui_zip], stdout=null, stderr=null) + #shutil.move(j(vanilla_packs_path, eui_file_name), eui_path) + for eui_cu_file_name in eui_cu_file_names: + eui_cu_file = g(j(modsave_path, eui_cu_file_name + "*"))[0] + orig_eui_file = g(j(eui_path, "*", eui_cu_file_name))[0] + shutil.move(orig_eui_file, orig_eui_file + ".orig") + shutil.copyfile(eui_cu_file, orig_eui_file) + subprocess.run([szip, 'a', modded_eui_zip_name, eui_path], stdout=null, stderr=null) +else: + #Unzip EUI + print("Unzipping EUI...") + subprocess.run([szip, 'x', modded_eui_zip_path], stdout=null, stderr=null) + + +#Unzip modpack zip +print("Unzipping Modpack...") +subprocess.run([szip, 'x', j(vanilla_packs_path, modpack_zip)], stdout=null, stderr=null) + + +#Manage mod files +for mod_file in g(mod_files, recursive = True): + mod_file_path = mod_file.split(os.sep) + mod_file_name = mod_file_path[len(mod_file_path) - 1] + + #IGE UI compat file + if mod_file_name == ige_compat_file_name: + print("Providing IGE-EUI-compat...") + shutil.move(mod_file, mod_file + ".orig") + shutil.copyfile(g(j(modsave_path, ige_compat_file_name + "*"))[0], mod_file) + + #Delete UI overwrite duplicates + if mod_file_name in delete_file_names: + print("Removing overwriting file " + mod_file_name + "...") + os.remove(mod_file) + + #Find out if modcompat unit panel needed + if mod_file_name in unit_panel_modcompat_file_names: + print("UnitPanel modcompat need detected...") + unit_panel_modcompat_needed = True + +#Delete useless desktop.ini (Thanks True...) +ini_files = re.sub(r"\.\w+$", ".ini", mod_files) +for ini_file in g(ini_files, recursive = True): + ini_file_path = ini_file.split(os.sep) + ini_file_name = ini_file_path[len(ini_file_path) - 1] + if ini_file_name == "desktop.ini": + print("Removing useless desktop.ini (Thanks True)...") + os.remove(mod_file) + + +#Get stuff from UI files +for ui_file in g(ui_files): + with open(ui_file, 'r') as file: + lines = file.readlines() + + ui_file_path = ui_file.split(os.sep) + ui_file_name = ui_file_path[len(ui_file_path) - 1] + + print("Getting tags from " + ui_file_name + "...") + + load_tags[ui_file_name] = [] + + for line in lines: + if line.startswith(load_tag): + load_tags[ui_file_name].append(line) + +#Insert stuff into EUI files +for eui_file in g(eui_files): + eui_file_path = eui_file.split(os.sep) + eui_file_name = eui_file_path[len(eui_file_path) - 1] + + #Base UI files + if eui_file_name in load_tags.keys(): + print("Writing tags to " + eui_file_name + "...") + with open(eui_file, 'a') as file: + file.write('\n') + for load_tag in load_tags[eui_file_name]: + file.write(load_tag) + #Modcompat unit panel + elif eui_file_name == unit_panel_file_name and unit_panel_modcompat_needed: + print("Providing EUI-UnitPanel-Modcompat...") + shutil.move(eui_file, eui_file + ".orig") + shutil.copyfile(g(j(modsave_path, unit_panel_file_name + "*"))[0], eui_file) + + +#Move EUI folder +print("Moving EUI folder...") +shutil.move(eui_path, modpack_path) +print("Removing UI folder...") +shutil.rmtree(ui_path) + +#Zip modpack folder +print("Zipping Modpack...") +subprocess.run([szip, 'a', modpack_name + "_EUI.7z", modpack_path], stdout=null, stderr=null) + +##Move modpack folder +#print("Moving Modspack folder") +#shutil.move(modpack_path, j(base_path, modpack_folder_name)) + +null.close() +print("Done.\n") \ No newline at end of file diff --git a/EUI_Converter/EUI_Converter.pyproj b/EUI_Converter/EUI_Converter.pyproj new file mode 100644 index 0000000000000000000000000000000000000000..eb302b261ac87743cb9969a21ffc0132c4aee410 --- /dev/null +++ b/EUI_Converter/EUI_Converter.pyproj @@ -0,0 +1,35 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>ea28d07f-cd43-45d1-b01d-1eeeb360d215</ProjectGuid> + <ProjectHome>.</ProjectHome> + <StartupFile>EUI_Converter.py</StartupFile> + <SearchPath> + </SearchPath> + <WorkingDirectory>.</WorkingDirectory> + <OutputPath>.</OutputPath> + <Name>EUI_Converter</Name> + <RootNamespace>EUI_Converter</RootNamespace> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <DebugSymbols>true</DebugSymbols> + <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> + <DebugSymbols>true</DebugSymbols> + <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging> + </PropertyGroup> + <ItemGroup> + <Compile Include="EUI_Converter.py" /> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" /> + <!-- Uncomment the CoreCompile target to enable the Build command in + Visual Studio and specify your pre- and post-build commands in + the BeforeBuild and AfterBuild targets below. --> + <!--<Target Name="CoreCompile" />--> + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> +</Project> \ No newline at end of file diff --git a/EUI_Converter/EUI_Converter.sln b/EUI_Converter/EUI_Converter.sln new file mode 100644 index 0000000000000000000000000000000000000000..607c2680c4ef84eaf5c63155899a80d9184503cd --- /dev/null +++ b/EUI_Converter/EUI_Converter.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31019.35 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "EUI_Converter", "EUI_Converter.pyproj", "{EA28D07F-CD43-45D1-B01D-1EEEB360D215}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{081253F6-C547-4C81-B928-0D7472199986}" + ProjectSection(SolutionItems) = preProject + ..\.gitattributes = ..\.gitattributes + ..\.gitignore = ..\.gitignore + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EA28D07F-CD43-45D1-B01D-1EEEB360D215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA28D07F-CD43-45D1-B01D-1EEEB360D215}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D4E8032E-FBFB-40D5-8FEC-B7EF67F27664} + EndGlobalSection +EndGlobal diff --git a/Highlights.xml.cuc b/Highlights.xml.cuc new file mode 100644 index 0000000000000000000000000000000000000000..efcb992d049ee7a2e6d31263cdc9f287b86f61a2 --- /dev/null +++ b/Highlights.xml.cuc @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<Highlights> + <style name="" type="HexContour" width=".2" texture="hex_contour2.dds"/> + <style name="SampleFilledHex" type="FilledHex" width="1" color="0,0,0,128"/> + <style name="SampleStaticTexture" type="StaticTexture" texture="static_texture_highlight.dds"/> + <style name="SampleHexModel" type="HexModel" model="SelectedAniOneTurn.fxsxml"/> + <style name="EditorHexStyle1" type="HexContour" width=".15" texture="Solid_Contour.dds"/> + <style name="EditorHexStyle2" type="HexContour" width=".08" texture="Solid_Contour.dds"/> + <style name="EditorHexStyle3" type="HexContour" width=".03" texture="Solid_Contour.dds"/> + <style name="TempBorder" type="HexContour" width=".2" texture="hex_contour2.dds"/> + <style name="GroupBorder" type="SplineBorder" width ="6" texture="spline_border_contour2.dds" color="255,128,0,200"/> + <style name="ValidFireTargetBorder" type="StaticTexture" texture="static_texture_highlight.dds"/> + <style name="FireRangeBorder" type="SplineBorder" width="6" texture="spline_border_contour2.dds" color="255,0,0,200"/> + <style name="MovementRangeBorder" type="SplineBorder" width="4" texture="spline_border_contour2.dds" color="100,185,245,240"/> + <style name="AMRBorder" type="StaticTexture" texture="static_texture_highlight.dds"/> + <style name="TradeRoute" type="HexContour" width=".2" texture="hex_contour2.dds"/> + <style name="GUHB" type="HexContour" width=".2" texture="hex_contour2.dds"/> + + <!-- EUI styles --> + + <style name="HexContour" type="HexContour" width=".2" texture="hex_contour2.dds" /> + + <style name="CityOverlap" type="FilledHex" width ="1" color="255,140,0,64" /> + + <style name="OverlapFill" type="FilledHex" width ="1" color="0,0,255,50" /> + <style name="OverlapOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="0,0,255,164" /> + + <style name="WorkedFill" type="FilledHex" width ="1" color="0,255,0,50" /> + <style name="WorkedOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="0,255,0,164" /> + + <style name="UnlockedFill" type="FilledHex" width ="1" color="7,107,0,50" /> + <style name="UnlockedOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="7,107,0,164" /> + + <style name="OwnedFill" type="FilledHex" width ="1" color="0,140,255,50" /> + <style name="OwnedOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="0,140,255,164" /> + + <style name="BuyFill" type="FilledHex" width ="1" color="255,215,0,50" /> + <style name="BuyOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="255,215,0,192" /> + + <style name="VacantFill" type="FilledHex" width ="1" color="0,140,255,50" /> + <style name="VacantOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="0,140,255,164" /> + + <style name="EnemyFill" type="FilledHex" width ="1" color="255,0,0,64" /> + <style name="EnemyOutline" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="255,0,0,200" /> + + <style name="CityLimits" type="SplineBorder" width ="7" texture="hex_contour1.dds" color="0,0,0,164" /> + +</Highlights> diff --git a/IGE_Window.lua.euicompat b/IGE_Window.lua.euicompat new file mode 100644 index 0000000000000000000000000000000000000000..b910f9a89e74d524cf4f55b48a71b2eaceea60c5 --- /dev/null +++ b/IGE_Window.lua.euicompat @@ -0,0 +1,682 @@ +-- Released under GPL v3 +-------------------------------------------------------------- +include("IGE_API_All"); +print("IGE_Window"); + +IGE = nil; +local initialized = false; +local currentPlot = nil; +local oldCurrentPlot = nil; +local mouseHandlers = {}; +local mouseMode = 0; +--local busy = false; + + +--=============================================================================================== +-- INIT-SHOW-HIDE +--=============================================================================================== +local function OnSharingGlobalAndOptions(_IGE) + IGE = _IGE; + OnUpdatedOptions(_IGE); +end +LuaEvents.IGE_SharingGlobalAndOptions.Add(OnSharingGlobalAndOptions); + +------------------------------------------------------------------------------------------------- +function OnInitialize() + Resize(Controls.MainGrid); + local sizeX, sizeY = UIManager:GetScreenSizeVal(); + local offset = (sizeX - 320) - Controls.MainGrid:GetSizeX(); + Controls.MainGrid:SetOffsetX(offset > 0 and offset * 0.5 or -10); + + if sizeY < 1000 then + Controls.PanelsContainer:SetOffsetY(47); + LowerSizeY(Controls.MainGrid, 21); + end + + if not UI.CompareFileTime then + print("Pirate version, autosave disabled."); + Controls.ReloadButton:SetHide(true); + Controls.SaveButton:SetHide(true); + Controls.AutoSave:SetHide(true); + end +end +LuaEvents.IGE_Initialize.Add(OnInitialize); + +------------------------------------------------------------------------------------------------- +local function IsVisible() + return not Controls.Container:IsHidden(); +end + +------------------------------------------------------------------------------------------------- +local function SetBusy(flag, loading) + busy = flag; + UI.SetBusy(flag);--[[ + Controls.MainButton:SetDisabled(busy); + UIManager:SetUICursor(busy and 1 or 0);]] +end + +------------------------------------------------------------------------------------------------- +local function ClosePopups() + LookUpControl("InGame/WorldView/InfoCorner/TechPanel"):SetHide(true); + LookUpControl("InGame/TechAwardPopup"):SetHide(true); + LookUpControl("InGame/ProductionPopup"):SetHide(true); + Controls.ChooseReligionPopup:SetHide(true); + Controls.ChoosePantheonPopup:SetHide(true); + Controls.ProductionPopup:SetHide(true); + Controls.OptionsPanel:SetHide(true); + Controls.WonderPopup:SetHide(true); +end + +------------------------------------------------------------------------------------------------- +local function OpenCore() + if not initialized then + LuaEvents.IGE_Initialize(); + initialized = true; + print("Initialization completed"); + end + + IGE.revealMap = false; + OnUpdateUI() + LuaEvents.IGE_Showing(); + UpdateMouse(); + + ClosePopups(); + UI.SetInterfaceMode(InterfaceModeTypes.INTERFACEMODE_SELECTION); + print("OpenCore - step1"); + Events.SystemUpdateUI.CallImmediate(SystemUpdateUIType.BulkHideUI); + Events.SerialEventMouseOverHex.Add(OnMouseMoveOverHex); + print("OpenCore - step2"); + Events.CameraViewChanged.Add(UpdateMouse); + Controls.Container:SetHide(false); + print("OpenCore - done"); +end + +------------------------------------------------------------------------------------------------- +local reportedError = false; +local function OnInitializationError(err) + if reportedError then return end + reportedError = true; + + print("Failed to open IGE:"); + err = FormatError(err, 1); + + -- Show popup to user + local str = L("TXT_KEY_IGE_LOADING_ERROR").."[NEWLINE][ICON_PIRATE] "..err; + Events.SerialEventGameMessagePopup( { Type = ButtonPopupTypes.BUTTONPOPUP_TEXT, Data1 = 800, Option1 = true, Text = str } ); + + -- Restore things up + Events.SystemUpdateUI.CallImmediate(SystemUpdateUIType.BulkShowUI); + Events.ClearHexHighlights(); + LuaEvents.IGE_Show_Fail(); + return false +end + +------------------------------------------------------------------------------------------------- +local function Open() + if not IsVisible() and not busy then + SetBusy(true); + + -- More than one version installed? + local pingData = { count = 0 }; + LuaEvents.IGE_PingAllVersions(pingData); + if (pingData.count > 1) then + local str = L("TXT_KEY_IGE_MORE_THAN_ONE_VERSION_ERROR"); + Events.SerialEventGameMessagePopup( { Type = ButtonPopupTypes.BUTTONPOPUP_TEXT, Data1 = 800, Option1 = true, Text = str } ); + SetBusy(false); + return; + end + + -- Try to init data + reportedError = false; + local status, err = xpcall(OpenCore, OnInitializationError); + if not status then + SetBusy(false); + return; + end + + -- Autosave + print("SaveFile - begin"); + if IGE.autoSave then + SaveFile(IGE.cleanUpFiles); + end + print("SaveFile - done"); + + -- Restore current plot + if oldCurrentPlot then + SetCurrentPlot(oldCurrentPlot); + end + + SetBusy(false); + print("SetBusy - done"); + LuaEvents.IGE_Update(); + end +end + +------------------------------------------------------------------------------------------------- +local function Close(keepBulkUIHidden, takingSeat) + if IsVisible() and not busy then + SetBusy(true); + if not takingSeat then + if Game.GetActivePlayer() ~= IGE.initialPlayerID then + Game.SetActivePlayer(IGE.initialPlayerID); + end + end + + leftButtonDown = false; + rightButtonDown = false; + oldCurrentPlot = currentPlot; + SetCurrentPlot(nil); + LuaEvents.IGE_Closing(takingSeat); + + if not keepBulkUIHidden then + Events.SystemUpdateUI.CallImmediate(SystemUpdateUIType.BulkShowUI); + end + ClosePopups(); + + Events.SerialEventMouseOverHex.Remove(OnMouseMoveOverHex); + Events.CameraViewChanged.Remove(UpdateMouse); + + Controls.Container:SetHide(true); + UI.SetInterfaceMode(InterfaceModeTypes.INTERFACEMODE_SELECTION); + SetMouseMode(IGE_MODE_NONE); + Map.RecalculateAreas(); + + SetBusy(false); + end +end +Controls.CloseButton:RegisterCallback(Mouse.eLClick, function() Close(false, false) end); + +------------------------------------------------------------------------------------------------- +local function OnForceQuit(takingSeat) + Close(false, takingSeat); +end +LuaEvents.IGE_ForceQuit.Add(OnForceQuit); + +------------------------------------------------------------------------------------------------- +local function CloseAndKeepUIHidden() + Close(true, false); +end +Events.SearchForPediaEntry.Add(CloseAndKeepUIHidden); +Events.GoToPediaHomePage.Add(CloseAndKeepUIHidden); + + +--=============================================================================================== +-- MOUSE HOVER +--=============================================================================================== +local selectedNewPlot = false; +local highlightedPlots = {}; +function SetCurrentPlot(plot) + selectedNewPlot = (plot ~= currentPlot); + currentPlot = plot; + + LuaEvents.IGE_SelectedPlot(plot); + LuaEvents.IGE_Update(); +end + +------------------------------------------------------------------------------------------------- +local function HighlightPlot(plot, color) + if plot then + Events.SerialEventHexHighlight(ToHexFromGrid(Vector2(plot:GetX(), plot:GetY())), true, color); + end +end + +------------------------------------------------------------------------------------------------- +function UpdateMouse(mouseOver, gridX, gridY) + if gridX == nil then + gridX, gridY = UI.GetMouseOverHex(); + end + if mouseOver == nil then + mouseOver = Controls.Background:HasMouseOver(); + end + + local shift = UIManager:GetShift(); + local plot = mouseOver and Map.GetPlot(gridX, gridY) or nil; + Events.ClearHexHighlights(); + + if mouseMode == IGE_MODE_PAINT then + if plot then + local color = rightButtonDown and Vector4(1.0, 0.0, 0.0, 1) or Vector4(0, 1.0, 0, 1); + HighlightPlot(plot, color); + + if shift then + for neighbor in Neighbors(plot) do + HighlightPlot(neighbor, color); + end + end + + if rightButtonDown then + LuaEvents.IGE_PaintPlot(2, plot, shift); + end + end + + elseif mouseMode == IGE_MODE_PLOP then + if plot then + local color = rightButtonDown and Vector4(1, 0, 0, 1) or Vector4(0, 1, 0, 1); + HighlightPlot(plot, color); + end + + elseif mouseMode == IGE_MODE_EDIT_AND_PLOP then + if currentPlot then + local rightClickCurrentPlot = (rightButtonDown and plot == currentPlot); + local color = rightClickCurrentPlot and Vector4(0, 1, 0, 1) or Vector4(1, 0, 0, 1); + HighlightPlot(currentPlot, color); + end + + elseif mouseMode == IGE_MODE_EDIT then + if currentPlot then + HighlightPlot(currentPlot, Vector4(1, 0, 0, 1)); + end + end + + -- Plots that have been undone + for i, v in ipairs(highlightedPlots) do + HighlightPlot(v, Vector4(0, 0, 1, 1)); + end + + LuaEvents.IGE_BroadcastingMouseState(mouseOver, gridX, gridY, plot, shift); +end + +------------------------------------------------------------------------------------------------- +function OnMouseMoveOverHex(hexX, hexY) + UpdateMouse(nil, hexX, hexY); +end + +------------------------------------------------------------------------------------------------- +function OnBackgroundMouseEnter() + if IsVisible() then + leftButtonDown = false; + rightButtonDown = false; + UpdateCursor(true); + UpdateMouse(true); + end +end +Controls.Background:RegisterCallback(Mouse.eMouseEnter, OnBackgroundMouseEnter); + +------------------------------------------------------------------------------------------------- +function OnBackgroundMouseExit() + if IsVisible() then + leftButtonDown = false; + rightButtonDown = false; + UpdateCursor(false); + UpdateMouse(false); + end +end +Controls.Background:RegisterCallback(Mouse.eMouseExit, OnBackgroundMouseExit); + +------------------------------------------------------------------------------------------------- +function UpdateCursor(hasMouseOver) + local cursor = 0; + if hasMouseOver then + if mouseMode > IGE_MODE_EDIT_AND_PLOP then + cursor = 8; + elseif mouseMode > 0 then + cursor = 3; + end + end + UIManager:SetUICursor(cursor); +end + +------------------------------------------------------------------------------------------------- +function SetMouseMode(mode) + if mode == mouseMode then return end + leftButtonDown = false; + rightButtonDown = false; + mouseMode = mode; + UpdateCursor(); + UpdateMouse(); +end +LuaEvents.IGE_SetMouseMode.Add(SetMouseMode); + +------------------------------------------------------------------------------------------------- +function OnFlashPlot(plot) + table.insert(highlightedPlots, plot); + UpdateMouse(); + + LuaEvents.IGE_Schedule(nil, 1.0, function() + table.remove(highlightedPlots, 1); + UpdateMouse(); + end); +end +LuaEvents.IGE_FlashPlot.Add(OnFlashPlot); + + +--=============================================================================================== +-- INPUTS +--=============================================================================================== +mouseHandlers[IGE_MODE_NONE] = function(uiMsg) +end + +------------------------------------------------------------------------------------------------- +mouseHandlers[IGE_MODE_EDIT] = function(uiMsg) + if uiMsg == MouseEvents.RButtonDown then + SetCurrentPlot(nil); + UpdateMouse(); + elseif uiMsg == MouseEvents.LButtonDown then + SetCurrentPlot(Map.GetPlot(UI.GetMouseOverHex())); + UpdateMouse(); + end +end + +------------------------------------------------------------------------------------------------- +mouseHandlers[IGE_MODE_EDIT_AND_PLOP] = function(uiMsg) + if uiMsg == MouseEvents.RButtonDown then + SetCurrentPlot(nil); + UpdateMouse(); + elseif uiMsg == MouseEvents.LButtonDown then + SetCurrentPlot(Map.GetPlot(UI.GetMouseOverHex())); + UpdateMouse(); + + if UIManager:GetShift() then + LuaEvents.IGE_Plop(1, Map.GetPlot(UI.GetMouseOverHex()), true); + end + end +end + +------------------------------------------------------------------------------------------------- +mouseHandlers[IGE_MODE_PAINT] = function(uiMsg) + if uiMsg == MouseEvents.RButtonUp then + UpdateMouse(); + elseif uiMsg == MouseEvents.RButtonDown then + LuaEvents.IGE_BeginPaint(); + UpdateMouse(); + end +end + +------------------------------------------------------------------------------------------------- +mouseHandlers[IGE_MODE_PLOP] = function(uiMsg) + if uiMsg == MouseEvents.RButtonUp then + UpdateMouse(); + elseif uiMsg == MouseEvents.RButtonDown then + UpdateMouse(); + LuaEvents.IGE_Plop(2, Map.GetPlot(UI.GetMouseOverHex()), UIManager:GetShift()); + end +end + +------------------------------------------------------------------------------------------------- +function InputHandler(uiMsg, wParam, lParam) + if IsVisible() then + if uiMsg == MouseEvents.LButtonDown then + selectedNewPlot = false; + leftButtonDown = true; + elseif uiMsg == MouseEvents.LButtonUp then + leftButtonDown = false; + elseif uiMsg == MouseEvents.RButtonDown then + rightButtonDown = true; + elseif uiMsg == MouseEvents.RButtonUp then + rightButtonDown = false; + end + + if Controls.Background:HasMouseOver() then + local func = mouseHandlers[mouseMode]; + func(uiMsg); + end + + if uiMsg == MouseEvents.LButtonDown then + return false; + elseif uiMsg == MouseEvents.LButtonUp then + return selectedNewPlot; + elseif uiMsg == MouseEvents.RButtonDown then + return true; + elseif uiMsg == MouseEvents.RButtonUp then + return true; + elseif uiMsg == KeyEvents.KeyUp then + UpdateMouse(); + if wParam == Keys.Z and UIManager:GetControl() then + return true; + end + + -- Shortcuts + elseif uiMsg == KeyEvents.KeyDown then + if ProcessShortcuts(wParam) then + UpdateMouse(); + return true; + else + UpdateMouse(); + end + end + end + + -- Open IGE + if uiMsg == KeyEvents.KeyDown and wParam == Keys.I and UIManager:GetControl() then + Toggle(); + return true; + end + + return false; +end +ContextPtr:SetInputHandler(InputHandler); + +------------------------------------------------------------------------------------------------- +function ProcessShortcuts(key) + -- Escape = quit + if key == Keys.VK_ESCAPE then + Close(false, false); + return true; + end + + -- Ctrl + Z = undo + if key == Keys.Z and UIManager:GetControl() then + rightButtonDown = false; + leftButtonDown = false; + LuaEvents.IGE_Undo(); + return true; + end + -- Ctrl + Y = redo + if key == Keys.Y and UIManager:GetControl() then + rightButtonDown = false; + leftButtonDown = false; + LuaEvents.IGE_Redo(); + return true; + end + + -- F5 = quicksave + -- Ctrl + F5 = save and reload + if key == Keys.VK_F5 then + if UIManager:GetControl() then + OnReloadButtonClick(); + else + OnSaveButtonClick(); + end + return true; + end + + -- F1-F4 and F6-F8 : panels + if key == Keys.VK_F1 then + LuaEvents.IGE_SetTab("TERRAIN_EDITION"); + return true; + end + if key == Keys.VK_F2 then + LuaEvents.IGE_SetTab("CITIES_AND_UNITS"); + return true; + end + if key == Keys.VK_F3 then + LuaEvents.IGE_SetTab("TERRAIN_PAINTING"); + return true; + end + if key == Keys.VK_F4 then + LuaEvents.IGE_SetTab("UNITS"); + return true; + end + if key == Keys.VK_F6 then + LuaEvents.IGE_SetTab("PLAYERS"); + return true; + end + if key == Keys.VK_F7 then + LuaEvents.IGE_SetTab("TECHS"); + return true; + end + if key == Keys.VK_F8 then + LuaEvents.IGE_SetTab("POLICIES"); + return true; + end +end + + +--=============================================================================================== +-- OPTIONS AND CONTROLS +--=============================================================================================== +function OnUpdatedOptions(IGE) + Controls.AutoSave:SetCheck(IGE.autoSave); + Controls.ShowResources:SetCheck(IGE.showResources); + Controls.ShowUnknownResources:SetCheck(IGE.showUnknownResources); + Controls.DisableStrategicView:SetCheck(IGE.disableStrategicView); + Controls.CleanUpFiles:SetCheck(IGE.cleanUpFiles); + Controls.ShowYields:SetCheck(IGE.showYields); + Controls.SafeMode:SetCheck(IGE.safeMode); +end +LuaEvents.IGE_UpdatedOptions.Add(OnUpdatedOptions); + +------------------------------------------------------------------------------------------------- +function OnOptionControlChanged() + local options = {}; + options.autoSave = Controls.AutoSave:IsChecked(); + options.cleanUpFiles = Controls.CleanUpFiles:IsChecked(); + options.disableStrategicView = Controls.DisableStrategicView:IsChecked(); + options.showUnknownResources = Controls.ShowUnknownResources:IsChecked(); + options.showResources = Controls.ShowResources:IsChecked(); + options.showYields = Controls.ShowYields:IsChecked(); + options.safeMode = Controls.SafeMode:IsChecked(); + LuaEvents.IGE_UpdateOptions(options); +end +Controls.CleanUpFiles:RegisterCheckHandler(OnOptionControlChanged); +Controls.DisableStrategicView:RegisterCheckHandler(OnOptionControlChanged); +Controls.ShowUnknownResources:RegisterCheckHandler(OnOptionControlChanged); +Controls.ShowResources:RegisterCheckHandler(OnOptionControlChanged); +Controls.ShowYields:RegisterCheckHandler(OnOptionControlChanged); +Controls.SafeMode:RegisterCheckHandler(OnOptionControlChanged); +Controls.AutoSave:RegisterCheckHandler(OnOptionControlChanged); + +------------------------------------------------------------------------------------------------- +function OnSaveButtonClick() + SaveFile(IGE.cleanUpFiles); +end +Controls.SaveButton:RegisterCallback(Mouse.eLClick, OnSaveButtonClick); + +------------------------------------------------------------------------------------------------- +function OnReloadButtonClick() + LuaEvents.IGE_ConfirmPopup(L("TXT_KEY_IGE_CONFIRM_SAVE_AND_RELOAD"), function() + local fileName = "IGE - reload"; + if IGE.cleanUpFiles then + DeleteFiles(fileName); + end + UI.SaveGame(fileName, true); + LoadFile(fileName); + end); +end +Controls.ReloadButton:RegisterCallback(Mouse.eLClick, OnReloadButtonClick); + +------------------------------------------------------------------------------------------------- +local function OnRevealMapButtonClick() + IGE.revealMap = true; + LuaEvents.IGE_ToggleRevealMap(IGE.revealMap); +end +Controls.RevealMapButton:RegisterCallback(Mouse.eLClick, OnRevealMapButtonClick); + +------------------------------------------------------------------------------------------------- +local function OnCoverMapButtonClick() + IGE.revealMap = false; + LuaEvents.IGE_ToggleRevealMap(IGE.revealMap); +end +Controls.CoverMapButton:RegisterCallback(Mouse.eLClick, OnCoverMapButtonClick); + +------------------------------------------------------------------------------------------------- +function OnUpdateUI() + local notHuman = not Players[Game.GetActivePlayer()]:IsHuman() + Controls.CoverMapButton:SetHide(notHuman or not IGE.revealMap); + Controls.RevealMapButton:SetHide(notHuman or IGE.revealMap); + Controls.IGECameraButton:SetHide(notHuman) +end +LuaEvents.IGE_Update.Add(OnUpdateUI) +LuaEvents.IGE_ToggleRevealMap.Add(OnUpdateUI); + +------------------------------------------------------------------------------------------------- +local function OnIGECameraHome() + local plot = IGE.currentPlayer:GetStartingPlot() + if plot then + print("Go home") + UI.LookAt(plot) + end +end +Controls.IGECameraButton:RegisterCallback(Mouse.eLClick, OnIGECameraHome); + +------------------------------------------------------------------------------------------------- +local function ToggleOptions() + local hidden = Controls.OptionsPanel:IsHidden(); + Controls.OptionsPanel:SetHide(not hidden); +end +Controls.MainButton:RegisterCallback(Mouse.eRClick, ToggleOptions); + +------------------------------------------------------------------------------------------------- +function Toggle() + if IsVisible() then + Close(); + else + Open(); + end +end +Controls.MainButton:RegisterCallback(Mouse.eLClick, Toggle); + +------------------------------------------------------------------------------------------------- +local TOP = 0; +local BOTTOM = 1; +local LEFT = 2; +local RIGHT = 3; + +local function ScrollMouseEnter(which) + if which == TOP then + Events.SerialEventCameraStartMovingForward(); + elseif which == BOTTOM then + Events.SerialEventCameraStartMovingBack(); + elseif which == LEFT then + Events.SerialEventCameraStartMovingLeft(); + else + Events.SerialEventCameraStartMovingRight(); + end +end + +local function ScrollMouseExit(which) + if which == TOP then + Events.SerialEventCameraStopMovingForward(); + elseif which == BOTTOM then + Events.SerialEventCameraStopMovingBack(); + elseif which == LEFT then + Events.SerialEventCameraStopMovingLeft(); + else + Events.SerialEventCameraStopMovingRight(); + end +end +Controls.ScrollTop:RegisterCallback( Mouse.eMouseEnter, ScrollMouseEnter); +Controls.ScrollTop:RegisterCallback( Mouse.eMouseExit, ScrollMouseExit); +Controls.ScrollTop:SetVoid1(TOP); + +Controls.ScrollBottom:RegisterCallback( Mouse.eMouseEnter, ScrollMouseEnter); +Controls.ScrollBottom:RegisterCallback( Mouse.eMouseExit, ScrollMouseExit); +Controls.ScrollBottom:SetVoid1(BOTTOM); + +Controls.ScrollLeft:RegisterCallback( Mouse.eMouseEnter, ScrollMouseEnter); +Controls.ScrollLeft:RegisterCallback( Mouse.eMouseExit, ScrollMouseExit); +Controls.ScrollLeft:SetVoid1(LEFT); + +Controls.ScrollRight:RegisterCallback( Mouse.eMouseEnter, ScrollMouseEnter); +Controls.ScrollRight:RegisterCallback( Mouse.eMouseExit, ScrollMouseExit); +Controls.ScrollRight:SetVoid1(RIGHT); + +LuaEvents.IGE_ShareGlobalAndOptions(); +print("IGE loaded"); + +----------------------------------------------------------------------------------------- +-- Changes by bc1 to hook up IGE's Main Button to the rigth of the Top Panel's left stack +----------------------------------------------------------------------------------------- + +ContextPtr:SetUpdate( function() + ContextPtr:ClearUpdate(); + local topPanelInfoStack = LookUpControl( "/InGame/TopPanel/TopPanelInfoStack" ); + if topPanelInfoStack then + Controls.MainButton:ChangeParent( topPanelInfoStack ); + Controls.MainButton:SetOffsetVal( 0, 0 ); + topPanelInfoStack:ReprocessAnchoring(); + print( "IGE main button moved successfully." ); + else + print( "IGE main button move attempt failed." ); + end +end); \ No newline at end of file diff --git a/UnitPanel.lua.eui.modcompat b/UnitPanel.lua.eui.modcompat new file mode 100644 index 0000000000000000000000000000000000000000..cbff06968cb61bbdce3a6dad72a628e1d74d3ef4 --- /dev/null +++ b/UnitPanel.lua.eui.modcompat @@ -0,0 +1,1470 @@ +--========================================================== +-- Unit Panel +-- Re-written by bc1 using Notepad++ +-- code is common using gk_mode and bnw_mode switches +--========================================================== + +Events.SequenceGameInitComplete.Add(function() + +include "UserInterfaceSettings" +local UserInterfaceSettings = UserInterfaceSettings + +include "GameInfoCache" -- warning! booleans are true, not 1, and use iterator ONLY with table field conditions, NOT string SQL query +local GameInfo = GameInfoCache + +include "IconHookup" +local IconHookup = IconHookup +local Color = Color +local PrimaryColors = PrimaryColors +local BackgroundColors = BackgroundColors + +include "GetUnitBuildProgressData" +local GetUnitBuildProgressData = GetUnitBuildProgressData + +--========================================================== +-- Minor lua optimizations +--========================================================== + +local gk_mode = Game.GetReligionName ~= nil +local bnw_mode = Game.GetActiveLeague ~= nil +--local debug_print = print + +local ceil = math.ceil +local floor = math.floor +local max = math.max +local pairs = pairs +--local print = print +local format = string.format +local concat = table.concat +local insert = table.insert +local remove = table.remove +local tostring = tostring + +local ActionSubTypes = ActionSubTypes +local ActivityTypes = ActivityTypes +local ContextPtr = ContextPtr +local Controls = Controls +local DomainTypes = DomainTypes +local Events = Events +local Game = Game +local GameDefines = GameDefines +local GameInfoActions = GameInfoActions +local GameInfo_Automates = GameInfo.Automates +local GameInfo_Builds = GameInfo.Builds +local GameInfo_Missions = GameInfo.Missions +local GameInfo_Units = GameInfo.Units +local L = Locale.ConvertTextKey +local ToUpper = Locale.ToUpper +--local PlotDistance = Map.PlotDistance +local Mouse = Mouse +local OrderTypes = OrderTypes +local Players = Players +local Teams = Teams +local ToHexFromGrid = ToHexFromGrid +local DoSelectCityAtPlot = UI.DoSelectCityAtPlot +local GetHeadSelectedCity = UI.GetHeadSelectedCity +local GetHeadSelectedUnit = UI.GetHeadSelectedUnit +--local GetSelectedUnitID = UI.GetSelectedUnitID +local GetUnitFlagIcon = UI.GetUnitFlagIcon +local GetUnitPortraitIcon = UI.GetUnitPortraitIcon +local LookAt = UI.LookAt +local SelectUnit = UI.SelectUnit + +include "InstanceManager" +local actionIM = InstanceManager:new("UnitAction", "UnitActionButton", Controls.ActionStack) + +--========================================================== +-- Globals +--========================================================== + +local g_activePlayerID, g_activePlayer, g_activeTeamID, g_activeTeam, g_activeTechs +local g_AllPlayerOptions = { UnitTypes = {}, UnitsInRibbon = {} } + +local g_ActivePlayerUnitTypes +local g_ActivePlayerUnitsInRibbon = {} +local g_isHideCityList, g_isHideUnitList, g_isHideUnitTypes + +--[[ + _ _ _ _ ___ ____ _ _ _ ____ _ _ _ +| | | |_ __ (_) |_ ___ ( _ ) / ___(_) |_(_) ___ ___ | _ \(_) |__ | |__ ___ _ __ +| | | | '_ \| | __/ __| / _ \/\ | | | | __| |/ _ \/ __| | |_) | | '_ \| '_ \ / _ \| '_ \ +| |_| | | | | | |_\__ \ | (_> < | |___| | |_| | __/\__ \ | _ <| | |_) | |_) | (_) | | | | + \___/|_| |_|_|\__|___/ \___/\/ \____|_|\__|_|\___||___/ |_| \_\_|_.__/|_.__/ \___/|_| |_| +]] + +-- NO_ACTIVITY, ACTIVITY_AWAKE, ACTIVITY_HOLD, ACTIVITY_SLEEP, ACTIVITY_HEAL, ACTIVITY_SENTRY, ACTIVITY_INTERCEPT, ACTIVITY_MISSION +local g_activityMissions = { +--[ActivityTypes.ACTIVITY_AWAKE or -1] = nil, +[ActivityTypes.ACTIVITY_HOLD or -1] = false, --GameInfo_Missions.MISSION_SKIP, -- only when moves left > 0 +--[ActivityTypes.ACTIVITY_SLEEP or -1] = GameInfo_Missions.MISSION_SLEEP, -- can be sleep or fortify +[ActivityTypes.ACTIVITY_HEAL or -1] = GameInfo_Missions.MISSION_HEAL, +[ActivityTypes.ACTIVITY_SENTRY or -1] = GameInfo_Missions.MISSION_ALERT, +[ActivityTypes.ACTIVITY_INTERCEPT or -1] = GameInfo_Missions.MISSION_AIRPATROL, +--[ActivityTypes.ACTIVITY_MISSION or -1] = GameInfo_Missions.MISSION_MOVE_TO, +[-1] = nil } + +local MAX_HIT_POINTS = GameDefines.MAX_HIT_POINTS or 100 +local AIR_UNIT_REBASE_RANGE_MULTIPLIER = GameDefines.AIR_UNIT_REBASE_RANGE_MULTIPLIER +local RELIGION_MISSIONARY_PRESSURE_MULTIPLIER = GameDefines.RELIGION_MISSIONARY_PRESSURE_MULTIPLIER or 1 +local MOVE_DENOMINATOR = GameDefines.MOVE_DENOMINATOR + +local g_unitsIM, g_citiesIM, g_unitTypesIM, g_units, g_cities, g_unitTypes + +local g_cityFocusIcons = { +--[CityAIFocusTypes.NO_CITY_AI_FOCUS_TYPE or -1] = "", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FOOD or -1] = "[ICON_FOOD]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_PRODUCTION or -1] = "[ICON_PRODUCTION]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GOLD or -1] = "[ICON_GOLD]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_SCIENCE or -1] = "[ICON_RESEARCH]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_CULTURE or -1] = "[ICON_CULTURE]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_GREAT_PEOPLE or -1] = "[ICON_GREAT_PEOPLE]", +[CityAIFocusTypes.CITY_AI_FOCUS_TYPE_FAITH or -1] = "[ICON_PEACE]", +[-1] = nil } + +local g_UnitTypeOrder = {} +do + local i = 1 + for unit in GameInfo.Units() do + g_UnitTypeOrder[ unit.ID ] = i + i = i + 1 + end +end + +local function SortByVoid2( a, b ) + return a and b and a:GetVoid2() > b:GetVoid2() +end + +--========================================================== +-- Mod Panels +--========================================================== + +local addinActions = {} +local addinBuilds = {} + +function OnUnitPanelActionAddin(action) + print(string.format("Adding UnitPanel action %s (%s)", Locale.ConvertTextKey(action.Title), action.Name)) + table.insert(addinActions, action) +end +LuaEvents.UnitPanelActionAddin.Add(OnUnitPanelActionAddin) + +-- function OnUnitPanelBuildAddin(build) + -- print(string.format("Adding UnitPanel build %s (%s)", Locale.ConvertTextKey(build.Title), build.Name)) + -- table.insert(addinBuilds, build) +-- end +-- LuaEvents.UnitPanelBuildAddin.Add(OnUnitPanelBuildAddin) + +do + g_uiAddins = {} + local Modding = Modding + local insert = table.insert + local uiAddins = g_uiAddins + for addin in Modding.GetActivatedModEntryPoints("UnitPanelAddin") do + local addinFile = Modding.GetEvaluatedFilePath(addin.ModID, addin.Version, addin.File) + if addinFile then + print("Loading MOD UnitPanelUIAddin\n", Modding.GetModProperty(addin.ModID, addin.Version, "Name"), addin.File ) + insert(uiAddins, ContextPtr:LoadNewContext( addinFile.EvaluatedPath:match("(.*)%..*"))) + end + end +end +ContextPtr:LoadNewContext("EvilSpiritsMission") +ContextPtr:LoadNewContext("THTanukiMission") + +--========================================================== +-- Tooltip Utilities +--========================================================== + +local function lookAtPlot( plot ) + local hex = ToHexFromGrid{ x=plot:GetX(), y=plot:GetY() } + Events.GameplayFX( hex.x, hex.y, -1 ) + return LookAt( plot ) +end + +local function lookAtUnit( unit ) + if unit then + return lookAtPlot( unit:GetPlot() ) + end +end + +--========================================================== +-- Ribbon Manager +--========================================================== + +local function g_RibbonManager( name, stack, scrap, createAllItems, initItem, callbacks, tooltips, closure, toolTipCallback ) + local index = {} + local spares = {} + + local function Create( item, itemID, itemOrder ) + if item then + local instance = remove( spares ) + local button + if instance then +--debug_print("Recycle from scrap", name, instance, "item", itemID, item and item:GetName() ) + button = instance.Button + button:ChangeParent( stack ) + else + instance = { m_PromotionIcons={} } +--debug_print("Create new ", name, instance, "item", itemID, item and item:GetName() ) + ContextPtr:BuildInstanceForControl( name, instance, stack ) + -- Setup Tootip Callbacks + for controlID, toolTipType in pairs( tooltips ) do + instance[ controlID ]:SetToolTipCallback( function( control ) + control:SetToolTipCallback( function( control ) return toolTipCallback( control, closure( button ) ) end ) + control:SetToolTipType( toolTipType ) + end) + end + -- Setup action Callbacks + button = instance.Button + for event, callback in pairs( callbacks ) do + button:RegisterCallback( event, callback ) + end + end + index[ itemID ] = instance + button:SetVoids( itemID, itemOrder ) + return initItem( item, instance ) +--else print( "Failed attempt to add an item to the list", itemID ) + end + end + +return{ + Create = Create, + + Destroy = function( itemID ) + local instance = index[ itemID ] +--debug_print( "Remove item from list", name, "item", itemID, instance ) + if instance then + index[ itemID ] = nil + insert( spares, instance ) + instance.Button:ChangeParent( scrap ) + end + end, + + Initialize = function( isHidden ) +--debug_print("Initializing ", name, " stack", "hidden ?", isHidden ) + for itemID, instance in pairs( index ) do + insert( spares, instance ) + instance.Button:ChangeParent( scrap ) + index[ itemID ] = nil + end + if not isHidden then +--debug_print("Initializing ", name, " stack contents" ) + createAllItems( Create ) + end + end, + + }, index +end + +--========================================================== +-- Item Functions +--========================================================== + +local function UpdateCity( city, instance ) + if city and instance then + local itemInfo, portraitOffset, portraitAtlas, buildPercent + local turnsRemaining = city:GetProductionTurnsLeft() + local productionNeeded = city:GetProductionNeeded() + local storedProduction = city:GetProduction() + city:GetOverflowProduction() + city:GetFeatureProduction() + local orderID, itemID = city:GetOrderFromQueue() + if orderID == OrderTypes.ORDER_TRAIN then + itemInfo = GameInfo.Units + portraitOffset, portraitAtlas = GetUnitPortraitIcon( itemID, g_activePlayerID ) + elseif orderID == OrderTypes.ORDER_CONSTRUCT then + itemInfo = GameInfo.Buildings + elseif orderID == OrderTypes.ORDER_CREATE then + itemInfo = GameInfo.Projects + elseif orderID == OrderTypes.ORDER_MAINTAIN then + itemInfo = GameInfo.Processes + turnsRemaining = nil + productionNeeded = 0 + end + if itemInfo then + itemInfo = itemInfo[ itemID ]or{} + itemInfo = IconHookup( portraitOffset or itemInfo.PortraitIndex, 64, portraitAtlas or itemInfo.IconAtlas, instance.CityProduction ) + if productionNeeded > 0 then + buildPercent = 1 - max( 0, storedProduction/productionNeeded ) + else + buildPercent = 0 + end + instance.BuildMeter:SetPercents( 0, buildPercent ) + else + turnsRemaining = nil + end + instance.CityProduction:SetHide( not itemInfo ) + instance.BuildGrowth:SetString( turnsRemaining ) + instance.CityPopulation:SetString( city:GetPopulation() ) + local foodPerTurnTimes100 = city:FoodDifferenceTimes100() + if foodPerTurnTimes100 < 0 then + instance.CityGrowth:SetString( " [COLOR_RED]" .. (floor( city:GetFoodTimes100() / -foodPerTurnTimes100 ) + 1) .. "[ENDCOLOR] " ) + elseif city:IsForcedAvoidGrowth() then + instance.CityGrowth:SetString( "[ICON_LOCKED]" ) + elseif city:IsFoodProduction() or foodPerTurnTimes100 == 0 then + instance.CityGrowth:SetString() + else + instance.CityGrowth:SetString( " "..city:GetFoodTurnsLeft().." " ) + end + + local isNotPuppet = not city:IsPuppet() + local isNotRazing = not city:IsRazing() + local isNotResistance = not city:IsResistance() + local isCapital = city:IsCapital() + + instance.CityIsCapital:SetHide( not isCapital ) + instance.CityIsPuppet:SetHide( isNotPuppet ) + instance.CityFocus:SetText( isNotRazing and isNotPuppet and g_cityFocusIcons[city:GetFocusType()] ) + instance.CityQuests:SetText( city:GetWeLoveTheKingDayCounter() > 0 and "[ICON_HAPPINESS_1]" or (GameInfo.Resources[city:GetResourceDemanded()] or {}).IconString ) + instance.CityIsRazing:SetHide( isNotRazing ) + instance.CityIsResistance:SetHide( isNotResistance ) + instance.CityIsConnected:SetHide( isCapital or not g_activePlayer:IsCapitalConnectedToCity( city ) ) + instance.CityIsBlockaded:SetHide( not city:IsBlockaded() ) + instance.CityIsOccupied:SetHide( not city:IsOccupied() or city:IsNoOccupiedUnhappiness() ) + instance.Name:SetString( city:GetName() ) + + local culturePerTurn = city:GetJONSCulturePerTurn() + instance.BorderGrowth:SetString( culturePerTurn > 0 and ceil( (city:GetJONSCultureThreshold() - city:GetJONSCultureStored()) / culturePerTurn ) ) + + local percent = 1 - city:GetDamage() / ( gk_mode and city:GetMaxHitPoints() or GameDefines.MAX_CITY_HIT_POINTS ) + instance.Button:SetColor( Color( 1, percent, percent, 1 ) ) + end +end + +local function UpdateUnit( unit, instance, nextInstance ) + if unit and instance then + local unitMovesLeft = unit:MovesLeft() + local pip + if unitMovesLeft >= unit:MaxMoves() then + pip = 0 -- cyan (green) + elseif unitMovesLeft > 0 then + if unit:IsCombatUnit() and unit:IsOutOfAttacks() then + pip = 96 -- orange (gray) + else + pip = 32 -- green (yellow) + end + else + pip = 64 -- red + end + local damage = unit:GetDamage() + local percent + if damage <= 0 then + percent = 1 + elseif instance == Controls then + percent = 1 - damage / MAX_HIT_POINTS / 3 + else + percent = 1 - damage / MAX_HIT_POINTS + end + + local info + local text + local buildID = unit:GetBuildType() + + if buildID ~= -1 then -- unit is actively building something + info = GameInfo_Builds[buildID] + text = GetUnitBuildProgressData( unit:GetPlot(), buildID, unit ) + if text > 99 then text = nil end + + elseif unit:IsEmbarked() then + info = GameInfo_Missions.MISSION_EMBARK + + elseif unit:IsReadyToMove() then + + elseif unit:IsAutomated() then + if unit:IsWork() then + info = GameInfo_Automates.AUTOMATE_BUILD + elseif bnw_mode and unit:IsTrade() then + info = GameInfo_Missions.MISSION_ESTABLISH_TRADE_ROUTE + else + info = GameInfo_Automates.AUTOMATE_EXPLORE + end + + elseif unit:LastMissionPlot() ~= unit:GetPlot() then + info = GameInfo_Missions.MISSION_MOVE_TO + + elseif unit:IsWaiting() then + local activityType = unit:GetActivityType() + info = g_activityMissions[ activityType ] + if not info and unitMovesLeft > 0 then +--print( "ACTIVITY_MISSION", unit:GetName(), unit:GetX(), unit:GetY(), unit:GetPlot() ~= unit:LastMissionPlot() ) + if info == false then + info = GameInfo_Missions.MISSION_SKIP + elseif unit:IsGarrisoned() then + info = GameInfo_Missions.MISSION_GARRISON + elseif unit:IsEverFortifyable() then + info = GameInfo_Missions.MISSION_FORTIFY + else + info = GameInfo_Missions.MISSION_SLEEP + end + end + + elseif unitMovesLeft > 0 then + info = GameInfo_Missions.MISSION_MOVE_TO + end + + repeat + instance.Button:SetColor( Color( 1, percent, percent, 1 ) ) + instance.MovementPip:SetTextureOffsetVal( 0, pip ) + instance.Mission:SetHide( not( info and IconHookup( info.IconIndex, 45, info.IconAtlas, instance.Mission ) ) ) + instance.MissionText:SetText( text ) + if nextInstance then + instance, nextInstance = nextInstance + instance.MovementPip:Play() + else + break + end + until false + end +end + +local function FilterUnit( unit ) + return unit and g_ActivePlayerUnitsInRibbon[ unit:GetUnitType() ] +end + +--========================================================== +-- Unit Ribbon "Object" +--========================================================== +local CallFlagManagerUpdateUnitPromotions = LuaEvents.CallFlagManagerUpdateUnitPromotions.Call + +g_unitsIM, g_units = g_RibbonManager( "UnitInstance", Controls.UnitStack, Controls.Scrap, + function( Create ) -- createAllItems( Create ) + local unitID + for unit in g_activePlayer:Units() do + if FilterUnit( unit ) then + unitID = unit:GetID() + Create( unit, unitID, g_UnitTypeOrder[unit:GetUnitType()] * 65536 + unitID % 65536 ) + end + end + Controls.UnitStack:SortChildren( SortByVoid2 ) + end, + function( unit, instance ) -- initItem( item, instance ) + local portraitOffset, portraitAtlas = GetUnitPortraitIcon( unit ) + IconHookup( portraitOffset, 64, portraitAtlas, instance.Portrait ) + if unit == GetHeadSelectedUnit() then + instance.MovementPip:Play() + else + instance.MovementPip:SetToBeginning() + end + UpdateUnit( unit, instance ) + return CallFlagManagerUpdateUnitPromotions( unit ) + end, + {-- the callback function table names need to match associated instance control ID defined in xml + [Mouse.eLClick] = function( unitID ) + local unit = g_activePlayer:GetUnitByID( unitID ) + SelectUnit( unit ) + lookAtUnit( unit ) + end, + [Mouse.eRClick] = function( unitID ) + lookAtUnit( g_activePlayer:GetUnitByID( unitID ) ) + end, + },--/unit callbacks + { + Button = "EUI_UnitTooltip", + MovementPip = "EUI_ItemTooltip", + Mission = "EUI_ItemTooltip", + }, + function( button ) + return g_activePlayer:GetUnitByID( button:GetVoid1() ) + end, + LuaEvents.UnitToolTips.Call +)--/unit ribbon object +--========================================================== + +LuaEvents.EUI_UnitRibbonTable( g_units ) + +--========================================================== +-- City Ribbon "Object" +--========================================================== +g_citiesIM, g_cities = g_RibbonManager( "CityInstance", Controls.CityStack, Controls.Scrap, + function( Create ) -- createAllItems( Create ) + for city in g_activePlayer:Cities() do + Create( city, city:GetID() ) + end + end, + UpdateCity, -- initItem( item, instance ) + {-- the callback function table names need to match associated instance control ID defined in xml + [Mouse.eLClick] = function( cityID ) + local city = g_activePlayer:GetCityByID( cityID ) + if city then + DoSelectCityAtPlot( city:Plot() ) + end + end, + [Mouse.eRClick] = function( cityID ) + local city = g_activePlayer:GetCityByID( cityID ) + if city then + lookAtPlot( city:Plot() ) + end + end, + },--/city callbacks + { + Button = "EUI_CityProductionTooltip", + CityPopulation = "EUI_CityGrowthTooltip", +-- CityProduction = "EUI_CityProductionTooltip", +-- BuildMeter = "EUI_ItemTooltip", +-- GrowthMeter = "EUI_ItemTooltip", + CityIsCapital = "EUI_ItemTooltip", + CityIsPuppet = "EUI_ItemTooltip", + CityIsOccupied = "EUI_ItemTooltip", + CityIsResistance = "EUI_ItemTooltip", + CityIsRazing = "EUI_ItemTooltip", + CityIsConnected = "EUI_ItemTooltip", + CityIsBlockaded = "EUI_ItemTooltip", + CityFocus = "EUI_ItemTooltip", + CityGrowth = "EUI_ItemTooltip", + CityQuests = "EUI_ItemTooltip", + BuildGrowth = "EUI_ItemTooltip", + BorderGrowth = "EUI_ItemTooltip", + }, + function( button ) + return g_activePlayer:GetCityByID( button:GetVoid1() ) + end, + LuaEvents.CityToolTips.Call +)--/city ribbon object +--========================================================== + +--[[ + _ _ _ _ ____ _ +| | | |_ __ (_) |_ | _ \ __ _ _ __ ___| | +| | | | '_ \| | __| | |_) / _` | '_ \ / _ \ | +| |_| | | | | | |_ | __/ (_| | | | | __/ | + \___/|_| |_|_|\__| |_| \__,_|_| |_|\___|_| +]] + +local g_screenWidth , g_screenHeight = UIManager:GetScreenSizeVal() +local g_topOffset0 = Controls.CityPanel:GetOffsetY() +local g_topOffset = g_topOffset0 +local g_bottomOffset0 = Controls.UnitPanel:GetOffsetY() +local g_bottomOffset = g_bottomOffset0 + +local g_Actions = {} +local g_Promotions = {} +local g_UnusedControls = Controls.Scrap + +local g_lastUnit -- Used to determine if a different unit has been selected. +local g_isWorkerActionPanelOpen = false + +local g_unitPortraitSize = Controls.UnitPortrait:GetSizeX() + +local g_actionButtonSpacing = OptionsManager.GetSmallUIAssets() and 42 or 58 + +--[[ +local g_actionIconSize = OptionsManager.GetSmallUIAssets() and 36 or 50 +local g_recommendedActionButton = {} +ContextPtr:BuildInstanceForControlAtIndex( "UnitAction", g_recommendedActionButton, Controls.WorkerActionStack, 1 ) +--Controls.RecommendedActionLabel:ChangeParent( g_recommendedActionButton.UnitActionButton ) +local g_existingBuild = {} +ContextPtr:BuildInstanceForControl( "UnitAction", g_existingBuild, Controls.WorkerActionStack ) +g_existingBuild.WorkerProgressBar:SetPercent( 1 ) +g_existingBuild.UnitActionButton:SetDisabled( true ) +g_existingBuild.UnitActionButton:SetAlpha( 0.8 ) +--]] + +--========================================================== +-- Event Handlers +--========================================================== + +local function OnUnitActionClicked( actionID ) + local action = GameInfoActions[actionID] + if action and g_activePlayer:IsTurnActive() then + Game.HandleAction( actionID ) + if action.SubType == ActionSubTypes.ACTIONSUBTYPE_PROMOTION then + Events.AudioPlay2DSound("AS2D_INTERFACE_UNIT_PROMOTION") + end + end +end + +Controls.CycleLeft:RegisterCallback( Mouse.eLClick, +function() + -- Cycle to next selection. + Game.CycleUnits(true, true, false) +end) + +Controls.CycleRight:RegisterCallback( Mouse.eLClick, +function() + -- Cycle to previous selection. + Game.CycleUnits(true, false, false) +end) + +local function OnUnitNameClicked() + -- go to this unit + lookAtUnit( GetHeadSelectedUnit() ) +end +Controls.UnitNameButton:RegisterCallback( Mouse.eLClick, OnUnitNameClicked ) + +do + local UnitToolTipCall = LuaEvents.UnitToolTip.Call + Controls.UnitPortraitButton:SetToolTipCallback( function( control ) + control:SetToolTipCallback( function() return UnitToolTipCall( GetHeadSelectedUnit(), L"TXT_KEY_CURRENTLY_SELECTED_UNIT", "----------------" ) end ) + control:SetToolTipType( "EUI_UnitTooltip" ) + end) +end +Controls.UnitPortraitButton:RegisterCallback( Mouse.eLClick, OnUnitNameClicked ) + +Controls.UnitPortraitButton:RegisterCallback( Mouse.eRClick, +function() + local unit = GetHeadSelectedUnit() + Events.SearchForPediaEntry( unit and unit:GetNameKey() ) +end) + +local function OnEditNameClick() + + if GetHeadSelectedUnit() then + Events.SerialEventGameMessagePopup{ + Type = ButtonPopupTypes.BUTTONPOPUP_RENAME_UNIT, + Data1 = GetHeadSelectedUnit():GetID(), + Data2 = -1, + Data3 = -1, + Option1 = false, + Option2 = false + } + end +end +Controls.EditButton:RegisterCallback( Mouse.eLClick, OnEditNameClick ) +Controls.UnitNameButton:RegisterCallback( Mouse.eRClick, OnEditNameClick ) + +--========================================================== +-- Utilities +--========================================================== +local function ResizeCityUnitRibbons() +--debug_print("ResizeCityUnitRibbons" ) + local maxTotalStackHeight = g_screenHeight - g_topOffset - g_bottomOffset + local halfTotalStackHeight = floor(maxTotalStackHeight / 2) + + Controls.CityStack:CalculateSize() + local cityStackHeight = Controls.CityStack:GetSizeY() + Controls.UnitStack:CalculateSize() + local unitStackHeight = Controls.UnitStack:GetSizeY() + + if unitStackHeight + cityStackHeight <= maxTotalStackHeight then + unitStackHeight = false + halfTotalStackHeight = false + elseif cityStackHeight <= halfTotalStackHeight then + unitStackHeight = maxTotalStackHeight - cityStackHeight + halfTotalStackHeight = false + elseif unitStackHeight <= halfTotalStackHeight then + cityStackHeight = maxTotalStackHeight - unitStackHeight + unitStackHeight = false + else + cityStackHeight = halfTotalStackHeight + unitStackHeight = halfTotalStackHeight + end + + Controls.CityScrollPanel:SetHide( not halfTotalStackHeight ) + if halfTotalStackHeight then + Controls.CityStack:ChangeParent( Controls.CityScrollPanel ) + Controls.CityScrollPanel:SetSizeY( cityStackHeight ) + Controls.CityScrollPanel:CalculateInternalSize() + else + Controls.CityStack:ChangeParent( Controls.CityPanel ) + end + Controls.CityPanel:ReprocessAnchoring() +-- Controls.CityPanel:SetSizeY( cityStackHeight ) + + Controls.UnitScrollPanel:SetHide( not unitStackHeight ) + if unitStackHeight then + Controls.UnitStack:ChangeParent( Controls.UnitScrollPanel ) + Controls.UnitScrollPanel:SetSizeY( unitStackHeight ) + Controls.UnitScrollPanel:CalculateInternalSize() + else + Controls.UnitStack:ChangeParent( Controls.UnitPanel ) + end + Controls.UnitPanel:ReprocessAnchoring() +end + +local function UpdateUnits() + local activePlayer = g_activePlayer + for unitID, instance in pairs( g_units ) do + UpdateUnit( activePlayer:GetUnitByID( unitID ), instance ) + end +end + +local function UpdateCities() + local activePlayer = g_activePlayer + for cityID, instance in pairs( g_cities ) do + UpdateCity( activePlayer:GetCityByID( cityID ), instance ) + end +end + +local function UpdateSpecificCity( playerID, cityID ) + if playerID == g_activePlayerID then + UpdateCity( g_activePlayer:GetCityByID( cityID ), g_cities[cityID] ) + end +end + +local function SelectUnitType( isChecked, unitTypeID ) -- Void2, control ) + g_ActivePlayerUnitsInRibbon[ unitTypeID ] = isChecked + g_unitsIM.Initialize( g_isHideUnitList ) + ResizeCityUnitRibbons() + -- only save player 0 preferences, not other hotseat player's + if g_activePlayerID == 0 then + UserInterfaceSettings[ "RIBBON_"..GameInfo_Units[ unitTypeID ].Type] = isChecked and 1 or 0 + end +end +local function ResizeUnitTypesPanel() +-- if not g_isHideUnitTypes then + local n = Controls.UnitTypesStack:GetNumChildren() + Controls.UnitTypesStack:SetWrapWidth( ceil( n / ceil( n / 5 ) ) * 64 ) + Controls.UnitTypesStack:CalculateSize() + local x, y = Controls.UnitTypesStack:GetSizeVal() + if y<64 then y=64 elseif y>320 then y=320 end + Controls.UnitTypesPanel:SetSizeVal( x+40, y+85 ) + Controls.UnitTypesScrollPanel:SetSizeVal( x, y ) + Controls.UnitTypesScrollPanel:CalculateInternalSize() + Controls.UnitTypesScrollPanel:ReprocessAnchoring() +end +local function AddUnitType( unit, unitID ) + local unitTypeID = unit:GetUnitType() + g_ActivePlayerUnitTypes[ unitID or unit:GetID() ] = unitTypeID + local instance = g_unitTypes[ unitTypeID ] + if instance then +--debug_print( "Add unit:", unit:GetID(), unit:GetName(), "type:", instance, unitTypeID, "count:", n ) + return instance.Count:SetText( instance.Count:GetText()+1 ) + else +--debug_print( "Add unit:", unit:GetID(), unit:GetName(), "new type:", unitTypeID ) + g_unitTypesIM.Create( unit, unitTypeID, -g_UnitTypeOrder[unitTypeID] ) + if unitID then + Controls.UnitTypesStack:SortChildren( SortByVoid2 ) + return ResizeUnitTypesPanel() + end + end +end +--========================================================== +-- Unit Options "Object" +--========================================================== +g_unitTypesIM, g_unitTypes = g_RibbonManager( "UnitTypeInstance", Controls.UnitTypesStack, Controls.Scrap, + function() -- createAllItems + g_ActivePlayerUnitTypes = {} + for unit in g_activePlayer:Units() do + AddUnitType( unit ) + end + Controls.UnitTypesStack:SortChildren( SortByVoid2 ) + return ResizeUnitTypesPanel() + end, + function( unit, instance ) -- initItem( item, instance ) + local portrait = instance.Portrait + local portraitOffset, portraitAtlas = GetUnitPortraitIcon( unit ) + portrait:SetHide(not ( portraitOffset and portraitAtlas and IconHookup( portraitOffset, portrait:GetSizeX(), portraitAtlas, portrait ) ) ) + instance.CheckBox:RegisterCheckHandler( SelectUnitType ) + local unitTypeID = unit:GetUnitType() + instance.CheckBox:SetCheck( g_ActivePlayerUnitsInRibbon[ unitTypeID ] ) + instance.CheckBox:SetVoid1( unitTypeID ) + instance.Count:SetText("1") + end, + {-- the callback function table names need to match associated instance control ID defined in xml + [Mouse.eRClick] = function( unitTypeID ) + local unit = GameInfo.Units[ unitTypeID ] + if unit then + Events.SearchForPediaEntry( unit.Description ) + end + end, + },--/unit options callbacks + {-- the tooltip function names need to match associated instance control ID defined in xml + Button = "EUI_ItemTooltip", + },--/units options tooltips + function( button ) + return button:GetVoid1() + end, + LuaEvents.UnitPanelItemTooltip.Call +)--/unit options object + +local function CreateUnit( playerID, unitID ) --hexVec, unitType, cultureType, civID, primaryColor, secondaryColor, unitFlagIndex, fogState, selected, military, notInvisible ) + if playerID == g_activePlayerID then + local unit = g_activePlayer:GetUnitByID( unitID ) +--debug_print("Create unit", unitID, unit and unit:GetName() ) + if unit then + AddUnitType( unit, unitID ) + if FilterUnit( unit ) then + g_unitsIM.Create( unit, unitID, g_UnitTypeOrder[unit:GetUnitType()] + unitID / 65536 ) + Controls.UnitStack:SortChildren( SortByVoid2 ) + return ResizeCityUnitRibbons() + end + end + end +end + +local function CreateCity( hexPos, playerID, cityID ) --, cultureType, eraType, continent, populationSize, size, fowState ) + if playerID == g_activePlayerID then + g_citiesIM.Create( g_activePlayer:GetCityByID( cityID ), cityID ) + return ResizeCityUnitRibbons() + end +end + +local function DestroyUnit( playerID, unitID ) + if playerID == g_activePlayerID then + g_unitsIM.Destroy( unitID ) + local unitTypeID = g_ActivePlayerUnitTypes[ unitID ] + local instance = g_unitTypes[ unitTypeID ] +--debug_print( "Destroy unit", unitID, "type:", g_ActivePlayerUnitTypes[ unitID ], instance, "previous count:", instance.Count ) + g_ActivePlayerUnitTypes[ unitID ] = nil + if instance then + local n = instance.Count:GetText() - 1 + if n <= 0 then + g_unitTypesIM.Destroy( unitTypeID ) + ResizeUnitTypesPanel() + else + instance.Count:SetText( n ) + end + end + return ResizeCityUnitRibbons() + end +end + +local function DestroyCity( hexPos, playerID, cityID ) + if playerID == g_activePlayerID then + g_citiesIM.Destroy( cityID ) + return ResizeCityUnitRibbons() + end +end + + +local function SetHide( ... ) + for _, control in pairs{...} do + control:SetHide( true ) + end +end + +local function SetShow( ... ) + for _, control in pairs{...} do + control:SetHide( false ) + end +end + +local function SetTextAndFontSize( control, text, x ) + control:SetText( text ) + for i = 24, 14, -2 do + control:SetFontByName( "TwCenMT"..i ) + if control:GetSizeVal() <= x then + break + end + end +end + +local function DeselectLastUnit( unit ) + if g_lastUnit then + local lastUnitID = g_lastUnit:GetID() + Events.UnitSelectionChanged( g_lastUnit:GetOwner(), lastUnitID, 0, 0, 0, false, false ) + local instance = g_units[ lastUnitID ] + if instance then + instance.MovementPip:SetToBeginning() + UpdateUnit( g_lastUnit, instance ) + end + end + g_lastUnit = unit +end + +local g_infoSource = { + [ ActionSubTypes.ACTIONSUBTYPE_PROMOTION or -1 ] = GameInfo.UnitPromotions, + [ ActionSubTypes.ACTIONSUBTYPE_INTERFACEMODE or -1 ] = GameInfo.InterfaceModes, + [ ActionSubTypes.ACTIONSUBTYPE_MISSION or -1 ] = GameInfo.Missions, + [ ActionSubTypes.ACTIONSUBTYPE_COMMAND or -1 ] = GameInfo.Commands, + [ ActionSubTypes.ACTIONSUBTYPE_AUTOMATE or -1 ] = GameInfo.Automates, + [ ActionSubTypes.ACTIONSUBTYPE_BUILD or -1 ] = GameInfo.Builds, + [ ActionSubTypes.ACTIONSUBTYPE_CONTROL or -1 ] = GameInfo.Controls, + [-1] = nil +} + +local UnitActionToolTipCall = LuaEvents.UnitActionToolTip.Call +local function UnitActionToolTip( button ) + button:SetToolTipCallback( UnitActionToolTipCall ) + button:SetToolTipType( "EUI_UnitAction" ) +end + +local function UpdateUnitPanel() + actionIM:ResetInstances() + -- Retrieve the currently selected unit. + local unit = GetHeadSelectedUnit() +-- Events.GameplayAlertMessage( "SerialEventUnitInfoDirty, GetHeadSelectedUnit=".. tostring(unit and unit:GetName())..", last unit="..tostring(g_lastUnit and g_lastUnit:GetName()) ) +--debug_print( "UpdateUnitPanel", "GetHeadSelectedCity", GetHeadSelectedCity() and GetHeadSelectedCity():GetName(), "GetHeadSelectedUnit", GetHeadSelectedUnit()and GetHeadSelectedUnit():GetName(), "Last unit", g_lastUnit and g_lastUnit:GetName() ) + if unit then + local unitID = unit:GetID() + -- Selected Unit + if unit ~= g_lastUnit then + DeselectLastUnit( unit ) + local hexPosition = ToHexFromGrid{ x = unit:GetX(), y = unit:GetY() } + Events.UnitSelectionChanged( unit:GetOwner(), unitID, hexPosition.x, hexPosition.y, 0, true, false ) + end + local unitMovesLeft = unit:MovesLeft() / MOVE_DENOMINATOR + local unitPlot = unit:GetPlot() + + -- Unit Name + SetTextAndFontSize( Controls.UnitName, ToUpper( L( unit:IsGreatPerson() and unit:HasName() and unit:GetNameNoDesc() or unit:GetName() ) ), Controls.UnitNameButton:GetSizeVal()-50 ) + + -- Unit Actions + local canPromote = unit:IsPromotionReady() + local GameCanHandleAction = Game.CanHandleAction + local numBuildActions = 0 + local action, instance, button, buildTurnsLeft, buildProgress, buildTime, canBuild, isBuildRecommended + + --========================================================== + --Mod Actions + --========================================================== + local function SetToolTip(sTitle, sToolTip) + print(string.format("Setting Tooltip: Title: %s - Tooltip: %s", sTitle, sToolTip)) + local ttTable = {} + TTManager:GetTypeControlTable( "EUI_UnitAction", ttTable ) + + ttTable.UnitActionHelp:SetText(string.format("[NEWLINE]%s", sToolTip)) + ttTable.UnitActionText:SetText(string.format("[COLOR_POSITIVE_TEXT]%s[ENDCOLOR]", Locale.ConvertTextKey(sTitle))) + ttTable.UnitActionHotKey:SetText("") + + ttTable.UnitActionMouseover:DoAutoSize() + local mouseoverSize = ttTable.UnitActionMouseover:GetSize(); + if (mouseoverSize.x < 350) then + ttTable.UnitActionMouseover:SetSizeX(350) + end + end + + for _, action in pairs(addinActions) do + print(string.format("Processing UnitPanel action %s (%s)", Locale.ConvertTextKey(action.Title), action.Name)) + if (action.Condition == nil or + (type(action.Condition) == "function" and action.Condition(action, unit)) or + (type(action.Condition) ~= "function" and action.Condition)) then + local instance = actionIM:GetInstance() + + if ((type(action.Disabled) == "function" and action.Disabled(action, unit)) or + (type(action.Disabled) ~= "function" and action.Disabled)) then + instance.UnitActionButton:SetAlpha(0.4) + instance.UnitActionButton:SetDisabled(true) + else + instance.UnitActionButton:SetAlpha(1.0) + instance.UnitActionButton:SetDisabled(false) + end + + IconHookup(action.PortraitIndex, actionIconSize, action.IconAtlas, instance.UnitActionIcon) + local sTitle + if (type(action.Title) == "function") then + sTitle = action.Title(action, unit) + else + sTitle = action.Title + end + + local sToolTip + if (type(action.ToolTip) == "function") then + sToolTip = action.ToolTip(action, unit) + else + sToolTip = action.ToolTip + end + instance.UnitActionButton:SetToolTipCallback(function() SetToolTip(sTitle, sToolTip) end) + + if (type(action.Action) == "function") then + instance.UnitActionButton:RegisterCallback(Mouse.eLClick, function() action.Action(action, unit, Mouse.eLClick) end) + instance.UnitActionButton:RegisterCallback(Mouse.eRClick, function() action.Action(action, unit, Mouse.eRClick) end) + else + instance.UnitActionButton:RegisterCallback(Mouse.eLClick, function() end) + instance.UnitActionButton:RegisterCallback(Mouse.eRClick, function() end) + end + end + end + + -- for _, build in pairs(addinBuilds) do + -- if (build.Condition == nil or + -- (type(build.Condition) == "function" and build.Condition(build, unit)) or + -- (type(build.Condition) ~= "function" and build.Condition)) then + -- local instance = g_BuildIM:GetInstance() + -- instance.UnitActionButton:SetAnchor( "L,B" ) + -- instance.UnitActionButton:SetOffsetVal((numBuildActions % numberOfButtonsPerRow) * buttonSize + buttonPadding + buttonOffsetX, math.floor(numBuildActions / numberOfButtonsPerRow) * buttonSize + buttonPadding + buttonOffsetY) + -- numBuildActions = numBuildActions + 1 + + -- if ((type(build.Disabled) == "function" and build.Disabled(build, unit)) or + -- (type(build.Disabled) ~= "function" and build.Disabled)) then + -- instance.UnitActionButton:SetAlpha(0.4) + -- instance.UnitActionButton:SetDisabled(true) + -- else + -- instance.UnitActionButton:SetAlpha(1.0) + -- instance.UnitActionButton:SetDisabled(false) + -- end + + -- IconHookup(build.PortraitIndex, actionIconSize, build.IconAtlas, instance.UnitActionIcon) + -- local sToolTip + -- if (type(build.ToolTip) == "function") then + -- sToolTip = build.ToolTip(build, unit) + -- else + -- sToolTip = build.ToolTip + -- end + -- instance.UnitActionButton:SetToolTipCallback(function() SetToolTip(build.Title, sToolTip) end) + + -- if (type(build.Build) == "function") then + -- instance.UnitActionButton:RegisterCallback(Mouse.eLClick, function() build.Build(build, unit, Mouse.eLClick) end) + -- instance.UnitActionButton:RegisterCallback(Mouse.eRClick, function() build.Build(build, unit, Mouse.eRClick) end) + -- else + -- instance.UnitActionButton:RegisterCallback(Mouse.eLClick, function() end) + -- instance.UnitActionButton:RegisterCallback(Mouse.eRClick, function() end) + -- end + + -- if (recommendedBuild == nil and + -- ((type(build.Recommended) == "function" and build.Recommended(build, unit)) or + -- (type(build.Recommended) ~= "function" and build.Recommended))) then + -- recommendedBuild = build; + -- IconHookup(build.PortraitIndex, actionIconSize, build.IconAtlas, Controls.RecommendedActionImage) + + -- if (type(build.Build) == "function") then + -- Controls.RecommendedActionButton:RegisterCallback(Mouse.eLClick, function() build.Build(build, unit, Mouse.eLClick) end) + -- Controls.RecommendedActionButton:RegisterCallback(Mouse.eRClick, function() build.Build(build, unit, Mouse.eRClick) end) + -- else + -- Controls.RecommendedActionButton:RegisterCallback(Mouse.eLClick, function() end) + -- Controls.RecommendedActionButton:RegisterCallback(Mouse.eRClick, function() end) + -- end + + -- Controls.RecommendedActionButton:SetToolTipCallback(function() SetToolTip(build.Title, sToolTip) end) + -- Controls.RecommendedActionLabel:SetText(Locale.ConvertTextKey(build.Title)) + -- end + -- end + -- end + + --========================================================== + + for actionID = 0, #GameInfoActions do + action = GameInfoActions[ actionID ] + if action and action.Visible ~= false then + instance = g_Actions[ actionID ] + if GameCanHandleAction( actionID, unitPlot, true ) then + if instance then + button = instance.UnitActionButton + else + instance = {} + instance.isBuild = action.SubType == ActionSubTypes.ACTIONSUBTYPE_BUILD + instance.isBuildType = instance.isBuild or action.Type == "INTERFACEMODE_ROUTE_TO" or action.Type == "AUTOMATE_BUILD" + instance.isPromotion = action.SubType == ActionSubTypes.ACTIONSUBTYPE_PROMOTION + instance.isException = instance.isPromotion or action.Type == "COMMAND_CANCEL" or action.Type == "COMMAND_STOP_AUTOMATION" + instance.recommendation = (bnw_mode and (L"TXT_KEY_UPANEL_RECOMMENDED" .. "[NEWLINE]") or "") .. L( tostring( action.TextKey or action.Type ) ) + if action.Type == "MISSION_FOUND" then + instance.UnitActionButton = Controls.BuildCityButton + else + ContextPtr:BuildInstanceForControl( "UnitAction", instance, g_UnusedControls ) + instance.WorkerProgressBar:SetHide( not instance.isBuild ) + local info = ( g_infoSource[ action.SubType ] or {} )[ action.Type ] + if info then + instance.IconIndex = info.IconIndex or info.PortraitIndex + instance.IconAtlas = info.IconAtlas + IconHookup( instance.IconIndex, instance.UnitActionIcon:GetSizeX(), instance.IconAtlas, instance.UnitActionIcon ) + end + end + button = instance.UnitActionButton + button:RegisterCallback( Mouse.eLClick, OnUnitActionClicked ) + button:SetVoid1( actionID ) + button:SetToolTipCallback( UnitActionToolTip ) + instance.ID = actionID + g_Actions[ actionID ] = instance + end + if unitMovesLeft > 0 or instance.isException then + if instance.isPromotion then + numBuildActions = numBuildActions + 1 + button:ChangeParent( Controls.WorkerActionStack ) + + elseif instance.isBuildType and not canPromote then + numBuildActions = numBuildActions + 1 + if unitMovesLeft > 0 and not isBuildRecommended and unit:IsActionRecommended( actionID ) then + isBuildRecommended = true + button:ChangeParent( Controls.RecommendedActionIcon ) + Controls.RecommendedActionLabel:SetText( instance.recommendation ) + else + button:ChangeParent( Controls.WorkerActionStack ) + end + if instance.isBuild then + canBuild = true + buildTurnsLeft, buildProgress, buildTime = GetUnitBuildProgressData( unitPlot, action.MissionData, unit ) + instance.WorkerProgressBar:SetPercent( buildProgress / buildTime ) + instance.UnitActionText:SetText( buildTurnsLeft > 0 and buildTurnsLeft or nil ) + end + else + button:ChangeParent( Controls.ActionStack ) + end + -- test w/o visible flag (ie can train right now) + if GameCanHandleAction( actionID, unitPlot, false ) then + button:SetAlpha( 1.0 ) + button:SetDisabled( false ) + else + button:SetAlpha( 0.6 ) + button:SetDisabled( true ) + end + instance.isVisible = true + elseif instance.isVisible then + button:ChangeParent( g_UnusedControls ) + instance.isVisible = false + end + elseif instance and instance.isVisible then + instance.UnitActionButton:ChangeParent( g_UnusedControls ) + instance.isVisible = false + end + end -- action.Visible + end -- GameInfoActions loop + + if numBuildActions > 0 or canPromote then + Controls.WorkerActionPanel:SetHide( false ) + g_isWorkerActionPanelOpen = true + Controls.RecommendedAction:SetHide( not isBuildRecommended ) +--[[ + local improvement = canBuild and not canPromote and GameInfo.Improvements[ unitPlot:GetImprovementType() ] + local build = improvement and GameInfo_Builds{ ImprovementType = improvement.Type }() + if build then + numBuildActions = numBuildActions + 1 + IconHookup( build.IconIndex, g_actionIconSize, build.IconAtlas, g_existingBuild.UnitActionIcon ) + end + g_existingBuild.UnitActionButton:SetHide( not build ) +--]] + Controls.WorkerText:SetHide( canPromote ) + Controls.PromotionText:SetHide( not canPromote ) + Controls.PromotionAnimation:SetHide( not canPromote ) + Controls.EditButton:SetHide( not canPromote ) + Controls.WorkerActionStack:SetWrapWidth( isBuildRecommended and 232 or ceil( numBuildActions / ceil( numBuildActions / 5 ) ) * g_actionButtonSpacing ) + Controls.WorkerActionStack:CalculateSize() + local x, y = Controls.WorkerActionStack:GetSizeVal() + Controls.WorkerActionPanel:SetSizeVal( max( x, 200 ) + 50, y + 96 ) + Controls.WorkerActionStack:ReprocessAnchoring() + else + Controls.WorkerActionPanel:SetHide( true ) + g_isWorkerActionPanelOpen = false + end + + -- Unit XP + if unit:IsCombatUnit() or unit:GetDomainType() == DomainTypes.DOMAIN_AIR then + local iLevel = unit:GetLevel() + local iExperience = unit:GetExperience() + local iExperienceNeeded = unit:ExperienceNeeded() + Controls.XPMeter:LocalizeAndSetToolTip( "TXT_KEY_UNIT_EXPERIENCE_INFO", iLevel, iExperience, iExperienceNeeded ) + Controls.XPMeter:SetPercent( iExperience / iExperienceNeeded ) + Controls.XPFrame:SetHide( false ) + else + Controls.XPFrame:SetHide( true ) + end + + -- Unit Flag + local flagOffset, flagAtlas = GetUnitFlagIcon( unit ) + IconHookup( flagOffset, 32, flagAtlas, Controls.UnitIcon ) + IconHookup( flagOffset, 32, flagAtlas, Controls.UnitIconShadow ) + + -- Unit Portrait + local portraitOffset, portraitAtlas = GetUnitPortraitIcon( unit ) + IconHookup( portraitOffset, g_unitPortraitSize, portraitAtlas, Controls.UnitPortrait ) + + -- Unit Promotions + for promotion in GameInfo.UnitPromotions() do + if promotion.ShowInUnitPanel ~= false then + instance = g_Promotions[ promotion.ID ] + if unit:IsHasPromotion( promotion.ID ) then + if instance then + instance.EarnedPromotion:ChangeParent( Controls.EarnedPromotionStack ) + else + instance = {} + ContextPtr:BuildInstanceForControl( "EarnedPromotionInstance", instance, Controls.EarnedPromotionStack ) + IconHookup( promotion.PortraitIndex, 32, promotion.IconAtlas, instance.UnitPromotionImage ) + instance.EarnedPromotion:SetToolTipString( ( promotion._Name or "???" ) .. "[NEWLINE][NEWLINE]" .. L(promotion.Help or "???") ) + g_Promotions[ promotion.ID ] = instance + end + instance.isVisible = true + elseif instance and instance.isVisible then + instance.EarnedPromotion:ChangeParent( g_UnusedControls ) + instance.isVisible = false + end + end + end + + -- Unit Movement + if unit:GetDomainType() == DomainTypes.DOMAIN_AIR then + local unitRange = unit:Range() + Controls.UnitStatMovement:SetText( unitRange .. "[ICON_MOVES]" ) + Controls.UnitStatMovement:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_UNIT_MAY_STRIKE_REBASE", unitRange, unitRange * AIR_UNIT_REBASE_RANGE_MULTIPLIER / 100 ) + else + local text = format(" %.3g/%g[ICON_MOVES]", unitMovesLeft, unit:MaxMoves() / MOVE_DENOMINATOR ) + Controls.UnitStatMovement:SetText( text ) + Controls.UnitStatMovement:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_UNIT_MAY_MOVE", text ) + end + + -- Unit Strength + local strength = ( unit:GetDomainType() == DomainTypes.DOMAIN_AIR and unit:GetBaseRangedCombatStrength() ) + or ( not unit:IsEmbarked() and unit:GetBaseCombatStrength() ) or 0 + if strength > 0 then + Controls.UnitStatStrength:SetText( strength .. "[ICON_STRENGTH]" ) + Controls.UnitStatStrength:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_STRENGTH_TT" ) + elseif gk_mode and unit:GetSpreadsLeft() > 0 then + -- Religious units + Controls.UnitStatStrength:SetText( floor(unit:GetConversionStrength()/RELIGION_MISSIONARY_PRESSURE_MULTIPLIER) .. "[ICON_PEACE]" ) + Controls.UnitStatStrength:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_RELIGIOUS_STRENGTH_TT" ) + elseif bnw_mode and unit:GetTourismBlastStrength() > 0 then + Controls.UnitStatStrength:SetText( unit:GetTourismBlastStrength() .. "[ICON_TOURISM]" ) + Controls.UnitStatStrength:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_TOURISM_STRENGTH_TT" ) + else + Controls.UnitStatStrength:SetText() + end + + -- Ranged Strength + local rangedStrength = unit:GetDomainType() ~= DomainTypes.DOMAIN_AIR and unit:GetBaseRangedCombatStrength() or 0 + if rangedStrength > 0 then + Controls.UnitStatRangedAttack:SetText( rangedStrength .. "[ICON_RANGE_STRENGTH]" .. unit:Range() ) + Controls.UnitStatRangedAttack:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_RANGED_ATTACK_TT" ) + elseif gk_mode and unit:GetSpreadsLeft() > 0 then + -- Religious units + local unitReligion = unit:GetReligion() + local icon = (GameInfo.Religions[unitReligion] or {}).IconString + Controls.UnitStatRangedAttack:SetText( icon and (unit:GetSpreadsLeft()..icon) ) + Controls.UnitStatRangedAttack:SetToolTipString( L(Game.GetReligionName(unitReligion))..": "..L"TXT_KEY_UPANEL_SPREAD_RELIGION_USES_TT" ) +-- elseif gk_mode and GameInfo_Units[unit:GetUnitType()].RemoveHeresy then +-- Controls.UnitStatRangedAttack:LocalizeAndSetText( "TXT_KEY_UPANEL_REMOVE_HERESY_USES" ) +-- Controls.UnitStatRangedAttack:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_REMOVE_HERESY_USES_TT" ) + elseif bnw_mode and unit:CargoSpace() > 0 then + Controls.UnitStatRangedAttack:SetText( L"TXT_KEY_UPANEL_CARGO_CAPACITY" .. " " .. unit:CargoSpace() ) + Controls.UnitStatRangedAttack:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_CARGO_CAPACITY_TT", unit:GetName() ) + else + Controls.UnitStatRangedAttack:SetText() + end + Controls.UnitStats:CalculateSize() + Controls.UnitStats:ReprocessAnchoring() + + -- Unit Health Bar + local damage = unit:GetDamage() + if damage ~= 0 then + local healthPercent = 1.0 - (damage / MAX_HIT_POINTS) + local barSize = 123 * healthPercent + if healthPercent <= .33 then + Controls.RedBar:SetSizeY(barSize) + Controls.RedAnim:SetSizeY(barSize) + Controls.GreenBar:SetHide(true) + Controls.YellowBar:SetHide(true) + Controls.RedBar:SetHide(false) + elseif healthPercent <= .66 then + Controls.YellowBar:SetSizeY(barSize) + Controls.GreenBar:SetHide(true) + Controls.YellowBar:SetHide(false) + Controls.RedBar:SetHide(true) + else + Controls.GreenBar:SetSizeY(barSize) + Controls.GreenBar:SetHide(false) + Controls.YellowBar:SetHide(true) + Controls.RedBar:SetHide(true) + end + Controls.HealthBar:LocalizeAndSetToolTip( "TXT_KEY_UPANEL_SET_HITPOINTS_TT", MAX_HIT_POINTS-damage, MAX_HIT_POINTS ) + Controls.HealthBar:SetHide(false) + else + Controls.HealthBar:SetHide(true) + end + + -- Unit Stats + UpdateUnit( unit, Controls, g_units[ unitID ] ) + + Controls.UnitStatBox:SetHide( bnw_mode and unit:IsTrade() ) + + -- These controls need to be shown since potentially hidden depending on previous selection + SetShow( Controls.EarnedPromotionStack, Controls.UnitTypeFrame, Controls.CycleLeft, Controls.CycleRight, Controls.ActionStack ) + Controls.ActionStack:CalculateSize() + Controls.ActionStack:ReprocessAnchoring() + + else + -- Deselect last unit, if any + DeselectLastUnit() + -- Attempt to show currently selected city + unit = GetHeadSelectedCity() + if unit then + -- City Name + SetTextAndFontSize( Controls.UnitName, ToUpper( L(unit:GetName()) ), Controls.UnitNameButton:GetSizeVal()-50 ) + + -- City Portrait + IconHookup( 0, g_unitPortraitSize, "CITY_ATLAS", Controls.UnitPortrait ) + + -- Hide various aspects of Unit Panel since they don't apply to the city. + SetHide( Controls.EarnedPromotionStack, Controls.UnitTypeFrame, Controls.CycleLeft, Controls.CycleRight, Controls.XPFrame, Controls.UnitStatBox, Controls.WorkerActionPanel, Controls.ActionStack ) + g_isWorkerActionPanelOpen = false + end + end + if (not unit) ~= Controls.Panel:IsHidden() then + if unit then + g_bottomOffset = g_bottomOffset0 + Controls.UnitTypesPanel:SetOffsetVal( g_unitPortraitSize * 0.625, 120 ) + else + g_bottomOffset = 35 + Controls.UnitTypesPanel:SetOffsetVal( 80, -40 ) + end + Controls.Panel:SetHide( not unit ) + Controls.Actions:SetHide( not unit ) + Controls.UnitPanel:SetOffsetY( g_bottomOffset ) + ResizeCityUnitRibbons() + end +end + +local function UpdateOptions() + + local option = UserInterfaceSettings.UnitTypes == 0 + if g_isHideUnitTypes ~= option then + g_unitTypesIM.Initialize( option ) + ResizeUnitTypesPanel() + g_isHideUnitTypes = option + Controls.UnitTypesOpen:SetHide( option ) + Controls.UnitTypesClose:SetHide( not option ) + end + + option = UserInterfaceSettings.UnitRibbon == 0 + if g_isHideUnitList ~= option then + g_isHideUnitList = option + local AddOrRemove = option and "Remove" or "Add" + Events.SerialEventUnitCreated[ AddOrRemove ]( CreateUnit ) + Events.SerialEventUnitDestroyed[ AddOrRemove ]( DestroyUnit ) + Events.ActivePlayerTurnStart[ AddOrRemove ]( UpdateUnits ) + end + g_unitsIM.Initialize( option ) + Controls.UnitPanel:SetHide( option ) + Controls.UnitTypesPanel:SetHide( option or g_isHideUnitTypes ) + + option = UserInterfaceSettings.CityRibbon == 0 + if g_isHideCityList ~= option then + g_isHideCityList = option + local AddOrRemove = option and "Remove" or "Add" + Events.SerialEventCityCreated[ AddOrRemove ]( CreateCity ) + Events.SerialEventCityDestroyed[ AddOrRemove ]( DestroyCity ) + Events.SerialEventCityCaptured[ AddOrRemove ]( DestroyCity ) + Events.SerialEventCityInfoDirty[ AddOrRemove ]( UpdateCities ) + Events.SerialEventCitySetDamage[ AddOrRemove ]( UpdateSpecificCity ) + Events.SpecificCityInfoDirty[ AddOrRemove ]( UpdateSpecificCity ) + end + g_citiesIM.Initialize( option ) + Controls.CityPanel:SetHide( option ) + + UpdateUnitPanel() + ResizeCityUnitRibbons() +end + +Controls.UnitTypesButton:RegisterCallback( Mouse.eLClick, +function() + UserInterfaceSettings.UnitTypes = g_isHideUnitTypes and 1 or 0 + return UpdateOptions() +end) + +local function SetActivePlayer()-- activePlayerID, prevActivePlayerID ) + -- initialize globals + if g_activePlayerID then + g_AllPlayerOptions.UnitTypes[ g_activePlayerID ] = g_ActivePlayerUnitTypes + g_AllPlayerOptions.UnitsInRibbon[ g_activePlayerID ] = g_ActivePlayerUnitsInRibbon + end + g_activePlayerID = Game.GetActivePlayer() + g_activePlayer = Players[ g_activePlayerID ] + g_activeTeamID = g_activePlayer:GetTeam() + g_activeTeam = Teams[ g_activeTeamID ] + g_activeTechs = g_activeTeam:GetTeamTechs() + g_ActivePlayerUnitTypes = g_AllPlayerOptions.UnitTypes[ g_activePlayerID ] or {} + g_ActivePlayerUnitsInRibbon = g_AllPlayerOptions.UnitsInRibbon[ g_activePlayerID ] + if not g_ActivePlayerUnitsInRibbon then + g_ActivePlayerUnitsInRibbon = {} + for row in GameInfo.Units() do + g_ActivePlayerUnitsInRibbon[ row.ID ] = UserInterfaceSettings[ "RIBBON_"..row.Type ] ~= 0 + end + end + + -- set civilization icon and color + local civInfo = GameInfo.Civilizations[ g_activePlayer:GetCivilizationType() ] or {} + IconHookup( civInfo.PortraitIndex, 128, civInfo.IconAtlas, Controls.BackgroundCivSymbol ) + Controls.UnitIcon:SetColor( PrimaryColors[ g_activePlayerID ] ) + Controls.UnitIconBackground:SetColor( BackgroundColors[ g_activePlayerID ] ) + + return UpdateOptions() +end + +SetActivePlayer() +Events.GameplaySetActivePlayer.Add( SetActivePlayer ) +Events.GameOptionsChanged.Add( UpdateOptions ) +Events.SerialEventUnitInfoDirty.Add( UpdateUnitPanel ) +--[[ +Events.UnitActionChanged.Add( +function( playerID, unitID ) + if playerID == g_activePlayerID then + local instance = g_units[ unitID ] + if instance then + return UpdateUnit( g_activePlayer:GetUnitByID( unitID ), instance ) + end + end +end) +--]] +Events.SerialEventEnterCityScreen.Add( +function() + DeselectLastUnit() +end) + +local g_infoCornerYmax = { +[InfoCornerID.None or -1] = g_topOffset0, +[InfoCornerID.Tech or -1] = OptionsManager.GetSmallUIAssets() and 150 or 225, +[-1] = nil } + +Events.OpenInfoCorner.Add( function( infoCornerID ) + g_topOffset = g_infoCornerYmax[infoCornerID] or 380 + Controls.CityPanel:SetOffsetY( g_topOffset ) + return UpdateOptions() +end) + +--[[ +Events.EndCombatSim.Add( function( + attackerPlayerID, + attackerUnitID, + attackerUnitDamage, + attackerFinalUnitDamage, + attackerMaxHitPoints, + defenderPlayerID, + defenderUnitID, + defenderUnitDamage, + defenderFinalUnitDamage, + defenderMaxHitPoints ) + if attackerPlayerID == g_activePlayerID then + local instance = g_units[ attackerUnitID ] + if instance then + local toolTip = instance.Button:GetToolTipString() + if toolTip then + toolTip = toolTip .. "[NEWLINE]" + else + toolTip = "" + end + toolTip = toolTip + .."Attack: " + .. " / " .. tostring( attackerPlayerID ) + .. " / " .. tostring( attackerUnitID ) + .. " / " .. tostring( attackerUnitDamage ) + .. " / " .. tostring( attackerFinalUnitDamage ) + .. " / " .. tostring( attackerMaxHitPoints ) + .. " / " .. tostring( defenderPlayerID ) + .. " / " .. tostring( defenderUnitID ) + .. " / " .. tostring( defenderUnitDamage ) + .. " / " .. tostring( defenderFinalUnitDamage ) + .. " / " .. tostring( defenderMaxHitPoints ) + instance.Button:SetToolTipString( toolTip ) + end + elseif defenderPlayerID == g_activePlayerID then + local instance = g_units[ defenderUnitID ] + if instance then + local toolTip = instance.Button:GetToolTipString() + if toolTip then + toolTip = toolTip .. "[NEWLINE]" + else + toolTip = "" + end + toolTip = toolTip + .."Defense: " + .. " / " .. tostring( attackerPlayerID ) + .. " / " .. tostring( attackerUnitID ) + .. " / " .. tostring( attackerUnitDamage ) + .. " / " .. tostring( attackerFinalUnitDamage ) + .. " / " .. tostring( attackerMaxHitPoints ) + .. " / " .. tostring( defenderPlayerID ) + .. " / " .. tostring( defenderUnitID ) + .. " / " .. tostring( defenderUnitDamage ) + .. " / " .. tostring( defenderFinalUnitDamage ) + .. " / " .. tostring( defenderMaxHitPoints ) + instance.Button:SetToolTipString( toolTip ) + end + end +end) +--]] +-- Process request to hide enemy panel +LuaEvents.EnemyPanelHide.Add( + function( isEnemyPanelHide ) + if g_isWorkerActionPanelOpen then + Controls.WorkerActionPanel:SetHide( not isEnemyPanelHide ) + end + if not g_isHideUnitTypes and not g_isHideUnitList then + Controls.UnitTypesPanel:SetHide( not isEnemyPanelHide ) + end + end) +local EnemyUnitPanel = LookUpControl( "/InGame/WorldView/EnemyUnitPanel" ) +local isHidden = ContextPtr:IsHidden() +ContextPtr:SetShowHideHandler( + function( isHide, isInit ) + if not isInit and isHidden ~= isHide then + isHidden = isHide + if isHide and EnemyUnitPanel then + EnemyUnitPanel:SetHide( true ) + end + end + end) +ContextPtr:SetHide( false ) + +end)