import sys,random,time from pyglet import image from pyglet.gl import * from pyglet.graphics import TextureGroup from pyglet.window import key, mouse from processQueue import ProcessQueue from shape import Shape3D from loadSource import * if sys.version_info[0] >= 3: xrange = range if sys.version_info[0] * 10 + sys.version_info[1] >= 38: time.clock = time.process_time class World(object): def __init__(self, sectorSize = 16, gravity = 20): # A Batch is a collection of vertex lists for batched rendering. self.batch = pyglet.graphics.Batch() # A mapping from position to the texture of the block at that position. # This defines all the blocks that are currently in the world. self.world = {} # Same mapping as `world` but only contains blocks that are shown. self.shown = {} # Mapping from position to a pyglet `VertextList` for all shown blocks. self._shown = {} # Mapping from sector to a list of positions inside that sector. self.sectors = {} self.sectorSize = sectorSize # Simple function queue implementation. The queue is populated with # _show_block() and _hide_block() calls The queue contains calls to # _show_block() and _hide_block() so this method should be called if # add_block() or remove_block() was called with immediate=False self.processQueue = ProcessQueue() #the gravity of the World self.gravity = gravity # Which sector the player is currently in. self.sector = None self.mode = "day" def skyColor(self): if self.mode == "day": return(0.5, 0.69, 1.0, 1) elif self.mode == "night": return(0.05, 0, 0.15, 1) def changeMode(self,mode): if mode == "day" or mode == "night": self.mode = mode else: raise ValueError("The mode should be either 'day' or ' night'") def loadWorld(self,file): pass def saveWorld(Self,file): pass def updateWorld(self,freshPeriod,player): self.processQueue.process_queue(1.0 / freshPeriod) sector = self._sectorize(player.position) if sector != self.sector: self._change_sectors(self.sector, sector) if self.sector is None: self.processQueue.process_entire_queue() self.sector = sector def setupWorld(self): """ Initialize the world by placing all the blocks. """ n = 80 # 1/2 width and height of world s = 1 # step size y = 0 # initial y height for x in xrange(-n, n + 1, s): for z in xrange(-n, n + 1, s): # create a layer MARBLE.coordinates an GRASS.coordinates everywhere. self.add_block((x, y - 2, z), GRASS, immediate=False) self.add_block((x, y - 3, z), MARBLE, immediate=False) if x in (-n, n) or z in (-n, n): # create outer walls. for dy in xrange(-2, 3): self.add_block((x, y + dy, z), MARBLE, immediate=False) # generate the hills randomly o = n - 10 for _ in xrange(120): a = random.randint(-o, o) # x position of the hill b = random.randint(-o, o) # z position of the hill c = -1 # base of the hill h = random.randint(1, 6) # height of the hill s = random.randint(4, 8) # 2 * s is the side length of the hill d = 1 # how quickly to taper off the hills t = random.choice([GRASS, STONE, BRICK]) for y in xrange(c, c + h): for x in xrange(a - s, a + s + 1): for z in xrange(b - s, b + s + 1): if (x - a) ** 2 + (z - b) ** 2 > (s + 1) ** 2: continue if (x - 0) ** 2 + (z - 0) ** 2 < 5 ** 2: continue self.add_block((x, y, z), t, immediate=False) s -= d # decrement side lenth so hills taper off def collide(self, position, creature): """ Checks to see if the player at the given `position` and `height` is colliding with any blocks in the world. Parameters ---------- position : tuple of len 3 The (x, y, z) position to check for collisions at. height : int or float The height of the player. Returns ------- position : tuple of len 3 The new position of the player taking into account collisions. """ # How much overlap with a dimension of a surrounding block you need to # have to count as a collision. If 0, touching terrain at all counts as # a collision. If .49, you sink into the ground, as if walking through # tall GRASS.coordinates. If >= .5, you'll fall through the ground. pad = 0.25 p = list(position) np = self._normalize(position) for face in [( 0, 1, 0),( 0,-1, 0),(-1, 0, 0),( 1, 0, 0),( 0, 0, 1),( 0, 0,-1)]: # check all surrounding blocks for i in xrange(3): # check each dimension independently if not face[i]: continue # How much overlap you have with this dimension. d = (p[i] - np[i]) * face[i] if d < pad: continue for dy in xrange(creature.height): # check each height op = list(np) op[1] -= dy op[i] += face[i] if tuple(op) not in self.world: continue p[i] -= (d - pad) * face[i] if face == (0, -1, 0) or face == (0, 1, 0): # You are colliding with the ground or ceiling, so stop # falling / rising. creature.dy = 0 break return tuple(p) def hit_test(self, position, vector, max_distance=8): """ Line of sight search from current position. If a block is intersected it is returned, along with the block previously in the line of sight. If no block is found, return None, None. Parameters ---------- position : tuple of len 3 The (x, y, z) position to check visibility from. vector : tuple of len 3 The line of sight vector. max_distance : int How many blocks away to search for a hit. @return the position of the block that is intersected with the sight and the previous position(for adding blocks) """ m = 8 x, y, z = position dx, dy, dz = vector previous = None for _ in xrange(max_distance * m): key = self._normalize((x, y, z)) if key != previous and key in self.world: return key, previous previous = key x, y, z = x + dx / m, y + dy / m, z + dz / m return None, None def add_block(self, position, block, immediate=True): """ Add a block with the given `texture` and `position` to the world. Parameters ---------- position : tuple of len 3 The (x, y, z) position of the block to add. texture : list of len 3 The coordinates of the texture squares. Use `tex_coords()` to generate. immediate : bool Whether or not to draw the block immediately. """ if position in self.world: self.remove_block(position, immediate) self.world[position] = block self.sectors.setdefault(self._sectorize(position), []).append(position) if immediate: if self._exposed(position): self.show_block(position) self._check_neighbors(position) def remove_block(self, position, immediate=True): """ Remove the block at the given `position`. Parameters ---------- position : tuple of len 3 The (x, y, z) position of the block to remove. immediate : bool Whether or not to immediately remove block from canvas. """ del self.world[position] self.sectors[self._sectorize(position)].remove(position) if immediate: if position in self.shown: self.hide_block(position) self._check_neighbors(position) def _check_neighbors(self, position): """ Check all blocks surrounding `position` and ensure their visual state is current. This means hiding blocks that are not _exposed and ensuring that all _exposed blocks are shown. Usually used after a block is added or removed. """ x, y, z = position for dx, dy, dz in [( 0, 1, 0),( 0,-1, 0),(-1, 0, 0),( 1, 0, 0),( 0, 0, 1),( 0, 0,-1)]: key = (x + dx, y + dy, z + dz) if key not in self.world: continue if self._exposed(key): if key not in self.shown: self.show_block(key) else: if key in self.shown: self.hide_block(key) def _exposed(self, position): """ Returns False is given `position` is surrounded on all 6 sides by blocks, True otherwise. """ x, y, z = position for dx, dy, dz in [( 0, 1, 0),( 0,-1, 0),(-1, 0, 0),( 1, 0, 0),( 0, 0, 1),( 0, 0,-1)]: if (x + dx, y + dy, z + dz) not in self.world: return True return False def show_block(self, position, immediate=True): """ Show the block at the given `position`. This method assumes the block has already been added with add_block() Parameters ---------- position : tuple of len 3 The (x, y, z) position of the block to show. immediate : bool Whether or not to show the block immediately. """ if immediate: block = self.world[position] self.shown[position] = block x, y, z = position vertex_data = Shape3D.cube_vertices(x, y, z, 0.5) texture_data = list(block.coordinates) # create vertex list # FIXME Maybe `add_indexed()` should be used instead self._shown[position] = self.batch.add(24, GL_QUADS, block.texture, ('v3f/static', vertex_data), ('t2f/static', texture_data)) else: self.processQueue.enqueue(self.show_block, position, True) def hide_block(self, position, immediate=True): """ Hide the block at the given `position`. Hiding does not remove the block from the world. Parameters ---------- position : tuple of len 3 The (x, y, z) position of the block to hide. immediate : bool Whether or not to immediately remove the block from the canvas. """ if immediate: self.shown.pop(position) self._shown.pop(position).delete() else: self.processQueue.enqueue(self.hide_block, position, True) def _show_sector(self, sector): """ Ensure all blocks in the given sector that should be shown are drawn to the canvas. """ for position in self.sectors.get(sector, []): if position not in self.shown and self._exposed(position): self.show_block(position, False) def _hide_sector(self, sector): """ Ensure all blocks in the given sector that should be hidden are removed from the canvas. """ for position in self.sectors.get(sector, []): if position in self.shown: self.hide_block(position, False) def _change_sectors(self, before, after): """ Move from sector `before` to sector `after`. A sector is a contiguous x, y sub-region of world. Sectors are used to speed up world rendering. """ before_set = set() after_set = set() pad = 4 for dx in xrange(-pad, pad + 1): for dy in [0]: # xrange(-pad, pad + 1): for dz in xrange(-pad, pad + 1): if dx ** 2 + dy ** 2 + dz ** 2 > (pad + 1) ** 2: continue if before: x, y, z = before before_set.add((x + dx, y + dy, z + dz)) if after: x, y, z = after after_set.add((x + dx, y + dy, z + dz)) show = after_set - before_set hide = before_set - after_set for sector in show: self._show_sector(sector) for sector in hide: self._hide_sector(sector) def _sectorize(self,position): """ Returns a tuple representing the sector for the given `position`. Parameters ---------- position : tuple of len 3 Returns ------- sector : tuple of len 3 """ x, y, z = self._normalize(position) x, y, z = x // self.sectorSize, y // self.sectorSize, z // self.sectorSize return (x, 0, z) def _normalize(self,position): """ Accepts `position` of arbitrary precision and returns the block containing that position. Parameters ---------- position : tuple of len 3 Returns ------- block_position : tuple of ints of len 3 """ x, y, z = position x, y, z = (int(round(x)), int(round(y)), int(round(z))) return (x, y, z)