[GS] Actually smart timer for multiplayer

Niklaos

Chieftain
Joined
Jun 11, 2020
Messages
5
Hey guys,

I'm new to modding. First thank to all for you who have been working on mods and providing documentation out there.
I just spent the evening writing up some basic timer logic in Lua. It works OK but I would like to take it one step forward.

Ideally, I would like to read through each player's action queue (the actions that the game force you to do before you can do next turn) and allocate time for each action.

That would allow my timer to be smarter, for instance instead of allocating a flat 15 seconds per city I could allocate 5 seconds per city +20 seconds per city that needs a production order.
Similarly, I would like to know when someone is founding a religion and give say 5 minutes. I was hoping that ":GetReligion():CanCreatePantheon(true)" would do this but, it does not... In fact I don't understand why it keeps flipping to true every other turn.
I've been able to get the tech and civics timer to work with "GetTechs():GetTurnsLeft()" and "GetCulture():GetTurnsLeftOnCurrentCivic()" but that's really not great.

I found this fantastic list of methods: https://docs.google.com/spreadsheets/d/1HQSUOmw_pI8dNSr1kmun4qAHj6SsOVfa1vGTbk5mVvs/edit#gid=0

Is there more somewhere or is that all of it?

Thanks for your help.
 
https://docs.google.com/spreadsheet...D_xTTld4sFEyMxrDoPwX2NUFc/edit#gid=1205978888

Sticking "true" into a method that does not require or use argument data will make no difference. Player:GetReligion():CanCreatePantheon() is a Boolean querry -- you can only read the data, you cannot change anything with that method. So adding "true" as an argument has no effect. If the player has not chosen a Pantheon and is in a condition where that player can create a Pantheon, the querry will return Boolean true, otherwise it will return Boolean false.

Some querries will require argument data for a player ID #, or a city ID #, but generally these types of querries are "contants" that are invoked by the dot syntax rather than an "instance". Constants need more data to give a valid Boolean return because they are usually game or map-level querries, whereas Player, Unit, Plot, or City querries are Object-Level and the "instance" implies the needed data for whether that specific Object (ie, that player instance or that city instance) can do "X" or has "X". ChimpanG's spreadsheet does a pretty excellent job of showing when argument data is required for a given lua method.

City:GetBuildings():HasBuilding(iBuildingIndex) is an example of an instance querry that does require argument data because in order for the game to answer the querry as to whether the city has the given building it needs to know which building is in question.
 
Niklaos - check Multiplayer Helper.

I have build a smart timer in it that literally modulate the time depending on event from pantheon being founded, war, cities etc...
It is used both casualy on CPL and CivFR as well as in tournament depending on the settings you are using.
 
Niklaos - check Multiplayer Helper.

I have build a smart timer in it that literally modulate the time depending on event from pantheon being founded, war, cities etc...
It is used both casualy on CPL and CivFR as well as in tournament depending on the settings you are using.

Oh Nice, I wouldn't have been able to get a timer working without your CPL mod as an example! Great inspiration, thank you for clearing the road, I copy paste a bunch of your code :D

Regarding Player:GetReligion():CanCreatePantheon(), it is definitely not working. It gives me true every 2 turns... I did remove the true passed as a the parameter. I thought that was weird but I saw that in some other code that I Googled. Here is my log for the first few turns of my game: https://paste.ofcode.org/8e2yMjwmDbBtAsaNqej8Ch
As you can see, It is reliably going true/false disregarding what I do in game. Could this be a bug in the latest version of the game? Or I'm I doing something wrong? Here is my code for the SmartTimer function: https://paste.ofcode.org/twdSzS6sbPPmbTbTQbHZsi

Except for the CanCreatePantheon issue, the rest seems to work.
My next step is to add time for cities that have a construction pending. I'm getting an error that I don't understand when I try to iterate through the cities.

Also something that might be of interest for you: I added a formula that replace the series of if statements that you have to add base time as the game becomes more complex. I thought that the steps were a little rough. Graph the formula in a Graph calc, it might be helpful for you. The formula works up to 500 turns.
 
mmm yes that s indeed quite odd.

Maybe isolate the Players having this one as true so maybe we could refine who/what/why we have this behaviour ?
 
https://docs.google.com/spreadsheet...D_xTTld4sFEyMxrDoPwX2NUFc/edit#gid=1205978888

Sticking "true" into a method that does not require or use argument data will make no difference. Player:GetReligion():CanCreatePantheon() is a Boolean querry -- you can only read the data, you cannot change anything with that method. So adding "true" as an argument has no effect.
Some of the game's core functions that return boolean can also take optional boolean arguments (TerrainBuilder.CanHaveFeature for example), but without the API, it's hard to tell which ones (and what is the effect of true/false/nil)
 
mmm yes that s indeed quite odd.

Maybe isolate the Players having this one as true so maybe we could refine who/what/why we have this behaviour ?

What do you mean by Isolate? This a game that I launch to test my timer. I do a duel map with me and an AI so there is only one human player (me). You can see the log has a section for each human at the top of each new timer round.

I'm going to try with your timer directly from Steam tonight but I think that it has the same behavior.
 
Alright I've now spent my evening struggling with cities build queue.

I'm having a bit of an issue Working through the build queue to figure out if the city needs to get a new production. This is what I have so far:

Code:
if player:GetCities() ~= nil then
    local playerCities:table = player:GetCities()
    for _, city in playerCities:Members() do
        local buildQueue:table = city:GetBuildQueue()
        print("City-Unit:", buildQueue:GetCurrentProductionTypeHash())
        print("City-Turn:", buildQueue:GetTurnsLeft(buildQueue:GetCurrentProductionTypeHash()))
    end   
end

I think this should work to give me 1 when the unit is about to be done. Unfortunately, GetCurrentProductionTypeHash is not available in the gameplay script scope.
I think that GetTurnsLeft takes a type hash but how I'm supposed to get it from the Gameplay script scope.

CurrentlyBuilding gives me unit name's. Nice but not really useful, none of the other methods seem helpful for what I'm trying to do. Also where is the QueueSize method that tell me how many units are in the queue?
 
If you look at Chimpang's spreadsheet there are two column on the left. These tell you whether the lua method is valid in a GameplayScript or a UserInterface lua context. Anything that is not shown as valid for a GameplayScript will not be recognized in any lua script that is executed via a "AddGameplayScripts" modinfo action.

As a general rule, methods that are valid in a GameplayScript and which require a game-element ID # will accept the Index number of the game-element. So if we know that if
Code:
CityObject:GetBuildQueue():CurrentlyBuilding()
returns "BUILDING_MONUMENT" we can find the Index # for that construction item via
Code:
iCurrentItemIndex = GameInfo.Buildings["BUILDING_MONUMENT"].Index
However, if the city was not constructing a Building this would give us a nil value error for attempting to index a value that was gorsnitch, so we would rather do something like
Code:
local sQeueItem = CityObject:GetBuildQueue():CurrentlyBuilding()
local sQeueItemType = "NONE"
local iQeueItemIndex = -1
local sQeueItemLocalized = "NONE"
if (sQeueItem ~= "NONE") and (sQeueItem ~= nil) then
	if (GameInfo.Units[sQeueItem] ~= nil) then
		sQeueItemType = "UNIT"
		iQeueItemIndex = GameInfo.Units[sQeueItem].Index
		sQeueItemLocalized = Locale.Lookup(GameInfo.Units[iQeueItemIndex].Name)
	end
	if (GameInfo.Buildings[sQeueItem] ~= nil) then
		sQeueItemType = "BUILDING"
		iQeueItemIndex = GameInfo.Buildings[sQeueItem].Index
		sQeueItemLocalized = Locale.Lookup(GameInfo.Buildings[iQeueItemIndex].Name)
	end
end
local sCityName = CityObject:GetName()
local sCityNameLocalized = Locale.Lookup(sCityName)
print(sCityNameLocalized .. " is producing a " .. sQeueItemType .. " : " .. sQeueItemLocalized)

if CityObject:GetBuildings():HasBuilding(iQeueItemIndex) then
	print("HUH !? The city already has this Building!")
	print("OH! I need my code to check whether the item being produced is a building!")
end
if (sQeueItemType ~= "NONE") and (sQeueItemType == "BUILDING") then
	print("CityObject:GetBuildings():HasBuilding(" .. iQeueItemIndex .. ") returns boolean " .. tostring(CityObject:GetBuildings():HasBuilding(iQeueItemIndex)))
end
There are some efficiencies that could be applied to this example code but I wrote it as I did to make the methods that can be used a bit more easy to understand.

Note that since cities can be producing nothing, a unit, a building, a project, a district we need to understand that CityObject:GetBuildQueue():CurrentlyBuilding() will not always be of a "type" that we are interested in.
 
Back
Top Bottom