PlayerType getActivePlayer()
Returns the index of the human player currently active and using the local
machine. In a single-player game this returns the human player, in a Hotseat
game returns the currently active human player, in a network/PBEM game
returns the player on whose machine this code is run. This is useful for
running player-specific code in MP games.
Bad Example (makes MP games go out of synch, do NOT use this in MP):
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
gc.getPlayer(0).setGold(1000000)
Above code gives 1 million gold to player 0, if the machine this code is
run on is used by this player. For other machines in an MP game this code
is not run -- they are not aware player 0 got 1 million gold -- so the game
goes out of synch, generating OOS errors. So never use the
getActivePlayer() method to change the gamestate in MP! It's main use is
for UI events:
Good Example (displays the message 'You are player 0' to player 0 and
only player 0):
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
CyInterface().addMessage(0, False, 0, "You are player 0", "", 0, "", CyGame().getReplayMessageColor(0), 0, 0, False, False)
TheLopez said:Caesium, here is the entry from: http://civilization4.net/files/modding/PythonAPI_v160/
for the gc.getActivePlayer() method:
Code:PlayerType getActivePlayer() Returns the index of the human player currently active and using the local machine. In a single-player game this returns the human player, in a Hotseat game returns the currently active human player, in a network/PBEM game returns the player on whose machine this code is run. This is useful for running player-specific code in MP games. Bad Example (makes MP games go out of synch, do NOT use this in MP): [code] if gc.getGame().getActivePlayer() == gc.getPlayer(0): gc.getPlayer(0).setGold(1000000)
Above code gives 1 million gold to player 0, if the machine this code is run on is used by this player. For other machines in an MP game this code is not run -- they are not aware player 0 got 1 million gold -- so the game goes out of synch, generating OOS errors. So never use the getActivePlayer() method to change the gamestate in MP! It's main use is for UI events:
Good Example (displays the message 'You are player 0' to player 0 and only player 0):
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
CyInterface().addMessage(0, False, 0, "You are player 0", "", 0, "",
CyGame().getReplayMessageColor(0), 0, 0, False, False)
[/CODE]
So you need to use gc.getGame().getActivePlayer() instead of gc.getActivePlayer()
CyPlayer* CyGlobalContext::getCyActivePlayer()
{
PlayerTypes pt = GC.getGameINLINE().getActivePlayer();
return pt != NO_PLAYER ? getCyPlayer(pt) : NULL;
}
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
gc.getPlayer(0).setGold(1000000)
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
CyInterface().addMessage(0, False, 0, "You are player 0", "", 0, "", CyGame().getReplayMessageColor(0), 0, 0, False, False)
def Revolution(self, inputClass):
activePlayer = gc.getPlayer(self.iActivePlayer)
if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_CLICKED) :
if (activePlayer.canRevolution(0)):
messageControl = CyMessageControl()
messageControl.sendUpdateCivics(self.m_paeDisplayCivics)
screen = self.getScreen()
screen.hideScreen()
def Revolution(self, inputClass):
activePlayer = gc.getPlayer(self.iActivePlayer)
if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_CLICKED) :
if (activePlayer.canRevolution(0)):
[b]gc.getPlayer(activePlayer).setCivics([i]Whatever the arguments are[/i])[/b]
screen = self.getScreen()
screen.hideScreen()
Just as a confirmation, in case I'm missing something, this code would not cause the game to OOS as it would run on all machines, right?Gerikes said:Code:if gc.getGame().getActivePlayer() == gc.getPlayer(0): gc.getPlayer(0).setGold(1000000)
gc.getPlayer(0).setGold(1000000)
If I understand him correctly, you understand him rightThe Great Apple said:Just as a confirmation, in case I'm missing something, this code would not cause the game to OOS as it would run on all machines, right?
Code:gc.getPlayer(0).setGold(1000000)
The Great Apple said:Just as a confirmation, in case I'm missing something, this code would not cause the game to OOS as it would run on all machines, right?
Code:gc.getPlayer(0).setGold(1000000)
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
gc.getPlayer(0).setGold(1000000)
if gc.getGame().getActivePlayer() == gc.getPlayer(0):
if (object of player [b]0[/b]) == (object of player 0):
gc.getPlayer(0).setGold(1000000)
if (object of player [b]1[/b]) == (object of player 0)
Gerikes said:Edit: Caesium, sure I'll take a look. I think getting multiplayer to work correctly is so interesting. Maybe I'll start freelancing myself to mods so I can just work on those problems![]()
Gaurav said:But how do you go about testing multiplayer mods? You can't cheat!
The Great Apple said:It would return 1. It returns the ID of the player playing on that machine at the present time.
CyInterface().addMessage([b]CyGame().getActivePlayer()[/b], True, 10, message, None, 2, None, ColorTypes(8), 0, 0, False, False)
[b]ePlayer = pUnit.getOwner()[/b]
[b]if ( ePlayer == CyGame().getActivePlayer() ):[/b]
CyInterface().addMessage([b]ePlayer[/b], True, 10, message, None, 2, None, ColorTypes(8), 0, 0, False, False)
Teg_Navanis said:I guess you'll have to work with the function's argsList. Normally, you can easily find out whose turn it is (sometimes the current player is an argument of the function (iPlayer), in most other cases it is the owner of the unit that is moving/attacking/... or the city that is being upgraded). I don't think there is a function though.
Gerikes said:That's correct. Using just that code alone, since it will run on all the computers in the game, player 0 on every computer will get the gold. The local player is not going to always be player 0. Normally the host is 0, the next player to enter is 1, etc.
But in the example BAD code, it doesn't work.
Code:if gc.getGame().getActivePlayer() == gc.getPlayer(0): gc.getPlayer(0).setGold(1000000)
Imagine this run on a 1v1 game, where there are two players who both currently have 0 gold. Suddenly, an event happens, and the bad example python code is run...
----------------------------
Player 0 (Host's Computer)
----------------------------
On player 0 (the host), the line of code:
Code:if gc.getGame().getActivePlayer() == gc.getPlayer(0):
evaluates to..
Code:if (object of player [b]0[/b]) == (object of player 0):
This returns true, thus on the host's computer, the statements inside the if block is run:
Code:gc.getPlayer(0).setGold(1000000)
Thus, player 0 (the host) now has a ton of gold.
---------------------------
Player 1 (Client's Computer):
---------------------------
The first line of code evaluates to:
Code:if (object of player [b]1[/b]) == (object of player 0)
This is FALSE, so on the clients computer, the setGold function is not run, and thus the result is that on the client, both players still have zero gold. The host's player 0 has a ton, the client's player 0 still have none, thus OOS.
Edit: Caesium, sure I'll take a look. I think getting multiplayer to work correctly is so interesting. Maybe I'll start freelancing myself to mods so I can just work on those problems![]()
Gaurav said:I was wondering if you could finish this example and explain how to give the player who say, just hit ALT-G or whatever, 1,000,000 gold in multiplayer, without giving it to the other players, without causing an out of synch error. I am of course assuming this is possible without the SDK.
I am using this example because I figure it is relatively simple, even if (almost) useless. But if you have something similar around already implemented, that would basically work along the same line and show me what to do, feel free to use that.
##########################################
# Interface Screen
##########################################
def interfaceScreen(self, eMission, iPlotX, iPlotY, playerToPopFor):
# If the player has no selected unit, it couldn't of been them that
# popped up the screen.
if (CyInterface().getHeadSelectedUnit() == None):
return
self.pSelectedGroup = CyInterface().getHeadSelectedUnit().getGroup()
# Multiplayer check
if (self.pSelectedGroup.getOwner() != playerToPopFor):
return
if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_CLICKED):
if (inputClass.getFlags() & MouseFlags.MOUSE_RBUTTONUP):
# Cancel unit targetting on right-click
self.pScreen.hideScreen()
elif (inputClass.getFlags() & MouseFlags.MOUSE_LBUTTONUP):
# Turn the Page
if (inputClass.getFunctionName() == self.szTurnPageFunctionName):
self.page = (self.page + 1) % self.iTotalPages
self.pScreen.setState(self.szTurnPageFunctionName, False)
self.updateView()
# Get a target
if (inputClass.getFunctionName() == self.szUnitChooserPrefix):
unitPicked = self.plotUnits[self.page][inputClass.getID()]
# What a hack. Assume that the mission that needs to be called when targetting is done is one before the mission called to target.
# This is always the case at the time of this writing, but still a pretty big hack. Could be fixed by
# allowing xml tags in the missioninfos be able to see other missions.
[b]gc.getGame().selectionListGameNetMessage(int(GameMessageTypes.GAMEMESSAGE_PUSH_MISSION), self.eMissionInput - 1, unitPicked.getOwner(), unitPicked.getID(), gSC_ATTACK_MISSION_TARGET_DATA_INCLUDED_FLAG, False, False)[/b]
self.pScreen.hideScreen()
Arguments:
VOID selectionListGameNetMessage(INT eMessage, INT iData2, INT iData3, INT iData4, INT iFlags, BOOL bAlt, BOOL bShift)
Gerikes said:Well, I have another example here from Civcraft, but I'm not sure how helpful it will be. It shows how to have code that's only being run on one computer propagate it's message to the others. However, it involves pushing orders to a unit, which is a little bit easier since there is a function that pretty much does it for you. I'll put some extra comments for other things at the end, but as I haven't had the need to do things like what you asked yet, I'm hesitant to put up code since OOS problems are sometimes tough to find and debug, so I don't want to throw down that sort of code. The code I do use I'm pretty sure works, since I've been testing with it all along.
The Example
I have a screen that will only pop up on the screen of the player that selected a certain style of attack (since attacking a plot will allow you in Civcraft to choose the unit to attack, rather than the defender getting the best one).
The actual code to "popup" the screen is done whenever a user makes a certain unit do a mission. I used a screen rather than a pop-up because you seem to have much more control over how a screen looks, even if it takes more code to make it.
When I order a unit to run the mission that will start the screen, the message is passed to all computers that this unit is trying to do that mission. The pushMission function (which is where I have code to start the screen), then, will call the python function that starts up the screen on each computer. In the init code, however, I have a check to make sure that the screen only pops up for the owner of the unit who made the call.
Code:########################################## # Interface Screen ########################################## def interfaceScreen(self, eMission, iPlotX, iPlotY, playerToPopFor): # If the player has no selected unit, it couldn't of been them that # popped up the screen. if (CyInterface().getHeadSelectedUnit() == None): return self.pSelectedGroup = CyInterface().getHeadSelectedUnit().getGroup() # Multiplayer check if (self.pSelectedGroup.getOwner() != playerToPopFor): return
Thus, the screen is only created on that one player's monitor, and for everyone else the code stops executing through this function. This means that I now have to be careful, as anything in the rest of the code (including all the functions that handle input) will only affect that one player, and a wrong move can throw the game OOS.
Eventually, the player will select the graphic icon from the screen of the unit they want to attack. Here's the code that handles input:
Code:if (inputClass.getNotifyCode() == NotifyCode.NOTIFY_CLICKED): if (inputClass.getFlags() & MouseFlags.MOUSE_RBUTTONUP): # Cancel unit targetting on right-click self.pScreen.hideScreen() elif (inputClass.getFlags() & MouseFlags.MOUSE_LBUTTONUP): # Turn the Page if (inputClass.getFunctionName() == self.szTurnPageFunctionName): self.page = (self.page + 1) % self.iTotalPages self.pScreen.setState(self.szTurnPageFunctionName, False) self.updateView() # Get a target if (inputClass.getFunctionName() == self.szUnitChooserPrefix): unitPicked = self.plotUnits[self.page][inputClass.getID()] # What a hack. Assume that the mission that needs to be called when targetting is done is one before the mission called to target. # This is always the case at the time of this writing, but still a pretty big hack. Could be fixed by # allowing xml tags in the missioninfos be able to see other missions. [b]gc.getGame().selectionListGameNetMessage(int(GameMessageTypes.GAMEMESSAGE_PUSH_MISSION), self.eMissionInput - 1, unitPicked.getOwner(), unitPicked.getID(), gSC_ATTACK_MISSION_TARGET_DATA_INCLUDED_FLAG, False, False)[/b] self.pScreen.hideScreen()
(Ignore the comment about the hack. That has to do with the mission I want to run can't be determined from the old mission, since I can't add an entry in the XML file that says "when this mission is run, run this other mission". So, the "hack" is just to put the mission I do want to run one before the other in the XML, and then just subtract one from the eMissionInput variable to get the correct mission. This has nothing to do with what I'm talking about now, however)
The emboldened line is the line that is important. At first, I just had a "self.pSelectedGroup.pushMission(blah blah...)", but that wouldn't work since it only pushed the mission on the local computer running the code. It worked fine in single player, but multiplayer would be thrown out of whack. (This was especially hard to detect since the mission I used doesn't affect any of the pieces of data that an OOS checksum checks for (specifically, a unit's hit points), and thus the game was OOS, but there was no message being displayed).
So, instead I used the selectionListGameNetMessage. This function is used to do message-passing (where a "message" is a way to pass information between computers that only originates on one computer, such as the click on my unit-chooser screen), and in vanilla Civ is only used in the SDK to handle actions (where the "selection list" is the row of buttons on the bottom of your screen where you can do things). Specifically, it orders around the selected unit.
Code:Arguments: VOID selectionListGameNetMessage(INT eMessage, INT iData2, INT iData3, INT iData4, INT iFlags, BOOL bAlt, BOOL bShift)
The first argument uses the GameMessageType enumerated type for what to actually do. So, most of the times, you can get away with using this function for doing stuff. For example, I used it for GameMessageTypes.GAMEMESSAGE_PUSH_MISSION, then filled in the rest of the data for that mission. Note, however, that the mission pushed using selectionListGameNetMessage will affect the currently selected unit (more specifically, the currently selected unit's selection group), so trying to send a message to all computers to have some other group push a mission won't work (unless you change the selected unit, then change back, but that's a bit hackish if you ask me). You can also use "selectedCitiesGameNetMessage" to do orders with specific cities. This is how you can make a certain action
Other Thoughts
That should help you do many of the things you might want to do. However, you still are rather limited, it seems. First off, it just so happens that push mission was on the list, but "change gold" wasn't. So, how do you do things on the list? Also, there is the aformentioned item that the selected unit or city will be involved.
Also, the mission pushed (or whatever message you're using) can only affect the selected unit (or, in the case of selectedCitiesGameNetMessage, the selected cities). These two combined restrictions will eliminate the ability for doing many things.
So, what's the generic alternative? The CyMessageControl() function is probably the key. Like I said, I haven't had experience doing it, but I THINK that calling CyMessageControl().sendApplyEvent(...) using it's proper arguments could get you to run a custom event (which you would add to the self.Events variable in your CvEventManager class). I'm not sure if that's right, but it's a good alley to start checking out if that's the desired outcome.
Hopefully this has been helpful. I'm reading over it now and I start to think that many parts could be worded better. Maybe at some point, if I ever finally check out what the sendApplyEvent function does, I'll put all the MP and OOS tidbits of info I have and put it into a well-crafted tutorial, so I can stop insulting myself with attempts of explanations like this![]()
Gaurav said:Thanks so much for your help, this clears things up a lot. Since there are only a few limited GameMessageTypes, figuring out how to work sendApplyEvent will probably be useful at some point.
For my mod the set() function called most often is CyUnit.setName(). However I am not sure yet that is where my OOS errors come from, because they were in there before I ever added any of Ruff's unit naming mods. So I still have to learn how to track them down in the first place.
Gerikes said:One thing that might be quick and dirty is to see if the soren random number generator calls are different. By enabling the right combination of logging in the .ini file, you should be spawning a log (MPlog.txt, I think the name is) that gives you all of the movements that are happening, as well as a log of all the soren random number calls. If one computer makes a call to getSorenRandNum and another doesn't, then the seeds will go out of sync, and cause OOS. This might be why a unaltered gameplay mod may go out of sync, since either side isn't changing gameplay, but one may be making use of the soren random number generator, while the other one isn't.