1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

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

Discussion in 'Mod Creation Help' started by Zegangani, Nov 21, 2020.

  1. Zegangani

    Zegangani Prince

    Joined:
    Oct 9, 2020
    Messages:
    326
    Gender:
    Male
    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: Nov 21, 2020
  2. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,089
    Location:
    Illinois, USA
    "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: Nov 21, 2020
    Zegangani likes this.
  3. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,089
    Location:
    Illinois, USA
    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".
     
    Zegangani likes this.
  4. Zegangani

    Zegangani Prince

    Joined:
    Oct 9, 2020
    Messages:
    326
    Gender:
    Male
    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!!

    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.
     
  5. Zegangani

    Zegangani Prince

    Joined:
    Oct 9, 2020
    Messages:
    326
    Gender:
    Male
    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.
     
  6. Zegangani

    Zegangani Prince

    Joined:
    Oct 9, 2020
    Messages:
    326
    Gender:
    Male
    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.
     
  7. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    7,089
    Location:
    Illinois, USA
    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
     
    Zegangani likes this.
  8. Zegangani

    Zegangani Prince

    Joined:
    Oct 9, 2020
    Messages:
    326
    Gender:
    Male
    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!
     
    maconnolly likes this.

Share This Page