diff --git a/Assets/Text/cqui_text_notify.xml b/Assets/Text/cqui_text_notify.xml
new file mode 100644
index 0000000..254bb5d
--- /dev/null
+++ b/Assets/Text/cqui_text_notify.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ {1_cityname} has grown!
+
+
+ {1_cityname} has shrunk!
+
+
+ {1_cityname}'s territory has grown!
+
+
+
diff --git a/Assets/cqui_toplayer.lua b/Assets/cqui_toplayer.lua
index eb2c2dd..fc75db6 100644
--- a/Assets/cqui_toplayer.lua
+++ b/Assets/cqui_toplayer.lua
@@ -1,7 +1,209 @@
--This is a layer that lives at the very top of the UI. It's an excellent place for catching inputs globally or displaying a custom overlay
+-- This layer is the home of the paradox bar
+include("InstanceManager");
+include("supportfunctions");
+
+--Members
+local groupStackIM = InstanceManager:new("ParadoxBarGroupInstance", "Top", Controls.ParadoxBarStack); -- IM for the groups for the notifications
+local groupStackInstances: table = {}; --Instances of the groupings for the notifications
+local groupStacks: table = {}; --Instance managers for the individual notifications
+
+--These following tables define the various behaviors a notification can contain, modifying a given effect will affect ALL notifications which use them.
+--Feel free to add new behaviors here as necessary
+
+--Paradoxbar sound
+local paradoxBarSound = {
+ ["addSound"] = function() UI.PlaySound("NOTIFICATION_MISC_NEUTRAL"); end,
+ ["removeSound"] = function() UI.PlaySound("Map_Pin_Remove"); end
+}
+
+--Paradoxbar functions
+local paradoxBarFuncs = {
+ ["deleteOnRMB"] = function(instance, group)
+ instance.Button:RegisterCallback(Mouse.eRClick, function() RemoveNotification(instance, group); end);
+ end,
+ ["goToCity"] = function(instance, _, props)
+ instance.Button:RegisterCallback(Mouse.eLClick, function()
+ local city = props["city"];
+ UI:SelectCityID(city:GetID());
+ UI.LookAtPlotScreenPosition( city:GetX(), city:GetY(), 0.5, 0.5 );
+ end);
+ end,
+ ["debugPrint"] = function() print("Debug notification created"); end
+}
+
+--Paradoxbar behavior bundles
+local paradoxBarBundles = {
+ ["standard"] = function(instance, group) paradoxBarFuncs["deleteOnRMB"](instance, group); paradoxBarSound["addSound"](); end
+}
+
+--These are the default notification templates
+--Feel free to add to this as necessary
+
+--Paradoxbar stock data
+local paradoxBarStock = {};
+function NewTemplate(name, data, base)
+ local merged = {};
+ if(not data) then return end --There must be something here to differentiate from the base
+ if(paradoxBarStock[base]) then --If there's a base to work with, apply it first
+ merged = DeepCopy(paradoxBarStock[base]); --Copy the table instead of mangling the original
+ end
+ for k,v in pairs(data) do merged[k] = v; end --Overriding base values with given data table
+ paradoxBarStock[name] = merged; --Adding to stock table
+end
+NewTemplate("debug", {
+ ["group"] = "Debug", ["icon"] = "Controls_Circle", ["tooltip"] = "This is a debug tooltip", ["text"] = "Dbg", ["funcs"] = {paradoxBarFuncs["debugPrint"], paradoxBarBundles["standard"]}
+});
+NewTemplate("debug2", {
+ ["group"] = "Debug2", ["text"] = "Dbg2"
+}, "debug");
+NewTemplate("cityGrowth", {
+ ["group"] = "CityGrowth", ["icon"] = "ICON_CITIZEN", ["iconColor"] = "Green", ["tooltip"] = "LOC_CQUI_CITYGROWTH", ["funcs"] = {paradoxBarBundles["standard"], paradoxBarFuncs["goToCity"]}
+});
+NewTemplate("cityShrink", {
+ ["iconColor"] = "Red", ["tooltip"] = "LOC_CQUI_CITYSHRINK"
+}, "cityGrowth");
+
+-- This function handles adding new notifications to the paradox bar
+-- If an ID is supplied, paradoxBarStock is checked for a matching ID and loads presets if available. If additional values are supplied, they are used to overwrite the default values
+-- props is a table containing properties for overriding the defaults supplied by the template selected using the ID, you can also use this table for defining arbitrary parameters for use with attatched functions
+ -- group is used for the purposes of chunking notifications together and is mandatory, though, usually supplied by a preset, if employed
+ -- icon is the name of the texture/icon to be used. Not defining this will leave only a background portrait texture. This can also be a table with the desired X/Y offset values
+ -- noIconStretch is an optional true/false value which can be used to disable icon/texture scaling, forcing the given image to display in original size. Default behavior is as if this were set to false
+ -- iconColor is an optional value used to set the color hue of the icon element. See the in game use of ":SetColor" for examples of valid values
+ -- tooltip is the string to be used describing the notification in detail. Please add an LOC string to cqui_text_notify if you need to employ a new string
+ -- ttprops is a table of additional values to be injected into the LOC string. Limit of 5 parameters. See cqui_text_notify for examples concerning working with inserting into LOCs
+ -- text is drawn directly on top of the icon and is meant to be used lightly. It will look ugly if you use any more than a few characters
+-- funcs is an array of functions used for adding behavior to the notification. This is where closing behavior and other intricacies, like sound, are defined
+function AddNotification(ID, props, funcs)
+ local defaults = DeepCopy(paradoxBarStock[ID]); --Copy by value instead of by reference, so as not to mangle the real defaults table
+
+ --If no property table was provided at all, create an empty one
+ if(not props) then
+ props = {};
+ end
+
+ --Merges defaults with overrides supplied via props
+ if(defaults) then
+ if(defaults["group"] and not props["group"]) then
+ group = defaults["group"];
+ defaults["group"] = nil;
+ end
+ if(defaults["funcs"] and not props["funcs"]) then
+ funcs = defaults["funcs"];
+ defaults["funcs"] = nil;
+ end
+ for k,v in pairs(defaults) do props[k] = v; end
+ end
+
+ -- Group must be defined somehow or else we give up here
+ if(not group) then return; end
+
+ --Creates a group for a specific notification group if it does not exist
+ if(groupStackInstances[group] == nil) then
+ groupStackInstances[group] = groupStackIM:GetInstance();
+ groupStacks[group] = InstanceManager:new("ParadoxBarInstance", "Top", groupStackInstances[group].Stack);
+ --Binds the edit slideout button
+ local button = groupStackInstances[group].EditButton
+ local anim = groupStackInstances[group].EditAnim
+ button:RegisterCallback(Mouse.eMouseExit, function()
+ anim:Reverse();
+ end);
+ button:RegisterCallback(Mouse.eMouseEnter, function()
+ if(not anim:IsReversing()) then
+ anim:Reverse();
+ end
+ end);
+ end
+ local instance = groupStacks[group]:GetInstance();
+
+
+ --Applies properties.
+
+ --If a table is supplied instead of a string, treat the 2nd and 3rd values as texture offsets
+ if(props["icon"]) then
+ local iconElement;
+ if(props["noIconStretch"]) then
+ iconElement = instance.IconNoStretch;
+ else
+ iconElement = instance.Icon;
+ end
+ if(props["icon"][2] and props["icon"][3]) then
+ iconElement:SetTexture(props["icon"][2], props["icon"][3], props["icon"][1]);
+ else
+ --If we didn't provide an offset, there's no easy way to tell if the supplied string will refer to an icon or a texture. In this scenario, we just try both methods since it's a relatively cheap thing to fail at.
+ iconElement:SetIcon(props["icon"]);
+ iconElement:SetTexture(props["icon"]);
+ end
+ if(props["iconColor"]) then
+ if(type(props["iconColor"]) == "string") then
+ iconElement:SetColorByName(props["iconColor"]);
+ else
+ iconElement:SetColor(props["iconColor"]);
+ end
+ end
+ end
+
+ if(props["tooltip"]) then
+ if(not props["ttprops"]) then
+ props["ttprops"] = {};
+ end
+ instance.Button:LocalizeAndSetToolTip(props["tooltip"], props["ttprops"][1] or "X", props["ttprops"][2] or "X", props["ttprops"][3] or "X", props["ttprops"][4] or "X", props["ttprops"][5] or "X");
+ end
+ if(props["text"]) then
+ instance.Text:LocalizeAndSetText(props["text"]);
+ end
+ --Invokes functions
+ if(funcs) then
+ for _,v in ipairs(funcs) do
+ v(instance, group, props);
+ end
+ end
+ --Check if this asset has been used before and, if so, fix any mutable states that may have persisted
+ if(instance.Top:GetID() == "Exhausted") then
+ instance.Top:SetID("NotExhausted");
+ instance.AlphaAnimation:RegisterEndCallback(function() end);
+ instance.AlphaAnimation:SetToBeginning();
+ end
+ --Reveals completed notification
+ instance.Top:SetHide(false);
+ instance.AlphaAnimation:Play();
+end
+
+function RemoveNotification(instance, group)
+ instance.AlphaAnimation:RegisterEndCallback(function()
+ groupStacks[group]:ReleaseInstance(instance);
+ --This is a hacky workaround since releasing an instance doesn't necessarily actually remove it from a given stack
+ instance.Top:SetID("Exhausted");
+ for _,v in ipairs(groupStackInstances[group].Stack:GetChildren()) do
+ if(v:GetID() ~= "Exhausted") then return; end
+ end
+ --Removes the group stack from the main notification stack
+ Controls.ParadoxBarStack:ReleaseChild(groupStackInstances[group].Top);
+ groupStackInstances[group] = nil;
+ end)
+ instance.AlphaAnimation:Play();
+end
function Initialize()
+ groupStackIM:ResetInstances();
+ LuaEvents.CQUI_AddNotification.Add(AddNotification);
ContextPtr:SetHide(false);
+
+ --Implementing city population change
+ Events.CityPopulationChanged.Add(
+ function(playerID : number, cityID : number, newPopulation : number)
+ if(not Game:GetLocalPlayer()) then return end
+ if(playerID == Game:GetLocalPlayer()) then
+ local city = Players[playerID]:GetCities():FindID(cityID);
+ if(city:GetGrowth():GetFoodSurplus() < 0) then --This isn't a perfect heuristic, but the game doesn't make it easy to tell between a city that just grew and a city that just shrunk
+ AddNotification("cityShrink", {["ttprops"] = {city:GetName()}, ["city"] = city});
+ else
+ AddNotification("cityGrowth", {["ttprops"] = {city:GetName()}, ["city"] = city});
+ end
+ end
+ end
+ )
end
Initialize();
diff --git a/Assets/cqui_toplayer.xml b/Assets/cqui_toplayer.xml
index 7b4b8c2..406c4fa 100644
--- a/Assets/cqui_toplayer.xml
+++ b/Assets/cqui_toplayer.xml
@@ -1,7 +1,60 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cqui.modinfo b/cqui.modinfo
index c482054..0266d04 100644
--- a/cqui.modinfo
+++ b/cqui.modinfo
@@ -110,6 +110,7 @@
Assets/Text/cqui_text_general_pl.xmlAssets/Text/cqui_text_general_ru.xmlAssets/Text/cqui_text_general_zh.xml
+ Assets/Text/cqui_text_notify.xmlAssets/Text/cqui_text_settings.xmlAssets/Text/cqui_text_settings_de.xmlAssets/Text/cqui_text_settings_fr.xml
@@ -298,6 +299,7 @@
Assets/Text/cqui_text_general_pl.xmlAssets/Text/cqui_text_general_ru.xmlAssets/Text/cqui_text_general_zh.xml
+ Assets/Text/cqui_text_notify.xmlAssets/Text/cqui_text_settings.xmlAssets/Text/cqui_text_settings_de.xmlAssets/Text/cqui_text_settings_fr.xml