Attempting to revive network code

Does anyone care for me to make any updates to this chatroom code or leave it as is?

  • yes
  • no
  • i don’t care

0 voters

After doing some research on different network frameworks, I have decided to attempt to revive the networking example implementation of the DistributedObject-based chat room that was written by a CMU student back for version 1.0.5. I think this framework would be excellent for building multi-player games on top of.

As any of you know who have attempted to explore using this system who don’t have experience using Disney’s OTP server (myself included), it is basically devoid of documentation so any help along the way will be appreciated.

I have begun tracing through the code in an attempt to understand the processes that are taking place and which methods are getting called to send and receive data to/from the server/client as well as instantiating the DistributedObject’s and invoking methods on them. I also watched the video lectures on this subject to help try and understand the way the system is meant to be used. This being said, I’m still trying to wrap my head around it.

Feel free to correct any terminology that I might use incorrectly.

So far I have taken these steps:

Copied source code from 1.0.5 into 1.6.1 version that I am using.

I had to make 3 modifications right off the bat to get python to even show me a window.

In chatcore.py added:

from direct.showbase.DirectObject import DirectObject

This fixed a bug in the ChatWindow() definition that was attempting to inherit a model instead of a class.

Now the second problem was the fact that there was no dcFileNames set anywhere in my config.prc file so I was getting an error that it couldn’t read the dc file (which is set to None by default). I couldn’t find the correct syntax for this to go into config.prc, so I explicitly passed the value when instantiating the ServerRepository and the ClientRepository using the dcFileNames keyword.

if (server):
    self.serverrepository = ServerRepository(tcpPort=19233, udpPort=19233, dcFileNames=["sample.dc"])
self.clientrepository = ClientRepository(dcFileNames=["sample.dc"])

After I made this change I was still getting an error about not being able to read the dc file so I discovered that there was a modification needed in the sample.dc file. There is a deprecated keyword in this file that throws an error. This keyword is p2p. There are 2 ways so solve this one. I chose to define the keyword above the dclass definition:

keyword p2p;

It also works to just delete the p2p keyword. I wasn’t sure if this was something that the rest of the program needed so i left it in. EDIT: this keyword WAS used later to determine a p2p connection which didn’t work properly so I deleted the keyword

After I made this change, I finally saw the chat window appear.

…I’ll continue this tomorrow as it’s getting late… :open_mouth:

-=Zanz=-

I have finally gotten this app up and running. You can start up a server window and then connect to it via a client window. You can chat back and forth. I will be making a few more mods on this before I show the final changes I made as there is an issue when sending the datagram to set the avatar’s zone getting sent before the server repository is fully ready to accept packets (I think this is the problem). Right now I have a hack in place that sends the datagram twice to ensure it gets received. This works but its not the best solution.

Here is a screen shot of the app running on my machine:

hey looks a nice job - just a Q: do you started from this project?

Using that project astelix just posted,

I created a full server/client for a (mmo)rpg which has a chat and also sends receives information, coordinates and commands.

It needs a lot of optimisation, but it does what I need it to.

Good luck with this project

This is not the same as or based off of that code. This is a chat system that uses panda’s higher level DistributedObject system to control instances of objects across clients instead of handling datagrams at the lower level. I did not write this code, I only fixed it so it would run on 1.6 since it was originally written for 1.0.5.

Oh, that’s nice! I’m looking forward to try it :slight_smile: And I see no problems in there.

On other side, it’s not nice to convert something THAT old, AFAIK it makes BIG problems.

it has to be, there is nothing newer using the high level networking code that we have access to.

i see no problem with maintaining old code. if we don’t, it will rot. and rotten code is pretty much THE reason why applications will stop working one day. so it’s quite good to see people polishing it up and make it useable again.

heh good point Thomas
by the way it would be great to have the refreshed code available here for download, I guess many ppl will appreciate this

In order to make this work you will need to download the source code for 1.0.5 to get the image, the html doc and the module that runs the game loop. The one you are looking for is under Panda3D-1.0.5\samples\Feature-Tutorials–Networking. Just replace the 2 files chatcore.py and sample.dc with the following:

sample.dc

from chatcore import ChatControl,ChatAvatar

typedef uint32 DoId;

import types;


dclass ChatControl
{
  setPigLatinFlag(int16 flag) required broadcast;
};

dclass ChatAvatar
{
  setNick(string text) required broadcast;
  printMsg(string text) broadcast;
};

chatcore.py

# Original Code from panda3d 1.0.5 Networking tutorial
# modified by Zanz to work with 1.6 - May 2009

###########################################################
#
# Initialize Panda and import all the necessary Panda classes.
#
###########################################################

import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.showbase.ShowBaseGlobal import *
from direct.distributed.DistributedObject import *
from direct.distributed.ClientRepository import *
from direct.distributed.ServerRepository import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import DirectLabel
from random import randint
import sys
global TheChatWindow


###########################################################
#
# I got this piglatin function off the internet.
#
###########################################################

from string import splitfields, uppercase, lowercase, upper, lower

def append(w,suffix):
  if not w: return suffix
  elif w[-1] in uppercase: return w + upper(suffix)
  else: return w + lower(suffix)

def piglatin(s):
  out = ''
  for word in splitfields(s,' '):
    if word:
      # check for punctuation
      p = 0
      while word[p-1] not in uppercase+lowercase:
        p = p-1
      if p:
        punc = word[p:]
        word = word[:p]
      else: punc = ''

      # and pre-punc (e.g., parentheses)
      p = 0
      while word[p] not in uppercase+lowercase:
        p = p+1
      prepunc = word[:p]
      word = word[p:]

      # note capitalization
      if word[0] in uppercase: caps = 1
      else: caps = 0

      # remove up to the first vowel to make suffix
      p = 0
      while p < len(word) and word[p] not in "aoeuiyAOEUIY":
        p = p+1
      
      if not p:
        word = append( word, "yay" )
      else:
        word = append( word[p:], word[:p]+"ay")

      # recapitalize, as appropriate
      if caps: word = upper(word[0]) + word[1:]

      # restore any punctuation
      word = prepunc + word + punc

    out = out + ' ' + word
  return out[1:]

###########################################################
#
# All the chat avatars will be in zone 1.
#
###########################################################


class ChatControl(DistributedObject):

    def __init__(self, cr):
        DistributedObject.__init__(self, cr)
        self.pigLatinFlag = 0

    def setPigLatinFlag(self, enable):
        if (enable != self.pigLatinFlag):
            if (enable): TheChatWindow.scrollText("Chat Control: Pig Latin Activated")
            else:        TheChatWindow.scrollText("Chat Control: Pig Latin Deactivated")
        self.pigLatinFlag = enable

    def getPigLatinFlag(self):
        return self.pigLatinFlag


class ChatAvatar(DistributedObject):

    def __init__(self, cr):
        DistributedObject.__init__(self, cr)
        self.nick = 0

    def generate(self):
        if (self.isLocal()):
          self.nick = "U" + str(randint(1000000,9999999))

    def setNick(self,string):
        if (self.nick != 0):
          TheChatWindow.scrollText(self.nick + ": changing ID to " + string)
        self.nick = string

    def getNick(self):
        return self.nick

    def printMsg(self, msg):
        if (TheChatWindow.chatcontrol.pigLatinFlag):
          msg = piglatin(msg)
        TheChatWindow.scrollText(self.nick + ": " + msg)

###########################################################
#
# The Chat Window class.  This is the main controller class.
#
###########################################################

class ChatWindow(DirectObject):

    def __init__(self):
        global TheChatWindow
        TheChatWindow = self
        base.setSleep(0.05)
        self.initGUI()
        self.initEvents()
        self.displayDocs()
        self.serverrepository = 0
        self.clientrepository = 0
        self.chatavatar = 0
        self.chatcontrol = 0

    def displayDocs(self):
        self.scrollText("To become a server, type /server")
        self.scrollText("To become a client, type /connect HOSTNAME")
        self.scrollText("To change your nickname, type /nick NAME")
        self.scrollText("To toggle piglatin mode, type /pig")
        self.scrollText("")

    def initGUI(self):
        self.gui = DirectLabel(image='chatroom.png', scale=(1.707, 0, 1.707), relief = None)
        self.gui.setPos(0, 0, 0)
        self.gui.setTransparency(1)
        self.chatlines = []
        self.chateditor = self.createChatLine(-0.86)
        for i in range(0,8):
          self.chatlines.append(self.createChatLine(-0.54 + i*0.125))
        self.chateditor['text'] = '|'

    def initEvents(self):
        base.disableMouse()
        for i in range(48, 58):  self.accept(chr(i), self.keyNormal, [i])
        for i in range(97,122):  self.accept(chr(i), self.keyNormal, [i])
        for i in range(97,122):  self.accept('shift-'+chr(i), self.keyNormal, [i-32])
        self.accept('/', self.keyNormal, [47])
        self.accept('.', self.keyNormal, [46])
        self.accept('space',       self.keyNormal, [32])
        self.accept('enter',       self.keyEnter)
        self.accept('backspace',   self.keyBackSpace)

    def createChatLine(self,y):
          return DirectLabel(text='',
                       text_fg = (0,0,0,1),
                       text_bg = (0,0,0,0),
                       color = (0,0,0,1),
                       text_align = TextNode.ALeft,
                       text_scale = (0.07, 0.07),
                       pos = (-1.2,0,y))

    def scrollText(self, addline):
        count = len(self.chatlines)
        for i in range(count):
          self.chatlines[count-1-i]['text'] = self.chatlines[count-2-i]['text']
        self.chatlines[0]['text'] = addline

    def keyNormal(self,ascii):
        self.chateditor['text'] = self.chateditor['text'][:-1] + chr(ascii) + "|"

    def keyEnter(self):
        line = self.chateditor['text'][:-1]
        self.doCommand(line)
        self.chateditor['text'] = "|"

    def keyBackSpace(self):
        self.chateditor['text'] = self.chateditor['text'][:-2] + "|"

    def notConnectedError(self):
        if (self.clientrepository == 0):
            self.scrollText("Cannot do that until online.")
        else:
            self.scrollText("Wait until online.")

    def monitorConnection(self,task):
        if (self.clientrepository.isConnected()==0):
            self.scrollText("Connection Lost. You must exit now.")
            self.chatcontrol = 0
            return Task.done
        if (self.chatavatar==0):
            if (self.clientrepository.haveCreateAuthority()):
                self.chatavatar = self.clientrepository.createWithRequired("ChatAvatar",1)
                if (self.serverrepository != 0):
                    self.chatcontrol = self.clientrepository.createWithRequired("ChatControl",1)
        elif (self.chatcontrol == 0):
            cc = self.clientrepository.findAnyOfType(ChatControl)
            if (cc != None):
                self.chatcontrol = cc
                self.scrollText("You are online.")
        return Task.cont

    def connectCallback(self):
        print "Client connection established"
	#Here are the 2 method calls for setting the zone, it only gets there if it is sent twice
        #Need to put in a delay or something to fix this -=Z=-
        self.clientrepository.sendSetZoneMsg(1)
        base.taskMgr.add(self.monitorConnection,"monitorconnection")
        self.clientrepository.sendSetZoneMsg(1)

    def failureCallback(self):
        self.scrollText("Connection failed.  Exit and try again.")

    def initiateConnection(self,server,host):
        if (self.clientrepository!=0):
            self.scrollText("Already online.")
            return()
        #I added the dcFileNames keyword here -=Z=-
        if (server):
            self.serverrepository = ServerRepository(tcpPort=19233,
                udpPort=19233,dcFileNames=["sample.dc"])
        self.clientrepository = ClientRepository(dcFileNames=["sample.dc"])
        self.clientrepository.connect([URLSpec("http://"+host+":19233")],
                                      self.connectCallback,[],self.failureCallback,[])

    def sendWords(self,line):
        if (self.chatcontrol==0): return(self.notConnectedError())
        self.chatavatar.sendUpdate("printMsg", [line])
	#I added this so you could see your own message also -=Z=-
        self.scrollText(line)

    def setNick(self,nick):
        if (self.chatcontrol==0): return(self.notConnectedError())
        self.chatavatar.sendUpdate("setNick", [nick])
	#added a message so you can see that you typed in the nick correctly -=Z=-
        self.scrollText("You are now known as "+nick+".")

    def togglePig(self):
        if (self.chatcontrol==0): return(self.notConnectedError())
        self.chatcontrol.sendUpdate("setPigLatinFlag",[1-self.chatcontrol.getPigLatinFlag()])

    def doCommand(self,line):
        if   (line[:1]!="/"):          self.sendWords(line)
        elif (line[:6]=="/serve"):     self.initiateConnection(1,"127.0.0.1")
        elif (line[:9]=="/connect "):  self.initiateConnection(0,line[9:].strip())
        elif (line[:6]=="/nick "):     self.setNick(line[6:].strip())
        elif (line[:4]=="/pig"):       self.togglePig()
        else: self.scrollText("Syntax error: "+line)

Feel free to make mods to this. When I have time I will add a correction for the zone setting method call. Enjoy!

You may need to play with the spacing a little since copy and pasting the code into here had some odd spacing effects.

-=Zanz=-