Cromcrom lua stuff and questions about terrain features attrition mod

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Crédits:
Gedemon
LeeS
Infixo
TheRealHellBlazer
Isau
Gleb Bazov
And all the fine people sharing their knowledge and being helpful or supportive.


I don't want to clutter the main LUA thread with my noob questions and bits of codes, so I am opening this thread.
Ultimately, I want to create a mod that will damage units moving or standing into various terrains (especially jungles...)

First bit of code:
Code:
function printBlah()
    local localPlayer        = Players[Game.GetLocalPlayer()];
 --   local pPlayer = Players[pPlayerID];
 local pUnits = localPlayer:GetUnits()
 for k,v in pUnits:Members() do
  local unitPlot = v:GetLocation()
  local featureType = Map.GetPlot(unitPlot)
  local plotName = Plot.GetTerrainType(featureType)
  print(unitPlot)
  print(plotName)
  print(v:GetName())
--[[   local killUnit = math.random(1,5)
  if killUnit <= 1 then
  pUnits:Destroy(v)
  end ]]
   local unitDamage = math.random(30,60)
  v:ChangeDamage(unitDamage)
  print("inflicted "..unitDamage.." to unit")

 end
 print("bloh blah")
end

Events.TurnEnd.Add(printBlah)

This works. However, when a unit reaches 0 or less damage through this ChangeDamage function, it is not destroyed.
 
Last edited:

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Adding this
Code:
if v:GetDamage() >= 100 then
  print("A unit was destroyed through attrition.")
  pUnits:Destroy(v)
  end
solve's it, but this is so unelegant :-(

Now I have to add the feature recognition (jungle and swamps mainly), and add variables according to unit type (scouts would suffer less), technology (some techs would reduce attrition), improvments on terrain (no attrition in improved terrain).
Sigh...
 

anansethespider

Warlord
Joined
Oct 27, 2016
Messages
288
This is very interesting! There's been a mod floating around for some time that makes mountains passable. I would love to see a version that allowed passage of mountains but inflicted huge attrition damage, or had a chance to, unless there was an accompanying sherpa
 

Infixo

Deity
Joined
Jan 9, 2016
Messages
3,862
Location
Warsaw
but this is so unelegant :-(
How else would you like to destroy a unit if not with the Destroy function? Seems that devs have separated unit damaging from destroying and it's actually a good design. Might prevent 'accidental' deaths due to e.g. problems with damage calculations.

local featureType = Map.GetPlot(unitPlot) local plotName = Plot.GetTerrainType(featureType)
So others will not get confused - these functions are not returning feature type nor plot name.
 

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Definitely not, just some numbers and tables. I have to get into this stuff now :)
But you know, as a lua self taught messer, I am in awe everytime I can write Something that doesn't immediately bug, even if its mostly copy pasting others works :)

This is very interesting! There's been a mod floating around for some time that makes mountains passable. I would love to see a version that allowed passage of mountains but inflicted huge attrition damage, or had a chance to, unless there was an accompanying sherpa
Yes, definitely. Once I get the basics sorted out, this is definitely a feature >I would like to add. Although the sherpa thingy might hard to code for now (for me at least :) )
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
Code:
local iExtraDamage = math.random(30,60)
local iCurrentDamage = v:GetDamage()
if (iCurrentDamage + iExtraDamage) >= 100 then
	pUnits:Destroy(v)
else
	v:ChangeDamage(iExtraDamage)
end
or
Code:
local iExtraDamage = math.random(30,60)
if (v:GetDamage() + iExtraDamage) >= 100 then
	pUnits:Destroy(v)
else
	v:ChangeDamage(iExtraDamage)
end
I can't honestly remember now whether civ5's "ChangeDamage()" worked the same way with not doing an automatic "Kill()" when the adjusted amount of damage from lua was greater than 100, but I don't remember that there was an automatic Kill.

The only problem you might run into with killing off a member of Player:GetUnits() while processing through the list of units within Player:GetUnits() is that this might cause de-synch of the iteration through Player:GetUnits():Members. You may need to throw the unit object ("v" in the way you are using it) into a temporary table and only kill off the units added to the temporary table after you are finished running through the list of units.
Spoiler :
Code:
function printBlah()
	local localPlayer        = Players[Game.GetLocalPlayer()];
	--   local pPlayer = Players[pPlayerID];
	local pUnits = localPlayer:GetUnits()
	local tKillUnits = {}
	for k,v in pUnits:Members() do
		local unitPlot = v:GetLocation()
		local featureType = Map.GetPlot(unitPlot)
		local plotName = Plot.GetTerrainType(featureType)
		print(unitPlot)
		print(plotName)
		print(v:GetName())
		--[[   local killUnit = math.random(1,5)
			if killUnit <= 1 then
				pUnits:Destroy(v)
			end ]]

		local unitDamage = math.random(30,60)
		if (v:GetDamage() + unitDamage) >= 100 then
			table.insert(tKillUnits, v)
		else
			v:ChangeDamage(unitDamage)
			print("inflicted "..unitDamage.." to unit")
		end
	end
	--now we've completed the iteration through table pUnits:Members() and can safely delete units listed as members
	for k,pUnit in pairs(tKillUnits) do
		pUnits:Destroy(pUnit)
	end
	print("bloh blah")
end
Events.TurnEnd.Add(printBlah)
 
Last edited:

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Thanks LeeS, I love it.
This bit of code damages player unit every time it moves.
Code:
function damageAndDestroyUnit(pPlayer,iUnit)
 local tKillUnits = {}
 local pPlayerToCheck = Players[pPlayer]
 local pUnits = pPlayerToCheck:GetUnits()
 for i, pUnit in pUnits:Members() do
  if(tostring(pUnit:GetID()) == tostring(iUnit)) then
   local unitDamage = math.random(1,20)
   if (pUnit:GetDamage() + unitDamage) >= 100 then
    table.insert(tKillUnits, pUnit)
   else
    pUnit:ChangeDamage(unitDamage)
    print("inflicted "..unitDamage.." to unit")
   end
  end
 end
 for k,pUnit in pairs(tKillUnits) do
  pUnits:Destroy(pUnit)
  print("a unit was destroyed because of attrition")
 end   
end

function Attrition(PlayerID,UnitID,UnitX,UnitY)
 local localPlayer        = Players[0];
 if Players[PlayerID] == localPlayer then
  --print("bloh blah")
  --print(UnitID)
  damageAndDestroyUnit(PlayerID,UnitID)
 end
end

Events.UnitMoved.Add(Attrition)

Not perfect, certainly improvable, but my baby :)

Now for determining the terrain and feature the unit is moving into, and vary damage accordingly.
I will let you know :)
 

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
So, this weird stuff happens:
Code:
1  local plotToCheck = Map.GetPlotXY(UnitX,UnitY)
  print(plotToCheck)
2  local plotToCheckX = Plot.GetX(plotToCheck)
  print(plotToCheckX)   
3  local plotToCheckY = Plot.GetY(plotToCheck)
  print(plotToCheckY) 
4  local plotName = Plot.GetTerrainType(plotToCheck)
  print(plotName)
5  local plotType = GameInfo.Terrains[1].TerrainType
1 "works", because when a unit moves, 2 and 3 update accordingly. So I conclude the plot is well identified. However, when 4 fires, it returns an integer that has nothing to do with the actual terrain the unit moves on. 5 returns the proper TERRAIN_GRASS_HILLS, as 1 points to.
Any idea please ?
 

TheRealHellBlazer

Chieftain
Joined
Feb 26, 2017
Messages
59
Having done quote some work with maps can you not use:

plotToCheck:GetTerrainType()

Providing plotToCheck is a valid plot table.

Not 100% sure if that is usable past map generation though.
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
The preferred method is to not use "." syntax and instead use ":" syntax wherever possible. This is not possible for "Map.Something" or "Game.Something" or "GameInfo.Something", but for city, unit, plot, player you do not want to use "."

Code:
local plotToCheck = Map.GetPlotXY(UnitX,UnitY)
print(GameInfo.Terrains[plotToCheck:GetTerrainType()].TerrainType)

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

  1. PlotObject:GetTerrainType() returns the integer ID# of the TerrainType's Index number from table <Terrains>.
    • "TERRAIN_GRASS" is the first <Row> in the table and is therefore assigned Index # 0
    • "TERRAIN_GRASS_HILLS" is the second <Row> in the table and is therefore assigned Index # 1
  2. PlotObject:GetResourceType() returns the integer ID# of the ResourceType's Index number from table <Resources>.
    • "RESOURCE_BANANAS" is the first <Row> in the table and is therefore assigned Index # 0
    • "RESOURCE_CATTLE" is the second <Row> in the table and is therefore assigned Index # 1
  3. The Index #'s are auto assigned as an internal mechanic of the game and always start with #0 and work upwards from there in the order the <Rows> are added to the given table
  4. Pretty much every primary-type object within the game will work this way within lua: Buildings, Units, Terrains, Features, Resources, Improvements, Promotions, Policies, Technologies, Civics, etc.

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

If you wish to get the Index# for a particular type of Terrain, say "TERRAIN_DESERT_HILLS", and to stick this index # in a variable in your lua script, do:
Code:
local iDesertHills = GameInfo.Terrains["TERRAIN_DESERT_HILLS"].Index
You can then compare the result of iDesertHills directly against
Code:
plotToCheck:GetTerrainType()
like as this
Code:
if (plotToCheck:GetTerrainType() == iDesertHills) then
	print("The Plot is Desert Hills. Hurray!")
else
	print("The Plot is not Desert Hills. Boo! Hiss!")
end
 

Infixo

Deity
Joined
Jan 9, 2016
Messages
3,862
Location
Warsaw
I use GetTerrainType() as: eTerrain = pPlot:GetTerrainType() where pPlot is an object, so you need to use colon not dot. It returns a enumeration code for Terrain Type which then can be converted with GameInfo.Terrains[].
Don't confuse pPlot with iPlot - some plot functions take/give plot objects and some plot IDs (numbers). That's why using good variable names is so important in Lua.
 

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Thanks lads. However, plotToCheck:GetTerrainType() returns a number (ID) that when it is checked on the terrains in gameinfo, is definitely wrong (I got coast 15 and oceans16 instead of grassland and grassland hills).
My issue is that it identifies the wrong type of terrain.
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
It does not identify the wrong type of terrain, I've used it already in multiple civ6 mods.

You are not getting the plot object that you think you are getting from this:
Code:
local plotToCheck = Map.GetPlotXY(UnitX,UnitY)
Most likely because UnitX,UnitY are not giving you what you think they are giving you.
 

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Ok I got it.
after checking UnitX and UnitY,
local plotToCheck = Map.GetPlotXY(UnitX,UnitY) gives some wrong coordinates. I "assumed" (yeah, sometimes, not having official definitions does that...) that GetPlotXY required and X and Y coordinates. Maybe it does.
however,
Map.GetPlot(UnitX,UnitY)
does the trick.
So now I won't get weird results from stuff. I will now add variables for terrains and features. I might add some random events (like some disease for units in marshes, increasing the damages...)
Whatever, thanks again :)
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
Yeah, I just verified the same:
Code:
Civ6LUA_Testing_File2: The Unit's Plot GetTerrainType via Map.GetPlotXY() = TERRAIN_COAST
Civ6LUA_Testing_File2: The Unit's Plot GetTerrainType Map.GetPlot() = TERRAIN_DESERT
The correct result is " TERRAIN_DESERT"

I always use the Map.GetPlot() method to get a plot object. Didn't actually notice the difference in methods to get a plot from what you were using to what I always use until I double-checked my mods to verify the exact method I have been using and the exact way I was getting the XY to place within the Map.GetPlot() function.
Code:
function PlayerUnitMoved(iPlayer, iUnitID, iX, iY)
	if iPlayer == 0 then
		print("The Unit's Plot GetTerrainType via Map.GetPlotXY() = " .. GameInfo.Terrains[Map.GetPlotXY(iX, iY):GetTerrainType()].TerrainType)
		print("The Unit's Plot GetTerrainType Map.GetPlot() = " .. GameInfo.Terrains[Map.GetPlot(iX, iY):GetTerrainType()].TerrainType)
	end
end
Events.UnitMoved.Add(PlayerUnitMoved)
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
From one of several places Firaxis uses Map.GetPlotXY:
Code:
local plotX = pPlot:GetX();
local plotY = pPlot:GetY();

for dx = -2, 2 do
	for dy = -2,2 do
		local otherPlot = Map.GetPlotXY(plotX, plotY, dx, dy, 2);
		if(otherPlot) then
			if(otherPlot:IsNaturalWonder() == true) then
				ResetTerrain(otherPlot:GetIndex());
			end
		end
	end
end
It requires 5 arguments, the first two of which are the "center plot" XY coords. It appears to be meant to be used as a method to get an outlaying plot based on the XY of a starting plot and the "radial distance" to be used in X and Y from that central plot.

The "dx, dy" values appear to be the radial distance and direction (+/-) along X and Y from the original plot. The final argument of "2" appears to be an integer radius range-value limit. But it isn't clear to me how this method differs from Map.GetPlotXYWithRangeCheck(iStartingX, iStartingY, dx, dy, Radius) in the way the final Radius argument is used.

Map.GetPlotXYWithRangeCheck(iStartingX, iStartingY, dx, dy, Radius) won't give you a plot that is outside the range (specified for argument "Radius") from the starting plot XY (specified for arguments "iStartingX" and "iStartingY"). It is possible Map.GetPlotXY(iStartingX, iStartingY, dx, dy, Radius) acts in an identical manner and is just redundant because sometimes that sort of thing happens.
 

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Yes, thanks for helping me sorting that out.
Code:
function PlayerUnitMoved(iPlayer, iUnitID, iX, iY)
   if iPlayer == 0 then
        print("The Unit's Plot GetTerrainType via Map.GetPlotXY() = " .. GameInfo.Terrains[Map.GetPlotXY(iX, iY):GetTerrainType()].TerrainType)
        print("The Unit's Plot GetTerrainType Map.GetPlot() = " .. GameInfo.Terrains[Map.GetPlot(iX, iY):GetTerrainType()].TerrainType)
    end
end
Events.UnitMoved.Add(PlayerUnitMoved)
I am always in awe to see how "you guys" code so neatly and efficiently :)

BTW, would you happen to know a function that disables a tech, or remove it from the list of researchable techs ? I know they are not supposed to be, but I will need something like this for my upcoming Grand Mod :) An alternative would be to math.huge its research cost, but I rather have it disabled or removed.
 

LeeS

Imperator
Joined
Jul 23, 2013
Messages
7,241
Location
Illinois, USA
Currently so far as I am aware we have no method to selectively disable a tech via lua. You either remove it from the game in XML/SQL and rework the tech tree prereqs and what-not to accomodate, or you can set its research cost way up. But if you set the research cost way up you still need to deal with all the prereq issues and the units and buildings and such the tech unlocks.

The game right now is not really all that friendly in terms of who can research what being dynaically moddable in this way.
 

cromcrom

Cernu
Joined
Nov 11, 2005
Messages
268
Location
Chateauroux
Ok, thanks a lot. I will then look into this when the time comes.

Now for the best part: the system I aim :)
Base attrition: moving unit lose 3-10 HP per move. Normal recovery if doesn't move.
Mountain: attrition x 2
Desert, jungle, : attrition x 3
Ice: attrition x4

then, for every move, or when the unit doesn't move, there is a slight chance (mostly 15%, 40% for mountain, 60% Ice), that Something really bad happens: landslide, blizzard, heatwave, epidemics, that doubles the attrition.

roads greatly reduce attrition, as does era, and maybe some unit types.

Bring'hem ideas.
 
Top Bottom