Ryan F. Mercer
Whys
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:
CustomNotificationPanel.lua:
NotificationPanel.xml: (snippet)
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
_
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
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
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>
------------------------
Here are some different approaches offered by others:
- Sneaks'
- alpaca's
- Moaf's
_

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! 
