C Stack overflow error when looping through a player's units

Karatekid5

Warlord
Joined
Nov 11, 2012
Messages
197
Location
Pennsylvania
Building upon a previous mod I finished, I decided to add some code that removes the target player's policies and deletes their units (alongside the functions of removing all cities aside from the capital and removing all researched technologies from the target; for context this is a silly mod for AI-only games). While the policy removal loop works perfectly fine, looping through the PlotOwnerPlayer's units results in a C Stack Overflow that crashes the game. I defined the pPlotOwnerPlayer earlier in the code so it shouldn't be returning any nil values.

Code:
include("FLuaVector.lua")

print("Big Chungus")

local iUnitBigChungus = GameInfoTypes.UNIT_BIG_CHUNGUS


function ChungusPrimalReversion(iPlayer,iUnit,_,iPlotX, iPlotY, bDelay)

          local pPlayer = Players[iPlayer]         
         local pPlot = Map.GetPlot(iPlotX, iPlotY)
         local pUnit = pPlayer:GetUnitByID(iUnit);
         local iOriginalPlotOwner = pPlot:GetOwner()
               
          if (iOriginalPlotOwner ~= -1) and pUnit:GetUnitType()==iUnitBigChungus then -- (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                   for city in pPlotOwnerPlayer:Cities() do
                      if city:IsCapital() == false and city:IsOriginalMajorCapital() == false and city:IsHolyCityAnyReligion() == false then
                        local plot = city:Plot()
                        local hexpos = ToHexFromGrid(Vector2(plot:GetX(), plot:GetY()));
                        local cityID = city:GetID()
                       city:SetPopulation(0)
                        city:Kill()
                       print("Primal Reverted!")
                       Events.SerialEventCityDestroyed(hexpos, iOriginalPlotOwner, cityID, -1)
                     else
                        city:SetPopulation(1)
                        end
                  end

                  local RevertedTeam = Teams[pPlotOwnerPlayer:GetTeam()]
                  for tech in GameInfo.Technologies() do
                      if (tech.ID > 0) then
                         RevertedTeam:SetHasTech(tech.ID, false)
                      end
                   end

                  for policy in GameInfo.Policies() do
                    if (policy.ID > 0) then
                      pPlotOwnerPlayer:SetHasPolicy(policy.ID, false)
                     end
                  end

                  for pUnit in pPlotOwnerPlayer:Units() do
                      pUnit:Kill()
                    end
          end
end

GameEvents.UnitPrekill.Add(ChungusPrimalReversion);

It's a simple unit loop, what is causing it to overflow? Is it caused by running this in the PreKill Routine?

Furthermore, is it possible to loop through and delete (via SetNumRealBuilding) a city's buildings? I saw it mentioned on another thread that you can't directly loop through them so I was wondering if there was another way.
 
You are changing what you're looping over within the loop

A player has three units A B and C

When you delete A, the player no longer has three units but 2, so your loop control is now invalid.

You'll need to loop all the players units, store the ID's into a temporary array, then loop the array and delete the units by ID

"Is it caused by running this in the PreKill Routine?" Partly - you'll also need to not try to the kill the unit that's already being killed. There is a method on a unit to test if its "dying" (but I can't remember its name off the top of my head)
 
Ahhh, that makes sense, I should've thought about that. I can see how to use the API LeeS presented, though I don't know how to make temporary arrays and I haven't been able to find any good examples.

Also, I was thankfully able to look through buildings and delete using this:

Code:
if city:IsHasBuilding(building.ID) then
          if building.ID ~= iPalace then
                 city:SetNumRealBuilding(building.ID, 0)
           end
end

It deletes all buildings aside from those provided by policies and wonders, and has a check for the Palace to make sure it isn't deleted from the capital. Just in case anyone else finds this thread and is curious.

Also, can you loop through the civs a player has met by using "pPlayer:CanContact"?
 
Code:
local tTempTable = {}
for pUnit in pPlotOwnerPlayer:Units() do
	if not pUnit:IsDead() and not pUnit:IsDelayedDeath() then
		table.insert(tTempTable, pUnit:GetID())
	end
end
for i,iUnitID in ipairs(tTempTable) do
	pPlotOwnerPlayer:GetUnitByID(iUnitID):Kill()
end
Also, can you loop through the civs a player has met by using "pPlayer:CanContact"?
Erm ---- no. So far as I know that's not a valid player function.

Code:
  <api object="Team" method="IsHasMet">
      <method class="CvTeam" fn="isHasMet"/>
      <arg pos="1" type="TeamTypes" name="eIndex"/>
      <ret type="bool"/>
  </api>
You'd have to loop through all the other 63 players, get their team, and then check whether the 'pPlayer' team has met the other.
 
Thank you for the excellent temporary array example, I'll keep it in my notes for future projects! I got another C Stack Overflow but I probably just inserted it incorrectly, so I'll take care of that.


Erm ---- no. So far as I know that's not a valid player function.

I found it on Modiki so I wasn't completely sure, which is why I wanted to confirm. Thank you for correcting me, and I'll give this a shot! Though before I do, what should I put as the check for the for loop (the part that is "for tech", "for policy", etc.)? I'd assume "for team" but I just want to be sure.
 
You have to use a "regular" lua loop, not a "GameInfo" sort of loop:
Code:
local pOurPlayer = Players[iOurPlayer]
local pOurTeam = Teams[pOurPlayer:GetTeam()]

for iPlayerInLoop = 0,62 do
	if (iPlayerInLoop ~= iOurPlayer) then
		local pPlayerInLoop = Players[iPlayerInLoop]
		if pPlayerInLoop:IsEverAlive() and pPlayerInLoop:IsAlive() then
			local iLoopPlayerTeam = pPlayerInLoop:GetTeam()
			if pOurTeam:IsHasMet(iLoopPlayerTeam) then
				print("We have met Player # " .. iPlayerInLoop)
			end
		end
	end
end
I did not extend the loop through player #63 (the barbarians) because I assumed you'd not want to have your code do oddball things since I cannot remember whether the Barb player and team give anything meaningful for stuff like "IsHasMet".

And assuming I haven't jammed any Civ6 methods in there instead of Civ5 methods. I almost used "WasEverAlive" which is a Civ6 thing.
 
So far that loop has been working well. I haven't added any new code to it yet since I've been sidetracked by another small project, but it is returning the values that it should. Thank you again!

Said side project also involves a player loop though a different kind. The intent of this unit code is to check the plot owner of the plot a specific kind of unit was killed on. It then loops through every player and forces them to declare war on the plot owner. I fixed a nil value on line 23 but the code itself doesn't activate the war declarations, so I likely screwed up when setting up the loop. Most of the code does seem to run, though. Also I didn't add a IsHasMet check since this gimmick won't be active until after the World Congress has already convened (and this everyone has met each other). I studied some similar code of yours from another thread but I am unsure of where I made the mistake.

Code:
include("FLuaVector.lua")

print("BING BING")

local iUnitCrazyFrog = GameInfoTypes.UNIT_CRAZY_FROG


function CrazyFrogWars(iPlayer,iUnit,_,iPlotX, iPlotY, bDelay)
  if bDelay == true then

          local pPlayer = Players[iPlayer]         
         local pPlot = Map.GetPlot(iPlotX, iPlotY)
         local pUnit = pPlayer:GetUnitByID(iUnit);
         local iOriginalPlotOwner = pPlot:GetOwner()
               
          if (iOriginalPlotOwner ~= -1) and pUnit:GetUnitType()==iUnitCrazyFrog then -- (iOriginalPlotOwner ~= iPlayer) then
                  local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
                  --Events.AudioPlay2DSound("AS2D_CRAZY_FROG_MUSIC")
                  local pCrazyFrogTeam = Teams[pPlotOwnerPlayer:GetTeam()]
                  for playerID = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
                   if Players[iOriginalPlotOwner] ~= pPlayer then
                    local pPlayerTeam = pPlayer:GetTeam()
                       if pPlayer:IsAlive() and Teams[pPlayerTeam]:CanDeclareWar(pCrazyFrogTeam) then
                          Teams[pPlayerTeam]:DeclareWar(pCrazyFrogTeam)
                       end
                    end
                   end
          end
  end
end

GameEvents.UnitPrekill.Add(CrazyFrogWars);
 
Code:
function CrazyFrogWars(iPlayer,iUnit,_,iPlotX, iPlotY, bDelay)
	if bDelay == true then

		local pPlayer = Players[iPlayer]         
		local pPlot = Map.GetPlot(iPlotX, iPlotY)
		if (pPlot == nil) then
			print("[CrazyFrogWars] Map.GetPlot(" .. iPlotX .. "," ..  iPlotY .. ") returns a nil value for the plot: terminating")
			return
		end
		local pUnit = pPlayer:GetUnitByID(iUnit);
		local iOriginalPlotOwner = pPlot:GetOwner()

		if (iOriginalPlotOwner ~= -1) and pUnit:GetUnitType()==iUnitCrazyFrog then
			local pPlotOwnerPlayer = Players[iOriginalPlotOwner]
			local iCrazyFrogTeam = pPlotOwnerPlayer:GetTeam()
			--Events.AudioPlay2DSound("AS2D_CRAZY_FROG_MUSIC")
			for playerID = 0, GameDefines.MAX_MAJOR_CIVS-1, 1 do
				if playerID ~= iOriginalPlotOwner then
					local pLoopPlayer = Players[playerID]
					if pLoopPlayer:IsAlive() then
						local pLoopPlayerTeam = Teams[pLoopPlayer:GetTeam()]
						if pLoopPlayerTeam:CanDeclareWar(iCrazyFrogTeam) then
							pLoopPlayerTeam:DeclareWar(iCrazyFrogTeam)
						end
					end
				end
			end
		end
	end
end
Your code was referencing back to the original "iPlayer" value rather than the "playerID" value within the loop and running the CanDecareWar and DeclareWar commands on the original "iPlayer" value and the plot owner value.
 
Last edited:
I based the player loop code off of some code you made in an earlier thread of yours where you helped someone make an LUA script that puts every player at war with everyone else, so I probably made some mistakes when following the variables. Thank you for correcting them! I can now see a lot of errors I had made that I didn't notice before.

However, I gave this code a shot in-game, and while a comment I added to the script printed in the logs and told that most of the code was running, there were still no declarations of war forced by the LUA on the target player. No errors are being turned up either, so I'm out of ideas at this point. I apologize for needing so much assistance lately, with the references I had I hoped that I'd be able to code this idea by myself but I guess things didn't quite go as planned.
 
You won't see pop-ups, I cannot remember if you'll even see notifications. I know the code method works because it worked for me for this code
Code:
function EveryoneHatesEveryone(iPlayer)
	if Game.GetGameTurn() <= gLockedWarImmunityTurns then return end
	local pPlayer = Players[iPlayer]
	if not pPlayer:IsAlive() then return end
	if pPlayer:IsHuman() then return end
	if pPlayer:IsMinorCiv() then return end
	local pPlayersTeam = pPlayer:GetTeam()
	for iMajPlayer = 0, GameDefines.MAX_MAJOR_CIVS - 1 do
		if Players[iMajPlayer] ~= pPlayer then
			local OtherPlayer = Players[iMajPlayer]
			if not OtherPlayer:IsMinorCiv() then
				if OtherPlayer:IsAlive() then
					local OtherPlayerTeam = OtherPlayer:GetTeam()
					if Teams[pPlayersTeam]:IsHasMet(OtherPlayerTeam) then
						if Teams[pPlayersTeam]:CanDeclareWar(OtherPlayerTeam) then
							Teams[pPlayersTeam]:DeclareWar(OtherPlayerTeam)
							Teams[pPlayersTeam]:SetPermanentWarPeace(OtherPlayerTeam, true)
						else print("Teams[pPlayersTeam]:CanDeclareWar(OtherPlayerTeam) returned false")
							print("iPlayer value was " .. iPlayer .. " for " ..GameInfo.Civilizations[Players[iPlayer]:GetCivilizationType()].Type .. " and pPlayersTeam value was " .. pPlayersTeam)
							print("iMajPlayer value was " .. iMajPlayer .. " for " ..GameInfo.Civilizations[Players[iMajPlayer]:GetCivilizationType()].Type .. " and OtherPlayerTeam value was " .. OtherPlayerTeam)
							print("Teams[pPlayersTeam]:IsAtWar(OtherPlayerTeam) was " .. tostring(Teams[pPlayersTeam]:IsAtWar(OtherPlayerTeam)))
						end
					end
				end
			end
		end
	end
end
GameEvents.PlayerDoTurn.Add(EveryoneHatesEveryone)
I was also locking the war-state after the declare-war, which might be necessary. But I cannot remember whether it really was or not.
 
That's the exact code I used as a reference when building my own! :) And on each test I've checked Diplomacy Overview just to be sure and there it shows no At War statuses either.
 
I know it worked because as the human I was able to attack the AI players I had already met once the "starting peace" period was over. I was able to do so without having the game ask me if I wanted to declare war.
 
I had forgotten about the Starting Peace period! I decided to test it by advancing the game 20-30 turns and trying again to see if that was the reason why the code wasn't putting anybody at war. Sadly, I got the same result, nobody was at war at all. Yet I don't think this is a restriction because LUA from the InGame Editor can force players at war with each other from the get go. I also tried locking the war-state right afterward which didn't work either.

EDIT: Apparently I made a massive blunder! After swapping over to the corrected code you posted and making a couple of tweaks, I accidentally deleted 'GameEvents.UnitPrekill.Add(CrazyFrogWars);' without noticing, which is why the actual routine didn't seem to be running anymore. The code works flawlessly now, and even includes the war declaration notifications!

Again, thank you so much for all of your help with this! You and Whoward have been absolute lifesavers!
 
Last edited:
Top Bottom