Multiplayer compatible Python scripting

Help bring our custom Ages to life! Share tips and tricks, as well as code samples with other developers.

Multiplayer compatible Python scripting

Postby D'Lanor » Sat Dec 29, 2007 7:40 am

Currently it is not possible to explore user created ages with others. However, in order to avoid future problems user created ages should be prepared for multiplayer use.

In this topic we can share tips and tricks about multiplayer compatibility. I will make a start and if I think of something else I will add it later.
Last edited by D'Lanor on Sat Dec 29, 2007 7:48 am, edited 1 time in total.
"It is in self-limitation that a master first shows himself." - Goethe
User avatar
D'Lanor
 
Posts: 1980
Joined: Sat Sep 29, 2007 4:24 am

PtWasLocallyNotified

Postby D'Lanor » Sat Dec 29, 2007 7:44 am

PtWasLocallyNotified

This is probably the most important way to make your code multiplayer compatible. PtWasLocallyNotified(self.key) can be used in an OnNotify event to determine if it was the local client which triggered the action.

When for example a book is clicked you only want it to open for the player who clicked it:

Code: Select all
    def OnNotify(self, state, id, events):
        if ((id == actClickableObject.id) and state):
            print 'Someone clicked a book'
            if (PtWasLocallyNotified(self.key)):
                print 'It was you. Now we will open the book'
                ...

If you leave out the PtWasLocallyNotified condition here the book will flash into the face of all age players.

While the above example is obvious, sometimes multiplayer issues can be tricky. Forgetting a PtWasLocallyNotified condition often results in code being executed multiple times (as many times as there are players in the age). While this may not cause problems it does use unnecessary resources.
"It is in self-limitation that a master first shows himself." - Goethe
User avatar
D'Lanor
 
Posts: 1980
Joined: Sat Sep 29, 2007 4:24 am

PtFindAvatar

Postby D'Lanor » Sat Dec 29, 2007 7:47 am

PtFindAvatar

The offline game often uses PtGetLocalAvatar in OnNotify events. One must however be very careful when using PtGetLocalAvatar here. In the offline game it did not matter of course but in a multiplayer environment you have to make absolutely sure that an action is applied to the correct avatar. Especially avatar animations can be quite annoying when they warp you from your spot towards a lever somebody else has pulled.

In an OnNotify event PtFindAvatar(events) should be used instead of PtGetLocalAvatar(). PtFindAvatar is a clever piece of Cyan code to detect the avatar which triggered the event.

Note that PtWasLocallyNotified in this case will not work. If another player pulls a lever you should still see the animation applied to the other player's avatar.
"It is in self-limitation that a master first shows himself." - Goethe
User avatar
D'Lanor
 
Posts: 1980
Joined: Sat Sep 29, 2007 4:24 am

Re: Multiplayer compatible Python scripting

Postby Tsar Hoikas » Sat Dec 29, 2007 11:19 am

Net Forcing

Net forcing is a fairly important concept in creating multiplayer compatible ages. As its name implies, Net Forcing makes Uru "force the action over the network."

With that in mind, here is a simple method of showing netForcing in action.

Code: Select all
#This make the user invisible to himself only
avatar = PtGetLocalAvatar()
avatar.draw.netForce(0) #Do not send over network
avatar.draw.disable()

#This make the user invisible to everyone
avatar = PtGetLocalAvatar()
avatar.draw.netForce(1) #Send over network
avatar.draw.disable()


Now, let's move on to more complicated aspects of net-forcing. While sending updates to all the remote clients is indeed useful, it introduces a layer of complexity. Let's take a look at some ptAttributes (values passed to a python script via the plPythonFileMod)

Code: Select all
Activator = ptAttribActivator(1, "Activator: The Clickable", netForce=1) #Clickable object
Responder = ptAttribResponder(2, "RespMod: The Responder", netForce=0) #Action to run
LocalResp = ptAttribResponder(3, "RespMod: Local Responder (Easter Egg -- a stupid one)", netForce=0)


Now, to briefly explain, the ptAttribute automatically has netForcing disabled, so (for example), if I did not override the netForce=0 on the "Activator" variable, then if User X clicked the clickable, then User Y wouldn't know it; however, because netForce is set to true, then User Y will know when User X toggled the clickable.

Now, consider the following code OnNotify method for the above ptAttributes:
Code: Select all
def OnNotify(self, state, id, events):
    if(id == Activator.id and state):
        #Everyone in the age will execute this when anyone toggles the clickable
        Responder.run(self.key)
        if PtWasLocallyNotified(self.key):
            #Only the person who clicked will come here
            RespLocal.run(self.key)


Now everything should begin to make sense. When User X clicks on Activator, then everyone will run Responder which is NOT net forced... If it were, then everyone in the age would loop that action times the number of people in the age (because it was getting run on everyone in the age by each person in the age... Brain explosion yet? Yes? Good! That means you're doing as well as I did.). Finally, the user who clicked the Activator will see the RespLocal action, which is not netForced either (so only he can see it).
Image
Tsar Hoikas
Councilor of Technical Direction
 
Posts: 2180
Joined: Fri Nov 16, 2007 9:45 pm
Location: South Georgia

Re: Multiplayer compatible Python scripting

Postby Chacal » Mon Dec 31, 2007 1:10 am

Attaboys for both of you. This is what we need!
Chacal


"The weak can never forgive. Forgiveness is an attribute of the strong."
-- Mahatma Gandhi
User avatar
Chacal
 
Posts: 2508
Joined: Tue Nov 06, 2007 2:45 pm
Location: Quebec, Canada

isLocallyOwned

Postby D'Lanor » Tue Jan 01, 2008 6:10 pm

isLocallyOwned

If your age has SDL variables which can be changed by players you have to make sure that only one player (client) writes these changes to the vault. Especially when the change is a toggle (door open/close) SDL updates by multiple clients can produce nasty results.

The game owner mechanism can help you to sort this out. The game owner is the first player who arrives in a previously empty age instance. If the game owner links out or crashes another age player automatically becomes the game owner.

So how does it work? All you have to do is check the isLocallyOwned() property of an object in your age. If this check returns True the player is the game owner. The test object does not have to be a special object. You can pick any age object you like.
Cyan usually puts the object in a PythonFileMod but it works just as well with PtFindSceneobject() which is much easier to implement.

Example:
Code: Select all
#define the test object globally
testObj = None
...

    def OnServerInitComplete(self):
        global testObj
        testObj = PtFindSceneobject('YourObjectName', PtGetAgeName())



    def SomeFunction(self):
        if testObj.isLocallyOwned():
            print 'I am game owner. Toggling SDL'
            ageSDL = PtGetAgeSDL()
            sdl = ageSDL['YourSDLVariable'][0]
            if (not sdl):
                ageSDL['YourSDLVariable'] = (1,)
            else:
                ageSDL['YourSDLVariable'] = (0,)
User avatar
D'Lanor
 
Posts: 1980
Joined: Sat Sep 29, 2007 4:24 am

Re: Multiplayer compatible Python scripting

Postby Robert The Rebuilder » Wed Jan 02, 2008 10:16 am

Question for you, D'Lanor: Suppose I have an age that has a button which toggles the display of a hologram (via an SDL variable). Any visitor to the age should be able to push the button and toggle the hologram. However, if I use isLocallyOwned() to test whether the button-pusher owns the age before changing the SDL variable, does that mean that only the age owner can make the hologram appear? Won't the button be useless to everyone else?
Can we rebuild it? Yes, we can - here's how.

MOULagain KI# 1299

Myst Movie coming soon - spread the word!
User avatar
Robert The Rebuilder
 
Posts: 1383
Joined: Sat Sep 29, 2007 7:24 am
Location: Virginia, US

Re: Multiplayer compatible Python scripting

Postby Tsar Hoikas » Wed Jan 02, 2008 10:33 am

Changes to SDL are often netforced (not always, as D'Lanor pointed out to me), D'Lanor is showing you a way of changing an SDL variable without it getting in a loop times the number of people in the age.

So, to clarify, Hoikas on his Until Uru shard just ran:
Code: Select all
ageSDL = PtGetAgeSDL()
ageSDL['someVar'] = (1,)


Everyone is notified that 'someVar' is now set to the tuple (1,) and each client calls their OnSDLNotify method... Now if everyone in the age ran the above code, we would get into a massive, repeated action frenzy ;)

Hope that made sense.
Last edited by Tsar Hoikas on Wed Jan 02, 2008 10:59 am, edited 2 times in total.
Image
Tsar Hoikas
Councilor of Technical Direction
 
Posts: 2180
Joined: Fri Nov 16, 2007 9:45 pm
Location: South Georgia

Re: Multiplayer compatible Python scripting

Postby D'Lanor » Wed Jan 02, 2008 10:54 am

If you have already pinned the action to the button pusher there is no need to use isLocallyNotified. This is just one of the available methods to choose from. Cyan often uses it for complicated puzzles.

If you want to see another example that uses isLocallyNotified you can take a look at the Dynamic Fog Color I posted.

SDL changes btw are not caught by OnSDLNotify by default. You have to set them up for netforcing in OnServerInitComplete like this:

Code: Select all
def OnServerInitComplete(self):
    ageSDL = PtGetAgeSDL()
    ageSDL.sendToClients('YourSDLVariable')
    ageSDL.setFlags('YourSDLVariable', 1, 1)
    ageSDL.setNotify(self.key, 'YourSDLVariable', 0.0)

Now don't ask me which flags I am setting here. This is just something that I copied from Cyan ages when I noticed that my OnSDLNotify did not work. ;) Edit: Ok, I looked up those flags in the class ptSDL in Plasma.py...

Code: Select all
    def setFlags(self, name, sendImmediate, skipOwnershipCheck):
        pass

    def setNotify(self, selfkey, key, tolerance):
        pass


But even though the OnSDLNotify for your own age needs the above setup, the SDL change itself will reach all clients in spite of that, since it is caught (I think) by the generic global xAgeSDL...py files. Confusing, isn't it? :?
"It is in self-limitation that a master first shows himself." - Goethe
User avatar
D'Lanor
 
Posts: 1980
Joined: Sat Sep 29, 2007 4:24 am

Re: Multiplayer compatible Python scripting

Postby Robert The Rebuilder » Wed Jan 02, 2008 12:09 pm

In my OnNotify(), I use PtWasLocallyNotified() to pin the action to the button pusher. So, as you mentioned, I wouldn't need to use isLocallyOwned() in this case. Thanks for the clarification.

EDIT: And yes - I did initialize the flags on the SDL variables to force notification.
Can we rebuild it? Yes, we can - here's how.

MOULagain KI# 1299

Myst Movie coming soon - spread the word!
User avatar
Robert The Rebuilder
 
Posts: 1383
Joined: Sat Sep 29, 2007 7:24 am
Location: Virginia, US


Return to Scripting

Who is online

Users browsing this forum: No registered users and 3 guests