Calling events and finding enemy civs

AW Arcaeca

Deus Vult
Joined
Mar 10, 2013
Messages
3,019
Location
Operation Padlock ground zero
Those are two things I need quite a lot, but never actually know how to do.

For example, in an upcoming civ, the UU has this effect:
"+10% combat if it starts its turn adjacent to another *New Unit Name*. +15% combat if it starts its turn within 2 tiles of an enemy capital."

Hopefully that's not UP.
Anyway I ran into some problems whilst coding it. This is only a fragment of the code, i.e. not the +10% clause because that's already done and without problems.
Code:
GameEvents.PlayerDoTurn.Add(
function(playerID)
	pPlayer = Players[playerID]
	if (pPlayer:IsAlive()) then
		for pUnit in pPlayer:Units() do
			for iPlayer in Players do
				if (Teams[pPlayer:GetTeam()]:IsAtWar(Teams[iPlayer:GetTeam()]) then
					if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEWUNIT) then
						if (Map.PlotDistance(pUnit:GetX(), pUnit:GetY(), iPlayer:GetCapitalCity:GetX(), iPlayer:GetCapitalCity:GetY()) >= 2) then
							pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, true)
						else
							pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, false)
						end
					end
				end
			end
		end
	end
end)
My main issues are:
1) This line:
Code:
for iPlayer in Players do
Something tells me "Players" will require an extra parameter, but I don't know what. [iPlayer] immediately after it?

2) The event hook (I think that's what it's called) - GameEvents.PlayerDoTurn.Add. It's the only one I know but it sounds incredibly inconvenient to move your unit next to an enemy city but have to wait until your next turn for the extra bonus. Is anyone aware of a hook that fires when a unit is moved?

3) A better unit idea. :p Also it's worth pointing out that the unit isn't intended to have a bonus directly against cities (which is stealing the mandekalu's ability) but rather just in proximity of them, sort of to protect your melee/archery/siege units.

Any ideas?
TIA :goodjob:
 
To iterate over everyone in the Players table, the for statement is:

Code:
for iPlayer, pPlayer in pairs(Players) do

But, oftentimes, you only want to loop over every major Civilization, excluding CSes and Barbs to save time. To do that, there's a better way:

Code:
for i = 0, GameDefines.MAX_MAJOR_CIVS -1, 1 do
   local pPlayer = Players[i]



To trigger something when a unit is moved, use: GameEvents.UnitSetXY.Add. It passes the variables of iPlayer (player ID), iUnitID (the unit ID), iX (the unit's X position), iY (the unit's Y position).
 
Alright then, this is the revised coding:
Code:
GameEvents.UnitSetXY.Add(
function(pPlayer, pUnit, pUnitX, pUnitY)
	for iPlayer, pPlayer in pairs(Players) do
		if (pPlayer:IsAlive()) then
			for pUnit in pPlayer:Units() do
				local pUnitX = punit:GetX()
				local pUnitY = pUnit:GetY()
				if (pPlayer (Teams[pPlayer:GetTeam()]:IsAtWar(Teams[iPlayer:GetTeam()]) then
					if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEWUNIT) then
						if (Map.PlotDistance(pUnitX, pUnitY, iPlayer:GetCapitalCity:GetX(), iPlayer:GetCapitalCity:GetY()) >= 2) then
							pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, true)
						else
							pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, false)
						end
					end
				end
			end
		end
	end
end)
I'm not sure, though, about whether or not pPlayer will work in the hook as a parameter.
And also, the variables in the hook, they don't need to be defined straightaway, do they?
 
Code:
GameEvents.UnitSetXY.Add(
function(pPlayer, pUnit, pUnitX, pUnitY)
	for iPlayer, pPlayer in pairs(Players) do
		if (pPlayer:IsAlive()) then
			for pUnit in pPlayer:Units() do
				local pUnitX = punit:GetX()
				local pUnitY = pUnit:GetY()
				if (pPlayer (Teams[pPlayer:GetTeam()]:IsAtWar(Teams[iPlayer:GetTeam()]) then
					if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEWUNIT) then
						if (Map.PlotDistance(pUnitX, pUnitY, iPlayer:GetCapitalCity:GetX(), iPlayer:GetCapitalCity:GetY()) >= 2) then
							pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, true)
						else
							pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, false)
						end
					end
				end
			end
		end
	end
end)

Few things:

  • The UnitSetXY event passes IDs, not pointers. While naming those variables starting with a "p" won't actually break anything if you keep it consistent, it's still good practice to preface IDs with the letter "i" instead since it's the generally accepted naming convention.
  • You're defining your loop with a variable that was already defined (pPlayer), which could potentially cause an issue.
  • The way you've coded it, it is going to check every single unit on the map, every single time a unit moves one tile. This is going to introduce serious processing overhead between turns.
  • You've actually made it check to where the unit gets its promotion when it is further than two tiles from the enemy Capital.

Untested, but try this one.

Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	if iPlayer < GameDefines.MAX_MAJOR_CIVS then
		local pPlayer = Players[iPlayer]
		local pUnit = pPlayer:GetUnitByID(iUnit)
		if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEWUNIT) then
			for i = 0, GameDefines.MAX_MAJOR_CIVS -1, 1 do
				local pEnemyPlayer = Players[iEnemyPlayer]
				if Teams[pPlayer:GetTeam()]:IsAtWar(pEnemyPlayer:GetTeam()) then
					if (Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity:GetX(), pEnemyPlayer:GetCapitalCity:GetY()) <= 2) then
						pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, true)
						return
					else
						pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, false)
					end
				end
			end
		end
	end
end)
 
Few things:

  • The UnitSetXY event passes IDs, not pointers. While naming those variables starting with a "p" won't actually break anything if you keep it consistent, it's still good practice to preface IDs with the letter "i" instead since it's the generally accepted naming convention.
  • You're defining your loop with a variable that was already defined (pPlayer), which could potentially cause an issue.
  • The way you've coded it, it is going to check every single unit on the map, every single time a unit moves one tile. This is going to introduce serious processing overhead between turns.
  • You've actually made it check to where the unit gets its promotion when it is further than two tiles from the enemy Capital.

Untested, but try this one.

Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	if iPlayer < GameDefines.MAX_MAJOR_CIVS then
		local pPlayer = Players[iPlayer]
		local pUnit = pPlayer:GetUnitByID(iUnit)
		if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEWUNIT) then
			for i = 0, GameDefines.MAX_MAJOR_CIVS -1, 1 do
				local pEnemyPlayer = Players[iEnemyPlayer]
				if Teams[pPlayer:GetTeam()]:IsAtWar(pEnemyPlayer:GetTeam()) then
					if (Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity:GetX(), pEnemyPlayer:GetCapitalCity:GetY()) <= 2) then
						pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, true)
						return
					else
						pUnit:SetHasPromotion(GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION, false)
					end
				end
			end
		end
	end
end)

Is it still possible to do the whole "for iPlayer, pPlayer in pairs(Players) do" clause though? That way I could expand the bonus to being in the proximity of CSs. (I think)

And it fires when any unit on the entire map moves? Wasn't it specified as a parameter to check the movement of iUnit, which is only iPlayer's units?
 
Yes, you could use a for loop over the whole Players table if you want it to check CSes too.

UnitSetXY fires when any unit on the map moves. The variables it passes to the function are based on what unit was moved and who it belonged to.


Sent from my iPhone using Tapatalk
 
Well, uh, how about Events.UnitMoveQueue.Add instead? Or does that iterate over every unit on the map too?

And sort of related, I'm looking for a way to add a promo to a certain unit (not the same one as the other lua) if it moves into a city or directly adjacent to it, although, like the previous lua we've discussed, I'm trying to get it to not take effect at the beginning of your turn.
Code:
Events.UnitMoveQueue.Add(
function(iPlayer, iUnit, true)
	local iPlayer = Players[iPlayer]
	local iPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	for iUnit in iPlayer:Units() do
		if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
			for iCity in iPlayer:Cities() do
				if (Map.PlotDistance(iUnit:GetX(), iUnit:GetY(), iCity:GetX(), iCity:GetY()) <= 1) then
					if not (iUnit:IsHasPromotion(iPromotion)) then
						iUnit:SetHasPromotion(iPromotion, true)
					end
				else
					iUnit:SetHasPromotion(iPromotion, false)
				end
			end
		end
	end
end)
Also... based on how you've structured the lua - have patience with me, I'm a noob at lua and sql - where would I put "for pPlayer, iPlayer in pairs[Players] do"? And do I really need iPlayer, since you've added iEnemyPlayer?
 
Well, uh, how about Events.UnitMoveQueue.Add instead?

Never used that, but because it is an Event instead of a GameEvent, I wouldn't assume it would work very well. Events are typically only used to refresh or update the UI, meaning that they have a tendency to be useless if you want something which works for both players and AI. Based off of its name, it probably fires when the player moves a unit to a tile outside of their movement radius (i.e. the movement circles and arrows with how many turns it will take the unit to reach each tile).


UnitSetXY doesn't check each unit on the map every time it's fired; it's just that you had it coded that way since you had a "for Units()" loop inside of a "for * in pairs(Players)" loop.

And sort of related, I'm looking for a way to add a promo to a certain unit (not the same one as the other lua) if it moves into a city or directly adjacent to it, although, like the previous lua we've discussed, I'm trying to get it to not take effect at the beginning of your turn.

Instead of iterating over all cities, it would be better to just check the tile the unit is on, as well as all adjacent tiles to it, for the presence of a city. I can give you a code example later when I'm at my home computer.

Also... based on how you've structured the lua - have patience with me, I'm a noob at lua and sql - where would I put "for pPlayer, iPlayer in pairs[Players] do"? And do I really need iPlayer, since you've added iEnemyPlayer?

The only time you need to iterate over the Players for this code is to check which Civs that the unit owner is at war with (the pEnemyPlayer). For performance, you only want to check the specific unit that's moved when a UnitSetXY is called.
 
Well, as long as your coding works and can be extended to CSs too, I'll take it.

So if Events.UnitMoveQueue doesn't work, this is the updated lua using GameEvents.UnitSetXY:
Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	local iPlayer = Players[iPlayer]
	local iPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	for iUnit in iPlayer:Units() do
		local iUnitX = iUnit:GetX()
		local iUnitY = iUnit:GetY()
		if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
			for iCity in iPlayer:Cities() do
				if (Map.PlotDistance(iUnitX, iUnitY, iCity:GetX(), iCity:GetY()) <= 1) then
					if not (iUnit:IsHasPromotion(iPromotion)) then
						iUnit:SetHasPromotion(iPromotion, true)
					end
				else
					iUnit:SetHasPromotion(iPromotion, false)
				end
			end
		end
	end
end)

And (maybe) one last thing:
The unit - the first one we were trying to develop an ability for, with moving into an enemy capital and all - will have another ability, and that is, when it dies, the empire gains gold equal to the experience that unit accumulated in its lifetime. This is my guess on how to code that:
Code:
GameEvents.UnitKilledInCombat.Add(
function(mPlayer, iPlayer)
	for mPlayer, iPlayer in pairs(Players) do
		local pPlayer = Players[iPlayer]
		local mPlayer = Players[iEnemyPlayer]
		for pUnit in pPlayer:Units() do
			if (pUnit:GetUnitType() == GameInfoType.UNIT_NEWUNIT) then
				local pUnitExp = pUnit:GetExperience()
				if (pUnit:IsDead()) then
					pPlayer:ChangeGold(pUnitExp)
				else return end
			end
		end
	end
end)
I feel like, though, that I'm defining mPlayer twice and that mPlayer may not be necessary at all. Is that about right? It's only even in there because "killer" is listed as a parameter for GameEvents.UnitKilledInCombat and it has to come before "killee".
 
So if Events.UnitMoveQueue doesn't work, this is the updated lua using GameEvents.UnitSetXY:
Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	local iPlayer = Players[iPlayer]
	local iPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	for iUnit in iPlayer:Units() do
		local iUnitX = iUnit:GetX()
		local iUnitY = iUnit:GetY()
		if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
			for iCity in iPlayer:Cities() do
				if (Map.PlotDistance(iUnitX, iUnitY, iCity:GetX(), iCity:GetY()) <= 1) then
					if not (iUnit:IsHasPromotion(iPromotion)) then
						iUnit:SetHasPromotion(iPromotion, true)
					end
				else
					iUnit:SetHasPromotion(iPromotion, false)
				end
			end
		end
	end
end)

See, you're making it loop over all of the units again. UnitSetXY happens every time any unit on the map is moved, and it passes the UnitID to the function, so looping over all of the owner's units introduces unnecessary overhead when you can just use pPlayer:GetUnitByID() to make the function check only the unit that actually got moved.

Also, since you're checking specifically for capitals only, there won't be any need to loop over the enemy players' Cities; just get the X location and Y location of their Capital with pPlayer:GetCapitalCity():GetX() and pPlayer:GetCapitalCity():GetY().

The unit - the first one we were trying to develop an ability for, with moving into an enemy capital and all - will have another ability, and that is, when it dies, the empire gains gold equal to the experience that unit

This one is a little involved. You'll have to use both CanSaveUnit and UnitKilledInCombat, because once UnitKilledInCombat fires, you won't be able to get the killed unit's XP since it will already be dead. Take a look at this thread for some information on these functions. Basically, you'll need to define a local variable outside of the functions, have CanSaveUnit store the unit's XP, then make the UnitKilledInCombat function grant the gold to its owner.
 
Hey, sorry it took so long and for the bump. :blush: Kinda completely forgot about this thread. (Also I got lazy with modding for a while)
So I just now kind of realized what you meant by "looping over player units several times". Kind of sad, but true. So then the revised coding should go something like this?
Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	local iPlayer = Players[iPlayer]
	local iPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	local iUnit = iPlayer:GetUnitByID()
	local iUnitX = iUnit:GetX()
	local iUnitY = iUnit:GetY()
	if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
		for iCity in iPlayer:Cities() do
			if (Map.PlotDistance(iUnitX, iUnitY, iCity:GetX(), iCity:GetY()) <= 1) then
				if not (iUnit:IsHasPromotion(iPromotion)) then
					iUnit:SetHasPromotion(iPromotion, true)
				end
			else
				iUnit:SetHasPromotion(iPromotion, false)
			end
		end
	end
end)
Or should iPlayer:GetUnitByID() actually replace iUnit in the function parameters?

And now I'm even more confused on how to do the whole "gold and experience on unit death" thing, but this is my best guess:
Code:
GameEvents.CanSaveUnit.Add(
function(iPlayer, iUnit)
	local iPlayer = Players[iPlayer]
	local iUnit = iPlayer:GetUnitByID()
	local iUnitExp = iUnit:GetExperience()
	if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
		if (GameEvents.UnitKilledInCombat(iUnit)) then
			iPlayer:ChangeGold(iUnitExp)
		end
	end
end)
I'm assuming you probably can't use an event hook as a Boolean condition in lua... :crazyeye:
 
Hey, sorry it took so long and for the bump. :blush: Kinda completely forgot about this thread. (Also I got lazy with modding for a while)
So I just now kind of realized what you meant by "looping over player units several times". Kind of sad, but true. So then the revised coding should go something like this?
Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	local iPlayer = Players[iPlayer]
	local iPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	local iUnit = iPlayer:GetUnitByID()
	local iUnitX = iUnit:GetX()
	local iUnitY = iUnit:GetY()
	if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
		for iCity in iPlayer:Cities() do
			if (Map.PlotDistance(iUnitX, iUnitY, iCity:GetX(), iCity:GetY()) <= 1) then
				if not (iUnit:IsHasPromotion(iPromotion)) then
					iUnit:SetHasPromotion(iPromotion, true)
				end
			else
				iUnit:SetHasPromotion(iPromotion, false)
			end
		end
	end
end)
Or should iPlayer:GetUnitByID() actually replace iUnit in the function parameters?

Closer, but now you're defining variables which are already defined. Everything in the parentheses after the function declaration is already defined as a variable and has already been given an appropriate value. Though you will still need to get a pPlayer object from the iPlayer integer (pPlayer = Players[iPlayer]), and a pUnit object from the iUnitID integer (pUnit = pPlayer:GetUnitByID(iUnitID)). Also, you now aren't looping through the players, so it's only going to check if it is near its owner's capital.

Have you tried the code I provided earlier?

And now I'm even more confused on how to do the whole "gold and experience on unit death" thing, but this is my best guess:
Code:
GameEvents.CanSaveUnit.Add(
function(iPlayer, iUnit)
	local iPlayer = Players[iPlayer]
	local iUnit = iPlayer:GetUnitByID()
	local iUnitExp = iUnit:GetExperience()
	if (iUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
		if (GameEvents.UnitKilledInCombat(iUnit)) then
			iPlayer:ChangeGold(iUnitExp)
		end
	end
end)
I'm assuming you probably can't use an event hook as a Boolean condition in lua... :crazyeye:

Explaining this one might be a bit tough without an example, so I'll post a copy of one of my more recent Lua scripts. This script (aside from the StrengthFromVictoryPoints function) uses CanSaveUnit and UnitKilledInCombat to deal damage to adjacent units upon the death of a unit which has one or more promotions with a new attribute I defined in SQL. http://pastebin.com/FwzE07GS
 
Oooh. Forgot to put the player iterator in. :shake:
Alright, I took out the redundant iUnitX and iUnitY... unless those needed to remain defined? I also changed the player and unit variables so that I'm not defining things twice.
Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	local pPlayer = Players[iPlayer]
	local pPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	local pUnit = pPlayer:GetUnitByID(iUnitID)
	if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
		for pCity in pPlayer:Cities() do
			if (Map.PlotDistance(iUnitX, iUnitY, iCity:GetX(), iCity:GetY()) <= 1) then
				if not (pUnit:IsHasPromotion(pPromotion)) then
				iUnit:SetHasPromotion(pPromotion, true)
				end
			else
				iUnit:SetHasPromotion(pPromotion, false)
			end
		end
	end
end)
And no, I haven't tried your coding yet because we (I?) were still trying to figure out how to fit "for iPlayer, pPlayer in pairs(Players) do" instead of the MAX_PLAYERS and all that, so that the ability could be extended to being near CSs.

Going to look through the script and see what I can learn! :)
 
Oooh. Forgot to put the player iterator in. :shake:
Alright, I took out the redundant iUnitX and iUnitY... unless those needed to remain defined? I also changed the player and unit variables so that I'm not defining things twice.
Code:
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	local pPlayer = Players[iPlayer]
	local pPromotion = GameInfoTypes.PROMOTION_NEW_UNIT_PROMO
	local pUnit = pPlayer:GetUnitByID(iUnitID)
	if (pUnit:GetUnitType() == GameInfoTypes.UNIT_NEW_UNIT) then
		for pCity in pPlayer:Cities() do
			if (Map.PlotDistance(iUnitX, iUnitY, iCity:GetX(), iCity:GetY()) <= 1) then
				if not (pUnit:IsHasPromotion(pPromotion)) then
				iUnit:SetHasPromotion(pPromotion, true)
				end
			else
				iUnit:SetHasPromotion(pPromotion, false)
			end
		end
	end
end)

Looking better, but you still haven't made it iterate over any players, so it will still only check for being near the capital of the unit owner. While we don't want to iterate over all units, we do need to iterate over each player for whose capital we're checking proximity to.

And no, I haven't tried your coding yet because we (I?) were still trying to figure out how to fit "for iPlayer, pPlayer in pairs(Players) do" instead of the MAX_PLAYERS and all that, so that the ability could be extended to being near CSs.

Going to look through the script and see what I can learn! :)

My bad. Here's how it would look then (also going to localize more variables since it helps with processing speed and makes it easier to adjust the code if you want to paste it to another Civ with a different UU/promotion):

Code:
local iUUType = GameInfoTypes.UNIT_NEWUNIT
local iPromotionType = GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION

local iMaxCivs = GameDefines.MAX_MAJOR_CIVS
local iMaxPlayers = GameDefines.MAX_PLAYERS

GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	if iPlayer <  iMaxCivs then
		local pPlayer = Players[iPlayer]
		local pUnit = pPlayer:GetUnitByID(iUnit)
		if pUnit:GetUnitType() == iUUType then
			for i = 0, iMaxPlayers - 1, 1 do
				local pEnemyPlayer = Players[iEnemyPlayer]
				if Teams[pPlayer:GetTeam()]:IsAtWar(pEnemyPlayer:GetTeam()) then
					if (Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity:GetX(), pEnemyPlayer:GetCapitalCity:GetY()) <= 2) then
						pUnit:SetHasPromotion(iPromotionType, true)
						return
					else
						pUnit:SetHasPromotion(iPromotionType, false)
					end
				end
			end
		end
	end
end)
 
Looking better, but you still haven't made it iterate over any players, so it will still only check for being near the capital of the unit owner. While we don't want to iterate over all units, we do need to iterate over each player for whose capital we're checking proximity to.
I think this is getting a little confusing. :crazyeye: We're trying to figure out two different lua scripts at once. In this case, the code in question is the one that should give a promotion to a unit - incidentally, the Kingdom of Georgia's UU - when it's adjacent to a friendly city, and another promotion when it's actually stationed in the city. The reason I added in this thread is because I thought it was somewhat relevant because we were already trying to figure out how to trigger an event when a unit actually moves. My bad. :blush: Just for the record, this code is for Georgia.

The other code - the one that needs code to let the unspecified new civ's UU have it's effect near CSs - is the once that needs the player iterator.
My bad. Here's how it would look then (also going to localize more variables since it helps with processing speed and makes it easier to adjust the code if you want to paste it to another Civ with a different UU/promotion):

Code:
local iUUType = GameInfoTypes.UNIT_NEWUNIT
local iPromotionType = GameInfoTypes.PROMOTION_NEWUNIT_SPECIFIC_PROMOTION

local iMaxCivs = GameDefines.MAX_MAJOR_CIVS
local iMaxPlayers = GameDefines.MAX_PLAYERS

GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	if iPlayer <  iMaxCivs then
		local pPlayer = Players[iPlayer]
		local pUnit = pPlayer:GetUnitByID(iUnit)
		if pUnit:GetUnitType() == iUUType then
			for i = 0, iMaxPlayers - 1, 1 do
				local pEnemyPlayer = Players[iEnemyPlayer]
				if Teams[pPlayer:GetTeam()]:IsAtWar(pEnemyPlayer:GetTeam()) then
					if (Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity:GetX(), pEnemyPlayer:GetCapitalCity:GetY()) <= 2) then
						pUnit:SetHasPromotion(iPromotionType, true)
						return
					else
						pUnit:SetHasPromotion(iPromotionType, false)
					end
				end
			end
		end
	end
end)
And this will include CSs? Even though it still has all the "MAX_MAJOR_CIVS" and everything?
 
Erm, hate to majorly necro but the results of the coding (yes, it took 5 months to test the coding, apparently :p) - the coding from ViceVirtuoso's last post (adjusted for real names of the promotion and unit) and what I quoted in my previous post - show up 6 times in the lua.log as an error:

Code:
[27194.718] Runtime Error: C:\Users\AW\Documents\My Games\Sid Meier's Civilization 5\MODS\AW's Super S3cret C1v Mod (v 1)\LUA/UU_NearEnemyCapital.lua:22: attempt to index local 'pEnemyPlayer' (a nil value)
The game also crashes shortly after moving the unit, too. I can't say for certain that it's connected, but it's suspicious nonetheless.

I'm assuming the error boils down to this line:
Code:
local pEnemyPlayer = Players[iEnemyPlayer]
Because I can't see anywhere else in the code where the game would figure out what iEnemyPlayer is.

Given that, how should I restructure the code?

EDIT: &@#%$! Just realized I entirely forgot to code the XML for the promotion! :wallbash: So... let's see if the error still shows up if I add that piece of fairly vital XML to the mod...
 
Okay, so I did that, and... Oh, joy! Now I'm getting errors that don't give any clue as to the real problem!
Code:
[28634.077] Runtime Error: C:\Users\AW\Documents\My Games\Sid Meier's Civilization 5\MODS\AW's Super S3cret C1v Mod (v 1)\LUA/UU_NearEnemyCapital.lua:23: attempt to index a nil value
Don'tcha just love how specific the logs are? :p
This showed up a good 13 times in the log in about a 2-turn period before the game crashed.
Line 23 corresponds to:
Code:
					if (Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity():GetX(), pEnemyPlayer:GetCapitalCity():GetY()) <= 2) then
No clue here. I checked the spelling of pEnemyPlayer and it's identical. iUnitX and iUnitY are within the event hook, so I don't know what the problem could be...

Meanwhile, I'm trying to solve the errors occurring in two other lua files. I'm getting a similar error for this coding:
Code:
GameEvents.PlayerDoTurn.Add(
function(playerID)
	local pPlayer = Players[playerID]
	if (pPlayer:IsAlive()) then
		if (pPlayer:GetCivilizationType() == GameInfoTypes.CIVILIZATION_SUPER_SECRET) then
			[COLOR="Red"]local num_Techs = pPlayer:GetTeam():GetTeamTechs():GetNumTechsKnown()[/COLOR]
			pPlayer:ChangeCulture(num_Techs)
		end
	end
end)
Red line is what the game is apparently having trouble with.
 
A nil value means that there's something wrong with your function and you aren't getting a value. It's probably that one or more of your parameters is undefined.

You can always do a sanity check by printing the values of the variables and the functions, e.g.:
Code:
print( "My unit X: ".. iUnitX ..", Y: ".. iUnitY )
print( "Enemy capital X: ".. pEnemyPlayer:GetCapitalCity():GetX() ..", Y: ".. pEnemyPlayer:GetCapitalCity():GetY() )
print( "Distance to enemy capital: ".. Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity():GetX(), pEnemyPlayer:GetCapitalCity():GetY()) )
 
Alright... so this is getting most suspicious...
I added in a couple of print statements so that when the game crashed again I could see where the code broke away. This is vicevirtuoso's code that I'm using with print statements thrown in every so often:
Spoiler :
Code:
-- UU_NearEnemyCapital
-- Author: Vicevirtuoso
-- DateCreated: 4/27/2014 11:43:31 AM
--------------------------------------------------------------

-- Vicevirtuoso's way

print('Loaded = UU_NearEnemyCapital')

local iUUType = GameInfoTypes.UNIT_UU
local iPromotionType = GameInfoTypes.PROMOTION_UU_NEARCAP

local iMaxCivs = GameDefines.MAX_MAJOR_CIVS
local iMaxPlayers = GameDefines.MAX_PLAYERS

print('Variables predefined. Commence function:')
GameEvents.UnitSetXY.Add(
function(iPlayer, iUnit, iUnitX, iUnitY)
	if iPlayer <  iMaxCivs then
		local pPlayer = Players[iPlayer]
		print('pPlayer identified')
		local pUnit = pPlayer:GetUnitByID(iUnit)
		if pUnit:GetUnitType() == iUUType then
			print('Unit is valid.')
			for i = 0, iMaxPlayers - 1, 1 do
				local pEnemyPlayer = Players[i]
				if Teams[pPlayer:GetTeam()]:IsAtWar(pEnemyPlayer:GetTeam()) then
					print('Checking distance...')
					if (Map.PlotDistance(iUnitX, iUnitY, pEnemyPlayer:GetCapitalCity():GetX(), pEnemyPlayer:GetCapitalCity():GetY()) <= 2) then
						print('Result: Return true')
						pUnit:SetHasPromotion(iPromotionType, true)
						return
					else
						print('Result: Return false')
						pUnit:SetHasPromotion(iPromotionType, false)
					end
				end
			end
		end
	end
end)
So while playtesting I move the UU into a nearby, unclaimed plot, and the result is this:
Code:
[30381.179] UU_NearEnemyCapital: Yowzers! A unit has moved!
[30381.179] UU_NearEnemyCapital: pPlayer identified
[30381.179] UU_NearEnemyCapital: Unit is valid.
[30381.179] UU_NearEnemyCapital: Checking distance...
[30381.179] Runtime Error: C:\Users\AW\Documents\My Games\Sid Meier's Civilization 5\MODS\AW's NewCiv Mod (v 1)\LUA/UU_NearEnemyCapital.lua:30: attempt to index a nil value
The fact that "Checking distance" shows up dismays me. That means that the coding has gotten so far as to iterate through all the other players and find a player with whom I'm apparently at war with... on turn 0. Should it be doing that? Or is it just because it has detected the barbarians as an enemy team?

Whatever the reason, for the life of me I can't figure out why the game is getting so hung up on line 30, which is the one with Map.PlotDistance...
 
Back
Top Bottom