alternatives to InGame.xml

You could modify the existing UI files so they trigger your mod before triggering the save. Apart from having to keep them updated, this shouldn't generate any problems.

You could also use this: Controls.SaveButton:RegisterCallback( Mouse.eLClick, OnSave );

but I'm not sure if you can make sure your listener is executed before the one that actually saves the game.
That's a good idea!

I was looking at that, but I'm concerned about adding a modified base file to SaveUtils, as that could result in merging issues. Plus I am unsure I can catch an autosave.
I was trying to see if there are any sensible events related to saving a game, but couldn't find any. I'd also recommend attaching your save-trigger to that save button. Unfortunately you can't have two different functions registered to same mouse-event on those callbacks. It could be a compromise to save everytime the users mouse enters that button? Here's how you could accomplish that from you mod (without modifying core-files):
Code:
ContextPtr:LookUpControl("/InGame/GameMenu/SaveMenu/SaveButton"):RegisterCallback(Mouse.eMouseEnter, MyModsFunction)
 
In case someone else has the same issue:

When I was trying to remove the dependence on InGame.xml from Info Addict, I was having problems referencing context pointers for my main UI element (mainly to pop it up during leader discussions). Originally, I used ContextPtr:LookUpControl("/InGame/InfoAddictScreen") to grab the context but, once I started using InGameUIAddin exclusively, that path became invalid. I tried various different things to try to figure out the new path, such as a lot of calls to ContextPtr:LookUpControl(".."):GetID() from within my main context and trying to force it to exist beneath /InGame even when added from elsewhere. None of these worked. It seems like contexts loaded from InGameUIAddin become orphaned and cannot be referenced through LookUpControl(). There's evidence that this is the case because the code in InGame.xml saves those contexts in a local-global called g_uiAddins but it doesn't appear to be accessible from any other lua context.

After a discussion with alpaca, we figured out that I could save the context pointer in one of the super-globals (Player, Game, etc.) and reference that way from different lua contexts. Thanks alpaca for the suggestion b/c it works beautifully! :goodjob:

So, here's what I'm doing:

InfoAddictInit.lua is loaded through InGameUIAddin
Code:
-- Data collector
ContextPtr:LoadNewContext("InfoAddictCollector"); 

-- Main UI context for InfoAddict
Game.InfoAddictScreenContext = ContextPtr:LoadNewContext("InfoAddictScreen");
Game.InfoAddictScreenContext:SetHide(true);

Now I can reference the context pointer through the Game super-global. For example, from DiscussDialog.lua
Code:
-- Added by robk for InfoAddict mod, 2010.11.02
function OnInfoAddict()
  local InfoAddictControl = Game.InfoAddictScreenContext;
  UIManager:PushModal(InfoAddictControl);
end;
Controls.InfoAddictButton:RegisterCallback( Mouse.eLClick, OnInfoAddict );
-- END robk Edit

Once I got it figured out, it was a real simple solution. The next version of Info Addict (v8) will not include InGame.xml :woohoo:
 
SaveUtils' integrated ShareData functionality will allow you to share the pointer as well with the following code. A brief explination of the advantages of this approach can be found here: http://forums.civfanatics.com/showpost.php?p=9962449&postcount=96

Primarily, by standardizing this approach it becomes possible for individual mods to know where to look for data shared by other concurrent mods with greater flexibility in data retrieval. In the example I've provided below, the screen is being shared as a context-global and "InfoAddictInit" is the name of the context, providing a shared data scope specific to that lua file.

Code:
local screen = ContextPtr:LoadNewContext( "InfoAddictScreen" );
screen = share( "screen", screen, "InfoAddictInit" );
screen:SetHide( true );

Code:
function OnInfoAddict()
  local screen = share( "screen", screen, "InfoAddictInit" );
  UIManager:PushModal( screen );
end;
Controls.InfoAddictButton:RegisterCallback( Mouse.eLClick, OnInfoAddict );

Obviously, I can't force anyone to adopt this standard, but the functionality is automatically built into SaveUtils because SaveUtils uses it to share the cache as a super-global (no context), and I do believe there are advantages to having a standard for improved interoperability of concurrent mods.
 
Whys, don't be frustrated by this: I think it's not so much that people don't want to use ShareData but that they have trouble understanding it (me, for instance). You have a lot of comment text but it's mostly text walls and extremely hard to read.

For example, what the hell does
Code:
Adds boolean case for given key for given context to automatic integer-key
for given table.  Adds boolean case for given key for global context to
automatic integer-key for given table when context not given.
mean? Reading it makes my head spin and I have a fairly solid understanding of both language and logic.

The code itself is also formatted in a way that I have huge trouble reading it so when trying to understand it I always end up re-formatting it. And if I don't understand what something does I dislike using it but that's just my personal thing, of course.

I will have a look at ShareData again to see what it does, then get back to you.
 
I'm not frustrated alpaca, I'm simply advocating for what I believe is a good standard. And while that text might be difficult to read, it isn't really intended for reading, unless you intended to rewrite that function. As for it's purpose, that is pretty darn obvious, don't you think? What you didn't include in your choice of snippet above is that the name of the function is...

onHasShared( key, table, context );

Which truth be told, isn't a function you ever need to use. SaveUtils has the "easy" wrapper:
hasShared( key, context );

SaveUtils' easy wrappers allow you to ignore the fact that the share functionality is being peformed thru lua events, which have unique properties when used directly.

Basically, the point of ShareData is to be ignored. All the documentation you need is at the top of SaveUtils, including all the necessary explanation of ShareData functionality. ShareData just needs to be included, preferably as an InGameUIAddin. But that too is explained in SaveUtils.

As to answer your question, the "boolean case" means a true or a false. Anything "given" is passed as an argument. The key is used to identify the global you are checking for, and the automatic integer-key/index is the standard table.insert( tbl, item ); If you give a context, then it looks for the global by that key name for the given context. If you don't give a context, then it looks for the global by that key name in the super-global space (no context).

Basically, it asks, does this global exist within this context? Return a table containing true or false, I'll give you the table to use. The reason being it's a lua event, which can not return data, thus you must pass in a table to get the data. This is the reason the function is wrapped by SaveUtils. So you don't have to pass in the table and then extract the data. You can just call hasShared() and get a typical true or false.

But like I said, it doesn't really matter, especially the way I collapse resolved code. That code is never going to change. You shouldn't be rewriting it or picking it apart. Just use SaveUtils and it will tell you what you need to know.

Perhaps I will add something to this effect to the the OP of the ShareData page. If there is ever something people don't understand, I am more than willing to explain it. I have a thread for each component I create and I'm almost always available to respond to questions. I do apologize if I've created anything confusing.

Also, if anyone believe this standard is insufficient or could be improved, then please say so. I am also more than willing to partner with people and share credit. The purpose of all of this is to achieve the best possible coding standards for the community. Nothing less and nothing more.
 
This post will show you how to properly setup GUI buttons and popups. The most common (and easiest) way that people are setting up GUI elements, is to edit existing files such as InGame.xml and other GUI element files (such as TopPanel.lua). This is the bad way to do it, because it will create Mod incompatibility with the next modder who comes along and edits InGame.xml or the same file you're using.

The proper way separates your GUI elements from the existing core GUI stuff, so that if a player enables a mod which utilises InGame.xml (incorrectly) it does not impact on your mod. :)

Civ 5 GUI explained:
Firstly an explanation of how Civ 5's GUI works. The game's GUI works through a layered system. You have a base layer (WorldView) and then place other GUI layers on top (TopPanel, DiploList, etc). See the below diagram.



Notice how layer 1 is the main view (or WorldView as it's called in Civ 5). Then on top of that a number of other layers are placed on top to form the screen. This is how we separate our GUI elements, we create a new layer and place our GUI elements on it.

For this how to, I will demonstrate how to create a new button on the WorldView up with the diplomacy, policies and notifications buttons, and open two screens based on user interaction.

Creating a GUI layer:
The first thing one must do is create a new GUI layer. This is the foundation for letting us place GUI elements on screen, activating them correctly with player interaction, and processing commands based on the GUI element interacted with. I am then going to place a round button on the created layer for the player to interact with to open up my screens.

Code:
<?xml version="1.0" encoding="utf-8"?>
<Context ColorSet="Beige_Black_Alpha" FontStyle="Shadow" Font="TWCenMT16" >
	<LuaContext FileName="CTCGSplash" ID="CivTheCardGameSplash" Hidden="True" />
	<Container Size="80,80" Padding="0,0" Anchor="R,T" Offset="265,30" ID="CTCGButton" Hidden="1" >
		<!--=======================================================================================================================-->
		<!-- Civ The Card Game Button -->
		<!--=======================================================================================================================-->
		<Button ConsumeMouseOver="1" Anchor="C,C" Size="80,80" Offset="0,0" Texture="assets\UI\Art\Notification\NotificationFrameBase.dds" ID="CivTheCardGameButton" ToolTip="Civilization: The Card Game" 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="Art\cardsbutton.dds" />
			<!-- ACTIVE STATE -->
			<AlphaAnim  Anchor="C,C"  AnchorSide="O.O"  Offset="0,0" Size="80,80" Texture="Art\cardsbutton.dds"  Pause="0" Cycle="Bounce" Speed="1" AlphaStart="1" AlphaEnd="0" Hidden="1"/>
		</Button>
	</Container>
</Context>

Firstly you'll notice I have not defined a Grid. Grids are used to create outlined windows, but for this element I want transparency around it so that it blends into the interface. Thus I use a simple container.

The next important thing is the LuaContext line. A LuaContext is a Lua defined and controlled object. For LuaContexts these are defined in files. LuaContexts also remove the requirement to define the GUI object in the mod's Content properties as an InGameUIAddin. For this example I am using LuaContext to define the FIRST popup screen of my mod. The name of the file containing the XML definition for the GUI element is called CTCGSplash.xml, and for the LuaContext you set the FileName to the filename of the XML definition (without the extension). I have also given this LuaContext the ID of CivTheCardGameSplash, which is how Lua scripts call that object. The rest of the code defines my animated button, so that the outer rings flashes in the same way the policy and diplomacy buttons do.

The Lua scripting that sits behind this GUI layer definition is extremely simple. All we want this element to do is show in the WorldView and then call a popup window when the button is clicked on.

Code:
-- CTCGButton
-- Author: Thesh
-- DateCreated: 12/9/2010 5:34:24 AM
--------------------------------------------------------------

----------------------------------------------------------------
-- REQUIRED FUNCTIONS - DO NOT DELETE
----------------------------------------------------------------

-- Default show/hide call
function ShowHideHandler( bIsHide, bIsInit )
	Controls.CTCGButton:SetHide(false);
end
ContextPtr:SetShowHideHandler( ShowHideHandler );

-- Player click New Game button
Controls.CivTheCardGameButton:RegisterCallback(Mouse.eLClick, function()
	UIManager:QueuePopup( Controls.CivTheCardGameSplash, PopupPriority.BarbarianCamp );
end);

In the ShowHideHandler you'll see that the very simple turning on of our container is performed. The ShowHideHandler is called when the player clicks on the "Begin My Journey" button after the Dawn of Man text.

The code for the button click does one simple thing, it queues our popup into the game system, with a priority of BarbarianCamp. The element to be queued up in the system to show, is our LuaContext object. Little known fact: The priority BarbarianCamp is the highest priority, so will occur instantly. We cannot define our own priorities as yet.

So that is all to our GUI Layer! Now we must create our popups.



Creating Popups:
Creating popup windows is also similarly easy. Once again we will have an XML definition and a behind the scenes Lua script to process the actions and events. Here is the XML for the first screen of the popup:

Code:
<?xml version="1.0" encoding="utf-8"?>
<Context ColorSet="Beige_Black" Font="TwCenMT20" FontStyle="Shadow" >
	<LuaContext FileName="CTCG" ID="CivTheCardGameTable" Hidden="True" />
	<Container ID="MainSelection" Size="1000,740" Anchor="C,C" >
		<Grid Size="1000,740" Anchor="C,C" Offset="0,0" Padding="0,0" Style="Grid9DetailFive140" >
			<Label Anchor="C,T" Offset="0,15" Font="TwCenMT24" ColorSet="Beige_Black_Alpha" FontStyle="Shadow" String="Civilization the Card Game"  />
			<Label Anchor="C,T" Offset="0,60" Font="TwCenMT18" ColorSet="Beige_Black_Alpha" FontStyle="Shadow" String="A game by Soren Johnson and Dale Kent"  />
			<Image Anchor="C,C" Offset="0,0" ID="Splash" Texture="splash.dds" Size="500,360" />
			<Stack Anchor="L,C" Offset="0,0" Padding="12" StackGrowth="Bottom"  ID="MainMenuStack" >
				<GridButton Style="BaseButton"  ID="OKButton" Size="200,40" Anchor="C,C" Offset="20,0"  String="New Game" />
				<GridButton Style="BaseButton"  ID="InstructionButton" Size="200,40" Anchor="C,C" Offset="20,0"  String="Instructions" />
				<GridButton Style="BaseButton"  ID="WPCButton" Size="200,40" Anchor="C,C" Offset="20,0"  String="Visit WePlayCiv.com" />
				<GridButton Style="BaseButton"  ID="BackButton" Size="200,40" Anchor="C,C" Offset="20,0"  String="Back" />
			</Stack>
		</Grid>
	</Container>
</Context>

This time we are loading a window (a Grid is defined) which will contain a DDS image and four buttons. These four buttons will do various things, but the two we will focus on for this example are the "New Game" and the "Back" buttons. Also note the LuaContext near the top of the definition which is pointing to the second screen of the popup, a file called CTCG.xml with a Lua definition of CivTheCardGameTable.

And here is the backend Lua script for this popup window:

Code:
-- CTCGSplash
-- Author: Thesh
-- DateCreated: 12/9/2010 7:28:49 AM
--------------------------------------------------------------

----------------------------------------------------------------
-- Includes
----------------------------------------------------------------
include( "IconSupport" );
include( "InstanceManager" );

----------------------------------------------------------------
-- Globals
----------------------------------------------------------------

----------------------------------------------------------------
-- REQUIRED FUNCTIONS - DO NOT DELETE
----------------------------------------------------------------

-- Default show/hide call
function ShowHideHandler( bIsHide, bIsInit )
end
ContextPtr:SetShowHideHandler( ShowHideHandler );

-- Default "OnBack" destructor for this popup window
function OnBack()
	UIManager:DequeuePopup( ContextPtr );
end

-- This is the default key mapping function.  We only need to capture
-- the ESC key being pressed and map it to OnBack to exit the screen.
ContextPtr:SetInputHandler( function(uiMsg, wParam, lParam)
    if uiMsg == KeyEvents.KeyDown then
        if wParam == Keys.VK_ESCAPE then
			OnBack();
        end
    end
    return true;
end);

-----------------------------------------------------------------
-- Process the main menu buttons
-----------------------------------------------------------------

-- Player clicked the Back button
Controls.BackButton:RegisterCallback(Mouse.eLClick, function()
	UIManager:DequeuePopup( ContextPtr );
end);

-- Player click New Game button
Controls.OKButton:RegisterCallback(Mouse.eLClick, function()
	UIManager:QueuePopup( Controls.CivTheCardGameTable, PopupPriority.BarbarianCamp );
end);

This script has more content, due to the buttons on the screen. The first button we examine is the Back button. When the player clicks the Back button we want the popup to close and return to the WorldView. And so it is with the OKButton ("New Game" button) when the player clicks on it we want the second screen of our popup to open. So in the Lua script we make a call to QueuePopup to make a popup show on screen, and we make a call to DequeuePopup to close it again.

The ShowHideHandler is completely empty because we do not want to process anything when this window is opened/closed, we simply want to view it.



Mod Properties:
You may now be thinking that all you need to define is LuaContexts to call your GUI elements. This is correct for child GUI elements on your layer, but how do you actually tell the game to load your initial GUI element (the WorldView button in this example). Well, for the inital, base level, parent GUI element on your layer, you must still define a InGameUIAddin on the Content tab of the mod properties window in ModBuddy. But at least now you only need to define your parent elements instead of every single element, and you no longer need to hack InGame.xml.

Closing.....
So that is all there is to creating proper GUI elements in Civ 5. By using this method not only do you retain separate control from the rest of the Civ 5 GUI, but no other mod can interfere with your GUI elements. :)
 
Good stuff, but: What do you do if you want to change a UI element rather than add one?
 
Good stuff, but: What do you do if you want to change a UI element rather than add one?

I'm checking with someone. Sorry if it takes a bit of time though.

I believe there's ways to interact with controls in other GUI elements, but not take control of another GUI element. IE: Controls.ControlFromOtherElement:AddButton().

It will probably come down to scope though, and some convoluted way to actually do it.
 
I'm checking with Shaun Seckman. Sorry if it takes a bit of time though.

I believe there's ways to interact with controls in other GUI elements, but not take control of another GUI element. IE: Controls.ControlFromOtherElement:AddButton().

It will probably come down to scope though, and some convoluted way to actually do it.

To be honest I doubt there's a sensible way to do that without editing vanilla files. For example if you want the money tooltip in TopPanel to be modular you'd have to create something like an event where your client software can tell TopPanel to execute a function and add the return value to the tooltip every time it displays. Anyways, I'm not sure it's really worth the effort of putting those hours in.
 
Dale, with regards to <LuaContext FileName="CTCGSplash" .../>

CTCGButton.lua is loaded as an InGameUIAddin and works, but the lua console gives me a file load error for CTCGSplash.lua, unless I place that file in the base game directory. Am I doing something wrong? Do I also need to add CTCGSplash to the Content tab?

With the new patch, I found out that I had to fully qualify file names in InGameUIAddin based on the directory structure in my mod. Whereas before, I just put in "file.lua", I have to put in "Lua/file.lua" for it to find the file.
 
With the new patch, I found out that I had to fully qualify file names in InGameUIAddin based on the directory structure in my mod. Whereas before, I just put in "file.lua", I have to put in "Lua/file.lua" for it to find the file.

I'm not using any directory structure in modbuddy, just the 4 files at the root (CTCGButton.lua, CTCGButton.xml, CTCGSplash.lua, CTCGSplash.xml), and only one of those is being added in the content tab: CTCGButton.lua. It appears to be added properly with the button showing up in the GUI. It just can't seem to find CTCGSplash.lua. Then when I move CTCGSplash.lua and xml to C:\Program Files (x86)\Steam\SteamApps\common\sid meier's civilization v\Assets\UI\InGame, it finds them just fine.
 
How is the "import into VFS" attribute set on those files? I would try setting that to true on CTCGSplash.* if it is currently false.

<HUGS> :)

I don't know what that "import into VFS" is all about, but I know I never would have figured that out on my own.

I had to set it to "true" for both CTCGSplash.lua and CTCGSplash.xml.
 
<HUGS> :)

I don't know what that "import into VFS" is all about, but I know I never would have figured that out on my own.

I had to set it to "true" for both CTCGSplash.lua and CTCGSplash.xml.

Adds a file to the virtual file system. This can only have one file with the same name so they made an option for it. If you leave it false, it can mesh modular files with the same name together (like xmls that add things).
 
Top Bottom