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

Cromcrom lua stuff and questions about terrain features attrition mod

Discussion in 'Civ6 - Creation & Customization' started by cromcrom, Mar 26, 2017.

  1. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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: Mar 30, 2017
  2. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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...
     
  3. anansethespider

    anansethespider Warlord

    Joined:
    Oct 27, 2016
    Messages:
    289
    Gender:
    Male
    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
     
  4. Infixo

    Infixo Deity

    Joined:
    Jan 9, 2016
    Messages:
    3,685
    Gender:
    Male
    Location:
    Warsaw
    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.

    So others will not get confused - these functions are not returning feature type nor plot name.
     
  5. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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 :)

    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 :) )
     
  6. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    6,910
    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: Mar 26, 2017
  7. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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 :)
     
  8. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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 ?
     
  9. TheRealHellBlazer

    TheRealHellBlazer Chieftain

    Joined:
    Feb 26, 2017
    Messages:
    59
    Gender:
    Male
    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.
     
  10. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    Location:
    Chateauroux
    It returns an integer, that doesn't match 5.
     
  11. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    6,910
    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
     
  12. Infixo

    Infixo Deity

    Joined:
    Jan 9, 2016
    Messages:
    3,685
    Gender:
    Male
    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.
     
  13. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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.
     
  14. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    6,910
    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.
     
  15. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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 :)
     
  16. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    6,910
    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)
     
  17. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    6,910
    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.
     
  18. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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.
     
  19. LeeS

    LeeS Imperator Supporter

    Joined:
    Jul 23, 2013
    Messages:
    6,910
    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.
     
  20. cromcrom

    cromcrom Cernu

    Joined:
    Nov 11, 2005
    Messages:
    268
    Gender:
    Male
    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.
     

Share This Page