Getting the Current Production Progress of Units/Buildings in Lua

Zegangani

King
Joined
Oct 9, 2020
Messages
843
I want to get lua to show the current Production Progress of a Unit (and Building, but didn't try this latter) but I always get a Nil value from this code:

Code:
function OnUnitProductionHash(PlayerID, CityID, iProdcutionItemType, unitReference)
    local pPlayer = Players[PlayerID];
    local pCity =  pPlayer:GetCities():FindID(CityID)
    if pCity ~= nil then
        for row in GameInfo.Units() do
            local UnitHash = row.Hash
            local UnitTypeInfo = GameInfo.Units[row.UnitType];
            local UnitToBuild = row.Index
            local UnitName = Locale.Lookup(UnitTypeInfo.Name);
            print("Check 1", UnitName, UnitToBuild, UnitHash);
            if (UnitTypeInfo ~= nil) then
                local pBuildQueue: table = pCity:GetBuildQueue()
                local nProductionProgress : number = pBuildQueue:GetUnitProgress( UnitToBuild );
                print("Check 2", UnitName, UnitToBuild, UnitHash, nProductionProgress);
            end
        end
    end
end

The Nil value error comes from the line: "nProductionProgress : number = pBuildQueue:GetUnitProgress( UnitToBuild );"
(The Code is a little bit messy cause it's just for testing purposes)
I've tryed many things (changed the function's code, the HookEvents...) but I always get the Nil value.
Any Ideas how to get this to work?

Thanks in advance!
 
Last edited:

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
you've already set UnitToBuild to be the current unit's row index here
Code:
local UnitToBuild = row.Index
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
you've already set UnitToBuild to be the current unit's row index here
Code:
local UnitToBuild = row.Index

Forgot to fix that. But I get the Nil value Error anyway (I double checked it to be sure).
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
would be easier to help with the log.
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
would be easier to help with the log.

Here is what I get from the lua Log:
Code:
SRE_CityPanel: Check 1    Settler    0    -798712536    table: 00000000FB394C90
Runtime Error: C:\Users\1\Documents\My Games\Sid Meier's Civilization VI\Mods\Strategic Resources Expanded (V. 1)\SRE_CityPanel.lua:111: function expected instead of nil
stack traceback:
    C:\Users\1\Documents\My Games\Sid Meier's Civilization VI\Mods\Strategic Resources Expanded (V. 1)\SRE_CityPanel.lua:111: in function 'OnUnitProductionHash'
    [C]: in function 'func'
    [C]: in function '(anonymous)'
SRE_CityPanel: Check 1    Settler    0    -798712536    table: 00000000FB394C90
Runtime Error: C:\Users\1\Documents\My Games\Sid Meier's Civilization VI\Mods\Strategic Resources Expanded (V. 1)\SRE_CityPanel.lua:111: function expected instead of nil
stack traceback:
    C:\Users\1\Documents\My Games\Sid Meier's Civilization VI\Mods\Strategic Resources Expanded (V. 1)\SRE_CityPanel.lua:111: in function 'OnUnitProductionHash'
    [C]: in function 'func'
    [C]: in function '(anonymous)'

Line 111 is "nProductionProgress : number = pBuildQueue:GetUnitProgress( UnitToBuild );"
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
GetUnitProgress is not valid in a gameplay script so the first question would be whether you are running it in a User Interface context or in a GameplayScript ?

The error messages you are getting in the log are the usual kind a Gameplay Script spits when you attempt an invalid method -- usually a User Interface will either lock the panel or lock up the entire game when the panel is used if there is a nil function error.
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
how's SRE_CityPanel.lua loaded ?

edit: as LeeS is pointing, the context is important. from your file name I would have thought this was an UI file, but from the error it seems to be loaded in script context.
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
Yes, I'm running it in a GameplayScript. So it would only work correctly if made in a UI context?!

I was working on a Mod that would make Units require multiple resources in order to be produced. I managed to get that work. The City would only produce a unit if there is enough amount of a resource, if not it will remove the unit from the Productionqueue, and it won't show up till there is enough resources for it. But this would repeat for units that the city already started producing and (of some reason, such as deciding building something else) stopped at some point (the code would repeat if the city reterns to build a halfway finished unit). And I wanted to fix that. That's why I want to get the productionprogress for Units.
I would prefer Ai also getting affected by that. (That's not possible in a UI context)
I can post the code of it if you want.

Edit: would "HasProductionProgress" work in this case? And how can I get The current Number of a Specific Unit Type that a Player has? "pPlayer:GetUnits():GetCount();" shows only the Total amount of the Player's Units.
 
Last edited:

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
So far as I am aware there is nothing in Civ6 equivalent to Civ5's Unit Class Counts or similar methods
Code:
function CountAllPlayerUnits(iPlayer)
	local pPlayerUnits = Players[iPlayer]:GetUnits()
	local tTableOfUnitTypeCounts = {}
	for k,pUnit in pPlayerUnits:Members() do
		if (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
			if tTableOfUnitTypeCounts[pUnit:GetType()] then
				tTableOfUnitTypeCounts[pUnit:GetType()] = tTableOfUnitTypeCounts[pUnit:GetType()] + 1
			else
				tTableOfUnitTypeCounts[pUnit:GetType()] = 1
			end
		end
	end
	return tTableOfUnitTypeCounts
end
--------------------------------------------------------------------------------------------------------------------------------------------------------
function CountPlayerUnitsOfType(iPlayer, iUnitType, bByClass)
	local bCountByEquivalents = ((bByClass ~= nil) and bByClass or false)
	local iUnitCount = 0
	local tUnitCounts = CountAllPlayerUnits(iPlayer)
	if bCountByEquivalents then
		local sUnitName = GameInfo.Units[iUnitType].UnitType
		local bIsReplacement, bIsPrimary = false, false
		local iPrimaryUnitIndex, sPrimaryTypeName = -1, "NONE"
		for Row in GameInfo.UnitReplaces() do
			if Row.CivUniqueUnitType == sUnitName then
				bIsReplacement = true
				iPrimaryUnitIndex = GameInfo.Units[Row.ReplacesUnitType].Index
				sPrimaryTypeName = Row.ReplacesUnitType
				break
			elseif Row.ReplacesUnitType == sUnitName then
				bIsPrimary = true
				break
			end
		end
		if bIsReplacement then
			for Row in GameInfo.UnitReplaces() do
				if Row.CivUniqueUnitType ~= sUnitName then
					if Row.ReplacesUnitType == sPrimaryTypeName then
						local iReplacementIndex = GameInfo.Units[Row.CivUniqueUnitType].Index
						if tUnitCounts[iReplacementIndex] then
							iUnitCount = iUnitCount + tUnitCounts[iReplacementIndex]
						end
					end
				end
			end
			if (iPrimaryUnitIndex ~= -1) and (iPrimaryUnitIndex ~= iUnitType) and tUnitCounts[iPrimaryUnitIndex] then
				iUnitCount = iUnitCount + tUnitCounts[iPrimaryUnitIndex]
			end
		end
		if bIsPrimary then
			for Row in GameInfo.UnitReplaces() do
				if Row.ReplacesUnitType == sUnitName then
					local iReplacementIndex = GameInfo.Units[Row.CivUniqueUnitType].Index
					if tUnitCounts[iReplacementIndex] then
						iUnitCount = iUnitCount + tUnitCounts[iReplacementIndex]
					end
				end
			end
		end
	end
	if tUnitCounts[iUnitType] then
		iUnitCount = iUnitCount + tUnitCounts[iUnitType]
	end
	return iUnitCount
end
local iSettlerCount = CountPlayerUnitsOfType(0, GameInfo.Units["UNIT_SETTLER"].Index, false)
Third argument is whether to count directly based upon the supplied Unit Index # or by equivalent types of units.
  1. By using boolean true for the third argument it will act like a "Unit-Class" method in the sense the replacement unit-types will be considered, as will the 'default' unit-type for a unique replacement so long as the unique replacement is listed in the "UnitReplaces" table.
  2. If the original unit is unique but not listed in the "UnitReplaces" table then nothing except the original unit will be counted.
  3. If more than one unit-type replaces the same "primary" unit-type, then all of these will also be counted if the player owns any.

Also, see https://docs.google.com/spreadsheet...D_xTTld4sFEyMxrDoPwX2NUFc/edit#gid=1205978888
So far as we know HasProductionProgress is valid in both GameplayScript and UI Context but what the needed arguments are (if any) is unclear.
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
So far as I am aware there is nothing in Civ6 equivalent to Civ5's Unit Class Counts or similar methods
Code:
function CountAllPlayerUnits(iPlayer)
    local pPlayerUnits = Players[iPlayer]:GetUnits()
    local tTableOfUnitTypeCounts = {}
    for k,pUnit in pPlayerUnits:Members() do
        if (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
            if tTableOfUnitTypeCounts[pUnit:GetType()] then
                tTableOfUnitTypeCounts[pUnit:GetType()] = tTableOfUnitTypeCounts[pUnit:GetType()] + 1
            else
                tTableOfUnitTypeCounts[pUnit:GetType()] = 1
            end
        end
    end
    return tTableOfUnitTypeCounts
end
--------------------------------------------------------------------------------------------------------------------------------------------------------
function CountPlayerUnitsOfType(iPlayer, iUnitType, bByClass)
    local bCountByEquivalents = ((bByClass ~= nil) and bByClass or false)
    local iUnitCount = 0
    local tUnitCounts = CountAllPlayerUnits(iPlayer)
    if bCountByEquivalents then
        local sUnitName = GameInfo.Units[iUnitType].UnitType
        local bIsReplacement, bIsPrimary = false, false
        local iPrimaryUnitIndex, sPrimaryTypeName = -1, "NONE"
        for Row in GameInfo.UnitReplaces() do
            if Row.CivUniqueUnitType == sUnitName then
                bIsReplacement = true
                iPrimaryUnitIndex = GameInfo.Units[Row.ReplacesUnitType].Index
                sPrimaryTypeName = Row.ReplacesUnitType
                break
            elseif Row.ReplacesUnitType == sUnitName then
                bIsPrimary = true
                break
            end
        end
        if bIsReplacement then
            for Row in GameInfo.UnitReplaces() do
                if Row.CivUniqueUnitType ~= sUnitName then
                    if Row.ReplacesUnitType == sPrimaryTypeName then
                        local iReplacementIndex = GameInfo.Units[Row.CivUniqueUnitType].Index
                        if tUnitCounts[iReplacementIndex] then
                            iUnitCount = iUnitCount + tUnitCounts[iReplacementIndex]
                        end
                    end
                end
            end
            if (iPrimaryUnitIndex ~= -1) and (iPrimaryUnitIndex ~= iUnitType) and tUnitCounts[iPrimaryUnitIndex] then
                iUnitCount = iUnitCount + tUnitCounts[iPrimaryUnitIndex]
            end
        end
        if bIsPrimary then
            for Row in GameInfo.UnitReplaces() do
                if Row.ReplacesUnitType == sUnitName then
                    local iReplacementIndex = GameInfo.Units[Row.CivUniqueUnitType].Index
                    if tUnitCounts[iReplacementIndex] then
                        iUnitCount = iUnitCount + tUnitCounts[iReplacementIndex]
                    end
                end
            end
        end
    end
    if tUnitCounts[iUnitType] then
        iUnitCount = iUnitCount + tUnitCounts[iUnitType]
    end
    return iUnitCount
end
local iSettlerCount = CountPlayerUnitsOfType(0, GameInfo.Units["UNIT_SETTLER"].Index, false)
Third argument is whether to count directly based upon the supplied Unit Index # or by equivalent types of units.
  1. By using boolean true for the third argument it will act like a "Unit-Class" method in the sense the replacement unit-types will be considered, as will the 'default' unit-type for a unique replacement so long as the unique replacement is listed in the "UnitReplaces" table.
  2. If the original unit is unique but not listed in the "UnitReplaces" table then nothing except the original unit will be counted.
  3. If more than one unit-type replaces the same "primary" unit-type, then all of these will also be counted if the player owns any.

Also, see https://docs.google.com/spreadsheet...D_xTTld4sFEyMxrDoPwX2NUFc/edit#gid=1205978888
So far as we know HasProductionProgress is valid in both GameplayScript and UI Context but what the needed arguments are (if any) is unclear.

Wow! Thanks a lot LeeS!! You really helped me a lot with this.
I'm working on a "Deals with Units" Mod, and this is really helpful in getting that Mod to work.
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
So far as we know HasProductionProgress is valid in both GameplayScript and UI Context but what the needed arguments are (if any) is unclear.

I've tried it but I get this Error:
Code:
Runtime Error: Unknown production type.
stack traceback:
    [C]: in function 'lHasProductionProgress'
    C:\Users\1\Documents\My Games\Sid Meier's Civilization VI\Mods\Strategic Resources Expanded (V. 1)\SRE_CityPanel.lua:341: in function 'OnUnitBuildable'
    [C]: in function 'func'
    [C]: in function '(anonymous)'

from This Code:
Code:
function OnUnitBuildable( PlayerID, cityID, iProdcutionItemType)
    local pPlayer = Players[PlayerID];
    local pCity =  pPlayer:GetCities():FindID(CityID)
    print("City Selected.");
    if ( iProdcutionItemType  == 0 ) then
        for row in GameInfo.Resources() do
            local ResourceType = GameInfo.Resources[row.ResourceType]
            for row in GameInfo.Units() do
                local kBuildParameters = {};
                kBuildParameters.UnitType = row.Hash;
               
                local UnitTypeInfo = GameInfo.Units[row.UnitType];
                local UnitTypeHash = row.Hash
                local UnitType = row.UnitType
                local UnitName = Locale.Lookup(UnitTypeInfo.Name);
                print("Unit Check #1", UnitType, UnitName, UnitTypeHash, UnitTypeInfo);
                if (UnitType ~= nil) then
                    local pBuildQueue: table = pCity:GetBuildQueue()
                    local isInProductionProgress, results = pBuildQueue:HasProductionProgress( kBuildParameters, false, true );
                    print("Unit Check #2", UnitType, UnitName, UnitTypeInfo, isInProductionProgress);
                    for row in GameInfo.Units_BonusesAndResourceCosts() do
                        if (row.UnitType == UnitType) then
                            local AdditionalStrategicResource = GameInfo.Resources[row.AdditionalStrategicResource];
                            local fuelName = Locale.Lookup(AdditionalStrategicResource.Name);
                            local AdditionalStrategicResourceCost:number = row.AdditionalStrategicResourceCost;  
--                            print("UCheck #3", UnitType, UnitName, AdditionalStrategicResource, fuelName);
                            if (AdditionalStrategicResource ~= nil) then
                                if (ResourceType == AdditionalStrategicResource) then
                                    local mResource = AdditionalStrategicResource.Index
                                    local pResource:number = pPlayer:GetResources():GetResourceAmount(mResource);
                                    print("Unit Check Resource", AdditionalStrategicResourceCost, mResource, pResource, AdditionalStrategicResource, fuelName, UnitType, UnitName);
                                    if (AdditionalStrategicResourceCost > 0) then
                                        if (pResource >= AdditionalStrategicResourceCost ) then
                                            print("Unit is Set to be Buildable");
                                            local eUnitIndex = GameInfo.Units[row.UnitType].Index;
                                            pPlayer:GetUnits():SetBuildDisabled(eUnitIndex, false)
                                            print("This Unit: " .. UnitName .. " is Now going to be buildable!!", UnitType, UnitName, eUnitIndex)
                                        else
                                            local eUnitIndex = GameInfo.Units[row.UnitType].Index;
                                            pPlayer:GetUnits():SetBuildDisabled(eUnitIndex, true)
                                            print("This Unit: " .. UnitName .. " is Now not buildable!", UnitType, UnitName, eUnitIndex)
                                            print("Unit is Still not Buildable", UnitType, UnitName);
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
end

I've changed the "kBuildParameters" argumen with: UnitTypeInfo, UnitTypeHash, UnitType... and also with .Index prefix (UnitType.Index). But same Error.
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
you may want to check the game's code for example of key/value used with the kBuildParameters table passed to that function.
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France

Zegangani

King
Joined
Oct 9, 2020
Messages
843
Thanks Gedemon for the Answer!

Ok, I will try the Exposed members method. Didn't know that we can pass UI fonctions to GamePlayScript. Good to know!

No I haven't tryed "ProductionType.Units", but "iProductionItemType". I will give it a try.
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
iProductionItemType is a variable that you have to assign a value to in your code.

ProductionType is a table created by the game that contain constants used to ID something (in that case the Units, Buildings, Districts and Projects production types)

and it's "ProductionType.UNIT" the case is important here.
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
I've tryed "ProductionType.UNIT" but I get the same Error.

Now I'm trying the ExposedMember Method, but I'm not sure about this:
GameplayScript:
Code:
ExposedMembers.SRE = {};

function GetUnitProgress(playerID, CityID)
   return ExposedMembers.SRE.GetUnitProgress(playerID, CityID); ===> (I get a: function expected instead of nil value here)
end

function OnUnitBuildableCheck( PlayerID, cityID, iProdcutionItemType)
    code...
end

Events.CitySelectionChanged.Add( GetUnitProgress );
Events.CitySelectionChanged.Add( OnUnitBuildableCheck );
Events.PlayerResourceChanged.Add( OnUnitBuildableCheck );

UIScript:
Code:
function GetUnitProgress(playerID, CityID)
    local pPlayer = Players[playerID]
    local pCity =  pPlayer:GetCities():FindID(CityID)
    if pCity ~= nil then
        if ( iProdcutionItemType  == 0 ) then
            return pCity:GetBuildQueue():GetUnitProgress()
        end
    end
end
ExposedMembers.SRE.GetUnitProgress = GetUnitProgress;

I've never used this Method befor, so I don't have any clue how I should do it correctly.
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
just had a look, ProductionType is defined in Civ6Common.lua, and the arguments for HasProductionProgress are strings
Code:
ProductionType = {
    BUILDING    = "BUILDING",
    DISTRICT    = "DISTRICT",
    PROJECT        = "PROJECT",
    UNIT        = "UNIT"
}

which will be useless in your case as you want to test an unit type if I understand correctly...

for your code, you need to pass the unit type index when calling the function, and you may want to check if the UI file is loaded before Events.CitySelectionChanged is called the first time.
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
Ah! So that's why it doesn't work.

Yes, I want to use it for a UnitType.
For the UI file, for my understanding, I just have to make it trigerred by another Event that loads befor Events.CitySelectionChanged, for Example: Events.PlayerTurnActivated. Is that right?
 

Zegangani

King
Joined
Oct 9, 2020
Messages
843
I haven't made any Event for the UI file, so I'm not sure how I should check if it loads before Events.CitySelectionChanged.
 

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
10,980
Location
France
the "print" function is your (only) friend for Lua debugging, sometime there is a "print" every 2 lines in some parts of my code, help understand in which order things are called.
 
Top Bottom