from panda3d.core import ConfigVariableBool, TextNode, VBase4, Vec3
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.distributed.DistributedNode import DistributedNode
from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
from direct.gui import DirectGuiGlobals
from direct.showbase.EventGroup import EventGroup
from direct.showbase.PythonUtil import report
from direct.showbase.MessengerGlobal import messenger
from direct.distributed.GridParent import GridParent
if __debug__:
# For grid drawing
from direct.directtools.DirectGeometry import LineNodePath
from direct.showbase.PythonUtil import randFloat
from .CartesianGridBase import CartesianGridBase
# increase this number if you want to visualize the grid lines
# above water level
GRID_Z_OFFSET = 0.0
[docs]class DistributedCartesianGrid(DistributedNode, CartesianGridBase):
notify = directNotify.newCategory("DistributedCartesianGrid")
notify.setDebug(False)
VisualizeGrid = ConfigVariableBool("visualize-cartesian-grid", False)
RuleSeparator = ":"
[docs] def __init__(self, cr):
DistributedNode.__init__(self, cr)
# Let the derived classes instantiate the NodePath
self.visAvatar = None
self.gridVisContext = None
# Do we have grid lines visualized?
self._onOffState = False
if __debug__:
self.haveGridLines = 0
[docs] def generate(self):
DistributedNode.generate(self)
[docs] def disable(self):
DistributedNode.disable(self)
self.stopProcessVisibility()
[docs] def delete(self):
DistributedNode.delete(self)
# TODO: when teleporting off an island...
taskMgr.remove(self.taskName("processVisibility"))
[docs] def isGridParent(self):
# If this distributed object is a DistributedGrid return 1. 0 by default
return 1
[docs] def setCellWidth(self, width):
self.cellWidth = width
[docs] def setParentingRules(self, style, rule):
assert self.notify.debug("setParentingRules: style: %s, rule: %s" % (style, rule))
rules = rule.split(self.RuleSeparator)
assert len(rules) == 3
self.style = style
self.startingZone = int(rules[0])
self.gridSize = int(rules[1])
self.viewingRadius = int(rules[2])
# Store the center of the grid
cx = self.cellWidth * self.gridSize/2.0
self.centerPos = Vec3(cx, cx, 0)
if __debug__:
if self.VisualizeGrid:
self.visualizeGrid()
[docs] def getCenterPos(self):
return self.centerPos
[docs] def handleChildArrive(self, child, zoneId):
DistributedNode.handleChildArrive(self, child, zoneId)
if zoneId >= self.startingZone:
if not child.gridParent:
child.gridParent = GridParent(child)
child.gridParent.setGridParent(self, zoneId)
elif child.gridParent:
child.gridParent.delete()
child.gridParent = None
[docs] def handleChildArriveZone(self, child, zoneId):
DistributedNode.handleChildArrive(self, child, zoneId)
if zoneId >= self.startingZone:
if not child.gridParent:
child.gridParent = GridParent(child)
child.gridParent.setGridParent(self, zoneId)
elif child.gridParent:
child.gridParent.delete()
child.gridParent = None
[docs] def handleChildLeave(self, child, zoneId):
if child.gridParent:
child.gridParent.delete()
child.gridParent = None
[docs] @report(types = ['deltaStamp', 'avLocation', 'args'], dConfigParam = ['connector','shipboard'])
def startProcessVisibility(self, avatar):
if not self._onOffState:
# if we've been told that we're OFF, don't try
# to process visibilty
return
assert not self.cr._noNewInterests
if self.cr.noNewInterests():
self.notify.warning(
'startProcessVisibility(%s): tried to open a new interest during logout'
% self.doId)
return
taskMgr.remove(self.taskName("processVisibility"))
self.acceptOnce(self.cr.StopVisibilityEvent, self.stopProcessVisibility)
self.visAvatar = avatar
self.visZone = None
self.visDirty = True
taskMgr.add(
self.processVisibility, self.taskName("processVisibility"))
self.processVisibility(0)
[docs] @report(types = ['deltaStamp', 'avLocation', 'args'], dConfigParam = ['connector','shipboard'])
def stopProcessVisibility(self, clearAll=False, event=None):
self.ignore(self.cr.StopVisibilityEvent)
taskMgr.remove(self.taskName("processVisibility"))
if event is not None:
eventGroup = EventGroup('DistCartesianGrid.stopProcessVis',
doneEvent=event)
if self.gridVisContext is not None:
if event is not None:
removeEvent = eventGroup.newEvent('%s.removeInterest' % self.doId)
else:
removeEvent = None
self.cr.removeInterest(self.gridVisContext, removeEvent)
self.gridVisContext = None
else:
# if we were given an event but we have not interest open,
# just send the event right away
if event is not None:
messenger.send(event)
self.visAvatar = None
self.visZone = None
# sometimes we also need to remove vis avatar from
# my parent if it is also a grid
if clearAll:
if event is not None:
parentEvent = eventGroup.newEvent('%s.parent.removeInterest' % self.doId)
else:
parentEvent = None
##HACK BANDAID FOR PVP INSTANCES
if hasattr(self.cr.doId2do[self.parentId], "worldGrid"):
self.cr.doId2do[self.parentId].worldGrid.stopProcessVisibility(event=parentEvent)
[docs] def processVisibility(self, task):
if self.visAvatar is None:
# no avatar to process visibility for
return Task.done
if self.visAvatar.isDisabled():
self.visAvatar = None
return Task.done
if self.visAvatar.gameFSM.state == 'Cutscene':
return Task.cont
pos = self.visAvatar.getPos(self)
# Check to make sure our x and y are positive
dx = self.cellWidth * self.gridSize * .5
x = pos[0] + dx
y = pos[1] + dx
col = x // self.cellWidth
row = y // self.cellWidth
assert self.notify.debug(
"processVisibility: %s: avatar pos: %s %s" % (self.doId, x, y))
if (row < 0) or (col < 0) or (row > self.gridSize) or (col > self.gridSize):
assert self.notify.debug("processVisibility: %s: not on the grid" % (self.doId))
# If we are viewingRadius away from this entire grid,
# remove interest in any current visZone we may have
if self.gridVisContext:
self.cr.removeInterest(self.gridVisContext)
self.visZone = None
self.gridVisContext = None
return Task.cont
# Compute which zone we are in
zoneId = int(self.startingZone + ((row * self.gridSize) + col))
assert self.notify.debug("processVisibility: %s: row: %s col: %s zoneId: %s" %
(self.doId, row, col, zoneId))
if zoneId == self.visZone:
assert self.notify.debug(
"processVisibility: %s: interest did not change" % (self.doId))
if self.visDirty:
messenger.send(self.uniqueName("visibility"))
self.visDirty = False
return Task.cont
else:
assert self.notify.debug(
"processVisibility: %s: new interest" % (self.doId))
self.visZone = zoneId
if not self.gridVisContext:
self.gridVisContext = self.cr.addInterest(
self.getDoId(), self.visZone,
self.uniqueName("visibility"),
event = self.uniqueName("visibility"))
else:
assert self.notify.debug(
"processVisibility: %s: altering interest to zoneId: %s" %
(self.doId, zoneId))
event = None
if self.visDirty:
event = self.uniqueName("visibility")
self.cr.alterInterest(
self.gridVisContext, self.getDoId(), self.visZone,
event = event)
# If the visAvatar is parented to this grid, also do a
# setLocation
parentId = self.visAvatar.parentId
oldZoneId = self.visAvatar.zoneId
assert self.notify.debug(
"processVisibility: %s: parentId: %s oldZoneId: %s" %
(self.doId, parentId, oldZoneId))
if parentId == self.doId:
assert self.notify.debug(
"processVisibility: %s: changing location" %
(self.doId))
messenger.send("avatarZoneChanged", [self.visAvatar, self.doId, zoneId])
#self.handleAvatarZoneChange(self.visAvatar, zoneId)
self.visDirty = False
return Task.cont
# Update our location based on our avatar's position on the grid
# Assumes our position is correct, relative to the grid
[docs] def addObjectToGrid(self, av):
assert self.notify.debug("addObjectToGrid %s" % av)
# Get our pos relative to the island grid
pos = av.getPos(self)
# Figure out what zone in that island grid
zoneId = self.getZoneFromXYZ(pos)
# Do the wrtReparenting to the grid node
messenger.send("avatarZoneChanged", [av, self.doId, zoneId])
#self.handleAvatarZoneChange(av, zoneId)
[docs] def removeObjectFromGrid(self, av):
assert self.notify.debug("removeObjectFromGrid %s" % av)
# TODO: WHAT LOCATION SHOULD WE SET THIS TO?
#av.reparentTo(hidden)
if av.getParent() == self:
# only detach if object is directly parented
av.detachNode()
#av.b_setLocation(0, 0)
[docs] def handleAvatarZoneChange(self, av, zoneId):
assert self.notify.debug("handleAvatarZoneChange(%s, %s)" % (av.doId, zoneId))
# This method can be overridden by derived classes that
# want to do some special management when the avatar changes
# zones.
# Make sure this is a valid zone
if not self.isValidZone(zoneId):
assert self.notify.warning("handleAvatarZoneChange: not a valid zone (%s)" % zoneId)
return
# Set the location on the server
av.b_setLocation(self.doId, zoneId)
[docs] def turnOff(self):
self._onOffState = False
self.stopProcessVisibility()
[docs] def turnOn(self, av = None):
self._onOffState = True
if av:
self.startProcessVisibility(av)
##################################################
# Visualization Tools
##################################################
if __debug__:
[docs] def initializeGridLines(self):
# Grid Lines
self.gridColor = VBase4(0.4 + randFloat(0.4),
0.4 + randFloat(0.4),
0.4 + randFloat(0.4),
1)
# A Dark version of the grid color
color = self.gridColor * 0.5
color.setW(1)
self.lines = self.attachNewNode('gridLines')
self.minorLines = LineNodePath(self.lines)
self.minorLines.lineNode.setName('minorLines')
self.minorLines.setColor(color)
self.minorLines.setThickness(1)
self.majorLines = LineNodePath(self.lines)
self.majorLines.lineNode.setName('majorLines')
self.majorLines.setColor(color)
self.majorLines.setThickness(5)
self.centerLines = LineNodePath(self.lines)
self.centerLines.lineNode.setName('centerLines')
self.centerLines.setColor(VBase4(1, 0, 0, 0))
self.centerLines.setThickness(3)
# Load up grid parts to initialize grid object
# Polygon used to mark grid plane
# self.gridBack = base.loader.loadModel('models/misc/gridBack')
# self.gridBack.reparentTo(self)
# self.gridBack.setColor(0.2, 0.2, 0.2, 0.5)
self.cellLabelParent = None
self.markerParent = None
self.haveGridLines = 1
[docs] def updateGrid(self):
# Update grid lines based upon current grid spacing and grid size
# First reset existing grid lines
self.minorLines.reset()
self.majorLines.reset()
self.centerLines.reset()
# Now redraw lines
numLines = self.gridSize
scaledSize = numLines * self.cellWidth / 2.0
center = self.centerLines
minor = self.minorLines
major = self.majorLines
cw = self.cellWidth
dx = cw * self.gridSize * .5
for i in range(numLines+1):
icw = i * cw - dx
if i == numLines/2:
center.moveTo(icw, -scaledSize, GRID_Z_OFFSET)
center.drawTo(icw, scaledSize, GRID_Z_OFFSET)
center.moveTo(-scaledSize, icw, GRID_Z_OFFSET)
center.drawTo(scaledSize, icw, GRID_Z_OFFSET)
else:
if (i % 5) == 0:
major.moveTo(icw, -scaledSize, GRID_Z_OFFSET)
major.drawTo(icw, scaledSize, GRID_Z_OFFSET)
major.moveTo(-scaledSize, icw, GRID_Z_OFFSET)
major.drawTo(scaledSize, icw, GRID_Z_OFFSET)
else:
minor.moveTo(icw, -scaledSize, GRID_Z_OFFSET)
minor.drawTo(icw, scaledSize, GRID_Z_OFFSET)
minor.moveTo(-scaledSize, icw, GRID_Z_OFFSET)
minor.drawTo(scaledSize, icw, GRID_Z_OFFSET)
center.create()
minor.create()
major.create()
# self.gridBack.setScale(scaledSize)
self.labelCells()
[docs] def labelCells(self):
if self.cellLabelParent:
self.cellLabelParent.removeNode()
self.cellLabelParent = self.attachNewNode('cellLabels')
cw = self.cellWidth
scale = cw / 10.0
dx = cw * self.gridSize * .5
font = DirectGuiGlobals.getDefaultFont()
color = self.gridColor
for i in range(self.gridSize):
for j in range(self.gridSize):
zoneId = self.startingZone + ((j * self.gridSize) + i)
zoneStr = str(zoneId)
textNode = TextNode(zoneStr)
textNode.setText(zoneStr)
textNode.setFont(font)
textNode.setTextColor(color)
textNode.setAlign(TextNode.ACenter)
genTextNode = textNode.generate()
textNodePath = self.cellLabelParent.attachNewNode(genTextNode)
# Place the text node in the center of the cell
textNodePath.setPosHprScale((i * cw - dx) + (cw * 0.5), # x
(j * cw - dx) + (cw * 0.5), # y
GRID_Z_OFFSET+3.0, # z
# Lay them down flat
0, -90, 0, # hpr
scale, scale, scale)
self.cellLabelParent.flattenLight()
[docs] def markCells(self):
if self.markerParent:
self.markerParent.removeNode()
self.markerParent = self.attachNewNode('markers')
self.cellMarkers = []
dx = self.cellWidth * self.gridSize * .5
for i in range(self.gridSize):
for j in range(self.gridSize):
marker = base.loader.loadModel("models/misc/smiley")
marker.reparentTo(self.markerParent)
marker.setPos(i * self.cellWidth - dx,
j * self.cellWidth - dx,
GRID_Z_OFFSET + 1.0)
marker.setScale(5)
self.cellMarkers.append(marker)
[docs] def unmarkCells(self):
if self.markerParent:
self.markerParent.removeNode()
self.markerParent = None
[docs] def visualizeGrid(self):
if not self.haveGridLines:
self.initializeGridLines()
self.updateGrid()
[docs] def setWorldContext(self, worldContext):
pass
[docs] def clearWorldContext(self, event = None):
pass