• Civilization 7 has been announced. For more info please check the forum here .

Multiplayer Setup

MatzeHH

Warlord
Joined
Jan 8, 2006
Messages
210
Location
Germany
Hi there!

I have made a screen with python where the player can change some additional game options.
This screen appears once at game start and can not be accessed later in the game.
It works fine for single player games.
But in a multiplayer game i want this screen only be shown to the game host.
I couldn't find a function which returns the host.
Any idea how to solve this?

Matze
 
You mean

Code:
if gc.getGame().getActivePlayer() == gc.getPlayer(0):

should do it?

I Hope it doesn't result in synch problems.

Matze
 
MatzeHH said:
You mean

Code:
if gc.getGame().getActivePlayer() == gc.getPlayer(0):

should do it?

I Hope it doesn't result in synch problems.

Matze


Sync problems should only happen when you change the gamestate. If all you're doing is displaying information ("read-only" gamestate info), you should be fine, but if you try to push missions , change research or gold, etc., after that part of the code, it will go OOS.

Check out the CyMessageControl module for ways of getting around this restriction if you want to change some parts of the game state. For example, using pushMission after that code will not correctly sync, but using CyMessageControl().sendPushOrder() will not only push the order on your computer, but send a message to all other computers about the pushed order.
 
*bump*

I have solved this problem in the SDK:
Code:
void CvGame::init(HandicapTypes eHandicap)
{
[...]
	if ((isGameMultiPlayer() && gDLL->isFMPMgrHost())
		|| (GC.getInitCore().getType() == GAME_SP_NEW)
		|| (GC.getInitCore().getType() == GAME_SP_SCENARIO))
	{
		gDLL->getPythonIFace()->callFunction("CvScreensInterface", "showModOptionsScreen");
	}
[...]
}
isFMPMgrHost() does what I want.

So far, it works fine. The "ModOptionsScreen" is only shown to the game host.
On this screen I set some game options (see here). These options are members of the CvGame object.

Now, I have to send these options to the other players. It seems like the CvGame object is not synchronized automatically. Or maybe I have to put the code above to another function. I found out, that CvGame::init runs on every client. My first thought was that the CvGame object is initialized only on the hosts computer and is the copied to the clients. I was wrong about that.

Any idea how to send these options to the clients?

Edit: here is a list of all send functions in the SDK:
Code:
	virtual void sendPlayerInfo(PlayerTypes eActivePlayer) = 0;
	virtual void sendGameInfo(const CvWString& szGameName, const CvWString& szAdminPassword) = 0;
	virtual void sendPlayerOption(PlayerOptionTypes eOption, bool bValue) = 0;
	virtual void sendExtendedGame() = 0;
	virtual void sendAutoMoves() = 0;
	virtual void sendTurnComplete() = 0;
	virtual void sendJoinGroup(int iUnitID, int iHeadID) = 0;
	virtual void sendPushMission(int iUnitID, MissionTypes eMission, int iData1, int iData2, int iFlags, bool bShift) = 0;
	virtual void sendAutoMission(int iUnitID) = 0;
	virtual void sendDoCommand(int iUnitID, CommandTypes eCommand, int iData1, int iData2, bool bAlt) = 0;
	virtual void sendPushOrder(int iCityID, OrderTypes eOrder, int iData, bool bAlt, bool bShift, bool bCtrl) = 0;
	virtual void sendPopOrder(int iCity, int iNum) = 0;
	virtual void sendDoTask(int iCity, TaskTypes eTask, int iData1, int iData2, bool bOption) = 0;
	virtual void sendResearch(TechTypes eTech, int iDiscover, bool bShift) = 0;
	virtual void sendPercentChange(CommerceTypes eCommerce, int iChange) = 0;
	virtual void sendConvert(ReligionTypes eReligion) = 0;
	virtual void sendChat(const CvWString& szChatString, ChatTargetTypes eTarget) = 0;
	virtual void sendPing(int iX, int iY) = 0;
	virtual void sendPause(int iPauseID = -1) = 0;
	virtual void sendMPRetire() = 0;
	virtual void sendToggleTradeMessage(PlayerTypes eWho, TradeableItems eItemType, int iData, int iOtherWho, bool bAIOffer, bool bSendToAll = false) = 0;
	virtual void sendClearTableMessage(PlayerTypes eWhoTradingWith) = 0;
	virtual void sendImplementDealMessage(PlayerTypes eOtherWho, CLinkList<TradeData>* pOurList, CLinkList<TradeData>* pTheirList) = 0;
	virtual void sendChangeWar(TeamTypes iRivalTeam, bool bWar) = 0;
	virtual void sendChooseElection(VoteTypes eVote) = 0;
	virtual void sendDiploVote(VoteTypes eVote, int iChoice) = 0;
	virtual void sendContactCiv(NetContactTypes eContactType, PlayerTypes eWho) = 0;
	virtual void sendOffer() = 0;
	virtual void sendDiploEvent(PlayerTypes eWhoTradingWith, DiploEventTypes eDiploEvent, int iData1, int iData2) = 0;
	virtual void sendRenegotiate(PlayerTypes eWhoTradingWith) = 0;
	virtual void sendRenegotiateThisItem(PlayerTypes ePlayer2, TradeableItems eItemType, int iData) = 0;
	virtual void sendExitTrade() = 0;
	virtual void sendKillDeal(int iDealID) = 0;
	virtual void sendUpdateCivics(CivicTypes* paeCivics) = 0;
	virtual void sendDiplomacy(PlayerTypes ePlayer, CvDiploParameters* pParams) = 0;

But I doubt one of them can help me, because I want to send a member variable I created on my own.

Edit2: In CyMessageControl is a function namend sendApplyEvent. Does anybody know what this is for?

Matze
 
I was disapointed as well to find that there didn't seem to be a generic "send message" function listed there.

You might be able to make one, however, out of the "sendChat" function. As long as you put a special value at the beginning of the chat message, you can then construct a string with the values you want to pass over, and then parse it on the other side. I haven't tried doing anything like that, but I know it's possible to make sure that the chat message isn't put into the chat window if you don't want it to.

Edit: Yeah, CyMessageControl().sendApplyEvent() might work. I would assume it would call the event manager on the other computers:

Code:
def applyEvent( self, argsList ):
		'Apply the effects of an event '
		context, playerID, netUserData, popupReturn = argsList

The arguments don't seem to matchup correctly (popupReturn?), but that would be my guess.

Tell me how this comes out, I'm very interested!
 
Heureka! The combination of sendApplyEvent with applyEvent seems to be exactly what I need. It looks like you can define your own events and send them to the other players.
I have to do some more test runs and then I will tell you the way.

Matze
 
Ok, here we go!

Let's assume you have a file called CvMyEvent.py, from where you want to trigger the remote events.
In this file we define an ID for this event:
Code:
EventDoSomething=5012
Choose the number carefully, there are already some IDs in CvUtil, don't use one of them.

In CvEventManager.py we find some imports, let's add our file:
Code:
import CvMyEvent

In the part which starts with self.Events add your own event to the list:
Code:
self.Events={
	CvUtil.EventEditCityName : ('EditCityName', self.__eventEditCityNameApply, self.__eventEditCityNameBegin),
	CvUtil.EventEditCity : ('EditCity', self.__eventEditCityApply, self.__eventEditCityBegin),
	CvUtil.EventPlaceObject : ('PlaceObject', self.__eventPlaceObjectApply, self.__eventPlaceObjectBegin),
	CvUtil.EventAwardTechsAndGold: ('AwardTechsAndGold', self.__eventAwardTechsAndGoldApply, self.__eventAwardTechsAndGoldBegin),
	CvUtil.EventEditUnitName : ('EditUnitName', self.__eventEditUnitNameApply, self.__eventEditUnitNameBegin),
	CvUtil.EventWBAllPlotsPopup : ('WBAllPlotsPopup', self.__eventWBAllPlotsPopupApply, self.__eventWBAllPlotsPopupBegin),
	CvUtil.EventWBLandmarkPopup : ('WBLandmarkPopup', self.__eventWBLandmarkPopupApply, self.__eventWBLandmarkPopupBegin),
	CvUtil.EventWBScriptPopup : ('WBScriptPopup', self.__eventWBScriptPopupApply, self.__eventWBScriptPopupBegin),
	CvUtil.EventWBStartYearPopup : ('WBStartYearPopup', self.__eventWBStartYearPopupApply, self.__eventWBStartYearPopupBegin),
	CvMyEvent.EventDoSomething : ('DoSomething', self.__eventDoSomethingApply, self.__eventDoSomethingBegin),
}

The last line says, that if EventDoSomething is triggered, first call __eventDoSomethingBegin and then call __eventDoSomethingApply. This ist managed in the functions beginEvent() and applyEvent().

Now, we have to define the two functions:
Code:
def __eventDoSomethingBegin(self, argslist):
	return 0
	
def __eventDoSomethingApply(playerID, userData, popupReturn):
	a, b, c, d = userData
	[Do whatever you want with a, b, c and d here]
	return 0

I do nothing in eventDoSomethingBegin, because I have no idea how to pass arguments to this function.
Look at eventDoSomethingApply. playerID is the player who triggered this event. UserData is defined as a tuple, so it can hold as much data as you want. In my example it holds 4 values which I assign to a, b, c and d.
Those values can be of different types.
I don't know what popupReturn is.

Back to CvMyEvent.py

Here is the final call:
Code:
CyMessageControl().sendApplyEvent(EventDoSomething, EventContextTypes.EVENTCONTEXT_ALL, (a,b,c,d))
The first parameter is our ID. The event manager needs this to determine which event shall be called.
The seconed parameter is either EventContextTypes.EVENTCONTEXT_ALL or EventContextTypes.EVENTCONTEXT_SELF. This handles if the event is just for me or for all players. Note: ALL includes SELF.
The third parameter is our tuple. Note that the number of values has to be the same as in our event above. And don't forget the brackets around the tuple.

Well, that's all.
Maybe you deal with a CvCustomEventManager.py instead of the CvEventManager.py. That's no problem at all. The CEM.py needs it's own applyEvent():
Code:
def applyEvent( self, argsList ):
	'Apply the effects of an event '
	context, playerID, netUserData, popupReturn = argsList
	
	if(self.CustomEvents.has_key(context)):
		entry = self.CustomEvents[context]
		# the apply function
		return entry[1]( playerID, netUserData, popupReturn )   
	else:
		return CvEventManager.CvEventManager.applyEvent(self, argsList)
Assuming our event is in self.CustomEvents in the CEM.py instead of self.Events in CvEventManager.py.

Have fun!

Matze
 
Next problem:

The options screen shows up correctly and the activated options are sent to the other players.

But the game starts before the host has made his decisions. All other players can make their first moves before all options are set. That infects some options which are used on game start. I have to find a way to prevent the game from starting before the host has clicked on ok.

Matze
 
Nice job on the events. You might even consider throwing up a copy of that post into the tutorials forum.

It seems you already have a screen that acts similar to the "Dawn of Man" screen that pops up as the game starts. Perhaps if you were to take that screen and make it's dimensions the size of the entire screen (even if the panel stays the same size), and make sure that you set the screen to handle all inputs and that it doesn't close on "escape" (screen.setCloseOnEscape(False)). Then, you can place that screen on ALL players screens, but only the host can change options or hit "done" or whatever.

You can even set it so that the host sends EVERY change to other players as events, rather than just send all of them in the end, so that players can see in real-time the options being enabled/disabled. Lastly, you could place a button on this screen to open up the chat box (since it will not be available to them otherwise).

The tricky part, obviously, is making the screen only close when you want it to. Greying out the close button until the options are set makes sense, it's just making sure that you don't miss any other ways to close the screen.

If you want to have the other players look around the map, but not touch anything, you could always use the same technique, only instead of the rules panel just make the screen invisible, and see if you can still scroll and use the arrow keys and mouse, but not process any other inputs. This might require checking what's being input in the handleInput method, since I'm still confused as to what you handle and what doesn't and when :p
 
Gerikes said:
Then, you can place that screen on ALL players screens, but only the host can change options or hit "done" or whatever.

That was exactly the same idea I had some minutes before you wrote this.

You can even set it so that the host sends EVERY change to other players as events, rather than just send all of them in the end, so that players can see in real-time the options being enabled/disabled.

Yes, I did this and it works.

Everything I wanted to do works fine. Thank you!

Matze
 
Hi MatzeHH !

the way you achieve to pass data for multiplayer games is great , and i also want to create a pop up to set up some options at the beginning of game .

I've understood how to pass data throught this thread , but i've never done any pop up . So , that would be great if i can have a good example to drive me ,and the hardest work already done . Can i find the code of your pop up somewhere ? ( i searched but find nothing )

thanks ! Tcho !
 
Sto said:
Hi MatzeHH !

the way you achieve to pass data for multiplayer games is great , and i also want to create a pop up to set up some options at the beginning of game .

The question is, at which point exactly the popup shall appear. In my case ist was important, that it pops up before the Dawn of Man Screen.
Next question is, if you want to start the popup from python or from the SDK.

Matze
 
Tha fact is i want to pass custom data at the beginning of the game ... that was my first aim .

i.e : the list of the additionnal game option may be different from each player (customizable and coded by the player), so i wanted that all player may play a multiplayer game with the host settings without OOS .

what i want to do : in fact , i just begin modding (just touch mapscripting before) , and i want to make some additonal game option for FFH2 (turn the game to total war with fantasy option... as i like to play) . My first idea was to choose the game option with my mapscript -> very easy to do that for me . But the game option will not be playable with another scenario or mapscript . So , i was thinking of how i can do that , and an additionnal pop up at the beginnning of the game that goes like the description in this thread is the best solution .But i've never done any C++ code and don't want to touch the SDK for the moment ( i'm a beginner) . And i've never done pop up with data entries ( i tried 3 month ago without succes) , that's why i was very interested with your pop up (or screen) that work like the selection screen on a MP game . I wanted to take your code and examine how that goes (with the struture and the specific python code for popup ,that's very obscure for me) .

in short : No SDK . i Think that make the pop up by myself is too hard for me . because of the option change the state of the game ,it's important that the popup comes before the Dawn of Man Screen .

that's why i have the hope to have the code of a similary pop up , but don't know what that's involved (in a term of coding)?

Thanks for the quick reply ,Tcho ! :)
 
That's exactly what I described here.
But I used the SDK.

I think, it could be possible without the SDK, if you hold your variables in python.
I will give you the code for my pop up screen this afternoon (well, as long as you are in France, it should be afternoon at your place too.)

Matze
 
i ll study your link right now and i will be able to start coding since i know now how i will structure the mod :goodjob: , Many thanks ! :)
 
Top Bottom