LoneGazebo / Community-Patch-DLL

Community Patch for Civilization V - Brave New World
Other
280 stars 155 forks source link

On Mercenaries #1679

Closed JFDaly closed 7 years ago

JFDaly commented 8 years ago

I am here with a rundown of what the Mercenaries system would need, if you are still willing and able to incorporate it into the DLL sometime (in response to the issues that I have with TableSaverLoader).

Essentially, the Mercenaries system is a range of 'Contracts' that can be taken out by the player, exchanging Gold for a limited ownership of a particular unit (or units). Contracts are global - only one player can take one Contract at a given time, and they each cost Maintenance. Some also provide resources, to compensate for what would be deficiency otherwise when you contract Mounted Units or units requiring Iron/Oil, etc.

At the moment, I have 1 dummy policy per Contract, which I thought might increase stability (alleviating how much info is cached), but this appears not to be the case - so they're just needlessly cluttering up the database, pushing to that ~256 limit.

The following attributes are variable, and need to be cached: the player that has taken out the contract, the turns remaining on the contract, the maintenance of the contract, the unit assigned to the contract and any associated resources. I do wonder, however, if it would be possible to cut out the cached resources with some sort of unit:SetHasNoResourceRequirement(boolean) function - something to override the unit's resource requirement.

I have a table (JFD_Contracts) set up which stores the contracts in the database. So any functions would need to refer to the IDs of those entries. I consider the following functions to be what I'd need:

Game.SetContractOwner(contractID, playerID) -- would need to support playerID being set to nil. Game.GetContractOwner(contractID) -- returns playerID from the above. Game.SetContractUnit(contractID, unitID) Game.GetContractUnit(contractID) -- returns unitID) from the above Game.SetContractTurns(contractID, value) Game.GetContractTurns(contractID) -- returns value) from the above. Game.SetContractMaintenance(contractID, value) Game.GetContractMaintenance(contractID) -- returns value) from the above

GameEvents.ContractStarts(playerID, contractID)

And If nullifying the resource requirement of specific unit objects is not possible:

Game.SetContractResources(contractID, resourceID, value) Game.GetContractResources(contractID) -- returns resourceID and resource count from the above.

The following things are non-essential, and can be re-created in Lua, but would be convenient:

Game.IsContractActive(contractID) -- checks the playerID of Game.GetContractOwner() and, if null, returns false. Player:HasContract(contractID) -- checks if Game.GetContractOwner() equals this player's ID. GetContractTurns(contractID) should change SetContractTurns each turn by an established Game Define (which would only ever be 1, so could be hardcoded).

GameEvents.ContractEnds.Add(playerID, contractID) -- would only be needed if the turn time decay is automatically set up, and would trigger when that turn time reaches 0.

I'm not asking this to be worked on any time soon, of course, but let me know your thoughts on how possible this looks.

LoneGazebo commented 8 years ago

Shouldn't be too bad, though we'll have to define a set # of contract IDs for the game (for sizing the model). That will require a struct, which would be built on the specs of your JFD_Contracts table. Make sense?

JFDaly commented 8 years ago

Probably, but not to me. Just let me know what you'd need from me.

LoneGazebo commented 8 years ago

What's the structural nature of your JFD_Contracts table?

i.e.

Type Description Help Cost

etc.

JFDaly commented 8 years ago

This is the table. The cost (and Maintenance) are established according to the units on offer. EDIT: I don't use all of it though. Is it significant?

LoneGazebo commented 8 years ago

I'll be working on this soon enough. In the meantime, latest beta has your other requests (sovereignty yield and the other things you asked for).

JFDaly commented 8 years ago

Awesome, thanks. Take your time - plenty for me to do in the meantime.

LoneGazebo commented 8 years ago

@JFDaly, sorry for the delay, however I'm going to start working on this tomorrow. Any specific things you need aside from what was posted above? AI support, etc.?

JFDaly commented 8 years ago

No worries, take your time still.

I'm not sure how AI support would work - currently, its simulated, that is, there is a random chance for the AI to take out a Mercenary contract (randomly chosen from those that they can afford), provided they're at war and have at least 5 maintenance (as contracts cost maintenance), and have an army smaller than the human player's. This chance is based upon a Mercenary flavour (FLAVOR_JFD_MERCENARY). There is also a cap on the number of contracts the AI can take out.

AI support would be cool, but I'm not sure how it would function. But ideally wealthy civs would want to take out a Mercenary Contract rather than produce or purchase a unit, depending upon their Mercenary flavour. But it might be too complex; the current simulation works well enough.

LoneGazebo commented 7 years ago

Alright, the core functionality is in place. I notice you have values like 'mutiny chance' and 'disband chance.' Are those functions you would like me to push to the DLL? Or do you need lua hooks for those?

JFDaly commented 7 years ago

Nah, I think they work best in Lua, as they are. These merely come into play when the player has negative Gold per turn (and so can't pay the maintenance costs on Contracts). Each turn whilst in this state, there is a random chance for the Mercenaries from that Contract to disband or mutiny (depending upon the quality of the contract; mutiny being to turn into Barbarians), but I handle this efficiently enough.

LoneGazebo commented 7 years ago

Alrighty. How do you want these to hook into the DLL? Just by grabbing the data for a player's active contract manually? Also, I assume one contract at a time per player, yes?

Just to note, a lot of the lua commands you asked for (i.e. SetContractTurns) will be automated based on the XML data. - you should just be able to drop the data into the table and the DLL will do the rest for you.

LoneGazebo commented 7 years ago

Do you still need access to these, even if automated?

LoneGazebo commented 7 years ago

Right now, I've got this flow;

1.) Exposed Player:StartContract(ContractTypes eContract) to LUA 1a.) This function automatically sets Contract Unit, Turns, Maintenance, Contract Owner, and Start Turn) 1b.) Function pushes Contract to game-wide list of active contracts for all players - some basic information (# of contracts active, # still available) can be accessed via lua. 2.) All contract data for each player is available from table - so you can grab it by accessing the CvContractEntry functionality (this should eliminate a lot of excess lua). 3.) Game will automatically add maintenance from GPT, and there will be a lua hook to get maintenance (so you can add it to UI) 4.) Game checks for a unit to be a contract unit before deducting resources for them (so you don't need the resource cache function). 5.) The DLL checks every turn if the current turn exceeds the time limit for the contract- once exceeded, the contract is cancelled and erased, nullifying all player data and cleaning up the memory.

So, all you'll need to do is build the XML table for contracts, and then integrate the new lua checks into your code. Easy enough? Sound good so far?

JFDaly commented 7 years ago

Yes, one contract at a time, and that's across all players (no same contract between players), which should reduce the overhead.

Hm, automated contracts makes things tricky. Turns, for instance, whilst normally fixed at 25, can be modified via Wonders or Policies (e.g. I make the old Mercenary Army policy increase the number of turns a Contract lasts for). Similarly, Maintenance is modified by these, and the base Maintenance is determined based upon the quality of the units being given out. The unit that the Contract gives out is also random, which I can't do in the table.

So I'd ideally still have the SetContractTurns/Unit/Maintenance functions, if possible.

Omitting the resource deduction for contract units sounds fantastically helpful though. Every else seems good.

LoneGazebo commented 7 years ago

Ah, so what are the base values in the table for?

LoneGazebo commented 7 years ago

Furthermore, what if I added elements to the dll to make those functions automated as well? Less overhead the better.

LoneGazebo commented 7 years ago

Lua overhead*

JFDaly commented 7 years ago

Turns is just the default value - I'd considered originally that I might variate the value by contract, but never fancied the idea, so I'll be moving the standard 25 to a global Game Define in the next update.

Maintenance is unused atm - its leftover from when I tried a fixed maintenance system by contract, but this didn't work out because of the issue of having to pay both unit maintenance and contract maintenance, where unit maintenance was variable.

As for what could be automated...

Turns could be automated, if a ContractTurnsModifier were to be added, for Policies, Buildings, and Traits that allowed me to decrease/increase the turn length (e.g. my Hannibal (Carthage) mod, with Mercs., has a bonus to Contract length atm).

Maintenance is a bit tricky, unless it would be possible to have Contract units not cost maintenance, akin to them not requiring resources. As each Contract gives out a unique promotion, perhaps a NoMaintenance tag in UnitPromotions would be viable? I expect this would be handy for other things, too, if possible. Though, as above, I'd need a ContractMaintenanceModifier for Policies, Buildings, and Traits (e.g. the Jiayuguan Fort wonder that Mercs. adds reduces maintenance costs on Contracts).

Automating the unit selection is probably the hardest, and probably best kept in Lua. Atm, I have three nested XML tables, from which the Contract selects a random unit each era (if no one has taken the Contract out). These tables correspond to unit combat, unit class, and unit types selections. So my function creates a temporary table in Lua, fills it with all valid units from the specified combats/classes/types (accounting for Era and Unique Units), and then chooses one at random.

LoneGazebo commented 7 years ago

1.) Units could be defined as not requiring maintenance via contracts, yep. That's easy. 2.) I can contractturns/maintenance modifier values to the DLL, or I can add luahooks that you can connect to during contract genesis and edit them that way. Whichever you would prefer. 3.) Re: units, same – I can just add a hook, however your lua function sounds very similar to the 'getrandomunitcompetitive' DLL function that grabs a unit based on the current era.

Hooks aren't hard to do. Ultimately, I'm trying to avoid Get/Set statements, as they wreak havoc on the memory alloc. model in the DLL. The more data we can est. at contract creation the better, and the more I can automate the better, as it is easier for me to debug.

LoneGazebo commented 7 years ago

Re: the global define, don't do that, we'll just set the same default value in the XML for all contracts.

JFDaly commented 7 years ago

I think turns and maintenance can be automated then, with the modifier and no maintenance elements being all I really need viz. control over these values. Maintenance can be pulled from the XML table. That also means the maintenance can be automatically subtracted from GPT without any fuss, as you outlined above. Though I'd still need Get statements for both, for UI purposes. I did have a feature where you could extend the length of a contract before it ran out, but I can drop that to save overhead (especially since I never bothered with AI support for this).

Units I think I'd still need a hook for. It does sound similar to the DLL function, but most contracts have a limited pool, tied to specific combat or unit class to select from. Anything in the DLL would need to assign a random unit, pulled from the tables specifying the valid combats/classes/types, which then changed each era (if the contract wasn't active at the time). Which is another thing - I need to have a unit assigned to a contract before the contract is taken out (otherwise the player doesn't know what they're buying). Will this be a problem with how you've set it up?

Are global defines worse? I've been shifting some of my Lua variables to a global define in my other mods, but maybe I shouldn't be?

LoneGazebo commented 7 years ago

Global defines aren't bad, no, but I was just saying that since we have the XML slot, and the data is integrated at creation, there's no reason to go to a global. Global calls do 'stretch' the DLL a little, but not all that much.

I'll make sure you have get statements for all variables.

Alright, so by 'unit assigned to contract before they take it out' you mean for UI purposes? Not the default unit? Hmm...I'll need to look at the functionality then. How does it work? Are the randomly selected units updated every turn? Era? Only at contract creation?

JFDaly commented 7 years ago

Gotcha.

Basically it works like this as I have it: at game start, the Contracts that could be taken out (each Contract has a prereq era) are created, with a unit randomly assigned to it (in TSL, basically SetContractUnit), based upon the aforedescribed subtables. Then, each Era, these units are updated/changed, provided the Contract hasn't been taken out by a player and provided the obsolete era for the Contract hasn't passed. I use this data to populate the UI, so the player knows what units they'll get from the contract. I also use it to generate the unit when the contract is taken out.

LoneGazebo commented 7 years ago

Okay, that's a little different than I thought! Not to worry, the architecture is robust enough to handle. I just need an inactive memory element to shift things back to.

JFDaly commented 7 years ago

Sorry that I wasn't clear about how the units work in my original post - I can't really imagine how the system would've worked in the DLL, so I was just thinking back to how Piety/Sovereignty were implemented, and thought the random unit stuff could be left up to me and Lua.

LoneGazebo commented 7 years ago

No worries. Definitely not a problem – none of the work I've done will have to be scrapped, I'll just have to do some more memory work.

Re: era change for contract refresh – is that game era, or player era?

JFDaly commented 7 years ago

(Active) Player era. Is there such a thing as game era? All contracts updated off of the human player's change in era - they only need to do it once per era.

LoneGazebo commented 7 years ago

Game era is the average of all player eras in the game, yep. Since contracts will be in a global game list, it makes sense to use game era IMO.

Also, you want notification support? i.e. notifications when a contract begins/ends, notifications when contracts refresh, etc.?

JFDaly commented 7 years ago

Then Game Era is perfect.

Yup, I have notifications like that set up in Lua. But anything automated is great. Although I don't think I'd have control over the icon it uses, will I?

LoneGazebo commented 7 years ago

What icon should it use?

JFDaly commented 7 years ago

Ah, I have a replacement for the Notification Panel in Rise to Power (where Mercenaries lives), so would want to keep its unique one (which also right-clicks to the Contracts overview). So I'd probably keep those in Lua if it's all the same.

LoneGazebo commented 7 years ago

That's fine. Less work for me. :)

LoneGazebo commented 7 years ago

Alright, v1 is up and ready. Link to the CP below. Make sure you enable MOD_BALANCE_CORE_JFD.

All documentation is in the CoreTables/JFDContracts.xml file.

Your other request will have to wait until next week, as I'll be unavailable this weekend (unless I get some free time later tonight). (1) Community Patch (7-8b).zip

JFDaly commented 7 years ago

A few things:

1) Is the Contract_Flavours type hooked up? 2) It doesn't seem possible for the player to take out more than one Contract. This would also mean player:GetContractTurnsRemaining() and player:GetContractGoldMaintenance() don't work as I intended. Maybe a miscommunication on my part, but the ability to take out more than one contract per player is important. 3) Is there a limit to the number of Contracts I can add to the database? DoUpdateContracts() suggests there is, at 77. 4) Would it be possible to add an player:EndContract(contractID) method of sorts? So that a Contract can be ended before it would otherwise when the duration ended. This is to preserve the option for the player to break a contract, if they find the maintenance too much. 5) Game.GetActiveContract(contractID) crashes if the contractID is not actually active. Same for Game.GetInactiveContract(contractID), but the reverse (only if the contractID is active). 6) All units of the type given out by a Contract are maintenance/resource free, as opposed to just the units given out by the Contract. This includes unit spawned through InitUnit() methods and through training them normally.

Thanks for the hard work! I hope these issues/requests are easy enough to sort.

LoneGazebo commented 7 years ago
  1. It exists, but it is not hooked up to anything. I assumed you'd use it in your lua functions. If not, I can make a simple AI function for you to use.
  2. Ah, misunderstanding. I'll see about changing that.
  3. Shouldn't be, no.
  4. Yeah I can add that.
  5. Hmm okay.
  6. Ah, that's an issue. How do you spawn contract units? I guess I'm not sure how you did it before.
LoneGazebo commented 7 years ago

Re 5, those functions return NULL, so you should check for NULL in your function.

LoneGazebo commented 7 years ago

Alright, potential solution to 6: I've setup a secondary init unit function based out of player contracts for contract units that sets the specifically created unit(s) as a contract unit. You should call this instead of the normal init function for units.

LoneGazebo commented 7 years ago

Alright.

1 needs more info. 2 solved. 3 cannot confirm,

  1. added
  2. Tested, could not reproduce.
  3. DLL functions for contract unit spawns created. Should solve problem

Also added a new function for contracts that hooks into your table to populate a vectored set of lists (list is contracts x unittypes). Sets each contract up with a quantity of units that will spawn. This should make it easy for you to populate contracts with units, and the spawning is all handled dll-side.

LoneGazebo commented 7 years ago

Link below. Not compiled, so a little slower than usual. Check lua function changes in table doc before you begin! :) (1) Community Patch (7-22b).zip

JFDaly commented 7 years ago
  1. Gotcha. I was just wondering; I can indeed handle it in Lua.
  2. Excellent, will test this out.
  3. I didn't test it fully, just something I'd noticed (calling DoUpdateContracts() once seemed to trigger it 77 times). If you can't confirm, then that's a good sign.
  4. Thanks.
  5. Odd. I'll look into it again. Might just be a Firetuner thing.
  6. In the original Mercs., I use player:InitUnit(). I was planning to spawn the units based off of ContractStarted (as I need to hand out the Contract's unique promotions at the same time). I'll check out the changes and report back.

Thanks for addressing these so quickly!

JFDaly commented 7 years ago

Some more things:

1) How do I hook up the unit to the contract now? Is player:SetContractUnits() the main method I should be using? 2) Player:UnitIsActiveContractUnit() seems to return true for all units; I suppose it should return true only for units created via Contract? 3) Seeing as the init of units is automatic now with StartContract, could you set the PromotionType from the Contract table automatically? I could do this in Lua if UnitIsActiveContractUnit() works as I think it should, but automating the promotion would possibly be more efficient.

Otherwise, the other issues seems to have been addressed - multiple contracts at once works, contract-specific units don't cost maintenance whereas the rest do, etc. Thanks again!

LoneGazebo commented 7 years ago

1) use the Lua hook I provided, the one that I note in the merc table file. It wants you to return a value - that value is the number of that type of unit that will be added to the contract.

2) I'll check.

3) I can yep. Does it use the XML defined promotion?

JFDaly commented 7 years ago

1) Ah, you mean GetContractUnit()? I thought so. You must've accidentally removed it when you added in GetNumContractUnit(), as it's missing from the Contracts table this time. Unless I'm mistaken and the latter covers both the number and the unit now? 3) Yup, the one in the Contracts table already.

LoneGazebo commented 7 years ago

1.) Look at the game section of lua hooks, see if it is there. GetNum is for player, GetContractUnit is for the game level. 3.) Cool - if you mix and match units, all get same promotion? Or should I add in a check for promotion validity?

G

JFDaly commented 7 years ago
  1. Hm, it's not, but the hook does still exist. I'm not sure if it's not working, or I misunderstand how to set it up. Does this look right?
  2. There aren't any mix and match units - contracts will offer only one type at a time, so there's no need for a validity check, I'd say.
LoneGazebo commented 7 years ago
  1. Not exactly. Lemme go to my PC and I'll pull up code.
  2. Ah, I was looking at the wiki for mercs. In any case I'll add the functionality just so that, if you decide to have different units in mercs, you can. Might as well.
JFDaly commented 7 years ago

Yea, the Game.GetContractUnit(), which I thought retrieved what was returned in the GameEvents.GetContractUnit() has been removed (as with the GameEvent), so I'm a bit confused. Thanks for checking.

LoneGazebo commented 7 years ago

Okay, yeah, it is GetContractUnit. Here's the function;

GAMEEVENTINVOKE_HOOK(GAMEEVENT_ContractsRefreshed);

//This is expensive, so do it sparingly!
ContractList::iterator it;
for(it = m_InactiveContracts.begin(); it != m_InactiveContracts.end(); it++)
{
    CvContract kContract = (*it);
    for(it = m_InactiveContracts.begin(); it != m_InactiveContracts.end(); it++)
    {
        for(int iI = 0; iI < GC.getNumUnitInfos(); iI++)
        {
            UnitTypes eUnit = (UnitTypes)iI;

            if(eUnit == NO_UNIT)
                continue;

            CvUnitEntry* pkUnitEntry = GC.getUnitInfo(eUnit);
            if(pkUnitEntry)
            {
                int iValue = 0;
                if (GAMEEVENTINVOKE_VALUE(iValue, GAMEEVENT_GetNumContractUnit, it->m_eContract, eUnit) == GAMEEVENTRETURN_VALUE) 
                {       
                    if(iValue > 0)
                    {
                        for(int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++)
                        {
                            PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;
                            if(eLoopPlayer != NO_PLAYER && GET_PLAYER(eLoopPlayer).isMajorCiv())
                            {
                                GET_PLAYER(eLoopPlayer).GetContracts()->SetContractUnits(it->m_eContract, eUnit, iValue);
                            }
                        }
                    }
                }
            }
        }
    }
}
LoneGazebo commented 7 years ago

So your lua function needs to grab the contract and the unit type, and then return a value (1 thru whatever, depending on how many you want to spawn). That value is then seeded to the potential contracts of every player (i.e. the ones they can select from).

LoneGazebo commented 7 years ago

Do contract units go away when you stop paying for them? If so, would it be useful to have a luaunit function for getcontract(contracttypes econtract) for units?