Game.AddPlayer works in-game?

Pazyryk

Deity
Joined
Jun 13, 2008
Messages
3,584
Does this method work?

Game.AddPlayer(PlayerID newPlayer, LeaderType leader, CivilizationType civ)

I know there are issues with adding new CS during gameplay. But I'm not aware of other modder's experiences adding a major player. If it does work:
  • do I need to set PlayerID at the current observerID + 1?
  • can LeaderType really be set independently from CivilizationType? (i.e., in violation of the table that sets leader by civ in Civilizations.xml?)

What I want to do specifically is create a new player, create city(ies) and units, and then switch the active player over to this new civ.
 
Don't know about AddPlayer() but did experiment with leaders some time back.

While you can set the leader via the (modded) setup screens to be different to the one allocated to the civ and start a new game with the different leader, it all goes horribly wrong when you load a saved game, as the leader is not stored in the save header but deduced from the saved civilization
 
Don't know about AddPlayer() but did experiment with leaders some time back.

While you can set the leader via the (modded) setup screens to be different to the one allocated to the civ and start a new game with the different leader, it all goes horribly wrong when you load a saved game, as the leader is not stored in the save header but deduced from the saved civilization

As many things, it's clear that the system is only half-built -- never finished because they never implemented multiple leaders in expansion or other dlc.

How did you change leaders in the setup in the first place? I've changed civs via player slots in PreGame, but I don't remember any way to separately affect leader. If you can change leader, could you just do this after game load to re-instate the alternate leader?
 
Well, I tried this. You can add the new player with a city, and transfer yourself over with Game.SetActivePlayer. Two problems:
  • The whole map is revealed
  • The game goes into some kind of autoplay.

On the second point, it's kind of weird because Game.GetAIAutoPlay() returns 0, which is not normally the case when you are in autoplay (even stuck in infinite autoplay started from the Fire Tuner). The UI treats the new civ as active player in every way (popups work and diplo list has you as the new civ, which again is not normal in autoplay). However, the autoplay can be halted after a turn by Game.SetAIAutoPlay(1, iNewPlayer).

But that leaves you with the whole map revealed. I believe this situation could be fixed in the main map (with some plot revealed function), but not in minimap. But why is this new player getting the whole map revealed?

Perhaps, like CSs, I need to bring this civ into the game at init, then kill (by removing all units), then bring back later.


Edit: you can change Game.SetActivePlayer to one of the normal AI civs. The map (and the minimap!) correctly reflect the world as known by that particular AI player.
 
The game goes into some kind of autoplay... the autoplay can be halted after a turn by Game.SetAIAutoPlay(1, iNewPlayer).
I would try it this way:
-- initialize valid values for playerID, leaderTypeID, civilizationTypeID
-- playerID must target an empty Players[] slot within the MajorCiv range
Game.AddPlayer( playerID, leaderTypeID, civilizationTypeID );
PreGame.SetSlotStatus( playerID, SlotStatus.SS_TAKEN ); -- or SS_OBSERVER, whatever
-- give the player some units and/or cities
-- it might also be necessary to set the starting plot
-- and only afterwards:
Game.SetActivePlayer( playerID );
Have not actually tried, but interested by the results :D
 
@bc1, I'll try as you outline.


Sort of related to the topic:

A long, long time ago I tried out Game.SetActivePlayer to transfer over to an existing AI civ (not one I just added, but one that has been playing since the beginning). As I remember, it worked perfectly well with no complications. However, when I try this now, the transfer happens and things mostly appear as they should (main map is correct for new civ) except that the game is stuck at "Waiting for Other Players". I believe the game is sitting there waiting for player 0 to finish. [Edit: perhaps I can force this with Game.DoControl(GameInfoTypes.CONTROL_ENDTURN) when I'm back at my modding computer]

This almost seems like the inverse of my undesired autoplay problem above:
  • If player 0 is suddenly not the active player, the computer doesn't seem to know (yet) that this is an AI player.
  • If player 5 suddenly is the active player, the computer doesn't seem to know that this is no longer an AI player.
When I say "computer" above, I mean whatever drives an AI player (moving units and ending turns). The UI is instantly aware of who is the new active player.


On a strange side note, the "undesired autoplay" I'm seeing seems to be much much much faster than normal autoplay. I mean really fast. I'm not positive that everything is working right, though it seems mostly OK (except the popups happen for the active player so you have to click through those).
 
Actually you might want to try this:

Code:
-- initialize valid values for playerID, leaderID, civilizationID, teamID, handicapID, startingPlot, and set values for <string> placeholders
-- playerID must target an empty Players[] slot within the MajorCiv range

Game.AddPlayer( playerID, leaderID, civilizationID );

PreGame.SetLeaderName( playerID, <string> );
PreGame.SetCivilizationDescription( playerID, <string> );
PreGame.SetCivilizationShortDescription( playerID, <string> );
PreGame.SetCivilizationAdjective( playerID, <string> );
PreGame.SetPassword( playerID, "" );

PreGame.SetTeam( playerID, teamID );
PreGame.SetHandicap( playerID, handicapID );

PreGame.SetSlotStatus( playerID, SlotStatus.SS_TAKEN);
PreGame.SetSlotClaim( playerID, SlotClaim.SLOTCLAIM_ASSIGNED );

Players[ playerID ]:SetStartingPlot( startingPlot );

Isn't fog of war a team thing ? Setting the player's team may be key to getting that to work.
Getting the computer to know if player is a human (SlotStatus.SS_TAKEN) or a computer (SlotStatus.SS_COMPUTER) is likely driven by PreGame.SetSlotStatus.
 
I'm always confused about whether PreGame can have any effect on anything after you've entered the game. But perhaps it can in this situation. I guess I'll find out if I can set teamID with PreGame this way.

What is the range of valid teams? Generally, I see playerID = teamID, so I guess it is the same as the range of playerIDs?

Another question is whether I should jump over the observer player. Eg, on a small map with 6 full civs, PreGame.GetSlotStatus(6) returns SlotStatus.SS_OBSERVER. Should I go for player 7 in this case? Or anything from 7 to 21 would be fine?
 
What is the range of valid teams? Generally, I see playerID = teamID, so I guess it is the same as the range of playerIDs...

correct
 
I'm always confused about whether PreGame can have any effect on anything after you've entered the game. But perhaps it can in this situation. I guess I'll find out if I can set teamID with PreGame this way.
The in-game player:SetTeam( teamID ) calls the C++ equivalent to PreGame.SetTeam(), so PreGame.SetTeam() should work for a blank, newly created player. And the other properties require PreGame... Oh and Game.AddPlayer calls PreGame too :D
 
Bah! Getting two very different kinds of failure, depending on when I AddPlayer.

This way gives me the out of control autoplay:
Spoiler :
Code:
	--Add new player here?
	Game.AddPlayer(iNewPlayer, newLeaderID, newCivID)

	--Setup the new player in PreGame
	PreGame.SetLeaderName(iNewPlayer, GameInfo.Leaders[newLeaderID].Description)					
	PreGame.SetCivilizationDescription(iNewPlayer, newCivInfo.Description)
	PreGame.SetCivilizationShortDescription(iNewPlayer, newCivInfo.ShortDescription)
	PreGame.SetCivilizationAdjective(iNewPlayer, newCivInfo.Adjective)
	PreGame.SetPassword(iNewPlayer, "")
	PreGame.SetTeam(iNewPlayer, iTeam)
	PreGame.SetHandicap(iNewPlayer, oldPlayer:GetHandicapType())
	PreGame.SetSlotStatus(iNewPlayer, SlotStatus.SS_TAKEN)
	PreGame.SetSlotClaim(iNewPlayer, SlotClaim.SLOTCLAIM_ASSIGNED)

	local newPlayer = Players[iNewPlayer]
	newPlayer:SetStartingPlot(oldPlayer:GetStartingPlot())

The way below avoids the autoplay. However, I can't end the turn now.
>UI.CanEndTurn()
false
>Players[7]:GetEndTurnBlockingType()
-1
Game.DoControl(GameInfoTypes.CONTROL_ENDTURN) doesn't work, although I already knew that since ActionInfoPanel.lua tries exactly that under conditions above.

Spoiler :
Code:
	--Setup the new player in PreGame
	PreGame.SetLeaderName(iNewPlayer, GameInfo.Leaders[newLeaderID].Description)					
	PreGame.SetCivilizationDescription(iNewPlayer, newCivInfo.Description)
	PreGame.SetCivilizationShortDescription(iNewPlayer, newCivInfo.ShortDescription)
	PreGame.SetCivilizationAdjective(iNewPlayer, newCivInfo.Adjective)
	PreGame.SetPassword(iNewPlayer, "")
	PreGame.SetTeam(iNewPlayer, iTeam)
	PreGame.SetHandicap(iNewPlayer, oldPlayer:GetHandicapType())
	PreGame.SetSlotStatus(iNewPlayer, SlotStatus.SS_TAKEN)
	PreGame.SetSlotClaim(iNewPlayer, SlotClaim.SLOTCLAIM_ASSIGNED)

	--Add new player here?
	Game.AddPlayer(iNewPlayer, newLeaderID, newCivID)
	local newPlayer = Players[iNewPlayer]
	newPlayer:SetStartingPlot(oldPlayer:GetStartingPlot())

Either way seems to "work", in that iTeam gets set correctly.

One thing I should say is that I'm killing off player 0.
 
Solved one problem: The "runaway autoplay w/ popups" seems to be from no player having SlotStatus.SS_TAKEN. I can stop this by PreGame.SetSlotStatus(playerID, SlotStatus.SS_TAKEN) while it's going.


However, after fixing that, something is still breaking turn advancement. I finish up all my moves, click Next Turn, and get "UI thinks that we can't end turn, but the notification system disagrees". This print statement is from ActionInfoPanel.lua and the very next thing ActionInfoPanel tries to do is this: Game.DoControl(ControlTypes.CONTROL_ENDTURN).

But it doesn't do anything, nor does typing that in Fire Tuner. However, using ControlTypes.CONTROL_FORCEENDTURN does advance the turn. The game seems to play normally then except I can only end turn with Game.DoControl(ControlTypes.CONTROL_FORCEENDTURN) from then on.

There seem to be two solutions which fix the problem permanently:

Save and reload
Game.SetAIAutoPlay(turns, returnAsPlayerID) with turns > 0.

I checked all the PreGame slot status and slot claims and they are exactly the same before and after either "fix" above. So exactly what is broken and later fixed (by save/load or a short autorun) is mysterious to me.

The SetAIAutoPlay, by the way, is shifting active player to the next player with SlotStatus.SS_OBSERVER while it runs. Then it returns active player to whoever I ask for. But it must be doing something different (or more) than Game.SetActivePlayer() does by itself.


For that matter, I should note that SetAIAutoPlay is the only way I know to change players (successfully) in an unmodded game. Using Game.SetActivePlayer() shifts you over to another civ but leaves you stuck with "Waiting for other Players". But you can run Game.SetAIAutoPlay(turns, returnAsPlayerID) and come back as any other player and it works. Unfortunately, the player gets a peek at the whole map during that 1 turn of autoplay.

OK, I've now changed the topic from AddPlayer to changing active player. I think the thread title is basically solved. The problem now is screwed up turns after shifting active player.
 
1. Yes, that exactly explained the runnaway autoplay. All players in the game had SlotStatus.SS_COMPUTER. I feel like that's solved now. The problem now is the turn hang.

2. Yes, I've tried variations on:
Code:
	if Game.GetActivePlayer() == iOldPlayer  then
		PreGame.SetSlotStatus(iOldPlayer, SlotStatus.SS_COMPUTER)
		PreGame.SetSlotStatus(iNewPlayer, SlotStatus.SS_TAKEN)
		print("Changing active player to ", iNewPlayer)
		Game.SetActivePlayer(iNewPlayer, false)
	end
Full disclosure: the code I'm working with kills player 0. So a bit latter the dll has set player 0 to SlotStatus.SS_CLOSED. Perhaps I should keep this player alive for a turn or two to see if that avoids the turn hang.

3. That's solved now. This happened early in my testing from switching active player to someone with SlotStatus.SS_OBSERVER.

I feel like I almost understand autoplay now. Basically, you can launch it yourself by setting active player to an unadded player with SlotStatus.SS_OBSERVER (which gives you full map visibility and prevents any UI popups) and setting all other players to SlotStatus.SS_COMPUTER (which immediate starts autoplay even in an unmodded game). However, it not exactly like using SetAIAutoPlay: 1) unit animation is turned off (why?) which perhaps explains why it runs so much faster than my normal experience with autoplay (which I use a lot in my mod development); 2) SetAIAutoPlay returns control to any player (2nd arg) without the "turn-hang" problems I'm getting any time I use SetActivePlayer. Note: I haven't yet tried SetActivePlayer in an unmodded game in conjunction with changing SlotStatuses.

I'll try getting the PreGame settings more complete to see if that helps. I haven't set everything that appears to be settable, though what remains is stuff like SetCivilizationKey...

If that doesn't work, I'll post my full code.
 
Some progress...

I can swap player successfully in an unmodded game with:

>Game.SetActivePlayer(2, true); PreGame.SetSlotStatus(0, SlotStatus.SS_COMPUTER); PreGame.SetSlotStatus(2, SlotStatus.SS_TAKEN)

Everything works as expected and I can play on for a while. (Well, the minimap is always a bit screwy after player shifts, but not in a way that is unusable. And it corrects with game save/reload.)

I even killed player 0:

>Players[0]:KillUnits(); Players[0]:KillCities()
> PreGame.GetSlotStatus(0)
2

The game continues to play just fine. So now I know it's possible to change to another player and kill player 0...
 
OK, I think I've solved all my problems related to SetActivePlayer. So at least I'm back on the topic of the thread.

But I'm still stuck on this "turn hang" problem that always comes up with the added player. Here are the conditions for the turn hang:
  • Active player has shifted over to a new player added by Game.AddPlayer
  • And, the civ has (or ever had) a unit

There is no hang until the civ has a unit. Doesn't matter whether unit is Lua-inited or built by city. And it doesn't matter if I immediately delete the unit on the turn it is produced -- I still get the hang forever after.

For a while I thought it had something to do with what I did after to player 0. But that really doesn't seem to matter.

Once the hang happens, turns can only be progressed by Game.DoControl(ControlTypes.CONTROL_FORCEENDTURN). The human UI normally uses CONTROL_ENDTURN and this won't do it.


Here's the code. You can see in the commented-out code that I'm trying to "clone" a player as a new civilization. But the test code here simply adds the new player next to the existing one.
Code:
local g_nextAvailablePlayerIndex
for iPlayer = 1, GameDefines.MAX_PLAYERS - 1 do
	if PreGame.GetSlotStatus(iPlayer) == SlotStatus.SS_OBSERVER then
		g_nextAvailablePlayerIndex = iPlayer
		break
	end
end

function CloneAsDifferentCivilization(iOldPlayer, newCivID)
	--Swaps civ in-place. New civ will have cities, units, techs, policies, gold, faith, etc. from old civ
	local iNewPlayer = g_nextAvailablePlayerIndex
	g_nextAvailablePlayerID = g_nextAvailablePlayerIndex + 1
	local newCivInfo = GameInfo.Civilizations[newCivID]
	local oldPlayer = Players[iOldPlayer]
	local iTeam = oldPlayer:GetTeam()		--use same team to preserve FOW, techs & war status.

	--Get the proper leader for the new civ
	local newLeaderID
	for row in GameInfo.Civilization_Leaders() do
		if newCivInfo.Type == row.CivilizationType then
			newLeaderID = GameInfoTypes[row.LeaderheadType]
			break
		end
	end
	if not newLeaderID then
		error("PlayerCivSwap: No leader for new civ type")
	end


	--Add new player here?
	print("AddPlayer")
	Game.AddPlayer(iNewPlayer, newLeaderID, newCivID)

	--Setup the new player in PreGame
	PreGame.SetLeaderName(iNewPlayer, GameInfo.Leaders[newLeaderID].Description)					
	PreGame.SetCivilizationDescription(iNewPlayer, newCivInfo.Description)
	PreGame.SetCivilizationShortDescription(iNewPlayer, newCivInfo.ShortDescription)
	PreGame.SetCivilizationAdjective(iNewPlayer, newCivInfo.Adjective)
	PreGame.SetPassword(iNewPlayer, "")
	PreGame.SetCivilization(iNewPlayer, newCivID)
	PreGame.SetLeaderType(iNewPlayer, newLeaderID)
	PreGame.SetTeam(iNewPlayer, iTeam)
	PreGame.SetHandicap(iNewPlayer, oldPlayer:GetHandicapType())
	PreGame.SetSlotStatus(iNewPlayer, SlotStatus.SS_COMPUTER)
	PreGame.SetSlotClaim(iNewPlayer, SlotClaim.SLOTCLAIM_ASSIGNED)

	print("GetSlotStatus = ", PreGame.GetSlotStatus(iNewPlayer))

	local newPlayer = Players[iNewPlayer]
	newPlayer:SetStartingPlot(oldPlayer:GetStartingPlot())

	print("New player team is / should be= ", newPlayer:GetTeam(), iTeam)
	print("Starting plot, old/new = ", oldPlayer:GetStartingPlot(), newPlayer:GetStartingPlot())

	--Clone the old player
	[COLOR="Grey"]--[[ DISABLED
	for city in oldPlayer:Cities() do
		local plot = city:Plot()
		local bOccupied, bPuppet = city:IsOccupied(), city:IsPuppet()
		print(bOccupied, bPuppet)
		newPlayer:AcquireCity(city, false, false)
		local newCity = plot:GetPlotCity()
		print(newCity:GetName())
		print(newCity:IsOccupied())
		newCity:SetOccupied(bOccupied)
		newCity:SetPuppet(bPuppet)
		print(newCity:IsOccupied())
		--need: SetWeLoveTheKingDayCounter, SetResourceDemanded
		--what about build queue and progresses (can loop through everything if needed)
	end
	
	for unit in oldPlayer:Units() do
		local newUnit = newPlayer:InitUnit(unit:GetUnitType(), unit:GetX(), unit:GetY())
		newUnit:Convert(unit)	--gets promotions & damage, but not original owner
		if newUnit:GetOriginalOwner() == iOldPlayer then
			newUnit:SetOriginalOwner(iNewPlayer)
		end
		--unit movement?
	end

	newPlayer:SetGold(oldPlayer:GetGold())
	newPlayer:SetFaith(oldPlayer:GetFaith())
	newPlayer:SetJONSCulture(oldPlayer:GetJONSCulture())
	for policyBranchInfo in GameInfo.PolicyBranchTypes() do
		if oldPlayer:IsPolicyBranchUnlocked(policyBranchInfo.ID) then
			newPlayer:SetPolicyBranchUnlocked(policyBranchInfo.ID, true)
		end
	end
	for policyInfo in GameInfo.Policies() do
		if oldPlayer:HasPolicy(policyInfo.ID) then
			newPlayer:SetHasPolicy(policyInfo.ID, true)
		end
	end

	local researchQueue = {}
	for techInfo in GameInfo.Technologies() do
		local queuePos = oldPlayer:GetQueuePosition(techInfo.ID)
		if queuePos ~= -1 then
			researchQueue[queuePos] = techInfo.ID
		end
	end
	newPlayer:ClearResearchQueue()
	for i = 0, #researchQueue do
		newPlayer:PushResearch(researchQueue[i], false)
	end

	--Not needed by mod: trade routes, ideologies, influence, tourism, great works
	]][/COLOR]

	--DEBUG: just add new player and leave player 0 unmolested
	local oldCapital = oldPlayer:GetCapitalCity()
	local newCity = newPlayer:InitCity(oldCapital:GetX()+3, oldCapital:GetY())

	--Will this be the active player?
	if Game.GetActivePlayer() == iOldPlayer  then
		print("Changing active player to ", iNewPlayer)
		Game.SetActivePlayer(iNewPlayer, false)
		PreGame.SetSlotStatus(iOldPlayer, SlotStatus.SS_COMPUTER)
		PreGame.SetSlotStatus(iNewPlayer, SlotStatus.SS_TAKEN)
	end

	print("GetSlotStatus = old / new", PreGame.GetSlotStatus(iOldPlayer), PreGame.GetSlotStatus(iNewPlayer))

end

GameEvents.PlayerDoTurn.Add(function(iPlayer) print("PlayerDoTurn for player ", iPlayer) end)
 
That would set the game in hotseat mode, you're looking for trouble.
You need to tell it somehow that oldPlayer's turn is done, otherwise it will get stuck.
Try before the Game.SetActivePlayer() statement:
UI:SetCanEndTurn(true);
UI:ClearSelectedCities();
UI:ClearSelectionList();
Game.DoControl(ControlTypes.CONTROL_ENDTURN); --NOT CONTROL_FORCEENDTURN
 
No, that didn't clear the hang. The hang isn't on the old player anyway (the SlotStatus change fixes that).

By the way, you can reproduce the hang easily in an unmodded game:

Code:
Game.SetActivePlayer(2, false); PreGame.SetSlotStatus(0, SlotStatus.SS_COMPUTER); PreGame.SetSlotStatus(2, SlotStatus.SS_TAKEN)

But if you change the false to true, then no hang and the game plays on just fine (or seems to).


Does it really matter if I'm now in hotseat mode? Maybe that's the only way the dll can properly handle a change in active player.


Edit: But I'm not in hotseat!
Code:
Game.SetActivePlayer(2, true); PreGame.SetSlotStatus(0, SlotStatus.SS_COMPUTER); PreGame.SetSlotStatus(2, SlotStatus.SS_TAKEN)
--plays fine with no hang
> Game.IsHotSeat()
false
 
Back
Top Bottom