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

Joined
Oct 20, 2007
Messages
515
Location
Spokane, WA
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

_
 
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 registerNotification( name, addFunc, leftClick, rightClick )
	g_maxID = g_maxID + 1;
    g_customNotificationTable[ g_maxID ]["name"]		= name;
    g_customNotificationTable[ g_maxID ]["addFunc"]		= addFunc;
	g_customNotificationTable[ g_maxID ]["leftClick"]	= (type(leftClick) == "function") and leftClick or customLeftClick;
    g_customNotificationTable[ g_maxID ]["rightClick"]	= (type(rightClick) == "function") and rightClick or customRightClick;
end

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

	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();

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

	button:SetHide( false );
	button:SetVoid1( g_customId );
	button:RegisterCallback( Mouse.eLClick, g_customNotificationTable[iType]["leftClick"] );
	button:RegisterCallback( Mouse.eRClick, g_customNotificationTable[iType]["rightClick"] );
	button:SetToolTipString( toolTip );

	g_customActiveNotifications[g_customId] = iType;
	ProcessStackSizes();
end
LuaEvents.CustomNotification.Add( onCustomNotificationAdded );
 
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. :)
 
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.
 
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?
 
My head hurts less already! :D

Lua takes some getting used to, but I'm finding I really like it. It's like PHP and JavaScript had a baby.

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?

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.

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.

Sounds great. :)

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?

Let me know when you've answered these questions. :]
 
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.
 
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()} }
);

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?
 
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
 
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>
 
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?
 
Thalassicus, I can't find any of the files you mention in the zip.
 
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. :)
 
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?
 
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.
 
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. ;)
 
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.
 
Back
Top Bottom