Need a check if in friendly territory

Bluur

Chieftain
Joined
Dec 2, 2019
Messages
28
Hello,

New to modding, but not scripting/coding so any help is appreciated. I am making a mod that I would like to have a check if a unit is in friendly territory before executing the new function. There has to be a built in check for this for unit healing but I cant find it.


Best regards,
Bluur
 
Cool, thank you! What do the MinRange and MaxRange correspond to? Do these values change or is there a value that defines friendly vs. not friendly?
 
MinRange & MaxRange refer to the distance that is checked via the requirement to apply a modifier. In this instance, when both are set to 0, the modifier will only apply to units if they are standing ON a friendly territory plot.

Another RequirementID "NEAR_FRIENDLY_TERRITORY" keeps the MinRange at 0, and the MaxRange set to 4, so the modifier applies only to units if they are standing ON a friendly territory plot OR within 4 tiles of a friendly territory plot.

To designate a requirement for unfriendly territory, you'd need to set the "Inverse" column of the requirement to "True" or 1 in SQL.

FYI, there is also, a RequirementID "UNIT_IN_OWNER_TERRITORY_REQUIREMENT", that only checks if the unit is in your territory, not other friendly civ territories. This requirement has no RequirementArguments (i.e. MinRange/MaxRange) defined as is, but can be added if you set up your own requirement.
 
If I understand correctly than the code below may work?

Code:
    if IS_FRIENDLY_TERRITORY_REQUIREMENT="TRUE" then
EXECUTE FUNCTION
else break
 
If I understand correctly than the code below may work?

Code:
    if IS_FRIENDLY_TERRITORY_REQUIREMENT="TRUE" then
EXECUTE FUNCTION
else break
I'm really limited in my knowledge of Lua, so I can't tell you how that would work if at all. You certainly couldn't use a Requirement as part of a Lua statement. You might need to wait for someone who is more versed in that language.
 
Last edited:
Requirements are part of Modifiers subsystem, which is used only in DB via SQL definitions. You cannot use it Lua.

In Lua, you need to:
- get unit's owner - GetOwner()
- get unit's plot or location - GetLocation() or GetX() / GetY() or GetPlotID() and convert it into a plot
- get plot's owner - GetOwner()
- check the diplo relation between the unit's owner and plot's owner - several functions in Player:GetDiplomacy()

There is no "check if unit is in a friendly territory" in Lua.

You may also check out the scenarios - chances are that similar functionality has been implemented by Firaxis somwhere.
 
Disclaimer - I am trying to make a mod using the mod Passable mountains + static and moving attrition by Cromcrom as a base.

So i did some research and came up with this,

Code:
local pPlot = Unit:GetPlot()
local iPlotOwner = pPlot:GetOwner()
local pOtherPlayer
if iPlotOwner = -1 then
    pOtherPlayer = Players[iPlotOwner]   
    Events.TurnEnd.Add(CheckIdleDamage)
    Events.UnitMoved.Add(Attrition)       
else Break
end

The events work fine to apply damage to the units based on terrain without the if loop.

The Lua log gives the following error:

Runtime Error: C:\Users\John\Documents\My Games\Sid Meier's Civilization VI\Mods\Attrition\Rigale_StartScript.lua:227: attempt to index a nil value
stack traceback:
C:\Users\John\Documents\My Games\Sid Meier's Civilization VI\Mods\Attrition\Rigale_StartScript.lua:227: in function '(main chunk)'
[C]: in function '(anonymous)'
Lua callstack:
Error loading file where=, file=C:\Users\John\Documents\My Games\Sid Meier's Civilization VI\Mods\Attrition\Rigale_StartScript.lua
 
You have multiple concept and syntax errors.
  1. These lines result in values of "nil" being assigned to the two variables "pPlot" and "iPlotOwner"
    Code:
    local pPlot = Unit:GetPlot()
    local iPlotOwner = pPlot:GetOwner()
    This is because:

    (a) Unless the Unit-Object "Unit" has been defined already elsewhere within the same script, and that definition has already executed and is still valid when your sample chunk is run, "Unit" is an undefined variable at the time the code executes
    (b) "GetPlot()" is not a valid lua method for unit-objects in Civilization 6
  2. This is incorrect syntax for all versions of lua, regardless of whether used within a Civilization API or some other application of lua like a website interface were lua being used for that:
    Code:
    if iPlotOwner = -1 then
    lua uses "==" in a check for equality and "=" to make one value equal to another

    The proper method to make an equality check as you are attempting is
    Code:
    if iPlotOwner == -1 then
    This will not solve your conceptual problem however since a value of "nil" will never be equal to "-1"
  3. "break" is never capitalized
    • "break" is an lua "command-word"
    • "Break" will be seen as being an undefined variable with a value of "nil"
    In lua Capitalization matters.
  4. A code of Players[-1] is never valid under any conditions and will simply result in a value of "nil". This is exactly what will happen were your code to actually be able to run, since you are only executing the following line when variable "iPlotOwner" has a value of "-1"
    Code:
    pOtherPlayer = Players[iPlotOwner]
  5. Further, this line is completely un-necessary:
    Code:
    else Break
    • A conditional evaluation chunk never uses a "break" command
    • The lua "command-word" if initiates a conditional evaluation chunk
    • While "else" is valid syntax within a conditional evaluation chunk for what lua ought to do when the evaluation is not met, in the case of sample code you provided it is not necessary nor really desirable. You have nothing "else" for the code-chunk to do, so an "else" clause is unnecessary
  6. Conceptual and other syntax issues to one side for a moment, the correct syntax without unnecessary extras for your conditional evaluation chunk would be:
    Code:
    if iPlotOwner == -1 then
        pOtherPlayer = Players[iPlotOwner]   
        Events.TurnEnd.Add(CheckIdleDamage)
        Events.UnitMoved.Add(Attrition)       
    end
    In all cases when variable iPlotOwner is not equal to "-1" the code would not execute anything. The effect is the same as how you were attempting to structure an "else" clause, but as already mentioned in this particular example of code no "else" condition is needed.
  7. Unless functions "CheckIdleDamage" and "Attrition" are defined elsewhere within the same script you will get nil value errors for attempting to attach a nil value as a listener to a game "event-hook"
 
Last edited:
Thanks for taking the time to break this down! I understand the syntax errors and I now see that the example code I used for this was from the Civ 5 forum so the methods may not be usable CIv 6. I did some more playing around and came up with the code below. This is a function that deals damage with input from some other functions and works fine without my plot owner checking. I am only trying to apply damage to units outside my own territory for right now.

I thought I could get the player ID using UnitPlotOwner = UnitPlot:GetOwner, but it returns -1 even if inside my territory.

Code:
function damageAndDestroyUnit(pPlayer,iUnit,minDam,maxDam)
    local tKillUnits = {}
    local pPlayerToCheck = Players[pPlayer]
    local pUnits = pPlayerToCheck:GetUnits()   
    for i, pUnit in pUnits:Members(i) do
        --Get plots unit is occupying
        local UnitPlot = Map.GetPlot(pUnit:GetLocation())
        --Get owner of plot unit is occupying
        local UnitPlotOwner = UnitPlot:GetOwner()
        --Check if plot is owned by player
        if UnitPlotOwner ~= 0 then
            print("Plot owner "..UnitPlotOwner.." of unit")       
            if(tostring(pUnit:GetID()) == tostring(iUnit)) then
                local unitDamage = math.random(minDam,maxDam)
                if (pUnit:GetDamage() + unitDamage) >= 100 then
                    table.insert(tKillUnits, pUnit)
                else
                    pUnit:ChangeDamage(unitDamage)
                    print("inflicted "..unitDamage.." to unit")
                end
            end
        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)
        print("a unit was destroyed because of attrition")
    end           
end
 
  1. Are you passing a Player ID # for argument "pPlayer" to function "damageAndDestroyUnit()" ?
  2. You don't need the "i" argument in "pUnits:Members(i)"
  3. Units that are dead (and therefore have no valid location) are still listed as items in "pUnits:Members()"
  4. When the code executes properly for retrieving the owner ID# of the plot, "0" does not indicate no one. "-1" indicates no player owns the plot. "0" is the player ID # assigned to the human player in Single Player games and as I recall in Multiplayer Games will be the host human player.
  5. Since table "tKillUnits" will be entirely an array table when the "killing off" iteration is performed, you are better off using "ipairs" instead of "pairs". This simple alteration will be insurance against de-synch between different participants in a multiplayer game if the code is ever run in a multiplayer game.
  6. If you are passing a unit ID # as argument "iUnit" to function damageAndDestroyUnit it does not really make any sense to iterate through the entire table of the player's units.
  7. All you need to do to get the Unit-Object when you have only a unit ID # is to code as
    Code:
    local pUnit = Players[PlayerID]:GetUnits():FindID(iUnitID)
    Or, as it would be in your function:
    Code:
    local pPlayerToCheck = Players[pPlayer]
    local pUnits = pPlayerToCheck:GetUnits()   
    local pUnit = pUnits:FindID(iUnit)
  8. If "iUnit" is already being passed as the unit-object to function damageAndDestroyUnit then there would be no need to "grab" the unit-object: it will already be available as an inherent variable usable in the function
  9. Here is a quick function to check if the Unit is "dead":
    Code:
    function UnitIsDead(pUnit)
    	if pUnit then
    		if pUnit:IsDead() or pUnit:IsDelayedDeath() then
    			return true
    		end
    	else
    		--the pUnit data is nil, assume the unit is ded
    		return true
    	end
    	--safety valve point: assume the unit is NOT ded if we get here
    	return false
    end
  10. I always use the following method to get a unit's plot as a plot-object that can be used with plot API "methods":
    Code:
    local iUnitX, iUnitY = pUnit:GetX(), pUnit:GetY()
    local pPlot = Map.GetPlot(iUnitX, iUnitY)
    I've never had luck with the GetLocation() method. I cannot now remember whether the GetLocation() method is only valid in a UI script which your code is most definitely not.
  11. If your intention is to create attrition on only a specific unit via your function damageAndDestroyUnit and arguments "pPlayer" and "iUnit" are being sent ID #s for the player and the unit, you can simplify the code to
    Code:
    function damageAndDestroyUnit(pPlayer,iUnit,minDam,maxDam)
    	local pPlayerToCheck = Players[pPlayer]
    	local pUnits = pPlayerToCheck:GetUnits()   
    	local pUnit = pUnits:FindID(iUnit)
    	if (pUnit ~= nil) and (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
    		local UnitPlot = Map.GetPlot(pUnit:GetX(), pUnit:GetY())
    		if (UnitPlot ~= nil) and (UnitPlot:GetOwner() ~= pPlayer) then
    			local unitDamage = math.random(minDam,maxDam)
    			if (pUnit:GetDamage() + unitDamage) >= 100 then
    				pUnits:Destroy(pUnit)
    				print("a unit was destroyed because of attrition")
    			else
    				pUnit:ChangeDamage(unitDamage)
    				print("inflicted "..unitDamage.." to unit")
    			end
    		end
    	end
    end
  12. If your intention is to create attrition on all the player's units via one execution of function damageAndDestroyUnit then you really don't even need to pass the "iUnit" argument. You only need the other three arguments. And in this case you would execute through the list of the player's units via the "pUnits:Members()" method, check that each of these units is not already "dead" or giving a nil value, and then grab the plot object for that unit, etc. You would want to use the "tKillUnits" method as well as you never want to kill or otherwise remove an item in the Members() list while still iterating through the lust of Members().
 
I may have bitten off more than I can chew for my first mod, so I appreciate all your help - a good learning experience nonetheless.

I implemented your suggestions and it now works!!! - damage only gets applied outside my own territory. Are there other ways I can make this code more robust or efficient? I pasted the entire code this time with all the functions.

My next task will be to include a check for diplomatic relations - i.e. no damage applied in friendly territory and healing in allied territory. I have tried using Player:GetDiplomacy() as suggested previously by Infixo but with no success.



Code:
-- set base values for variables
local BaseAttritionMin,BaseAttritionMax = 1,6
local TerrainAttritionMul,CataChance,TerrainCataMul = 1,10,2
local TerrainAttritionMulLow,TerrainAttritionMulBelowAverage,TerrainAttritionMulAverage,TerrainAttritionMulStrong,TerrainAttritionMulHuge = 0.5,2,3,4,8
local CataChanceLow,CataChanceAverage,CataChanceAboveAverage,CataChanceStrong,CataChanceHuge = 70,150,250,350,600
local TerrainCataMulLow,TerrainCataMulBelowAverage,TerrainCataMulAverage,TerrainCataMulStrong,TerrainCataMulHuge = 2,3,5,8,11



function damageAndDestroyUnit(pPlayer,iUnit,minDam,maxDam)
    local tKillUnits = {}
    local pPlayerToCheck = Players[pPlayer]
    local pUnits = pPlayerToCheck:GetUnits()
    local pUnit = pUnits:FindID(iUnit)
    if (pUnit ~= nil) and (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
        local UnitPlot = Map.GetPlot(pUnit:GetX(), pUnit:GetY())
        local UnitPlotOwner = UnitPlot:GetOwner()
        if (UnitPlot ~= nil) and (UnitPlotOwner ~= pPlayer) then
            local unitDamage = math.random(minDam,maxDam)
            if (pUnit:GetDamage() + unitDamage) >= 100 then
                table.insert(tKillUnits, pUnit)
            else
                pUnit:ChangeDamage(unitDamage)
                print("inflicted "..unitDamage.." to unit")
                print("Player is "..UnitPlotOwner.." ID")
               
            end
        end
    end
    --now we've completed the iteration through table pUnits:Members() and can safely delete units listed as members
    for k,pUnit in ipairs(tKillUnits) do
        pUnits:Destroy(pUnit)
        print("a unit was destroyed because of attrition")
    end           
end


function getDamageFromTerrains(tTerrain)
    if tTerrain == "TERRAIN_GRASS" then 
        TerrainAttritionMul = TerrainAttritionMulLow
        CataChance = CataChanceLow
        TerrainCataMul = TerrainCataMulLow
    elseif tTerrain == "TERRAIN_GRASS_HILLS" then 
        TerrainAttritionMul = TerrainAttritionMulLow
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulLow
    elseif tTerrain == "TERRAIN_PLAINS" then 
        TerrainAttritionMul = TerrainAttritionMulLow
        CataChance = CataChanceLow
        TerrainCataMul = TerrainCataMulLow
    elseif tTerrain == "TERRAIN_PLAINS_HILLS" then 
        TerrainAttritionMul = TerrainAttritionMulLow
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulLow
   
   
   
    elseif tTerrain == "TERRAIN_DESERT" then 
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulAverage
    elseif tTerrain == "TERRAIN_DESERT_HILLS" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulAverage
    elseif tTerrain == "TERRAIN_DESERT_MOUNTAIN" then
        TerrainAttritionMul = TerrainAttritionMulStrong
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulStrong   
    elseif tTerrain == "TERRAIN_GRASS_MOUNTAIN" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulAverage
    elseif tTerrain == "TERRAIN_PLAINS_MOUNTAIN" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulAverage   
    elseif tTerrain == "TERRAIN_TUNDRA" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulBelowAverage   
    elseif tTerrain == "TERRAIN_TUNDRA_HILLS" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceAboveAverage
        TerrainCataMul = TerrainCataMulBelowAverage
    elseif tTerrain == "TERRAIN_TUNDRA_MOUNTAIN" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulStrong   
    elseif tTerrain == "TERRAIN_SNOW" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulAverage
    elseif tTerrain == "TERRAIN_SNOW_HILLS" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulAverage
    elseif tTerrain == "TERRAIN_SNOW_MOUNTAIN" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulHuge
    elseif tTerrain == "TERRAIN_COAST" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulAverage
    elseif tTerrain == "TERRAIN_OCEAN" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulAverage   
    end
 return TerrainAttritionMul,CataChance,TerrainCataMul
end

function getDamageFromFeatures(plotFeature)
    if plotFeature == "FEATURE_ICE" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulAverage
    end
    if plotFeature == "FEATURE_JUNGLE" then
        TerrainAttritionMul = TerrainAttritionMulAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulAverage       
    end
    if plotFeature == "FEATURE_MARSH" then
        TerrainAttritionMul = TerrainAttritionMulStrong
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulAverage
    end
    if plotFeature == "FEATURE_OASIS" then
        TerrainAttritionMul = TerrainAttritionMulLow
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulAverage   
    end
    return TerrainAttritionMul,CataChance,TerrainCataMul
end


function Attrition(PlayerID,UnitID,UnitX,UnitY)
    local experienceFromCata = false
    local feedBack = false
   
    -- give feedback only to player
    if PlayerID == 0 then feedBack = true end
   
    -- set values for terrains
    local plotToCheck = Map.GetPlot(UnitX,UnitY)
    local tTerrain = GameInfo.Terrains[plotToCheck:GetTerrainType()].TerrainType

    TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromTerrains(tTerrain)   

    -- check features. Takes precedence on terrain.
    if plotToCheck:GetFeatureType() > -1 then
        local plotFeature = GameInfo.Features[plotToCheck:GetFeatureType()].FeatureType   
        TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromFeatures(plotFeature)
    end

    -- check cata chance
    if math.random(1,1000) <= CataChance then
        experienceFromCata = true
        TerrainAttritionMul = TerrainCataMul
        if feedBack then
            Game.AddWorldViewText(0, "A catastrophe happened to this unit while moving", UnitX, UnitY, 0)
        end
    end
    -- reduce attrition and cata chance if road on plot.
    if plotToCheck:GetRouteType() > -1 then
        print("There is a route here, so much less attrition.")
        CataChance = CataChance/5
        TerrainCataMul = 1
        TerrainAttritionMul = TerrainAttritionMul*0.2
    end   
   
-- check chance to create a road when a unit moves
    local roadCheckAllowed = true
    if tTerrain == "TERRAIN_COAST" or "TERRAIN_OCEAN" then
        roadCheckAllowed = false
    end
    if roadCheckAllowed then
        local chanceToCreateRoad = math.random(1,1000)
        if chanceToCreateRoad <= 75 then
            if feedBack then
                Game.AddWorldViewText(0, "Your unit found and marked some easy path while moving here.", UnitX, UnitY, 0)
            end       
            WorldBuilder.MapManager():SetRouteType( plotToCheck,GameInfo.Routes["ROUTE_ANCIENT_ROAD"].Index, false )
        end
    end
    -- deal damages   
    local FinalAttritionMin = BaseAttritionMin*TerrainAttritionMul
    local FinalAttritionMax = BaseAttritionMax*TerrainAttritionMul   
   
    damageAndDestroyUnit(PlayerID,UnitID,FinalAttritionMin,FinalAttritionMax)
   
    -- give XP if a cata happens.    
    if experienceFromCata then
        local pPlayerToCheck = Players[PlayerID]
        local pUnits = pPlayerToCheck:GetUnits()
        for i, pUnit in pUnits:Members() do
            if(tostring(pUnit:GetID()) == tostring(UnitID)) then
                local currentXP = pUnit:GetExperience()
                local currentXPPoints = currentXP:GetExperiencePoints()+5
                --currentXPPoints = currentXPPoints+5
                local experienceChangeAmount = math.random((FinalAttritionMin/10)+1,(FinalAttritionMax/10)+1)
                currentXP:ChangeExperience(experienceChangeAmount)
            end
        end
    end       
end

function CheckIdleDamage()
    local players = Game.GetPlayers()
    for k, pPlayer in ipairs (players) do
        local pUnits = pPlayer:GetUnits()
        for i, pUnit in pUnits:Members() do
            local iUnitX, iUnitY = pUnit:GetX(), pUnit:GetY()
            local pPlot = Map.GetPlot(iUnitX, iUnitY)   
            local tTerrain = GameInfo.Terrains[pPlot:GetTerrainType()].TerrainType
            TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromTerrains(tTerrain)
            if pPlot:GetFeatureType() >-1 then 
                local fFeature = GameInfo.Features[pPlot:GetFeatureType()].FeatureType            
                TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromFeatures(fFeature)
            end
            local PlayerID = pPlayer:GetID()
            local UnitID = pUnit:GetID()
            local FinalAttritionMin = (BaseAttritionMin*TerrainAttritionMul)/2
            local FinalAttritionMax = (BaseAttritionMax*TerrainAttritionMul)/2   
            damageAndDestroyUnit(PlayerID,UnitID,FinalAttritionMin,FinalAttritionMax)
        end
    end
end

Events.TurnEnd.Add(CheckIdleDamage)
Events.UnitMoved.Add(Attrition)
 
I have not looked through your entire code as yet, and may not get an opportunity to do so for a couple of days at least, but this stands out to me:
Code:
RouteBuilder.SetRouteType(pPlot, iRouteType)
There's no real need to use the WorldBuilder version, although I would not think it would really hurt anything to do so.

"iRouteType" in the example above will be the "Index" value for the proper kind of route to add to the plot.

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

Also, investigate lua tables. You can get rid of the need for "getDamageFromTerrains(tTerrain)" by using an lua table.
Code:
local TerrainDataTable = { [GameInfo.Terrains["TERRAIN_GRASS"].Index] = {  TerrainAttritionMul = TerrainAttritionMulLow, CataChance = CataChanceLow, TerrainCataMul = TerrainCataMulLow } ,
[GameInfo.Terrains["TERRAIN_GRASS_HILLS"].Index] = {  TerrainAttritionMul = TerrainAttritionMulLow, CataChance = CataChanceAverage, TerrainCataMul = TerrainCataMulLow }  ,
….etc for the other terrain-types..... }
Then as needed the data can be pulled directly from the table:
Code:
function Attrition(PlayerID,UnitID,UnitX,UnitY)
    local experienceFromCata = false
    local feedBack = false
   
    -- give feedback only to player
    if PlayerID == 0 then feedBack = true end
   
    -- set values for terrains
    local plotToCheck = Map.GetPlot(UnitX,UnitY)
    local PlotTerrainDamages = TerrainDataTable[plotToCheck:GetTerrainType()]

    TerrainAttritionMul,CataChance,TerrainCataMul = PlotTerrainDamages.TerrainAttritionMul,  PlotTerrainDamages.CataChance,  PlotTerrainDamages.TerrainCataMul

  ...etc...…
Your code will be somewhat reduced in length in terms of the amount of code you need to create, but also since you are directly-extracting the data back out of the table, there is no need for the lua-script to evaluate multiple "elseif" clauses.

If you later decide that TerrainType "X" should not cause any ill effects, you can simply eliminate that Terrain's info from the lua table. Your code then changes slightly here
Code:
function Attrition(PlayerID,UnitID,UnitX,UnitY)
    local experienceFromCata = false
    local feedBack = false
   
    -- give feedback only to player
    if PlayerID == 0 then feedBack = true end
   
    -- set values for terrains
    local plotToCheck = Map.GetPlot(UnitX,UnitY)
     local TerrainAttritionMul,CataChance,TerrainCataMul = 0, 0, 0 --set to zero here to initialize and avoid scoping issues with data alterations within sub-chunks of the function
    if TerrainDataTable[plotToCheck:GetTerrainType()] then
         local PlotTerrainDamages = TerrainDataTable[plotToCheck:GetTerrainType()]
        TerrainAttritionMul,CataChance,TerrainCataMul = PlotTerrainDamages.TerrainAttritionMul,  PlotTerrainDamages.CataChance,  PlotTerrainDamages.TerrainCataMul
     end
  ...etc...…
No other changes will ever be needed to the code in such a case as far as grabbing the needed damage effect is concerned. Changes to the amounts of damage and the chances are made in the lua-table (including any eliminations of terrains that you later may decide can be considered "safe" for balance purposes). This is referred to as Data-Driven Code Techniques.
 
Last edited:
no worries! Any help is awesome. I'm just really happy the darn things works! now I just need to optimize and add some more features. I will definitely look into making the lua table. This will make updating values much easier.
 
So the following code works to damage units outside of friendly territory and even reduces that damage based on player era. This makes exploration extremely difficult in earlier eras, which was my goal - it;s almost impossible to pass deserts and tundra and mountains (now passable) results in almost immediate death. I am now struggling with balancing this mod. I have a separate mod (not mine) that removes healing in neutral or enemy territory but adds healing when adjacent to rivers. I would like to add this functionality to my mod but also include healing when adjacent to lakes as well. any ideas how to go about this? The lua script for my mod is below.

Code:
-- set base values for variables
local BaseAttritionMin,BaseAttritionMax = 1,6
local TerrainAttritionMul,CataChance,TerrainCataMul = 1,10,2
local TerrainAttritionMulLow,TerrainAttritionMulBelowAverage,TerrainAttritionMulAverage,TerrainAttritionMulStrong,TerrainAttritionMulHuge = 10,15,20,35,60
local CataChanceLow,CataChanceAverage,CataChanceAboveAverage,CataChanceStrong,CataChanceHuge = 150,250,350,500,800
local TerrainCataMulLow,TerrainCataMulBelowAverage,TerrainCataMulAverage,TerrainCataMulStrong,TerrainCataMulHuge = 2,3,5,8,15



function damageAndDestroyUnit(pPlayer,iUnit,minDam,maxDam)
    local tKillUnits = {}
    local pPlayerToCheck = Players[pPlayer]
    local pUnits = pPlayerToCheck:GetUnits()
    local pUnit = pUnits:FindID(iUnit)
    local pPlayerEra = pPlayerToCheck:GetEra()+1
    if (pUnit ~= nil) and (pUnit:IsDead() == false) and (pUnit:IsDelayedDeath() == false) then
        local UnitPlot = Map.GetPlot(pUnit:GetX(), pUnit:GetY())
        local UnitPlotOwner = UnitPlot:GetOwner()
        if (UnitPlot ~= nil) and (UnitPlotOwner ~= pPlayer) then
            local unitDamage = math.random(minDam,maxDam)
            if (pUnit:GetDamage() + unitDamage) >= 200 then
                table.insert(tKillUnits, pUnit)
            else
                pUnit:ChangeDamage(unitDamage)
                print("inflicted "..unitDamage.." to unit")
                print("Player is "..UnitPlotOwner.." ID")
                print("player era is "..pPlayerEra.." era")
            end
        end
    end
    --now we've completed the iteration through table pUnits:Members() and can safely delete units listed as members
    for k,pUnit in ipairs(tKillUnits) do
        pUnits:Destroy(pUnit)
        print("a unit was destroyed because of attrition")
    end           
end

function eraModifier(era, TerrainAttritionMul,CataChance,TerrainCataMul)
    if era == "1" then
        TerrainAttritionMul = TerrainAttritionMul+30
        CataChance = CataChance+150
        TerrainCataMul = TerrainCataMul+10
    elseif era == "2" then
        TerrainAttritionMul = TerrainAttritionMul+25
        CataChance = CataChance*150
        TerrainCataMul = TerrainCataMul+10
    elseif era == "3" then
        TerrainAttritionMul = TerrainAttritionMul+10
        CataChance = CataChance*100
        TerrainCataMul = TerrainCataMul+5
    elseif era == "4" then
        TerrainAttritionMul = TerrainAttritionMul+10
        CataChance = CataChance*75
        TerrainCataMul = TerrainCataMul+3
    elseif era == "5" then
        TerrainAttritionMul = TerrainAttritionMul
        CataChance = CataChance
        TerrainCataMul = TerrainCataMul
    elseif era == "6" then
        TerrainAttritionMul = TerrainAttritionMul
        CataChance = CataChance*0.5
        TerrainCataMul = TerrainCataMul
    elseif era == "7" then
        TerrainAttritionMul = TerrainAttritionMul*0.5
        CataChance = CataChance*0.25
        TerrainCataMul = TerrainCataMul*0.5
    elseif era == "8" then
        TerrainAttritionMul = TerrainAttritionMul*0.25
        CataChance = CataChance*0.1
        TerrainCataMul = TerrainCataMul*0.05
    elseif era == "9" then
        TerrainAttritionMul = TerrainAttritionMul*0
        CataChance = CataChance*0
        TerrainCataMul = TerrainCataMul*0
    end
    return TerrainAttritionMul,CataChance,TerrainCataMul
end


function getDamageFromTerrains(tTerrain)
    if string.find(tTerrain, "MOUNTAIN") then
        TerrainAttritionMul = TerrainAttritionMulHuge
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulHuge
    elseif tTerrain == "TERRAIN_OCEAN" then
        TerrainAttritionMul = TerrainAttritionMulAverage
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulHuge   
    elseif (string.find(tTerrain, "DESERT") or string.find(tTerrain, "TUNDRA") or string.find(tTerrain, "SNOW")) then
        TerrainAttritionMul = TerrainAttritionMulStrong
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulHuge
    elseif tTerrain == "TERRAIN_COAST" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulAverage
    else
        TerrainAttritionMul = TerrainAttritionMulLow
        CataChance = CataChanceLow
        TerrainCataMul = TerrainCataMulLow
    end
 return TerrainAttritionMul,CataChance,TerrainCataMul
end


function getDamageFromFeatures(plotFeature)
    if plotFeature == "FEATURE_ICE" then
        TerrainAttritionMul = TerrainAttritionMulStrong
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulHuge
    elseif plotFeature == "FEATURE_MARSH" then
        TerrainAttritionMul = TerrainAttritionMulStrong
        CataChance = CataChanceHuge
        TerrainCataMul = TerrainCataMulAverage
    elseif plotFeature == "FEATURE_BARRIER_REEF" then
        TerrainAttritionMul = TerrainAttritionMullow
        CataChance = CataChanceStrong
        TerrainCataMul = TerrainCataMulHuge
    elseif plotFeature == "FEATURE_JUNGLE" then
        TerrainAttritionMul = TerrainAttritionMulAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulStrong       
    elseif plotFeature == "FEATURE_FOREST" then
        TerrainAttritionMul = TerrainAttritionMulBelowAverage
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulStrong       
    elseif plotFeature == "FEATURE_OASIS" then
        TerrainAttritionMul = 0
        CataChance = CataChanceAverage
        TerrainCataMul = TerrainCataMulAverage   
    end
    return TerrainAttritionMul,CataChance,TerrainCataMul
end


function Attrition(PlayerID,UnitID,UnitX,UnitY)
    local experienceFromCata = false
    local feedBack = false
    local pPlayerToCheck = Players[PlayerID]
    local PlayerEra = pPlayerToCheck:GetEra()+1
    
    -- give feedback only to player
    if PlayerID == 0 then feedBack = true end
    
    -- set values for terrains
    local plotToCheck = Map.GetPlot(UnitX,UnitY)
    local tTerrain = GameInfo.Terrains[plotToCheck:GetTerrainType()].TerrainType

    TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromTerrains(tTerrain)
    
    -- check features. Takes precedence on terrain.
    if plotToCheck:GetFeatureType() > -1 then
        local plotFeature = GameInfo.Features[plotToCheck:GetFeatureType()].FeatureType   
        TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromFeatures(plotFeature)
    end
    
    --Era check
    TerrainAttritionMul,CataChance,TerrainCataMul = eraModifier(playerEra, TerrainAttritionMul,CataChance,TerrainCataMul)

    -- check cata chance
    if math.random(1,1000) <= CataChance then
        experienceFromCata = true
        TerrainAttritionMul = TerrainCataMul
        if feedBack then
            Game.AddWorldViewText(0, "A catastrophe happened to this unit while moving", UnitX, UnitY, 0)
        end
    end
    -- reduce attrition and cata chance if road on plot.
    if plotToCheck:GetRouteType() > -1 then
        print("There is a route here, so much less attrition.")
        CataChance = CataChance/5
        TerrainCataMul = 1
        TerrainAttritionMul = TerrainAttritionMul*0.2
    end   
    
-- check chance to create a road when a unit moves
    local roadCheckAllowed = true
    if tTerrain == "TERRAIN_COAST" or "TERRAIN_OCEAN" then
        roadCheckAllowed = false
    end
    if roadCheckAllowed then
        local chanceToCreateRoad = math.random(1,1000)
        if chanceToCreateRoad <= 75 then
            if feedBack then
                Game.AddWorldViewText(0, "Your unit found and marked some easy path while moving here.", UnitX, UnitY, 0)
            end       
            WorldBuilder.MapManager():SetRouteType( plotToCheck,GameInfo.Routes["ROUTE_ANCIENT_ROAD"].Index, false )
        end
    end
    -- deal damages   
    local FinalAttritionMin = BaseAttritionMin*TerrainAttritionMul
    local FinalAttritionMax = BaseAttritionMax*TerrainAttritionMul   
    
    damageAndDestroyUnit(PlayerID,UnitID,FinalAttritionMin,FinalAttritionMax)
    
    -- give XP if a cata happens.     
    if experienceFromCata then
        local pPlayerToCheck = Players[PlayerID]
        local pUnits = pPlayerToCheck:GetUnits()
        for i, pUnit in pUnits:Members() do
            if(tostring(pUnit:GetID()) == tostring(UnitID)) then
                local currentXP = pUnit:GetExperience()
                local currentXPPoints = currentXP:GetExperiencePoints()+5
                --currentXPPoints = currentXPPoints+5
                local experienceChangeAmount = math.random((FinalAttritionMin/10)+1,(FinalAttritionMax/10)+1)
                currentXP:ChangeExperience(experienceChangeAmount)
            end
        end
    end       
end

function CheckIdleDamage()
    local players = Game.GetPlayers()
    for k, pPlayer in ipairs (players) do
        local pUnits = pPlayer:GetUnits()
        for i, pUnit in pUnits:Members() do
            local iUnitX, iUnitY = pUnit:GetX(), pUnit:GetY()
            local pPlot = Map.GetPlot(iUnitX, iUnitY)   
            local tTerrain = GameInfo.Terrains[pPlot:GetTerrainType()].TerrainType
            TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromTerrains(tTerrain)
            if pPlot:GetFeatureType() >-1 then
                local fFeature = GameInfo.Features[pPlot:GetFeatureType()].FeatureType             
                TerrainAttritionMul,CataChance,TerrainCataMul = getDamageFromFeatures(fFeature)
            end
            local PlayerID = pPlayer:GetID()
            local UnitID = pUnit:GetID()
            local FinalAttritionMin = (BaseAttritionMin*TerrainAttritionMul)/2
            local FinalAttritionMax = (BaseAttritionMax*TerrainAttritionMul)/2   
            damageAndDestroyUnit(PlayerID,UnitID,FinalAttritionMin,FinalAttritionMax)
        end
    end
end

Events.TurnEnd.Add(CheckIdleDamage)
Events.UnitMoved.Add(Attrition)
 
https://www.lua.org/cgi-bin/demo

go to that page end execute the following code
Code:
if 1 == "1" then
    print("integer 1 equals text-string 1 is true")
else
    print("integer 1 equals text-string 1 is false")
end
Your function called eraModifier is not actually working. It is simply returning the original data sent to it. this line will retrieve the integer value for the Index # of the era the player is in and then add integer value 1 to that era index number:
Code:
local PlayerEra = pPlayerToCheck:GetEra()+1
You are then sending this integer value to your function:
Code:
--Era check
    TerrainAttritionMul,CataChance,TerrainCataMul = eraModifier(playerEra, TerrainAttritionMul,CataChance,TerrainCataMul)
However there is a further problem because while you think you are sending the data for pPlayerToCheck:GetEra()+1 what you are actually sending is nil because PlayerEra does not equal playerEra. So you are not attempting to compare an integer to a text-string, you are attempting to compare "Boolean" nil to a text-string. If you execute the following on the lua demo page you get the message for "false" in the demo page output box:
Code:
if nil == "1" then
    print("nil equals text-string 1 is true")
else
    print("nil equals text-string 1 is false")
end
----------------------------

Code:
Plot:IsMountain()
Plot:IsLake()
Plot:IsRiver()
Plot:IsWater()
Plot:IsRiver()
etc
See ChimpanG's online spreadsheet here:

https://docs.google.com/spreadsheet...D_xTTld4sFEyMxrDoPwX2NUFc/edit#gid=1205978888

for more complete information on the "plot" methods available.

Plot:IsWater() will be Boolean true for any plot that is a lake, a coastal sea plot, or a deep ocean plot.

Plot:IsMountain() will be Boolean true for any mountain plot regardless of the underlying "basic" terrain type.

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

Function CheckIdleDamage is still going to have issues because you are not checking whether the unit is "dead" before you attempt to use its XY location to retrieve a plot object.
 
Thank you for the awesome resources! I think I am going to rework this script from the beginning. I started with someone else script so it's hard for me to wrap my head around some of the functions. i really want to make this work so I will be back!
 
I have circled around to my original question... How do I use GetDiplomacy() to get the diplomatic state between a unit owner and plot owner?

EDIT: I tried the following and it seems to work

local pUnitOwnerDiplo = pPlayer:GetAi_Diplomacy():GetDiplomaticState(pUnitPlotOwner)

where pUnitPlotOwner is the ID of the plot owner and pPlayer is ID of the unit owner.
 
Last edited:
Back
Top Bottom