BULL: Mod Options

EmperorFool

Deity
Joined
Mar 2, 2007
Messages
9,633
Location
Mountain View, California
I'm adding a new feature to BULL now that will both solve an existing problem (OOS when players have different Pre-Chop and/or Sentry Healing settings) and provide a new capability (options that have a single setting shared among all players in MP games). Special thanks to Afforess for the idea for the shared options and being a sounding board for the design.

I'm seeking feedback from any modders that might make use of this--or anyone else with an interest for that matter.

  • There are some names that I'm not totally happy with (e.g. sync) for which I'd love some suggested alternatives.
  • Does anyone foresee any potential problems with this?
  • Is the limit of bool and int option values going to be too severe?
  • What have I not anticipated?
Step 1: Add CvModOptionInfo [complete]

This new info class has four fields:

  • shared - boolean, true means one value for all players, controlled by player 0
  • default - int, 0/1 for a boolean option, float and strings not supported
  • option ID - string, used to look up the option in BUG
  • define name - string, used to look up the value in GlobalDefinesAlt.xml when BUG isn't present.
To create a new option, modders need to add its name to CvEnum.h and a <ModOptionInfo> entry to CIV4ModOptionInfos.xml.

Code:
[B]// CvEnum.h[/B]
MODOPTION_PRECHOP_FORESTS,

[B]<!-- CvModOptionInfos.xml -->[/B]
<ModOptionInfo>
	<Type>MODOPTION_PRECHOP_FORESTS</Type>
	<Description>TXT_KEY_MOD_OPTION_PRECHOP_FORESTS</Description>
	<Help>TXT_KEY_MOD_OPTION_PRECHOP_FORESTS_HELP</Help>
	<bShared>0</bShared>
	<iDefault>1</iDefault>
	<OptionId>Actions__PreChopForests</OptionId>
	<DefineName>BUG_PRECHOP_FORESTS</DefineName>
</ModOptionInfo>

Obviously the <Description> and <Help> elements reference <Text> entries in a CIV4GameText.xml file. The help probably isn't needed but better to add it now than need it later.

Step 2: Store Settings [complete]

CvPlayer and CvInitCore will both get an int array of option values. Each class will have similar accessors for getting and setting the values.

Step 3: Synchronize Settings [complete]

I will add a new net message to BULL for sending an option value to all players. This will need to happen for all options when a game is started/loaded and whenever an option is changed by any player.

Shared options: Only player 0 will be able to change shared options in the BUG Options screen. While they will be grayed out for other players in MP games, they will see the up-to-date value and get an event message whenever it is changed.

EmperorFool has turned on Global Warming
EmperorFool has changed Global Warming Chance to 15​

Step 4: Add BUG Support

The <option> and <list> elements will gain two new optional attributes:

  • sync - string, the enum name minus the MODOPTION_ prefix
  • shared - bool, I don't think this is needed anymore since it's in the info
Code:
<option id="PreChopForests" key="Pre Chop Forests" 
        type="boolean" default="True"
        [B]sync="PRECHOP_FORESTS" shared="False"[/B]/>

BUG likely needs just one function to set the value in BULL on CyGlobalContext. BULL will use the info class to pick where to store the value locally and how to send the message.

BULL needs a function to all it to push a new shared option setting to BUG when it receives it in a net message.

BugOptionsScreen needs to know to disable a shared option when the active player isn't the host (player 0).

Important: Works with BULL alone but not BUG alone.

BUG is only involved to allow the settings to be changed easily via the BUG Options screen. Other than that this feature is handled entirely in BULL: storing, syncing, etc. This means that mods based on BUG without BULL will not be able to make use of this feature. This is probably the one thing that could bite me on the ass.

Is this too restrictive?

With some work I could implement this in BUG as well as a fallback for when BULL isn't available, but that's not very appealing. I've got a lot of other things I could be working on. Does anyone have a case where they'll need this feature in BUG alone?
 
Looks perfect for my needs, and from what I'm aware of RevDCM, all their options are simple boolean values, so this will work too. The only real downside I see is that this is going to suck for non-SDK modders. I guess we could add a couple Modder Options that are unused, just for that case, in the SDK for them. (3? 5?).

Has CvModOptionInfo actually been completed, or do you need me to add it?
 
CvModOptionInfo is done. I'm adding the accessors in CyGlobalContext and found something odd. There are two functions for CvPlayerInfo declared in CyGlobalContext.h: getPlayerOptionInfo() and getPlayerOptionInfoByIndex(), yet only the latter has a definition. The DLL should not compile AFAICT.

That's a good point about non-SDK modders. I was originally thinking that only the DLL would need synced options, but I suppose someone could write Python code that needs them. One idea is to have BUG define any missing ModOptionTypes from the configuration XML. Then when it passes the new value to BULL after being changed in the options screen, BULL would send the message but only store the option if its ModOptionTypes is less than NUM_MODOPTION_TYPES.

So you define two options in the DLL: MODOPTION_FOO and MODOPTION_BAR. NUM_MODOPTION_TYPES would be 2. Now in a config XML for BUG you give an <option> the sync attribute "BAZ". BUG sees there is no definition for BAZ so it creates it as 2 (current values are 0 and 1, so 2 is next). When BUG sends this to BULL, it seems that 2 is not < NUM so it doesn't try to set it internally.

This only makes sense for shared options since BUG will only store one value per option--not per player.

Hmm, no this won't work. How does the SDK currently allocate the space for buildings where it doesn't know in advance how many there will be? For player options (and thus mod options since I copied that code), it uses NUM_OPTION_TYPES enum for the number of options. Adding more in the XML would cause it to write past the end of the arrays.

Yes, we could provide space for 2 or 3 extras, but I'd rather make it work for any number than a set few. That's the whole reason I'm doing this in the first place: the two extra player option types weren't enough for BULL.
 
If we load options just like buildings, their <Type>s would be available via getInfoTypeForString() which BUG could use to acquire the ModOptionTypes value. SDK modders would define the ones they need to reference from code, and Python modders would be free to add more to the end of the XML file.

BULL will store them all whether or not they have a defined ModOptionTypes enum and make their values available to BUG--both shared and normal options.

This won't solve the HotSeat problem, but it will allow mods to get the values of some options of the other players if they need to, just as the SDK can.
 
If you can add more with just the <Type> element, why even use the enum, other than for NO_MODOPTIONTYPE = -1?
 
You don't have to use it, but if you're modding the SDK already you may as well define an enum for your new options to avoid calling getInfoTypeForString() every time you need the option.
 
Does getInfoTypeForString consume significant amounts of time?
 
No, it uses a hashtable which provides constant-time lookup, but this is still slower than not doing the lookup. ;) It's also nicer to read in code:

Code:
if (GC.getGameINLINE().getModOption(MODOPTION_PRECHOP_FORESTS))

vs.

Code:
if (GC.getGameINLINE().getModOption(GC.getInfoTypeForString("MODOPTION_PRECHOP_FORESTS")))

And the only cost is typing this in CvEnum.h:

Code:
MODOPTION_PRECHOP_FORESTS,
 
True, True. And it will work with GetInfoTypeForString as well then? I guess I can't argue against the best of both worlds. ;)

So...

How can I help?
 
Good question. I have the infos being read in and exposed to Python which turned out to be much less work than I expected. The next part is the message passing. I think testing will probably be the best way to help pretty soon.
 
Okay, so how will this work with PitBoss and Play-by-Email games? In these cases not all players are connected at the same time, and these mod options will not be saved in the game. I suspect that the settings will be controlled by whomever is active at the time.

For example, if you have Global Warming disabled while I have it enabled, the setting will bounce back and forth as we take our turns. I see no way around it short of storing the settings in the saved game.
 
I've completed all the BULL work save some minor tweaks. I'll pick it up again tomorrow and may even be able to commit.
 
Okay, so how will this work with PitBoss and Play-by-Email games? In these cases not all players are connected at the same time, and these mod options will not be saved in the game. I suspect that the settings will be controlled by whomever is active at the time.

For example, if you have Global Warming disabled while I have it enabled, the setting will bounce back and forth as we take our turns. I see no way around it short of storing the settings in the saved game.

Since you want to avoid adding read/write calls to the SDK, why not save all the values as scriptdata in python? Then, if you load a multiplayer game up, and aren't the host, load the game-wide values from python, and the rest from the ini's.
 
To be 100% effective I'd need to store every player's options as well as the shared game options.

Another thing I just thought of is reconnecting players. Whenever a human disconnects their player switches to AI. BULL will handle this fine, but when the human reconnects BULL will need to sync up their options. However, I don't know of any MP-specific events that I can intercept. Obviously the game must handle this for its own PlayerOptionTypes, but that's taken care of by the EXE I think.
 
To be 100% effective I'd need to store every player's options as well as the shared game options.

Another thing I just thought of is reconnecting players. Whenever a human disconnects their player switches to AI. BULL will handle this fine, but when the human reconnects BULL will need to sync up their options. However, I don't know of any MP-specific events that I can intercept. Obviously the game must handle this for its own PlayerOptionTypes, but that's taken care of by the EXE I think.

Fire an event when void CvPlayer::updateHuman() is called. It is only called in MP games when a computer takes over or a human takes over a computer slot.
 
@EF

Silly question time.

If I'm reading this right, if the new mod option is used by a modder, the control mechanism would be able to be accessed through the BUG option screens? Like how you can turn pre-chop on and off in a check box? (With the appropriate code, of course.)

Can this also be used in a single player context?

If so, I love you so much right now. :D
 
Lemon,

I believe the intent is to make it work without the BUG Options Screen, if for some reason, someone was using BULL w/o BUG. But yes, to answer your question, these are intended for the Bug Options Screens.

And Yes, these will work in SP just like normal.
 
Wonderful!

I love you both right now. :)

Back to the Python editing for me...
 
Thanks for that function, Afforess. That's exactly what I needed. Load and start game I can find easily since they have events I can search for, but updateHuman() sounds like the ticket for the third sync trigger.

Lemon, to clarify these options will look in BUG just like normal options. In single-player games you won't see any difference. In MP games the shared options will be active for the host player only, and whatever changes they make on the BUG Options screen will appear in other players' screens. Shared options are for things that affect the entire game such as Global Warming.

Non-shared options will also be sent to all players' PCs, but every player will have their own setting stored on every other players' PC. This is needed because every PC performs all actions individually. When you give an action to a unit, the action is sent to every PC, but it's the PCs that perform the action.

This will solve the "my worker on my PC stops chopping with 1 turn left because I have Pre-Chop on, but it keeps chopping on your PC because you have Pre-Chop off, and we go OOS" problem.

When I talk about working without BUG, I just mean that the underlying option-syncing mechanism doesn't require BUG to function. Just as with BULL today, without BUG you won't have a UI to change options in the game, but you can still set them in GlobalDefinesAlt.xml.

Thanks for the love!
 
Let's look at the API I've landed on. Python gets three new functions:

  1. CyGlobalContext.getNumModOptionInfos() -> int
  2. CyGlobalContext.getModOptionInfo(int iOption) -> CvModOptionInfo
  3. CyGlobalContext.setModOption(int iOption, int iValue) - bool (True if it was set)
Now, you may be wondering about #3. Why is there a single function for two option types (shared and player)? This function doesn't actually set the option value. Instead if sends one of two messages based on the shared field of the option. In the case of a player option, it sends the active player along.

CvGame and CvPlayer both define g/setModOption(), but they are not exposed to Python. These latter functions set the option value. I didn't expose them to Python because setting them directly won't broadcast the change to the other PCs; you'll get an OOS. I'm half-tempted to expose them anyway to allow for OOS testing, but is that asking for trouble?
 
Top Bottom