예제 #1
0
class HostMenu(DirectObject):
    def __init__(self):
        self.defaultBtnMap = base.loader.loadModel("gui/button_map")
        self.buttonGeom = (
            self.defaultBtnMap.find("**/button_ready"),
            self.defaultBtnMap.find("**/button_click"),
            self.defaultBtnMap.find("**/button_rollover"),
            self.defaultBtnMap.find("**/button_disabled"))

        defaultFont = loader.loadFont('gui/eufm10.ttf')

        self.logFrame = DirectScrolledFrame(
            canvasSize = (0, base.a2dRight * 2, -5, 0),
            frameSize = (0, base.a2dRight * 2,
                (base.a2dBottom+.2) * 2, 0),
            frameColor = (0.1, 0.1, 0.1, 1))
        self.logFrame.reparentTo(base.a2dTopLeft)

        # create the info and server debug output
        self.textscale = 0.1
        self.txtinfo = OnscreenText(
            scale = self.textscale,
            pos = (0.1, -0.1),
            text = "",
            align = TextNode.ALeft,
            fg = (0.1,1.0,0.15,1),
            bg = (0, 0, 0, 0),
            shadow = (0, 0, 0, 1),
            shadowOffset = (-0.02, -0.02))
        self.txtinfo.setTransparency(1)
        self.txtinfo.reparentTo(self.logFrame.getCanvas())

        # create a close Server button
        self.btnBackPos = Vec3(0.4, 0, 0.2)
        self.btnBackScale = 0.25
        self.btnBack = DirectButton(
            # Scale and position
            scale = self.btnBackScale,
            pos = self.btnBackPos,
            # Text
            text = "Quit Server",
            text_scale = 0.45,
            text_pos = (0, -0.1),
            text_fg = (0.82,0.85,0.87,1),
            text_shadow = (0, 0, 0, 1),
            text_shadowOffset = (-0.02, -0.02),
            text_font = defaultFont,
            # Frame
            geom = self.buttonGeom,
            frameColor = (0, 0, 0, 0),
            relief = 0,
            pressEffect = False,
            # Functionality
            command = self.back,
            rolloverSound = None,
            clickSound = None)
        self.btnBack.setTransparency(1)
        self.btnBack.reparentTo(base.a2dBottomLeft)

        # catch window resizes and recalculate the aspectration
        self.accept("window-event", self.recalcAspectRatio)
        self.accept("addLog", self.addLog)

    def show(self):
        self.logFrame.show()
        self.btnBack.show()

    def hide(self):
        self.logFrame.hide()
        self.btnBack.hide()

    def back(self):
        self.hide()
        base.messenger.send("stop_server")
        self.addLog("Quit Server!")

    def addLog(self, text):
        self.txtinfo.appendText(text + "\n")
        textbounds = self.txtinfo.getTightBounds()
        self.logFrame["canvasSize"] = (0, textbounds[1].getX(),
                                        textbounds[0].getZ(), 0)

    def recalcAspectRatio(self, window):
        """get the new aspect ratio to resize the mainframe"""
        # set the mainframe size to the window borders again
        self.logFrame["frameSize"] = (
            0, base.a2dRight * 2,
            (base.a2dBottom+.2) * 2, 0)
예제 #2
0
class Server(DirectObject):

    def __init__(self):
        DirectObject.__init__(self)
        self.accept("escape", self.quit)
        self.lastConnection = None
        self.cManager = QueuedConnectionManager()
        self.cListener = QueuedConnectionListener(self.cManager, 0)
        self.cReader = QueuedConnectionReader(self.cManager, 0)
        self.cWriter = ConnectionWriter(self.cManager,0)
        self.tcpSocket = self.cManager.openTCPServerRendezvous(SERVER_PORT, 1)
        self.cListener.addConnection(self.tcpSocket)
        taskMgr.add(self.listenTask, "serverListenTask", -40)
        taskMgr.add(self.readTask, "serverReadTask", -39)

        self.gameLogic = GameLogic()
        self.gameLogic.delegate = self;

        blackmaker = CardMaker("blackmaker")
        blackmaker.setColor(0,0,0,1)
        blackmaker.setFrame(-1.0, 1.0, -1.0, 1.0)
        instcard = NodePath(blackmaker.generate())
        instcard.reparentTo(render2d)

        self.screenText = OnscreenText(text="Server started ...\n",
            style=1, fg=(1,1,1,1), pos=(-1.31, 0.925), scale = .06)
        self.screenText.setAlign(0)

    ######################################################################################

    def listenTask(self, task):
        if self.cListener.newConnectionAvailable(): 
            rendezvous = PointerToConnection()
            netAddress = NetAddress()
            newConnection = PointerToConnection()
            if self.cListener.getNewConnection(rendezvous, netAddress, newConnection):
                newConnection = newConnection.p()
                self.cReader.addConnection(newConnection)
                CLIENTS[newConnection] = netAddress.getIpString()
                self.lastConnection = newConnection
                self.screenText.appendText("New connection established.\n")
            else: 
                self.screenText.appendText("getNewConnection returned false\n")
        return Task.cont 

    ######################################################################################

    def readTask(self, task):
        while 1: 
            (datagram, data, msgID) = self.nonBlockingRead(self.cReader) 
            if msgID is MSG_NONE: 
                break 
            else:
                self.handleDatagram(data, msgID, datagram.getConnection()) 
        return Task.cont 

    ######################################################################################

    def nonBlockingRead(self, qcr): 
        if self.cReader.dataAvailable(): 
            datagram = NetDatagram() 
            if self.cReader.getData(datagram): 
                data = PyDatagramIterator(datagram) 
                msgID = data.getUint16() 
            else: 
                data = None 
                msgID = MSG_NONE 
        else: 
            datagram = None 
            data = None 
            msgID = MSG_NONE 
        return (datagram, data, msgID)

    ######################################################################################

    def handleDatagram(self, data, msgID, client): 
        if msgID in Handlers.keys():
            Handlers[msgID](msgID, data, client)
        else:
            self.screenText.appendText("Unknown msgID: ")
            self.screenText.appendText(msgID)
            self.screenText.appendText("\n")
            self.screenText.appendText(data)
            self.screenText.appendText("\n")
        return

    ######################################################################################

    def msgAuth(self, msgID, data, client):
        name = data.getString()
        CLIENTS[client] = name
        self.gameLogic.addPlayer(name)
        pkg = PyDatagram()
        pkg.addUint16(SV_MSG_AUTH_RESPONSE)
        self.cWriter.send(pkg, client)
        self.screenText.appendText("Registered new client: ")
        self.screenText.appendText(name)
        self.screenText.appendText(" (")
        self.screenText.appendText(client.getAddress().getIpString())
        self.screenText.appendText(")\n")

    ######################################################################################

    def msgChat(self, msgID, data, senderClient):
        message = data.getString()
        self.screenText.appendText("Message: ")
        self.screenText.appendText(message)
        self.screenText.appendText("\n")
        pkg = PyDatagram()
        pkg.addUint16(SV_MSG_CHAT)
        pkg.addString(message)
        for receiverClient in CLIENTS:
            self.cWriter.send(pkg, receiverClient)

    ######################################################################################

    def msgDisconnectReq(self, msgID, data, client): 
        pkg = PyDatagram() 
        pkg.addUint16(SV_MSG_DISCONNECT_ACK) 
        self.cWriter.send(pkg, client) 
        del CLIENTS[client]
        self.cReader.removeConnection(client)

    ######################################################################################

    def handleCompleteSetup(self, msgID, data, senderClient):
        self.screenText.appendText("A new game will start... ")
        self.screenText.appendText(str(len(CLIENTS)))
        self.screenText.appendText(" pushies will fight to death.")
        setups = self.gameLogic.start()
        numberOfPlayers = len(setups)
        pkg = PyDatagram()
        pkg.addUint16(SV_MSG_START_GAME)
        pkg.addUint16(numberOfPlayers)
        for setup in setups:
            pkg.addString(setup["player"])
            pkg.addFloat32(setup["position"][0])
            pkg.addFloat32(setup["position"][1])
            pkg.addFloat32(setup["position"][2])
        for receiverClient in CLIENTS:    
            self.cWriter.send(pkg, receiverClient)

    ######################################################################################

    def handleMovementCommand(self, msgID, data, client):
        player = CLIENTS[client]
        movement = data.getUint8()
        status = data.getUint8()
        self.gameLogic.setPlayerMovement(player, movement, status)

    def handleJumpCommand(self, msgID, data, client):
        player = CLIENTS[client]
        status = data.getUint8()
        self.gameLogic.setPlayerJump(player, status)

    def handleChargeCommand(self, msgID, data, client):
        player = CLIENTS[client]
        status = data.getUint8()
        self.gameLogic.setPlayerCharge(player, status)

    ######################################################################################

    def sendPositionUpdates(self, updates):
        pkg = PyDatagram()
        pkg.addUint16(SV_MSG_UPDATE_POSITIONS)
        pkg.addUint16(len(updates))
        for update in updates:
            pkg.addString(update[0])
            pkg.addFloat32(update[1][0])
            pkg.addFloat32(update[1][1])
            pkg.addFloat32(update[1][2])
            pkg.addFloat32(update[2][0])
            pkg.addFloat32(update[2][1])
            pkg.addFloat32(update[2][2])
        for client in CLIENTS:
            self.cWriter.send(pkg, client)

    def sendStatusUpdates(self, updates):
        pkg = PyDatagram()
        pkg.addUint16(SV_MSG_UPDATE_STATES)
        pkg.addUint16(len(updates))
        for update in updates:
            pkg.addString(update["player"])
            pkg.addUint8(update["status"])
            pkg.addFloat32(update["health"])
            pkg.addUint8(update["charge"])
            pkg.addUint8(update["jump"])
        for client in CLIENTS:
            self.cWriter.send(pkg, client)

    def quit(self): 
        self.cManager.closeConnection(self.tcpSocket) 
        sys.exit() 
예제 #3
0
파일: HLevel.py 프로젝트: PlumpMath/HPanda
class HLevel():
    def __init__(self, showbase, physicsDebug=True):
        """


        :type self.Base: direct.showbase.ShowBase.ShowBase
        :type showbase: direct.showbase.ShowBase.ShowBase
        :type physicsDebug: bool
        """
        print "Creating level"
        self.Base = showbase
        self.debugDrawing = physicsDebug
        self.pause = False
        self.bulletSubstep = 0.008
        self.activeLog = False

    def loadAssets(self):
        print "Loading Assets"

    def renderAssets(self):
        print "Rendering assets"

    def setCamera(self):
        print "Setting camera"

    def setLights(self):
        print "Setting lights"

    def destroy(self):
        print "Destroying level"

    def loadEgg(self, egg):
        """
        :type egg: str
        :rtype : panda3d.core.NodePath
        """
        return self.Base.loader.loadModel(egg)

    def renderModel(self, model):
        """

        :type model: str
        """
        model.reparentTo(self.Base.render)

    def renderEgg(self, egg):
        """

        :type egg: str
        :return pada3d.core.NodePath
        """
        m = self.loadEgg(egg)
        self.renderModel(m)
        return m

    def setPhysics(self):
        print "Setting physics"
        self.world = BulletWorld()
        self.world.setGravity(Vec3(0, 0, -10))
        self.Base.taskMgr.add(self.physicsUpdate, "physicsUpdate", priority=0)
        if self.debugDrawing:
            print "Debug drawing"
            self.debug = BulletDebugNode("debug")
            self.debug.showWireframe(True)
            self.debug.showBoundingBoxes(False)
            self.debug.showNormals(True)
            self.debugNP = self.Base.render.attachNewNode(self.debug)
            self.debugNP.show()
            self.world.setDebugNode(self.debug)

    def physicsUpdate(self, task):
        if not self.pause:
            dt = globalClock.getDt()
            self.world.doPhysics(dt, 2, self.bulletSubstep)  # #0.009-0.008
            # print "Physics step"
        return task.cont

    def cameraShake(self, amplitud=0.01, frecuencia=5):
        """

        :type amplitud: float
        :type frecuencia: float
        """
        self.Base.camera.setZ(
            self.Base.camera,
            sin(globalClock.getRealTime() * frecuencia) * amplitud)

    def eggToStatic(self, egg, parent, margin=0.01, name="static"):
        """

        :type egg: str
        :type parent: panda3d.core.NodePath
        :type margin: float
        :type name: str
        :return: tuple(pada3d.bullet.BulletRigidBodyNode,panda3d.core.NodePath)
        """
        m = self.Base.loader.loadModel(egg)
        sTuple = modelToShape(m)
        sTuple[0].setMargin(margin)
        static = BulletRigidBodyNode(name)  # H
        static.addShape(sTuple[0], sTuple[1])
        np = parent.attachNewNode(egg)
        self.world.attachRigidBody(static)
        return static, np

    def togglePause(self):
        """

        """
        if self.pause:
            self.pause = False
        else:
            self.pause = True
        print "Toggle pause", self.pause

    def activateLog(self):
        self.activeLog = True
        self.logText = OnscreenText("NO LOG",
                                    scale=0.07,
                                    fg=(1, 0, 0, 0.8),
                                    bg=(0, 0, 1, 0.2),
                                    frame=(0, 1, 0, 0.2),
                                    pos=(-1.05, .9),
                                    mayChange=True,
                                    align=0)
        self.__logTextLenght = 0
        self.__logAbsoluteLenght = 0
        self.__logText = ""
        self.logText.reparentTo(self.Base.aspect2d)

    def log(self, *args):
        if self.activeLog is False:
            return None
        s = str(self.__logAbsoluteLenght) + ":"
        for a in args:
            s += str(a) + " "
        if self.__logTextLenght < 10:
            self.logText.appendText("\n" + s)
        else:
            self.__logTextLenght = 0
            self.logText.clearText()
            self.logText.setText(s)
        self.__logTextLenght += 1
        self.__logAbsoluteLenght += 1
        self.__logText.join(s)

    def loadFont(self, string):
        return self.Base.loader.loadFont(string)

    def drawLine(self,
                 fromP,
                 toP,
                 thickness=2,
                 color=(1, 0, 0, 1),
                 autoClear=True):
        if autoClear:
            try:
                self.debugLineNP.removeNode()
            except:
                pass
        self.debugLine = LineSegs("DebugLine")
        self.debugLine.reset()
        self.debugLine.setThickness(thickness)
        self.debugLine.setColor(color)
        self.debugLine.moveTo(fromP)
        self.debugLine.drawTo(toP)
        self.debugLineNode = self.debugLine.create()
        self.debugLineNP = NodePath(self.debugLineNode)
        self.debugLineNP.reparentTo(self.Base.render)
        return self.debugLineNP
예제 #4
0
class WorldBase(DirectObject):
    """
    Basic 3D world with logging and 2D display
    """
    def __init__(self, controller, mem_logger, display_categories):
        
        self.display_categories = display_categories
        
        self.labels = OnscreenText( "", style = 1, fg = ( 1, 1, 1, 1 ), pos = ( -1.0, 0.9 ), scale = .05 )
        self.txt = OnscreenText( "", style = 1, fg = ( 1, 1, 1, 1 ), pos = ( -0.5, 0.9 ), scale = .05 )

        taskMgr.add( self.loop, "loop" )

        self.enable_screen_updates = True
        self.log = KeplerLogger(mem_logger)        
        self.logging = False
        self.accept("p", self.key_p)
        self.accept( "escape", self.quit )
        self.accept("arrow_up", self.key_up)
        self.accept("arrow_down", self.key_down)
        self.accept("arrow_left", self.key_left)
        self.accept("arrow_right", self.key_right)
        self.accept("l", self.key_l)
        self.accept("s", self.key_s)
        self.accept("a", self.key_a)
        self.accept("f", self.key_f)
        self.accept("g", self.key_g)
        self.accept("d", self.key_d)
        self.accept("o", self.log_start_stop)
        self.accept("q", self.key_q)
        self.accept("w", self.key_w)
        self.accept("z", self.key_z)
        self.accept("x", self.key_x)
        self.accept("c", self.key_c)
        self.accept("v", self.key_v)
        self.accept("b", self.key_b)
        self.accept("n", self.key_n)
            
        self.controller = controller
        base.setFrameRateMeter(True)

        
        #self.txt = OnscreenText( "Nothing", style = 1, fg = ( 1, 1, 1, 1 ), shadow = ( .9, .9, .9, .5 ), pos = ( -1.0, 0.9 ), scale = .07 )
        # task to be called every frame
        
        self.step = 0
        self.last_time = time.time()
        
        self.text_refresh_band = 10
        self.text_refresh_count = self.text_refresh_band
        self._set_title("Keplermatic")
        
        self.init_gui()
        

    def init_gui(self):
        
        x = -0.9
        y = 0.7
        inc_y = -0.1
        cb = CallBack("Logging", self.display_categories, self)
        self.add_gui_checkbox("Logging", False, x, y, cb.callback)
        y += inc_y
        cb = CallBack("Update", self.display_categories,self)
        self.add_gui_checkbox("Update", True, x, y, cb.callback)
        y += inc_y  
        y += inc_y 
        keys = self.display_categories.keys()
        keys.sort()
  
        for k in keys:
            if k in ["axis1","axis2"]:
               cb = CallBack(k, self.display_categories,self)
               category_name = k
               enabled = self.display_categories[category_name]
               self.add_gui_checkbox(category_name, enabled, x, y, cb.callback)
               y += inc_y
        y += inc_y  
        y += inc_y 
                
        for k in keys:
            if k not in ["axis1","axis2"]:
                  cb = CallBack(k, self.display_categories,self)
                  category_name = k
                  enabled = self.display_categories[category_name]
                  self.add_gui_checkbox(category_name, enabled, x, y, cb.callback)
                  y += inc_y
        
  
    def _set_title(self, title):
      from pandac.PandaModules import ConfigVariableString
      mygameserver = ConfigVariableString("window-title","Panda")
      mygameserver.setValue(title)

    def loop(self, task):
        #self.last_time = time.time()
        delta_time = task.time - self.last_time
        # avoid division by 0
        if delta_time < 0.0001:
            delta_time = 0.0001
        self.last_time = task.time
        self.step += 1
        txt = ""
        self._refresh_text(txt)
        self.controller.loop(self.step, task.time, delta_time)
        
        if self.logging:
            data = self.controller.get_display_data()
            self.log.snapshot(get_data_logger(), self.step, task.time, data, ('self'))        
        return Task.cont

    def _refresh_text(self, txt):
        if not self.enable_screen_updates:
            return 
        self.text_refresh_count -=1
        if self.text_refresh_count <0:
            self.text_refresh_count = self.text_refresh_band
            data = self.controller.get_display_data()
            st = str(self.last_time)
            data['Time'] = (st, "sim", 0)
            data['step'] = (self.step, "sim", 0)
            
            self.txt.clearText()
            self.txt.setAlign(TextNode.ALeft)

            keys = data.keys()
            keys.sort()
            txt += "logging: %s\n" % self.logging      
            #print keys
            show_axis1 = self.display_categories['axis1']
            show_axis2 = self.display_categories['axis2']
            
            for name in keys:
                if name == 'self': continue
                display_data = data[name]                
                #print display_data
                value, category, axis = display_data   
                if category not in self.display_categories.keys():
                   category = "other"
                show_category = self.display_categories[category]
                show_it = show_category
                if not show_it:
                   #print "  %s hidden (category %s hidden)" % (name, category)
                   pass
                if axis == 1:
                    if show_axis1 == False:
                       #print "  %s hidden (axis 1)" % name
                       show_it = False
                if axis == 2:
                    if show_axis2 == False:
                       #print "  %s hidden (axis 2)" % name
                       show_it = False
                
                
                if show_it:   
                   #print "**** name %s, cat %s [%s], axis %s, value %s show1 %s, show2 %s" %(name, category, show_it, axis, value, show_axis1, show_axis2)              
                   value_str = "%s" % value
                   if type(value) == type(0.): # float
                      value_str = "%.10f" % value
                   txt += "%s: %s\n" % (name, value_str)
                else:   
                   pass
                   #print "XXXX  name %s, cat %s [%s], axis %s, value %s show1 %s, show2 %s" %(name, category, show_it, axis, value, show_axis1, show_axis2)              
                
            self.txt.appendText(txt)
 
    
    def add_gui_checkbox(self, text, value, x, y, callback):
       b = DirectCheckButton(text = text ,scale=.05, pos=(x,0,y), command=callback)
       b["indicatorValue"] = value
       return b
       

    def quit(self):
        self.controller.stop()
        if len(self.log.data) >0:
            self.log.dump(get_data_logger())
        sys.exit()
        
    def log_start_stop(self):
        
        if self.logging: # stopping
            self.controller.stop()
            self.log.dump(get_data_logger())
            self.log.data = []
        
        self.logging = not self.logging
        print "Logging %s" % self.logging 

    def key_p(self):
      self.enable_screen_updates = not self.enable_screen_updates
      print "enable_screen_updates=", self.enable_screen_updates
        
    def key_right(self):  
        self.controller.key_right()
    def key_left(self): 
        self.controller.key_left()
    def key_l(self): 
        self.controller.key_l()
    def key_s(self): 
        self.controller.key_s()
    def key_a(self): 
        self.controller.key_a()
    def key_f(self): 
        self.controller.key_f()
    def key_g(self):    
        self.controller.key_g()
    def key_d(self): 
        self.controller.key_d()
    def key_up(self):
        self.controller.key_up()
    def key_down(self):    
        self.controller.key_down()
    def key_q(self):    
        self.controller.key_q()
    def key_w(self):    
        self.controller.key_w()   
    def key_z(self):    
        self.controller.key_z()    
    def key_x(self):    
        self.controller.key_x()    
    def key_c(self):    
        self.controller.key_c()    
    def key_v(self):    
        self.controller.key_v()    
    def key_b(self):    
        self.controller.key_b()   
    def key_n(self):    
        self.controller.key_n()    
예제 #5
0
class HostMenu(DirectObject):
    def __init__(self):
        self.defaultBtnMap = base.loader.loadModel("gui/button_map")
        self.buttonGeom = (self.defaultBtnMap.find("**/button_ready"),
                           self.defaultBtnMap.find("**/button_click"),
                           self.defaultBtnMap.find("**/button_rollover"),
                           self.defaultBtnMap.find("**/button_disabled"))

        defaultFont = loader.loadFont('gui/eufm10.ttf')

        self.logFrame = DirectScrolledFrame(
            canvasSize=(0, base.a2dRight * 2, -5, 0),
            frameSize=(0, base.a2dRight * 2, (base.a2dBottom + .2) * 2, 0),
            frameColor=(0.1, 0.1, 0.1, 1))
        self.logFrame.reparentTo(base.a2dTopLeft)

        # create the info and server debug output
        self.textscale = 0.1
        self.txtinfo = OnscreenText(scale=self.textscale,
                                    pos=(0.1, -0.1),
                                    text="",
                                    align=TextNode.ALeft,
                                    fg=(0.1, 1.0, 0.15, 1),
                                    bg=(0, 0, 0, 0),
                                    shadow=(0, 0, 0, 1),
                                    shadowOffset=(-0.02, -0.02))
        self.txtinfo.setTransparency(1)
        self.txtinfo.reparentTo(self.logFrame.getCanvas())

        # create a close Server button
        self.btnBackPos = Vec3(0.4, 0, 0.2)
        self.btnBackScale = 0.25
        self.btnBack = DirectButton(
            # Scale and position
            scale=self.btnBackScale,
            pos=self.btnBackPos,
            # Text
            text="Quit Server",
            text_scale=0.45,
            text_pos=(0, -0.1),
            text_fg=(0.82, 0.85, 0.87, 1),
            text_shadow=(0, 0, 0, 1),
            text_shadowOffset=(-0.02, -0.02),
            text_font=defaultFont,
            # Frame
            geom=self.buttonGeom,
            frameColor=(0, 0, 0, 0),
            relief=0,
            pressEffect=False,
            # Functionality
            command=self.back,
            rolloverSound=None,
            clickSound=None)
        self.btnBack.setTransparency(1)
        self.btnBack.reparentTo(base.a2dBottomLeft)

        # catch window resizes and recalculate the aspectration
        self.accept("window-event", self.recalcAspectRatio)
        self.accept("addLog", self.addLog)

    def show(self):
        self.logFrame.show()
        self.btnBack.show()

    def hide(self):
        self.logFrame.hide()
        self.btnBack.hide()

    def back(self):
        self.hide()
        base.messenger.send("stop_server")
        self.addLog("Quit Server!")

    def addLog(self, text):
        self.txtinfo.appendText(text + "\n")
        textbounds = self.txtinfo.getTightBounds()
        self.logFrame["canvasSize"] = (0, textbounds[1].getX(),
                                       textbounds[0].getZ(), 0)

    def recalcAspectRatio(self, window):
        """get the new aspect ratio to resize the mainframe"""
        # set the mainframe size to the window borders again
        self.logFrame["frameSize"] = (0, base.a2dRight * 2,
                                      (base.a2dBottom + .2) * 2, 0)