Lua snippets for mod makers

bane_

Howardianism High-Priest
Joined
Nov 27, 2013
Messages
1,559
I believe this would be enough:

Code:
local iPolicyToBuild = GameInfoTypes["POLICY HERE"]
local iBuildingFromPolicy = GameInfoTypes["BUILDING HERE"]


function BelzorashOwesMeaBeer(iPlayer, iBuildingType)
	local pPlayer = Players[iPlayer]
	if iBuildingType == iBuildingFromPolicy then
		if pPlayer:HasPolicy(iPolicyToBuild) then
			return true
		end
		return false
	end
	return true
end


GameEvents.PlayerCanConstruct.Add(BelzorashOwesMeaBeer)
 
Hello bane!
Thank you!
Unfortunately, I have 30 policies that each unlock one specific building.
Is there something like an array I can fill?
 
Try this:

Code:
local tPolicyToBuild = {}
tPolicyToBuild[ID of Policy I] = ID of Building I
tPolicyToBuild[ID of Policy II] = ID of Building II
...
tPolicyToBuild[ID of Policy n] = ID of Building n


function BelzhorashOwesMeaBeer(iPlayer, iBuildingType)
	local pPlayer = Players[iPlayer]
	for i, v in pairs(tPolicyToBuild) do
		if v == iBuildingType then
			if pPlayer:IsHasPolicy(i) then
				return true
			else
				return false
			end
		end
	end
	return true
end


GameEvents.PlayerCanConstruct.Add(BelzhorashOwesMeaBeer)

It's another beer, though. Or a bear. A bear would be awesome.
 
The following, "create an empty array and assign values into it"
Code:
local tPolicyToBuild = {}
tPolicyToBuild[GameInfoTypes.POLICY_I] = GameInfoTypes.BUILDING_I
tPolicyToBuild[GameInfoTypes.POLICY_II] = GameInfoTypes.BUILDING_II
...
tPolicyToBuild[GameInfoTypes.POLICY_N] = GameInfoTypes.BUILDING_N

can be rewritten as, "create an array an initialise it"
Code:
local tPolicyToBuild = {
  [GameInfoTypes.POLICY_I] = GameInfoTypes.BUILDING_I,
  [GameInfoTypes.POLICY_II] = GameInfoTypes.BUILDING_II,
  ...
  [GameInfoTypes.POLICY_N] = GameInfoTypes.BUILDING_N
}

BUT, if we "invert" the logic from "this policy grants that building" to "this building requires that policy"
Code:
local tBuildToPolicy = {
  [GameInfoTypes.BUILDING_I] = GameInfoTypes.POLICY_I,
  [GameInfoTypes.BUILDING_II] = GameInfoTypes.POLICY_II,
  ...
  [GameInfoTypes.BUILDING_N] = GameInfoTypes.POLICY_N
}

the PlayerCanConstruct event handler can be rewritten without the loop

was
Code:
function BelzhorashOwesMeaBeer(iPlayer, iBuildingType)
	local pPlayer = Players[iPlayer]
	for i, v in pairs(tPolicyToBuild) do
		if v == iBuildingType then
			if pPlayer:IsHasPolicy(i) then
				return true
			else
				return false
			end
		end
	end
	return true
end
GameEvents.PlayerCanConstruct.Add(BelzhorashOwesMeaBeer)

becomes
Code:
function BelzhorashOwesSomeoneABear(iPlayer, iBuildingType)
	if (tBuildToPolicy[iBuildingType]) then
		return Players[iPlayer]:IsHasPolicy(tBuildToPolicy[iBuildingType])
	end
	
	return true
end
GameEvents.PlayerCanConstruct.Add(BelzhorashOwesSomeoneABear)

See also Data Driven Code Technique
 
Wow, thanks a lot, whoward!
I will try this today.

Just some questions, if you allow, for me to understand that piece of Lua:
- tBuildToPolicy is an two-dimensional array filled with GameInfoTypes.Buildings and GameInfoTypes.Policies.
- We give the variables iBuildingType and iPlayer to the function BelzhorashOwesSomeoneABear().

> I understand that the Script gets iPlayer from the global "environment". But where does iBuildingType come from? Is it an global variable, too? And what Building is meant by iBuildingType?

> Is IsHasPolicy a global function?

> What is the trigger to start the script? GameEvents.PlayerCanConstruct.Add(BelzhorashOwesSomeoneABear) is the action taken, but where do we tell the engine to fire off?


I hope these questions aren't too stupid, though.

Thank you, again!
 
IMPORTANT: If you are doing a "blind copy and paste" of the code in my post above, you just want the 3rd and 5th block!

Wow, thanks a lot, whoward!
Don't forget bane_ ... I just edited their initial code!

- tBuildToPolicy is an two-dimensional array filled with GameInfoTypes.Buildings and GameInfoTypes.Policies.
tBuildToPolicy is a ONE dimensional array, that is
Code:
tBuildToPolicy[i] = a

a TWO dimensional array would be
Code:
tBuildToPolicy[x][y] = b

A one-dimensional array is the classic list, a two-dimensional array is the classic table.

Humans typically think of lists/tables with ascending numerical indices - "first I do this, second I do that ..." or "the treasure is in grid 20 across and 35 up". Lua doesn't care what the index is - it can be a number or a string, think a list of rooms in your house by name and the colour you're going to paint them "Hall: pale green, Kitchen: Cream, Living Room: Lilac" (I should probably never be given a job as an interior designer!) Lua arrays can also contain "holes", think of an array that indexes racing number with F1 driver, so f1Name[44] = "Lewis Hamilton", we don't have all the values of f1Name[1] to f1Name[43], only some of them. You can also mix number and string indices in the same array - we could create f1Drivers[44] = "Lewis Hamilton" and f1Drivers["Lewis Hamilton"] = 44

- We give the variables iBuildingType and iPlayer to the function BelzhorashOwesSomeoneABear().
> I understand that the Script gets iPlayer from the global "environment". But where does iBuildingType come from? Is it an global variable, too? And what Building is meant by iBuildingType?

No, both iPlayer and iBuildingType are passed from within the game core DLL (C++ code) to the Lua script. iPlayer will be ANY valid player (human and AI) in the game, and not just the current player. iBuildingType will be what the game core DLL is trying to work out if the player can construct that building type.

> Is IsHasPolicy a global function?
Not a global function, but a method on the player object. You will see
Code:
local pPlayer = Players[iPlayer]
a lot. iPlayer is the integer id of the player under consideration. Players[] is a global array of all players in the game. pPlayer is then the "thing" (an object in programming terms) that represents that player.

> What is the trigger to start the script? GameEvents.PlayerCanConstruct.Add(BelzhorashOwesSomeoneABear) is the action taken, but where do we tell the engine to fire off?
As the game starts up, all Lua scripts (provided you have set them up properly, see the 3rd link in my sig) are executed. As the
Code:
GameEvents.PlayerCanConstruct.Add(BelzhorashOwesMeaBeer)
line is NOT within a function it executes and "hooks" the event, so that every time the event "fires" (ie the game core DLL wants to know if it can possibly construct a building) the BelzhorashOwesMeaBeer(iPlayer, iBuildingType) function (technically an event handler) is executed.

HTH (but it probably just adds more questions)

W
 
Hello whoward!
Actually, your explanations did not add more questions. ;)

I had a completely different (and obviously wrong) understanding how PlayerCanConstruct works. See, it reads like it would look what a player can construct. :mischief:
(Not easy to explain my confused thoughts, though.)

This:
and not just the current player
iPlayer is the integer id of the player under consideration
helped a lot to understand.

Thank you again and also thank you, bane_ for the first version of the script!

I think I owe both of you a
Spoiler :
30-full.jpg


Thanks,
belzho!
 
Hello whoward and bane_!
I now have tested the code and am running into errors which I don't understand.

First, using bane_'s array creation I get
PoliciesBuildings.lua:14: attempt to index global 'tBuildToPolicy' (a nil value)

Second, using whoward's array I get
PoliciesBuildings.lua:2: '}' expected (to close '{' at line 1) near '='


This is my code (with whoward's solution):
Code:
local tBuildToPolicy = {
  GameInfoTypes.BUILDINGCLASS_SPM_GRANARY = GameInfoTypes.POLICY_BRANCH01_01,
  GameInfoTypes.BUILDINGCLASS_SPM_BAKERY = GameInfoTypes.POLICY_BRANCH01_02,
  GameInfoTypes.BUILDINGCLASS_SPM_BUTCHER = GameInfoTypes.POLICY_BRANCH01_03,
}

function BelzhorashOwesSomeoneABear(iPlayer, iBuildingType)
	if (tBuildToPolicy[iBuildingType]) then
		return Players[iPlayer]:IsHasPolicy(tBuildToPolicy[iBuildingType])
	end
	
	return true
end
GameEvents.PlayerCanConstruct.Add(BelzhorashOwesSomeoneABear)


Please help me a (last) time.

Thank you!
 
Hello whoward and bane_!
I now have tested the code and am running into errors which I don't understand.

First, using bane_'s array creation I get


Second, using whoward's array I get



This is my code (with whoward's solution):
Code:
local tBuildToPolicy = {
  GameInfoTypes.[COLOR="red"][B]BUILDINGCLASS[/B][/COLOR]_SPM_GRANARY = GameInfoTypes.POLICY_BRANCH01_01,
  GameInfoTypes.[COLOR="red"][B]BUILDINGCLASS[/B][/COLOR]_SPM_BAKERY = GameInfoTypes.POLICY_BRANCH01_02,
  GameInfoTypes.[COLOR="red"][B]BUILDINGCLASS[/B][/COLOR]_SPM_BUTCHER = GameInfoTypes.POLICY_BRANCH01_03[COLOR="Red"][B],[/B][/COLOR]
}

function BelzhorashOwesSomeoneABear(iPlayer, iBuildingType)
	if (tBuildToPolicy[iBuildingType]) then
		return Players[iPlayer]:IsHasPolicy(tBuildToPolicy[iBuildingType])
	end
	
	return true
end
GameEvents.PlayerCanConstruct.Add(BelzhorashOwesSomeoneABear)


Please help me a (last) time.

Thank you!
  1. In one of his examples, whoward had an extra uneeded comma. A little hard to see, but shown in red in the code I quoted from your post.
  2. The PlayerCanConstruct GameEvent is for use with Buildings and not BuildingClasses, so 1st off you will never get a proper match-up between what is being passed to the function for variable iBuildingType.
    • iBuildingType will be an integer value that corresponds to a Building's <ID> column from game-table <Buildings>.
    • But by stating GameInfoTypes.BUILDINGCLASS_SPM_GRANARY (for example) you are telling the game to grab the data from the <ID> column from game-table <BuildingClasses>.
  3. Also, when I used whoward's method from a few posts back to streamline some code in one of my mods, I had to edit slighltly to be as like this:
    Code:
    local tBuildToPolicy = { [color="blue"][b][[/b][/color]GameInfoTypes.BUILDING_SPM_GRANARY[color="blue"][b]][/b][/color] = GameInfoTypes.POLICY_BRANCH01_01,
      [color="blue"][b][[/b][/color]GameInfoTypes.BUILDING_SPM_BAKERY[color="blue"][b]][/b][/color] = GameInfoTypes.POLICY_BRANCH01_02,
      [color="blue"][b][[/b][/color]GameInfoTypes.BUILDING_SPM_BUTCHER[color="blue"][b]][/b][/color] = GameInfoTypes.POLICY_BRANCH01_03 }
    
    function BelzhorashOwesSomeoneABear(iPlayer, iBuildingType)
    	if (tBuildToPolicy[iBuildingType]) then
    		return Players[iPlayer]:IsHasPolicy(tBuildToPolicy[iBuildingType])
    	end
    	
    	return true
    end
    GameEvents.PlayerCanConstruct.Add(BelzhorashOwesSomeoneABear)
    In order to avoid similar "nil value" errors as you were experiencing.
 
Also, when I used whoward's method ...

My bad for mixing JavaScript and Lua :blush:

I've corrected the original post
 
Thank you, LeeS!
Unfortunately, using your code results in an error:
'IsHasPolicy' (a nil value)

I can assure, that both the buildings and the policies are in the game and function properly.

Thank you!
 
Knew I should have checked that from the original code (as it looked odd), it should just be HasPolicy(), not IsHasPolicy()
 
You guys are awesome!
I'm going to try that this evening.

Thank you, once again!


Edit: To be fair, many functions look odd to me (wording-wise)... ;)
 
Consistency from Firaxis would help, as when checking for techs it is "IsHasTech()" and not "HasTech()"
 
Consistency from Firaxis would help, as when checking for techs it is "IsHasTech()" and not "HasTech()"
What is this method 'ConsistencyFromFiraxis(:crazyeye:)' to which you are referring? It doesn't appear to be either a game or player object. Maybe it is for use with a hidden unit.
My bad, folks! :blush::lol:
Well, I completely overlooked the extra 'Is' we were all using. :(
 
As an example of the sort of 'streamlining' thing whoward demonstrated earlier in thread:
  • I had this code where I wanted a specific list of Improvements having been completed to be able to fire a function in lua.
  • I did not want roads and railroads, for example, but I did want most of the others.
  • I could have coded to not fire my function when the improvements I did not want were completed, and to otherwise always fire, but this presumes there are no added improvements from other mods available to the game.
  • This all just as context for what I was doing.
The old text I originally wrote:
Code:
local gImprovementQuarry = GameInfoTypes.IMPROVEMENT_QUARRY
local gImprovementMine = GameInfoTypes.IMPROVEMENT_MINE
local gImprovementAcademy = GameInfoTypes.IMPROVEMENT_ACADEMY
local gImprovementCustomsHouse = GameInfoTypes.IMPROVEMENT_CUSTOMS_HOUSE
local gImprovementManufactory = GameInfoTypes.IMPROVEMENT_MANUFACTORY
local gImprovementCitadel = GameInfoTypes.IMPROVEMENT_CITADEL
local gImprovementHolySite = GameInfoTypes.IMPROVEMENT_HOLY_SITE
local gImprovementFarm = GameInfoTypes.IMPROVEMENT_FARM
local gImprovementPasture = GameInfoTypes.IMPROVEMENT_PASTURE
local gImprovementFishingBoats = GameInfoTypes.IMPROVEMENT_FISHING_BOATS
local gImprovementPlantation = GameInfoTypes.IMPROVEMENT_PLANTATION
local gImprovementCamp = GameInfoTypes.IMPROVEMENT_CAMP
local gImprovementWell = GameInfoTypes.IMPROVEMENT_WELL
local gImprovementPlatform = GameInfoTypes.IMPROVEMENT_OFFSHORE_PLATFORM
local gImprovementFort = GameInfoTypes.IMPROVEMENT_FORT

function ImprovementCompleted( playerID, x, y, improvementID )
	if (improvementID == gImprovementQuarry)
		or (improvementID == gImprovementMine)
		or (improvementID == gImprovementAcademy)
		or (improvementID == gImprovementCustomsHouse)
		or (improvementID == gImprovementManufactory)
		or (improvementID == gImprovementCitadel)
		or (improvementID == gImprovementFarm)
		or (improvementID == gImprovementPasture)
		or (improvementID == gImprovementFishingBoats)
		or (improvementID == gImprovementPlantation)
		or (improvementID == gImprovementCamp)
		or (improvementID == gImprovementWell)
		or (improvementID == gImprovementPlatform)
		or (improvementID == gImprovementFort)
		or (improvementID == gImprovementHolySite) then
		gFiredByImprovement = true
		print("ImprovementCompleted was fired with a value of " .. tostring(improvementID))
		WonderResources(playerID)
	end
end
GameEvents.BuildFinished.Add(ImprovementCompleted)
New Code using whoward's method, and creating a table to 'hold' the list of improvements I want to fire the function WonderResources(playerID) when one of these improvements is completed:
Code:
local gImprovementsConsidered = { [GameInfoTypes.IMPROVEMENT_QUARRY] = "true",
[GameInfoTypes.IMPROVEMENT_MINE] = "true",
[GameInfoTypes.IMPROVEMENT_ACADEMY] = "true",
[GameInfoTypes.IMPROVEMENT_CUSTOMS_HOUSE] = "true",
[GameInfoTypes.IMPROVEMENT_MANUFACTORY] = "true",
[GameInfoTypes.IMPROVEMENT_CITADEL] = "true",
[GameInfoTypes.IMPROVEMENT_HOLY_SITE] = "true",
[GameInfoTypes.IMPROVEMENT_FARM] = "true",
[GameInfoTypes.IMPROVEMENT_PASTURE] = "true",
[GameInfoTypes.IMPROVEMENT_FISHING_BOATS] = "true",
[GameInfoTypes.IMPROVEMENT_PLANTATION] = "true",
[GameInfoTypes.IMPROVEMENT_CAMP] = "true",
[GameInfoTypes.IMPROVEMENT_WELL] = "true",
[GameInfoTypes.IMPROVEMENT_OFFSHORE_PLATFORM] = "true",
[GameInfoTypes.IMPROVEMENT_FORT] = "true" }

function ImprovementCompleted( playerID, x, y, improvementID )
	if (gImprovementsConsidered[improvementID]) then
		gFiredByImprovement = true
		print("ImprovementCompleted was fired with a value of " .. tostring(improvementID))
		WonderResources(playerID)
	end
end
GameEvents.BuildFinished.Add(ImprovementCompleted)
I didn't gain much code simplification in creating the table as opposed to listing each Improvement under its own local variable name, but the code within function ImprovementCompleted() was both (a) greatly simplified and (b) more adaptive to changes I make later in the list of improvements I want to fire off function WonderResources(playerID). In the case of "b" all I need do is add or remove to/from the definition of table gImprovementsConsidered and the rest of the code is automatically adaptive to that change.
 
Could someone please tell me, how to make a Lua code in order to range attack with a ranged unit?
I have already used pUnit:PushMission(MISSION_MOVE_TO, x, y) to attack with a melee unit. But if I use MISSION_RANGED_ATTACK, then it apparantly does the same as MISSION_MOVE_TO, but no ranged attack.
However the AI succesfully uses the PushMission function for ranged attacks in CvTacticalAI.cpp. I do not know how to correct the DLL for a properly working ranged attack command in Lua.


EDIT: Ok, I solved the problem. Apparently Firaxis mixed up the missiontype enumerations. So I just used the right code instead of the mission type and it works. That is I have to use PushMission(19, x, y) instead of PushMission(MISSION_RANGED_ATTACK, x, y), because the actual code for a ranged attack is 19.
 
Back
Top Bottom