1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

CustomNotificationPanel.lua -- for easy addition of custom notifications.

Discussion in 'Civ5 - SDK / LUA' started by Whys, Nov 12, 2010.

  1. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    NotificationPanel.lua is a cleaned-up version of the original with a CustomNotificationPanel.lua include, which allows for the easy creation of additional custom notifications. The 2 example custom notifications are from my Building Resources mod.

    NotificationPanel.lua:
    Spoiler :
    Code:
    -- vymdt.01.2010.11.12.1515
    -- Created by: Whys Alives -Open source
    --===========================================================================
    -- NotificationPanel.lua
    --===========================================================================
    --[[
    Notifications manager.  For custom notifications use CustomNotificatonPanel.
    ]]
    include( "IconSupport" );
    include( "InstanceManager" );
    include( "CustomNotificationPanel" );
    --===========================================================================
    --[[
    Global Variables.
    ]]
    local g_ActiveNotifications  = {};
    local g_Instances            = {};
    
    --for dynamically sizing small notification stack.
    local DIPLO_SIZE_GUESS = 120;
    local _, screenY = UIManager:GetScreenSizeVal();
    local _, offsetY = Controls.OuterStack:GetOffsetVal();
    local g_SmallScrollMax = screenY - offsetY - DIPLO_SIZE_GUESS;
    
    --handled notification types.
    local g_NameTable = {};
    g_NameTable[-1]                                                              = "Generic";
    g_NameTable[NotificationTypes.NOTIFICATION_POLICY]                           = "SocialPolicy";
    g_NameTable[NotificationTypes.NOTIFICATION_MET_MINOR]                        = "MetCityState";
    g_NameTable[NotificationTypes.NOTIFICATION_MINOR]                            = "CityState";
    g_NameTable[NotificationTypes.NOTIFICATION_MINOR_QUEST]                      = "CityState";
    g_NameTable[NotificationTypes.NOTIFICATION_ENEMY_IN_TERRITORY]               = "EnemyInTerritory";
    g_NameTable[NotificationTypes.NOTIFICATION_CITY_RANGE_ATTACK]                = "Generic";
    g_NameTable[NotificationTypes.NOTIFICATION_BARBARIAN]                        = "Barbarian";
    g_NameTable[NotificationTypes.NOTIFICATION_GOODY]                            = "AncientRuins";
    g_NameTable[NotificationTypes.NOTIFICATION_BUY_TILE]                         = "BuyTile";
    g_NameTable[NotificationTypes.NOTIFICATION_CITY_GROWTH]                      = "CityGrowth";
    g_NameTable[NotificationTypes.NOTIFICATION_CITY_TILE]                        = "CityTile";
    g_NameTable[NotificationTypes.NOTIFICATION_DEMAND_RESOURCE]                  = "BonusResource";
    g_NameTable[NotificationTypes.NOTIFICATION_UNIT_PROMOTION]                   = "UnitPromoted";
    --g_NameTable[NotificationTypes.NOTIFICATION_WONDER_STARTED]                  = "WonderConstructed";
    g_NameTable[NotificationTypes.NOTIFICATION_WONDER_COMPLETED_ACTIVE_PLAYER]   = "WonderConstructed";
    g_NameTable[NotificationTypes.NOTIFICATION_WONDER_COMPLETED]                 = "WonderConstructed";
    g_NameTable[NotificationTypes.NOTIFICATION_WONDER_BEATEN]                    = "WonderConstructed";
    g_NameTable[NotificationTypes.NOTIFICATION_GOLDEN_AGE_BEGUN_ACTIVE_PLAYER]   = "GoldenAge";
    --g_NameTable[NotificationTypes.NOTIFICATION_GOLDEN_AGE_BEGUN]                = "GoldenAge";
    g_NameTable[NotificationTypes.NOTIFICATION_GOLDEN_AGE_ENDED_ACTIVE_PLAYER]   = "GoldenAge";
    --g_NameTable[NotificationTypes.NOTIFICATION_GOLDEN_AGE_ENDED]                = "GoldenAge";
    g_NameTable[NotificationTypes.NOTIFICATION_GREAT_PERSON_ACTIVE_PLAYER]       = "GreatPerson";
    --g_NameTable[NotificationTypes.NOTIFICATION_GREAT_PERSON]                    = "GreatPerson";
    g_NameTable[NotificationTypes.NOTIFICATION_STARVING]                         = "Starving";
    g_NameTable[NotificationTypes.NOTIFICATION_WAR_ACTIVE_PLAYER]                = "War";
    g_NameTable[NotificationTypes.NOTIFICATION_WAR]                              = "WarOther";
    g_NameTable[NotificationTypes.NOTIFICATION_PEACE_ACTIVE_PLAYER]              = "Peace";
    g_NameTable[NotificationTypes.NOTIFICATION_PEACE]                            = "PeaceOther";
    g_NameTable[NotificationTypes.NOTIFICATION_VICTORY]                          = "Victory";
    g_NameTable[NotificationTypes.NOTIFICATION_UNIT_DIED]                        = "UnitDied";
    g_NameTable[NotificationTypes.NOTIFICATION_CITY_LOST]                        = "CapitalLost";
    g_NameTable[NotificationTypes.NOTIFICATION_CAPITAL_LOST_ACTIVE_PLAYER]       = "CapitalLost";
    g_NameTable[NotificationTypes.NOTIFICATION_CAPITAL_LOST]                     = "CapitalLost";
    g_NameTable[NotificationTypes.NOTIFICATION_CAPITAL_RECOVERED]                = "CapitalRecovered";
    g_NameTable[NotificationTypes.NOTIFICATION_PLAYER_KILLED]                    = "CapitalLost";
    g_NameTable[NotificationTypes.NOTIFICATION_DISCOVERED_LUXURY_RESOURCE]       = "LuxuryResource";
    g_NameTable[NotificationTypes.NOTIFICATION_DISCOVERED_STRATEGIC_RESOURCE]    = "StrategicResource";
    g_NameTable[NotificationTypes.NOTIFICATION_DISCOVERED_BONUS_RESOURCE]        = "BonusResource";
    --g_NameTable[NotificationTypes.NOTIFICATION_POLICY_ADOPTION]                  = "Generic";
    g_NameTable[NotificationTypes.NOTIFICATION_DIPLO_VOTE]                       = "Generic";
    g_NameTable[NotificationTypes.NOTIFICATION_RELIGION_RACE]                    = "Generic";
    g_NameTable[NotificationTypes.NOTIFICATION_EXPLORATION_RACE]                 = "NaturalWonder";
    g_NameTable[NotificationTypes.NOTIFICATION_DIPLOMACY_DECLARATION]            = "Diplomacy";
    g_NameTable[NotificationTypes.NOTIFICATION_DEAL_EXPIRED_GPT]                 = "DiplomacyX";
    g_NameTable[NotificationTypes.NOTIFICATION_DEAL_EXPIRED_RESOURCE]            = "DiplomacyX";
    g_NameTable[NotificationTypes.NOTIFICATION_DEAL_EXPIRED_OPEN_BORDERS]        = "DiplomacyX";
    g_NameTable[NotificationTypes.NOTIFICATION_DEAL_EXPIRED_DEFENSIVE_PACT]      = "DiplomacyX";
    g_NameTable[NotificationTypes.NOTIFICATION_DEAL_EXPIRED_RESEARCH_AGREEMENT]  = "DiplomacyX";
    g_NameTable[NotificationTypes.NOTIFICATION_DEAL_EXPIRED_TRADE_AGREEMENT]     = "DiplomacyX";
    g_NameTable[NotificationTypes.NOTIFICATION_TECH_AWARD]                       = "TechAward";
    g_NameTable[NotificationTypes.NOTIFICATION_PLAYER_DEAL]                      = "Diplomacy";
    g_NameTable[NotificationTypes.NOTIFICATION_PLAYER_DEAL_RECEIVED]             = "Diplomacy";
    g_NameTable[NotificationTypes.NOTIFICATION_PLAYER_DEAL_RESOLVED]             = "Diplomacy";
    g_NameTable[NotificationTypes.NOTIFICATION_PROJECT_COMPLETED]                = "ProjectConstructed";
    g_NameTable[NotificationTypes.NOTIFICATION_TECH]                             = "Tech";
    g_NameTable[NotificationTypes.NOTIFICATION_PRODUCTION]                       = "Production";
    g_NameTable[NotificationTypes.NOTIFICATION_FREE_TECH]                        = "FreeTech";
    --===========================================================================
    --[[
    handles mouse left-click for standard notifications.
    ]]
    function GenericLeftClick( Id )
      UI.ActivateNotification( Id )
    end
    --===========================================================================
    --[[
    handles mouse right-click for standard notifications.
    ]]
    function GenericRightClick( Id )
      UI.RemoveNotification( Id )
    end
    --===========================================================================
    --[[
    Activates notification.
    ]]
    function OnNotificationAdded( id, iType, toolTip, summary, iGameValue, iExtraGameData )
      --[[print( "OnNotificationAdded(): "
          ..tostring( id )        .."|"..tostring( iType )         .."|"
          ..tostring( iGameValue ).."|"..tostring( iExtraGameData ).."|"
          ..tostring( summary )   .."|"..tostring( toolTip )
        );--]]
      if g_ActiveNotifications[id] ~= nil then
        print( "Redundant Notification Type: "..tostring( iType ) ); return;
      end
      local instance  = {};
      local name      = g_NameTable[iType];
      local button    = nil;
    
      if name == "Production" or name == "Tech" or name == "FreeTech" then
        --local frame  = Controls[name.."Frame"];
        --local title  = Controls[name.."Title"];
        local bg    = Controls[name.."BG"];
        local text  = Controls[name.."Text"];
        button      = Controls[name.."Button"];
        --title:SetText( name );
        --text:SetText( toolTip );
      else
        ContextPtr:BuildInstanceForControl( name.."Item", instance, Controls.SmallStack );
        g_Instances[id]  = instance;
    
        local root  = instance[name.."Container"];
        button      = instance[name.."Button"];
        instance.FingerTitle:SetText( summary );
        root:BranchResetAnimation();
    
        --if iType == NotificationTypes.NOTIFICATION_WONDER_STARTED
        if  iType == NotificationTypes.NOTIFICATION_WONDER_COMPLETED_ACTIVE_PLAYER or
            iType == NotificationTypes.NOTIFICATION_WONDER_COMPLETED or
            iType == NotificationTypes.NOTIFICATION_WONDER_BEATEN then
          if iGameValue ~= -1 then
            local portraitIndex = GameInfo.Buildings[iGameValue].PortraitIndex;
            if portraitIndex ~= -1 then
              IconHookup( portraitIndex, 80, GameInfo.Buildings[iGameValue].IconAtlas, instance.WonderConstructedAlphaAnim );        
            end
          end
          if iExtraGameData ~= -1 then
            CivIconHookup( iExtraGameData, 45, instance.CivIcon, instance.CivIconBG, instance.CivIconShadow, false, true );
            instance.WonderSmallCivFrame:SetHide(false);        
          else
            CivIconHookup( 22, 45, instance.CivIcon, instance.CivIconBG, instance.CivIconShadow, false, true );
            instance.WonderSmallCivFrame:SetHide(true);        
          end
        elseif iType == NotificationTypes.NOTIFICATION_PROJECT_COMPLETED then
          if iGameValue ~= -1 then
            local portraitIndex = GameInfo.Projects[iGameValue].PortraitIndex;
            if portraitIndex ~= -1 then
              IconHookup( portraitIndex, 80, GameInfo.Projects[iGameValue].IconAtlas, instance.ProjectConstructedAlphaAnim );        
            end
          end
          if iExtraGameData ~= -1 then
            CivIconHookup( iExtraGameData, 45, instance.CivIcon, instance.CivIconBG, instance.CivIconShadow, false, true );
            instance.ProjectSmallCivFrame:SetHide(false);        
          else
            CivIconHookup( 22, 45, instance.CivIcon, instance.CivIconBG, instance.CivIconShadow, false, true );
            instance.ProjectSmallCivFrame:SetHide(true);        
          end
        elseif  iType == NotificationTypes.NOTIFICATION_DISCOVERED_LUXURY_RESOURCE or
                iType == NotificationTypes.NOTIFICATION_DISCOVERED_STRATEGIC_RESOURCE or
                iType == NotificationTypes.NOTIFICATION_DISCOVERED_BONUS_RESOURCE or
                iType == NotificationTypes.NOTIFICATION_DEMAND_RESOURCE then
          local thisResourceInfo = GameInfo.Resources[iGameValue];
          local portraitIndex = thisResourceInfo.PortraitIndex;
          if portraitIndex ~= -1 then
            IconHookup( portraitIndex, 80, thisResourceInfo.IconAtlas, instance.ResourceImage );        
          end
        elseif iType == NotificationTypes.NOTIFICATION_EXPLORATION_RACE then
          local thisFeatureInfo = GameInfo.Features[iGameValue];
          local portraitIndex = thisFeatureInfo.PortraitIndex;
          if portraitIndex ~= -1 then
            IconHookup( portraitIndex, 80, thisFeatureInfo.IconAtlas, instance.NaturalWonderImage );        
          end
        elseif iType == NotificationTypes.NOTIFICATION_TECH_AWARD then
          local thisTechInfo = GameInfo.Technologies[iExtraGameData];
          local portraitIndex = thisTechInfo.PortraitIndex;
          if portraitIndex ~= -1 then
            IconHookup( portraitIndex, 80, thisTechInfo.IconAtlas, instance.TechAwardImage );        
          else
            instance.TechAwardImage:SetHide( true );
          end
        elseif  iType == NotificationTypes.NOTIFICATION_UNIT_PROMOTION or
                iType == NotificationTypes.NOTIFICATION_UNIT_DIED or
                iType == NotificationTypes.NOTIFICATION_GREAT_PERSON_ACTIVE_PLAYER or
                iType == NotificationTypes.NOTIFICATION_ENEMY_IN_TERRITORY then
          local thisUnitType = iGameValue;
          local thisUnitInfo = GameInfo.Units[thisUnitType];
          local portraitIndex = thisUnitInfo.PortraitIndex;
          if portraitIndex ~= -1 then
            IconHookup( portraitIndex, 80, thisUnitInfo.IconAtlas, instance.UnitImage );        
          end
        elseif iType == NotificationTypes.NOTIFICATION_WAR_ACTIVE_PLAYER then
          local index = iGameValue;
          CivIconHookup( index, 80, instance.WarImage, instance.CivIconBG, instance.CivIconShadow, false, true ); 
        elseif iType == NotificationTypes.NOTIFICATION_WAR then
          local index = iGameValue;
          CivIconHookup( index, 45, instance.War1Image, instance.Civ1IconBG, instance.Civ1IconShadow, false, true );
          index = iExtraGameData;
          CivIconHookup( index, 45, instance.War2Image, instance.Civ2IconBG, instance.Civ2IconShadow, false, true );
        elseif iType == NotificationTypes.NOTIFICATION_PEACE_ACTIVE_PLAYER then
          local index = iGameValue;
          CivIconHookup( index, 80, instance.PeaceImage, instance.CivIconBG, instance.CivIconShadow, false, true );
        elseif iType == NotificationTypes.NOTIFICATION_PEACE then
          local index = iGameValue;
          CivIconHookup( index, 45, instance.Peace1Image, instance.Civ1IconBG, instance.Civ1IconShadow, false, true );
          index = iExtraGameData;
          CivIconHookup( index, 45, instance.Peace2Image, instance.Civ2IconBG, instance.Civ2IconShadow, false, true );
        end
      end
      button:SetHide( false );
      button:SetVoid1( id );
      button:RegisterCallback( Mouse.eLClick, GenericLeftClick );
      button:RegisterCallback( Mouse.eRClick, GenericRightClick );
      button:SetToolTipString( toolTip );
    
      g_ActiveNotifications[id] = iType;
      ProcessStackSizes();
    end
    Events.NotificationAdded.Add( OnNotificationAdded );
    --===========================================================================
    --[[
    Deactivates notification.
    ]]
    function OnNotificationRemoved( id )
      --[[print( "NotificationRemoved(): "
          ..tostring( id ).."|"..tostring( g_ActiveNotifications[id] ).."|"
          ..tostring( g_NameTable[ g_ActiveNotifications[id] ] )
        );]]
      if g_ActiveNotifications[id] == nil then
        print( "Unknown Notification Id: "..tostring( id ) ); return;
      end
      local name = g_NameTable[g_ActiveNotifications[id]];
      if name == "Production" or name == "Tech" or name == "FreeTech" then
        Controls[name.."Button"]:SetHide( true );
      else
        if name == nil then name = "Generic"; end
        local instance = g_Instances[id];
        if instance ~= nil then
          Controls.SmallStack:ReleaseChild( instance[name.."Container"] );
          g_Instances[id] = nil;
        end
      end
      ProcessStackSizes();
    end
    Events.NotificationRemoved.Add( OnNotificationRemoved );
    --===========================================================================
    --[[
    Processes stack sizes.
    ]]
    function ProcessStackSizes()
      Controls.BigStack:CalculateSize();
      local bigY = Controls.BigStack:GetSizeY();
      Controls.SmallScrollPanel:SetSizeY( g_SmallScrollMax - bigY );
    
      Controls.SmallStack:CalculateSize();
      Controls.SmallStack:ReprocessAnchoring();
    
      Controls.SmallScrollPanel:CalculateInternalSize();
      if Controls.SmallScrollPanel:GetRatio() ~= 1 then
        Controls.SmallScrollPanel:SetOffsetVal( 20, 0 );
      else
        Controls.SmallScrollPanel:SetOffsetVal( 0, 0 );
      end
      Controls.OuterStack:CalculateSize();
      Controls.OuterStack:ReprocessAnchoring();
      --[[
      --for autosized background grids.
      local _, y = Controls.BigStack:GetSizeVal();
      if y > 0 then
        Controls.BigGrid:DoAutoSize();
        Controls.BigGrid:SetHide( false );
      else
        Controls.BigGrid:SetHide( true );
      end
      local _, y = Controls.SmallStack:GetSizeVal();
      if y > 0 then
        Controls.SmallGrid:DoAutoSize();
        Controls.SmallGrid:SetHide( false );
      else
        Controls.SmallGrid:SetHide( true );
      end
      --]]
    end
    --===========================================================================
    
    --exceptions.
    Controls["TechButton"]      :RegisterCallback( Mouse.eLClick, GenericLeftClick );
    Controls["TechButton"]      :RegisterCallback( Mouse.eRClick, GenericRightClick );
    Controls["ProductionButton"]:RegisterCallback( Mouse.eLClick, GenericLeftClick );
    Controls["ProductionButton"]:RegisterCallback( Mouse.eRClick, GenericRightClick );
    Controls["FreeTechButton"]  :RegisterCallback( Mouse.eLClick, GenericLeftClick );
    Controls["FreeTechButton"]  :RegisterCallback( Mouse.eRClick, GenericRightClick );
    
    UI.RebroadcastNotifications();
    ProcessStackSizes();
    --===========================================================================
    --END NotificationPanel.lua
    --===========================================================================
    -- Created by: Whys Alives -Open source
    

    CustomNotificationPanel.lua:
    Spoiler :
    Code:
    -- vymdt.01.2010.11.12.1515
    -- Created by: Whys Alives -Open source
    --===========================================================================
    -- CustomNotificationPanel.lua
    --===========================================================================
    --[[
    Custom notifications manager.
    
    Add unique integer keys greater than 1000 in g_customNameTable and assign
    notification names.  The integer corresponds to the first argument in
    LuaEvents.CustomNotification() and the name corresponds to context instance
    name+"Item" in NotificationPanel.xml, ie: <Instance Name="{name}Item" >.
    Then add desired behavior to "Custom Notification Handling" section below.
    Reassign mouse left-click and mouse right-click functions if desired.
    ]]
    --include( "WhysUtils" ); --provides: out().
    --===========================================================================
    --[[
    Global Variables.
    ]]
    local g_customActiveNotifications  = {};
    local g_customInstances            = {};
    local g_customData                 = {};
    local g_customId                   = 0;
    
    --handled custom notification types.
    local g_customNameTable = {};
    g_customNameTable[ 1001 ] = "BuildingResourcesGain";
    g_customNameTable[ 1002 ] = "BuildingResourcesLoss";
    --===========================================================================
    --[[
    handles default mouse left-click for custom notifications.  Uses fourth
    argument in LuaEvents.CustomNotification() to goto {x,y} map location.
    ]]
    function customLeftClick( id )
      if  type( g_customData[id] ) == "table" and
          type( g_customData[id]["location"] ) == "table" then
        local plot = Map.GetPlot( g_customData[id]["location"][1]
            , g_customData[id]["location"][2] );
        if plot ~= nil then UI.LookAt( plot, 0 ); end
      end
    end
    --===========================================================================
    --[[
    handles default mouse right-click for custom notifications.  Deactivates
    custom notification.
    ]]
    function customRightClick( id )
      if g_customActiveNotifications[id] == nil then
        print( "Unknown Custom Notification Id: "..tostring( id ) ); return;
      end
      local name = g_customNameTable[g_customActiveNotifications[id]];
      local instance = g_customInstances[id];
      if instance ~= nil then
        Controls.SmallStack:ReleaseChild( instance[name.."Container"] );
        g_customInstances[id] = nil;
      end
      ProcessStackSizes();
    end
    --===========================================================================
    --[[
    Activates custom notification.
    ]]
    function onCustomNotificationAdded( iType, summary, toolTip, data )
      g_customId = g_customId +1;
      --[[print( "OnNotificationAdded(): "
          ..tostring( g_customId ).."|"..tostring( iType ).."|"..out( data ).."|"
          ..tostring( summary ).."|"..tostring( toolTip )
        );--]]
      if g_customActiveNotifications[g_customId] ~= nil then
        print( "Redundant Custom Notification Type: "..tostring( iType ) ); return;
      end
      local instance = {};
      local name = g_customNameTable[iType];
    
      ContextPtr:BuildInstanceForControl( name.."Item", instance, Controls.SmallStack );
      g_customInstances[g_customId]  = instance;
      g_customData[g_customId] = data;
    
      local root   = instance[name.."Container"];
      local button = instance[name.."Button"];
      instance.FingerTitle:SetText( summary );
      root:BranchResetAnimation();
    
      local leftClick, rightClick = nil, nil;
      --.........................................................................
      -- Custom Notification Handling
    
      --BuildingResourcesGain, BuildingResourcesLoss.
      if iType == 1001 or iType == 1002 then
        --leftClick  = --replacement function.
        --rightClick = --replacement function.
        local image = instance["BuildingImage"];
        if type( g_customData[g_customId] ) == "table" then
          local buildingType = g_customData[g_customId]["buildingType"];
          local index = GameInfo.Buildings[buildingType].PortraitIndex;
          local atlas = GameInfo.Buildings[buildingType].IconAtlas;
          if index ~= -1 then IconHookup( index, 80, atlas, image ); end
        end
    
      --???
      --elseif iType == ??? then
        --do something.
    
      end
      --END Custom Notification Handling
      --.........................................................................
      if type( leftClick  ) ~= "function" then leftClick  = customLeftClick;  end
      if type( rightClick ) ~= "function" then rightClick = customRightClick; end
    
      button:SetHide( false );
      button:SetVoid1( g_customId );
      button:RegisterCallback( Mouse.eLClick, leftClick );
      button:RegisterCallback( Mouse.eRClick, rightClick );
      button:SetToolTipString( toolTip );
    
      g_customActiveNotifications[g_customId] = iType;
      ProcessStackSizes();
    end
    LuaEvents.CustomNotification.Add( onCustomNotificationAdded );
    --===========================================================================
    --[[
    Deactivates all custom notifications on end of turn.
    ]]
    function onTurnEnd()
      for id,type in pairs( g_customInstances ) do customRightClick( id ); end
    end
    Events.ActivePlayerTurnEnd.Add( onTurnEnd );
    --===========================================================================
    --END CustomNotificationPanel.lua
    --===========================================================================
    -- Created by: Whys Alives -Open source
    

    NotificationPanel.xml: (snippet)
    Spoiler :
    Code:
    	<!--=======================================================================================================================-->
    	<!-- Building Resources Gain -->
    	<!--=======================================================================================================================-->
    	<Instance Name="BuildingResourcesGainItem" >
    		<Container Anchor="R,C" Size="80,80" Offset="0,0"  Hidden="0" ID="BuildingResourcesGainContainer" ConsumeMouseButton="1" >
    			<SlideAnim Anchor="L,T" Style="NotificationSlide" ID="NotificationSlide" >
    				<AlphaAnim Style="NewFinger" />
    				<Button Anchor="C,C" Size="80,80" Offset="0,0" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" ID="BuildingResourcesGainButton"  Hidden="0" >
    
    					<ShowOnMouseOver>
    						<Image Anchor="C,C" Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" />
    						<AlphaAnim  Anchor="C,C"  AnchorSide="O.O"  Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationFrameGlow2.dds"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart="1" AlphaEnd="0" Hidden="0" />
    					</ShowOnMouseOver>
    					<Image Anchor="C,C" Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationYellow.dds" />
    					<AlphaAnim Anchor="C,C" Offset="0,-1" Size="80,80" Texture="assets\UI\Art\Icons\BuildingAtlas512.dds" ID="BuildingImage"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart=".5" AlphaEnd="1" />
    					<Label Anchor="C,C" Offset="0,0" String="" Font="TwCenMT20" ColorSet="Beige_Black_Alpha" FontStyle="Shadow" ID="BuildingResourcesGainCount" />
    				</Button>
    
    			</SlideAnim>
    		</Container>
    	</Instance>
    	<!--=======================================================================================================================-->
    	<!-- Building Resources Loss -->
    	<!--=======================================================================================================================-->
    	<Instance Name="BuildingResourcesLossItem" >
    		<Container Anchor="R,C" Size="80,80" Offset="0,0"  Hidden="0" ID="BuildingResourcesLossContainer" ConsumeMouseButton="1" >
    			<SlideAnim Anchor="L,T" Style="NotificationSlide" ID="NotificationSlide" >
    				<AlphaAnim Style="NewFinger" />
    				<Button Anchor="C,C" Size="80,80" Offset="0,0" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" ID="BuildingResourcesLossButton"  Hidden="0" >
    
    					<ShowOnMouseOver>
    						<Image Anchor="C,C" Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" />
    						<AlphaAnim  Anchor="C,C"  AnchorSide="O.O"  Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationFrameGlow2.dds"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart="1" AlphaEnd="0" Hidden="0" />
    					</ShowOnMouseOver>
    					<Image Anchor="C,C" Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationBlack.dds" />
    					<AlphaAnim Anchor="C,C" Offset="0,-1" Size="80,80" Texture="assets\UI\Art\Icons\BuildingAtlas512.dds" ID="BuildingImage"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart=".5" AlphaEnd="1" />
    					<Label Anchor="C,C" Offset="0,0" String="" Font="TwCenMT20" ColorSet="Beige_Black_Alpha" FontStyle="Shadow" ID="BuildingResourcesLossCount" />
    				</Button>
    
    			</SlideAnim>
    		</Container>
    	</Instance>
    

    The entire NotificationPanel.xml is too long to post, but the only modifications are shown here.

    ------------------------
    Here are some different approaches offered by others:

    - Sneaks'

    - alpaca's
    - Moaf's

    _
     
  2. Thalassicus

    Thalassicus Bytes and Nibblers

    Joined:
    Nov 9, 2005
    Messages:
    11,057
    Location:
    Texas
    You did a great job! I've gotta create notifications for some things I'm working on, and this should help.

    The way notifications are done in CiV baffles me. Mods with notifications (or that modify ingame.xml) are incompatible and must be manually merged, whereas other games using lua for addons (like WoW) don't have this issue. What's worse, everyone who needs to create notifications does it differently (TreeGrowth, Emigration, this thread). Hardcoding everything like CiV does is easier from a coder's point of view working with 1 unified program, but is not scalable or modular. It would have been more efficient to have a function displayNotification(title, tooltip, icon, addNotification, leftClick, rightClick) where the last three are user-defined functions the game calls to handle each event.

    Maybe it's possible to create our own modular engine...

    Are the "Instance" objects in NotificationPanel.xml tied to that file, or can they be placed in separate files? I'm guessing it's the first answer. :\

    How did you figure out how to use LuaEvents.CustomNotification.Add? I did a search on the CiV assets and this isn't used anywhere. I'm somewhat confused about the parameter list and how those values are passed.

    At the very least, I have an idea for how it could be modular with the existing code base. You're more familiar with the notification system than I am, would something like this work? Basically, a registerNotification function stores what to do when the notification is added or clicked into a table, then calls these functions from the table. I think this would let people simply include your NotificationPanel.lua and CustomNotificationPanel.lua files without modifying the code.

    PHP:
    local g_customActiveNotifications    = {};
    local g_customInstances                = {};
    local g_customData                    = {};
    local g_customId                    0;
    local g_customNotificationTable     = {};
    local g_maxID                         1000;

    function 
    registerNotificationnameaddFuncleftClickrightClick )
        
    g_maxID g_maxID 1;
        
    g_customNotificationTableg_maxID ]["name"]        = name;
        
    g_customNotificationTableg_maxID ]["addFunc"]        = addFunc;
        
    g_customNotificationTableg_maxID ]["leftClick"]    = (type(leftClick) == "function") and leftClick or customLeftClick;
        
    g_customNotificationTableg_maxID ]["rightClick"]    = (type(rightClick) == "function") and rightClick or customRightClick;
    end

    function onCustomNotificationAddediTypesummarytoolTipdata )
        
    g_customId g_customId +1;
        if 
    g_customActiveNotifications[g_customId] ~= nil then
            
    print( "Redundant Custom Notification Type: "..tostringiType ) ); return;
        
    end
        local instance 
    = {};
        
    local name g_customNotificationTable[iType]["name"];

        
    ContextPtr:BuildInstanceForControlname.."Item"instanceControls.SmallStack );
        
    g_customInstances[g_customId]    = instance;
        
    g_customData[g_customId] = data;

        
    local root   instance[name.."Container"];
        
    local button instance[name.."Button"];
        
    instance.FingerTitle:SetTextsummary );
        
    root:BranchResetAnimation();

        
    g_customNotificationTable[iType]["addFunc"]();

        
    button:SetHidefalse );
        
    button:SetVoid1g_customId );
        
    button:RegisterCallbackMouse.eLClickg_customNotificationTable[iType]["leftClick"] );
        
    button:RegisterCallbackMouse.eRClickg_customNotificationTable[iType]["rightClick"] );
        
    button:SetToolTipStringtoolTip );

        
    g_customActiveNotifications[g_customId] = iType;
        
    ProcessStackSizes();
    end
    LuaEvents
    .CustomNotification.AddonCustomNotificationAdded );
     
  3. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Yeah I tried a separate file, but it didn't like that. I believe the superglobal ContextPtr is the reason. As best I can tell, ContextPtr knows which built-in lua file is being executed and looks for instances in an xml file by the same name. I don't know if it's possible to create new contexts with just lua.

    I agree that too much in Civ is still hard coded, but then the code has come a long way from version 1. I don't believe modding was an original consideration in its design.

    Oh and I think it might look better like this...
    displayNotification(title, tooltip, data, addNotification, eventFunctions)
    ...where data can be anything and eventFunctions a table of event-keys to functions. :)
     
  4. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    LuaEvents allows you to register functions that can be called by other files, even files in concurrent mods. I believe it's mentioned in Kael's guide. I simply created the CustomNotification LuaEvent to be called from anywhere.

    I assume you're confused by my "data" argument. Often it makes for cleaner function calls to only give a single argument and use it as a package for a variable set of other arguments. For my own custom notifications, I wanted to pass an x,y location and a building type, but there is no reason to assume what kind of data someone might want to pass, so I leave it open by putting that data in a single table and identify the arguments by key-name. I reference data["location"] to obtain the x,y on leftClick and reference data["buildingType"] to obtain the proper icon. Point is, you can put what ever you want in data without altering the function definition.
     
  5. Thalassicus

    Thalassicus Bytes and Nibblers

    Joined:
    Nov 9, 2005
    Messages:
    11,057
    Location:
    Texas
    I suspected as much regarding the Context pointer.

    I understand the data element, entity classes are something I'm familiar with from other languages. What's confusing me is the LuaEvents.X.Add( Y ) procedure.

    I think the reason I'm getting confused is I'm used to the term "event" in the Blizzard sense. In that company's lingo, an event is a hardcoded operation in the source code that executes if certain things occur in the game engine, and you register listeners (triggers) with the event. When the event occurs, it also calls all listener functions. So when I look at LuaEvents.X.Add( Y ), I thought this tells the game "when event X occurs, run function Y".

    From what you're saying (and some help from my brother more experienced in Lua than I...) let me try and understand this clearly in C/Java terms.

    So, LuaEvents can basically be thought of as a class. LuaEvents.X.Add() appends method X to the LuaEvents class, with method X defined by the parameter of the Add method. Then when you call LuaEvents.X(), it executes that new method of the class.

    Is this the correct interpretation?




    Edit:

    Okay, from talking with my brother some more I realized the source of my confusion. It was incomprehensible to me that new functions could be added to the LuaEvents object, since no other programming language I've worked with has this sort of capability. As a result, the concept was so far out of my worldview I couldn't grasp what the heck it was doing! :lol: Even reading it in the modding guide made no sense to me. I think I understand it a little better now... the reason this can be done is because classes in Lua are maps of function names to function pointers, and we're just appending to the map. My head hurts less already! :D

    IF I understand it right, currently to create new notifications we add to the g_customNameTable table, and also insert operations to perform for when a notification is clicked or added. This is all done in CustomNotificationPanel.lua. Mods that alter this file differently to create new notifications must be merged. Is this right?

    The idea I have is a system where we can create and display custom notifications without requiring alteration of the shared code base. This would be simpler for end-users unfamiliar with programming. It doesn't matter how the implementation is done, is this concept possible? Or is it already possible with the code you've provided, and I'm just not seeing it?
     
  6. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Lua takes some getting used to, but I'm finding I really like it. It's like PHP and JavaScript had a baby.

    You don't have to do anything for clicking. It'll just use the default functions. But yes, for multiple mods you'd have to merge the CustomNotificationPanel.lua and NotificationPanel.xml files.

    Sounds great. :)

    Let me know when you've answered these questions. :]
     
  7. Thalassicus

    Thalassicus Bytes and Nibblers

    Joined:
    Nov 9, 2005
    Messages:
    11,057
    Location:
    Texas
    Alright, so I've finished the other parts of my task and started on custom notifications.

    First I'm just focusing on learning how to use the framework you provided. I'm getting an error, "attempt to index field FingerTitle (a nil value)" on line 83 of CustomNotificationPanel.lua. I've attached the project. I'm calling the CustomNotification function like this on line 63 of BC - General.lua:

    Code:
    LuaEvents.CustomNotification(1003,
    "City-State Captured",
    "Looted food supplies from a captured Maritime city-state allowed your Capital to gain 1 citizen.",
    { location={pPlot:GetX(), pPlot:GetY()} }
    );
    I've got BC - CombatNotifications.lua and BC - CombatNotifications.xml files in my project, with Content references, as is indicated is necessary in the other thread.
     
  8. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Does that work? I always do this:

    { ["location"]={pPlot:GetX(), pPlot:GetY()} }

    I ran into the nil finger a couple times while building the panel. I'm trying to remember the cause and the fix. Can you post your xml snippet?
     
  9. smellymummy

    smellymummy King

    Joined:
    Jul 31, 2002
    Messages:
    705
    i got the FingerTitle nil warning also when i tried to set up a quick test. it's probably built in another function that deals with setting up instances i just didnt bother digging to deep into it at the time
     
  10. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    It's related to what's highlighted in blue.
    Code:
    	<Instance Name="BuildingResourcesGainItem" >
    		<Container Anchor="R,C" Size="80,80" Offset="0,0"  Hidden="0" ID="BuildingResourcesGainContainer" ConsumeMouseButton="1" >
    			<SlideAnim Anchor="L,T" Style="NotificationSlide" ID="NotificationSlide" >
    				[COLOR="Blue"]<AlphaAnim Style="NewFinger" />[/COLOR]
    				<Button Anchor="C,C" Size="80,80" Offset="0,0" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" ID="BuildingResourcesGainButton"  Hidden="0" >
    
    					<ShowOnMouseOver>
    						<Image Anchor="C,C" Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" />
    						<AlphaAnim  Anchor="C,C"  AnchorSide="O.O"  Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationFrameGlow2.dds"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart="1" AlphaEnd="0" Hidden="0" />
    					</ShowOnMouseOver>
    					<Image Anchor="C,C" Offset="0,0" Size="80,80" Texture="assets\UI\Art\Notification\NotificationYellow.dds" />
    					<AlphaAnim Anchor="C,C" Offset="0,-1" Size="80,80" Texture="assets\UI\Art\Icons\BuildingAtlas512.dds" ID="BuildingImage"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart=".5" AlphaEnd="1" />
    					<Label Anchor="C,C" Offset="0,0" String="" Font="TwCenMT20" ColorSet="Beige_Black_Alpha" FontStyle="Shadow" ID="BuildingResourcesGainCount" />
    				</Button>
    
    			</SlideAnim>
    		</Container>
    	</Instance>
    
    Fingers must be what they are calling the panel entry, the block of whatever that slides down the list as you receive and remove notifications.

    It's used in the lua code here.
    Code:
    ContextPtr:BuildInstanceForControl( name.."Item", instance, Controls.SmallStack );
      g_customInstances[g_customId]  = instance;
      g_customData[g_customId] = data;
    
      local root   = instance[name.."Container"];
      local button = instance[name.."Button"];
      [COLOR="Blue"]instance.FingerTitle:SetText( summary );[/COLOR]
    
    So it appears FingerTitle isn't getting added to the instance table like root and button. I suppose if it were changed to...

    local fT = instance["FingerTitle"]; if( fT ~= nil) then fT:SetText( summary ); end

    ...but then you aren't going to get a summary line on your finger. <shrug>
     
  11. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Come to think of it, I'm betting the instance isn't being found at all. Are you concatenating "Item" to your Instance name in the xml?
     
  12. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Thalassicus, I can't find any of the files you mention in the zip.
     
  13. lemmy101

    lemmy101 Emperor

    Joined:
    Apr 10, 2006
    Messages:
    1,064
    I've had an issue with this where the notifications were not getting removed by right click or end of turn. In fact it seemed like the right click callback was never getting set. No idea what was going wrong. I did add support for icons so assumed that must have been what broke it, but after doing a diff on the original and mine and putting it back how it was, it still happened. :S

    ISDUCKSMain.lua (entry point, only called by modaction)
    Code:
    LuaEvents.CustomNotificationLeftClickCallback.Add(
    function(Id)
     
    end);
    
    LuaEvents.CustomNotificationRightClickCallback.Add(
    function(Id)
        LuaEvents.RemoveCustomNotification(Id, g_Type);
    end);
    
    table = {
    type=0,
    };
    
    LuaEvents.SetCustomNotificationDetails(table, "Generic", LuaEvents.CustomNotificationLeftClickCallback, LuaEvents.CustomNotificationRightClickCallback) 
    g_Type = table.type;
    notifyID = 0; 
    
    function ISNotification(toolTip, message,portrait, atlas)
    	LuaEvents.AddCustomNotification(notifyID, g_Type, toolTip, message, portrait, atlas);
    	notifyID = notifyID + 1;
    end
    LuaEvents.ISNotification.Add(ISNotification);
    
    Changed AddCustomNotification in NotificationPanel.lua:
    Code:
    LuaEvents.AddCustomNotification.Add(
    function(Id, type, toolTip, strSummary, forcePort, forceAt)
    
    	ForcedPortrait = forcePort;
    	ForcedAtlas = forceAt;
    	OnNotificationAdded(Id, type, toolTip, strSummary, -1, -1);
    end);
    
    As I say I removed the portrait stuff and the problem remained. :S

    Any suggestions what could be causing it?

    Thanks for the awesome tool. :)
     
  14. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    You're missing a semi-colon, but other than that, I don't see anything obvious. Can you show me the definition for LuaEvents.SetCustomNotificationDetails?
     
  15. lemmy101

    lemmy101 Emperor

    Joined:
    Apr 10, 2006
    Messages:
    1,064
    Yeah semi-colons don't actually matter I just put them in there out of habit. :D

    Code:
    LuaEvents.SetCustomNotificationDetails.Add(
    function(table, name, LeftClickCallback, RightClickCallback)
    	local newType;
    	if #g_CustomNotificationTypes == 0 then
    		newType = CUSTOM_NOTIFICATION_TYPES;
    	else
    		newType = g_CustomNotificationTypes[#g_CustomNotificationTypes] + 1;
    	end
    	g_CustomNotificationTypes[#g_CustomNotificationTypes + 1] = newType;
    
    	--print("Custom Notification set:", newType, "name:", name);
    	g_NameTable[newType] = name;
    	g_CustomLeftClickCallback[newType] = LeftClickCallback;
    	g_CustomRightClickCallback[newType] = RightClickCallback;
    
    	table.type = newType;
    end);
    
    Here it is. :) pretty sure it's not changed though. Either way that file has been completely identical to the one I downloaded from this thread with the problem still occurring, I only reverted to my iconified one when the problem persisted trying the unaltered one.
     
  16. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Ohhh... you are using Thalassicus' code. I don't think that functions. Just something he has been experimenting with. The only code I support is in the first post. ;)
     
  17. lemmy101

    lemmy101 Emperor

    Joined:
    Apr 10, 2006
    Messages:
    1,064
    How did that happen??? :D

    I think I'll do one of those things that happens at the end of an episode of an 80s sitcom....

    Thalassicuuuuuuuuuuuuuus!

    :D

    Oh well that answers that then, I think I musta saw your sig and assumed that's where it came from. :D
     
  18. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
  19. lemmy101

    lemmy101 Emperor

    Joined:
    Apr 10, 2006
    Messages:
    1,064
  20. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Welcome. :)

    Thalassicus wants to make it more modular so custom notifications don't have to be merged between mods. I'm guessing it's possible, but we haven't gotten there just yet.
     

Share This Page