Lua function/code to disable the ability to produce a Unit

Zegangani

King
Joined
Oct 9, 2020
Messages
897
I'm currently working on a Mod that makes Units require multiple resources (for Costs and Maintenance, for example: Unit_A requires 10 Iron, 10 Coal and consumes 1 Niter, and 1 Coal per turn). To get this on a New Table: is not a big Deal. Make the Resources consumed by the Game (via Lua): can be done. But how to make the production panel Hide a Unit to not get produced if there is not enough resources available (the code to hide a unit/mark it as can not be produced)? I've searched all the lua files that have something to do with production in a City, but couldn't fegure it out. I've only found a luaObject that is maybe already defined: buildQueue:CanProduce( kBuildParameters, true ). I've tried to change it to false but I get a lua Error.

So Now I'm asking you Guys how I can accomplish this(What's the code for it)? Any Help is appreciated!

I also have another problem: when I try to add a lua funcion in order to match the UnitType from Units Table with the UnitType in my new Table, the UnitType from Units Table attempt to index a number value.

Here is the code:
Code:
function RecourceAmountAvailable(unitReference)
    if (GameInfo.Units_BonusesAndResourceCosts~= nil) then
        for row in GameInfo.Units_BonusesAndResourceCosts() do
            if (row.UnitType == unitReference.UnitType) then
                local AdditionalStrategicResource = GameInfo.Resources[row.AdditionalStrategicResource];
                if (AdditionalStrategicResource ~= nil) then
                    local AdditionalStrategicResourceCost = row.AdditionalStrategicResourceCost;
                    if (AdditionalStrategicResourceCost > 0) then
                        print("Unit Can Be Built");
                        return false ;
                    else
                        bCanProduce = buildQueue:CanProduce( row.Hash, false );
                        print("Unit Can not Be Built");
                        return true ;
                    end
                end
            end
        end
    end
end

Events.TurnBegin.Add( RecourceAmountAvailable );

I've no Clue how to solve this.

Edit: I used the Event: TurnBegin, because I didn't find any Event that coud fire when selecting the production panel or intering the citypanel.
 
Last edited:
"unitReference" is not a value associated with any UnitType Index or Hash number. It will always be a turn number
Code:
function OnTurnBegun(iTurnNumber)
	print("Turn number " .. iTurnNumber .. " has begun")
end
Events.TurnBegin.Add(OnTurnBegun)
The "TurnBegin" event only ever passes one argument, for the Turn Number. It does not matter in lua what you call the variable name, it matters what the hook Event passes to all functions hooked into it, and the sort of data that is sent as arguments. This will operate exactly the same as the previous example
Code:
function OnTurnBegun(Cheeseburger)
	print("Turn number " .. Cheeseburger .. " has begun")
end
Events.TurnBegin.Add(OnTurnBegun)
-------------------------------------------

Second, this cannot be done
Code:
if (row.UnitType == unitReference.UnitType) then
because you have not accessed the Units XML/SQL table to get the data for the row within the "Units" table where the integer value of "unitReference" matches to the table Index number of a unit defined within table Units. The proper method to get the row data from table units for a Unit Index number is
Code:
local tUnitRowData = GameInfo.Units[unitReference]
local sUnitType = tUnitRowData.UnitType
On turn number zero however you will get "UNIT_SETTLER" because the game assigns the first row in every table with table Index number "0" for the purposes of lua. On turn number 1 you will get "UNIT_BUILDER", on turn number 2 you will get "UNIT_TRADER".

Eventually as the game progresses you will get nil value errors because the turn number will be greater than the highest row Index number within table Units. Table Units only has X number of rows, so once the turn number is equal to or greater than that number, you will get errors because your code would be shoving a Turn Number into what needs to be a lua Unit Index #.

--------------------------------------------------------------------

In order to get any given unit to not show as a valid production item you will need to re-write the game's ProductionPanel.lua file so that it does not show the unit as an option, but this will only work for the human player because AI players do not make use of the lua User Interface scripts -- they work directly "behind the scenes" with and from the game engine.

------------------------------------------------------------------

Third, returning true or false from a function hooked into Events.TurnBegin will have no effect whatever because the game is not expecting nor looking for any return value from any such function.


[edited to fix some typos that might have been confusing]
 
Last edited:
You can in fact enable and disable production or purchase of any unit for any player via lua, but the effects are player-wide for that player, and any units they are currently producing would go *poof* from their production list in any of their cities, including those they have already spent production on when the lua script disables the unit for that player.

Player:GetUnits():SetBuildDisabled(eUnitIndex, Boolean)
added by Gathering Storm
may not be usable in Vanilla or Rise and Fall but I have not tested for this

“Boolean” is set to boolean true to disable the unit and boolean false to allow build of the unit.
"Player" is a placeholder for the Player Object, and "eUnitIndex" is a valid unit type Index Number from table "Units".
 
As always LeeS, your Guidance is realy apreciated.
Now I see what I have done wrong. So That's why i get a Number value!!

In order to get any given unit to not show as a valid production item you will need to re-write the game's ProductionPanel.lua file so that it does not show the unit as an option, but this will only work for the human player because AI players do not make use of the lua User Interface scripts -- they work directly "behind the scenes" with and from the game engine.
That's exactly what I wanted to avoid. If the Mod doesn't also apply for the AI, then there is no much use for it. I guess I just have to try something else to make this to work.
 
You can in fact enable and disable production or purchase of any unit for any player via lua, but the effects are player-wide for that player, and any units they are currently producing would go *poof* from their production list in any of their cities, including those they have already spent production on when the lua script disables the unit for that player.

"Player" is a placeholder for the Player Object, and "eUnitIndex" is a valid unit type Index Number from table "Units".

Too bad that it also affects other Cities that are Producing Units. If it was just for The City building The Unit affected from the code that would be Ok.
I guess I have to forget about that (disabling unit production), but I will try to make at least the game consume a resource per turn where a Unit is required to.
 
Code:
local tUnitRowData = GameInfo.Units[unitReference]
local sUnitType = tUnitRowData.UnitType
I tried this but I get a Lua Error: "attempt to index a nil value" with "local sUnitType = tUnitRowData.UnitType" line. I've also tried it outside the function as a main chunk but same result.
 
Code:
local bAddTerraCottaUnits = true
local iTerraCottaIndex = GameInfo.Buildings["BUILDING_TERRACOTTA_ARMY"].Index
function OnTerraCottaCompleted(iPlotX, iPlotY, iBuildingIndex, iPlayer, iCityID, iPercentComplete)
	if (iBuildingIndex == iTerraCottaIndex) and (iPercentComplete == 100) then
		local pPlayer = Players[iPlayer]
		local tTrackTable, tCreateTable = {}, {}
		local pLoopUnits = pPlayer:GetUnits()
		for k,pUnit in pLoopUnits:Members() do
			if (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
				local iUnitType = pUnit:GetType()
				local tUnitData = GameInfo.Units[iUnitType]
				if (tTrackTable[iUnitType] == nil) and (tUnitData.FormationClass == "FORMATION_CLASS_LAND_COMBAT") and (tUnitData.PromotionClass ~= "PROMOTION_CLASS_RECON") then
					tTrackTable[iUnitType] = tUnitData.UnitType
					table.insert(tCreateTable, tUnitData.UnitType)
				end
			end
		end
		for k,sUnitType in ipairs(tCreateTable) do
			UnitManager.InitUnitValidAdjacentHex(iPlayer, sUnitType, iPlotX, iPlotY, 1);
			print("OnTerraCottaCompleted caused a " .. sUnitType .. " unit to be created")
		end
	end
end
if bAddTerraCottaUnits then
	print("")
	print("=================================================================")
	print("Events.WonderCompleted.Add(OnTerraCottaCompleted) :-->: executed")
	print("=================================================================")
	print("")
	Events.WonderCompleted.Add(OnTerraCottaCompleted)
else
	print("")
	print("========================================================================")
	print("Events.WonderCompleted.Add(OnTerraCottaCompleted) :-->: was NOT executed")
	print("========================================================================")
	print("")
end
  1. The hook event triggers on completion of a world wonder
  2. If the building Index # is the same as I have already placed into variable iTerraCottaIndex then the rest of the code executes
  3. Using the argument data passed from the game engine into the lua hook event a Player Object is obtained, and from that a "Player Units" object is obtained
  4. All the members of the "Player Units" object are then processed
  5. Each unit's UnitType Index Number is obtained by
    Code:
    local iUnitType = pUnit:GetType()
    This will match to an index number from the SQL table "Units"
  6. An lua table is created which then is given all the info for that UnitType from the matching row within the SQL table "Units"
    Code:
    local tUnitData = GameInfo.Units[iUnitType]
  7. I'm then using a little trickery with an lua table I called "tTrackTable" to determine whether the same UnitType has already been processed.
    • A player can have 100 Crossbowmen, all of which will have the same value in lua for their Index Number from the SQL "Units" table. Each of these 100 Crossbowmen will have its own unit "object" in lua, and its own individualized unit ID # for that player, but they all share the same SQL-"Units" Index Number.
    • Two different players can each have a unit whose individualized unit ID # is for example 123456789 but these would be two different units each with its own Unit Object data.
    • The same sort of thing is also true of Buildings, Districts, etc.: each of these will be its own game object but for example each copy of "BUILDING_TEMPLE" present within a game will use the same BuildingType Index # from SQL table "Buildings".
  8. In the same line the FormationClass of the unit and the PromotionClass of the unit are being accessed and compared against certain requirements to sort out unwanted units.
  9. If all the conditional checks are passed the unit's Index number is added as data to the "track" lua table and the Unit's UnitType text string like "UNIT_SETTLER" is added as data to the lua table "tCreateTable"
  10. Once all units have been processed, the code then executes through the resulting "tCreateTable" and creates one copy of every type of Land Combat Non-Recon unit the player already owns.
  11. I need to grab the "UnitType" text string because the UnitManager.InitUnitValidAdjacentHex(args) method wants a text string like "UNIT_SETTLER" instead of an lua Unit-Type Index number
  12. If I were to do all this stuff here "out of context" the code would not work because the lua script has no starting data to work upon
    Code:
    local pPlayer = Players[iPlayer]
    local tTrackTable, tCreateTable = {}, {}
    local pLoopUnits = pPlayer:GetUnits()
    for k,pUnit in pLoopUnits:Members() do
    	if (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
    		local iUnitType = pUnit:GetType()
    		local tUnitData = GameInfo.Units[iUnitType]
    		if (tTrackTable[iUnitType] == nil) and (tUnitData.FormationClass == "FORMATION_CLASS_LAND_COMBAT") and (tUnitData.PromotionClass ~= "PROMOTION_CLASS_RECON") then
    			tTrackTable[iUnitType] = tUnitData.UnitType
    			table.insert(tCreateTable, tUnitData.UnitType)
    		end
    	end
    end
    for k,sUnitType in ipairs(tCreateTable) do
    	UnitManager.InitUnitValidAdjacentHex(iPlayer, sUnitType, iPlotX, iPlotY, 1);
    	print("OnTerraCottaCompleted caused a " .. sUnitType .. " unit to be created")
    end
    iPlayer for example would have a value of nil and therefore the code would puke on this line
    Code:
    local pLoopUnits = pPlayer:GetUnits()
    because it would be attempting to index a nil value
  13. lua is completely context-dependant (we refer to this as "scope") in the sense that unless our code already has the starting data made available to it, the scripts cannot process because they have nothing but nil values to execute upon
    • Lua "hook" event pass us this data which we then must use to create the variables or other data we need to make our overall code execute
    • We can put code at the root level of our script but in such cases the script needs a hard-coded value to work upon, or we must for example loop through all players in a game and get their player ID #'s upon which we can then work (for example)
      Code:
      for iPlayer = 1, 63 do
      	local pPlayer = Players[iPlayer]
      	if pPlayer:IsAlive() and pPlayer:IsMajor() then
      		print("we discovered an alive major AI player: it is player number " .. iPlayer)
      	end
      end
 
So the code would not work because the lua script has no starting data to work upon, and that's why I get a nil value! And same thing apply for iplayer too. That's good to know! and Thank you for the Example!
Now I know where I have to look at when coding in Lua in the Future.

LeeS, you are a Big Asset to this Communuty, and your Willingnes to help other Modders is really enviable! So, Thanks a Lot!
 
Top Bottom