def run_build(self, builddata): block, metadata, x, y, z, face = builddata # Don't place items as blocks. if block.slot not in blocks: raise BuildError("Couldn't build item %r as block" % block) # Check for orientable blocks. if not metadata and block.orientable(): metadata = block.orientation(face) if metadata is None: # Oh, I guess we can't even place the block on this face. raise BuildError("Couldn't orient block %r on face %s" % (block, face)) # Make sure we can remove it from the inventory first. if not self.player.inventory.consume((block.slot, 0), self.player.equipped): # Okay, first one was a bust; maybe we can consume the related # block for dropping instead? if not self.player.inventory.consume((block.drop, 0), self.player.equipped): raise BuildError("Couldn't consume %r from inventory" % block) # Offset coords according to face. adjust_coords_for_face((x, y, z), face) # Set the block and data. dl = [self.factory.world.set_block((x, y, z), block.slot)] if metadata: dl.append(self.factory.world.set_metadata((x, y, z), metadata)) return DeferredList(dl)
def update_powered_block(self, x, y, z, enabled): """ Update a powered non-redstone block. """ neighbors = ((x - 1, y, z), (x + 1, y, z), (x, y, z - 1), (x, y, z + 1)) affected = [] for neighbor in neighbors: block = factory.world.sync_get_block(neighbor) if block == blocks["redstone-wire"].slot: args = neighbor + (enabled,) self.update_wires(*args) elif block == blocks["redstone-torch"].slot and enabled: metadata = factory.world.sync_get_metadata(neighbor) face = blocks["redstone-torch"].face(metadata) target = adjust_coords_for_face(neighbor, face) if target == (x, y, z): # We should turn off this torch. factory.world.sync_set_block(neighbor, blocks["redstone-torch-off"].slot) affected.append(neighbor) return affected
def run_build(self, builddata): block, metadata, x, y, z, face = builddata # Don't place items as blocks. if block.slot not in blocks: raise BuildError("Couldn't build item %r as block" % block) # Check for orientable blocks. if not metadata and block.orientable(): metadata = block.orientation(face) if metadata is None: # Oh, I guess we can't even place the block on this face. raise BuildError("Couldn't orient block %r on face %s" % (block, face)) # Make sure we can remove it from the inventory first. if not self.player.inventory.consume( (block.slot, 0), self.player.equipped): # Okay, first one was a bust; maybe we can consume the related # block for dropping instead? if not self.player.inventory.consume(block.drop, self.player.equipped): raise BuildError("Couldn't consume %r from inventory" % block) # Offset coords according to face. x, y, z = adjust_coords_for_face((x, y, z), face) # Set the block and data. dl = [self.factory.world.set_block((x, y, z), block.slot)] if metadata: dl.append(self.factory.world.set_metadata((x, y, z), metadata)) return DeferredList(dl)
def pre_build_hook(self, player, builddata): item, metadata, x, y, z, face = builddata if item.slot != blocks["chest"].slot: returnValue((True, builddata, False)) x, y, z = adjust_coords_for_face((x, y, z), face) bigx, smallx, bigz, smallz = split_coords(x, z) # chest orientation according to players position if face == "-y" or face == "+y": orientation = ('+x', '+z', '-x', '-z')[((int(player.location.yaw) \ - 45 + 360) % 360) / 90] else: orientation = face # Chests have some restrictions on building: # you cannot connect more than two chests. (notchian) ccs = chestsAround(self.factory, (x, y, z)) ccn = len(ccs) if ccn > 1: # cannot build three or more connected chests returnValue((False, builddata, True)) chunk = yield self.factory.world.request_chunk(bigx, bigz) if ccn == 0: metadata = blocks["chest"].orientation(orientation) elif ccn == 1: # check gonna-be-connected chest is not connected already n = len(chestsAround(self.factory, ccs[0])) if n != 0: returnValue((False, builddata, True)) # align both blocks correctly (since 1.8) # get second block x2, y2, z2 = ccs[0] bigx2, smallx2, bigz2, smallz2 = split_coords(x2, z2) # new chests orientation axis according to blocks position pair = x - x2, z - z2 ornt = {(0, 1): "x", (0, -1): "x", (1, 0): "z", (-1, 0): "z"}[pair] # if player is faced another direction, fix it if orientation[1] != ornt: # same sign with proper orientation # XXX Probably notchian logic is different here # but this one works well enough orientation = orientation[0] + ornt metadata = blocks["chest"].orientation(orientation) # update second block's metadata if bigx == bigx2 and bigz == bigz2: # both blocks are in same chunk chunk2 = chunk else: chunk2 = yield self.factory.world.request_chunk(bigx2, bigz2) chunk2.set_metadata((smallx2, y2, smallz2), metadata) # Not much to do, just tell the chunk about this tile. chunk.tiles[smallx, y, smallz] = ChestTile(smallx, y, smallz) builddata = builddata._replace(metadata=metadata) returnValue((True, builddata, False))
def pre_build_hook(self, player, builddata): item, metadata, x, y, z, face = builddata if item.slot == items["sign"].slot: # Buildin' a sign, puttin' it on a wall... builddata = builddata._replace(block=blocks["wall-sign"]) # Offset coords according to face. if face == "-x": builddata = builddata._replace(metadata=0x4) x -= 1 elif face == "+x": builddata = builddata._replace(metadata=0x5) x += 1 elif face == "-y": # Ceiling Sign is watching you read. returnValue((False, builddata)) elif face == "+y": # Put +Y signs on signposts. We're fancy that way. Also, # calculate the proper orientation based on player # orientation. # 180 degrees around to orient the signs correctly, and then # 23 degrees to get the sign to midpoint correctly. metadata = ((player.location.yaw + 180) * 16 // 360) % 0xf builddata = builddata._replace(block=blocks["signpost"], metadata=metadata) y += 1 elif face == "-z": builddata = builddata._replace(metadata=0x2) z -= 1 elif face == "+z": builddata = builddata._replace(metadata=0x3) z += 1 bigx, smallx, bigz, smallz = split_coords(x, z) # Let's build a sign! chunk = yield factory.world.request_chunk(bigx, bigz) s = Sign(smallx, y, smallz) chunk.tiles[smallx, y, smallz] = s elif item.slot == blocks["chest"].slot: x, y, z = adjust_coords_for_face((x, y, z), face) bigx, smallx, bigz, smallz = split_coords(x, z) # Not much to do, just tell the chunk about this chest. chunk = yield factory.world.request_chunk(bigx, bigz) c = Chest(smallx, y, smallz) chunk.tiles[smallx, y, smallz] = c returnValue((True, builddata))
def pre_build_hook(self, player, builddata): item, metadata, x, y, z, face = builddata if item.slot != blocks["furnace"].slot: returnValue((True, builddata, False)) x, y, z = adjust_coords_for_face((x, y, z), face) bigx, smallx, bigz, smallz = split_coords(x, z) # the furnace cannot be oriented up or down if face == "-y" or face == "+y": orientation = ('+x', '+z', '-x', '-z')[((int(player.location.yaw) \ - 45 + 360) % 360) / 90] metadata = blocks["furnace"].orientation(orientation) builddata = builddata._replace(metadata=metadata) # Not much to do, just tell the chunk about this tile. chunk = yield self.factory.world.request_chunk(bigx, bigz) chunk.tiles[smallx, y, smallz] = FurnaceTile(smallx, y, smallz) returnValue((True, builddata, False))
def use_hook(self, player, target, button): # Block coordinates. x, y, z = target.location.x, target.location.y, target.location.z # Offset coords according to direction. A painting does not occupy a # block, therefore we drop the pickup right in front of the block it # is attached to. face = direction_to_face[target.direction] x, y, z = adjust_coords_for_face((x, y, z), face) # Pixel coordinates. coords = (x * 32 + 16, y * 32, z * 32 + 16) self.factory.destroy_entity(target) self.factory.give(coords, (items["paintings"].slot, 0), 1) packet = make_packet("destroy", count=1, eid=[target.eid]) self.factory.broadcast(packet) # Force the chunk (with its entities) to be saved to disk. self.factory.world.mark_dirty((x, y, z))
def run_circuit(self, x, y, z): """ Iterate through a circuit, starting at the given block, and return a list of circuits which have been affected. """ world = factory.world block = factory.world.sync_get_block((x, y, z)) neighbors = ((x - 1, y, z), (x + 1, y, z), (x, y, z - 1), (x, y, z + 1)) affected = set() if block == blocks["lever"].slot: # Power/depower the block the lever is attached to. metadata = factory.world.sync_get_metadata((x, y, z)) powered = metadata & 0x8 face = blocks["lever"].face(metadata & ~0x8) target = adjust_coords_for_face((x, y, z), face) if powered: self.powered.add(target) else: self.powered.discard(target) affected.add(target) else: # Let's update anything around us. l = self.update_powered_block(x, y, z, (x, y, z) in self.powered) affected.update(l) if block == blocks["redstone-torch"].slot: # Turn on neighboring wires, as appropriate. for coords in neighbors: if (world.get_block(coords) == blocks["redstone-wire"].slot): self.update_wires(factory, coords[0], coords[1], coords[2], True) if block == blocks["redstone-torch-off"].slot: # Turn off neighboring wires, as appropriate. for coords in neighbors: if (world.get_block(coords) == blocks["redstone-wire"].slot): self.update_wires(factory, coords[0], coords[1], coords[2], False) elif block == blocks["redstone-wire"].slot: # Get wire status from neighbors. if any(world.get_block(coords) == blocks["redstone-torch"].slot for coords in neighbors): # We should probably be lit. self.update_wires(factory, x, y, z, True) else: # Find the strongest neighboring wire, and use that. new_level = max(factory.world.get_metadata(coords) for coords in neighbors if factory.world.get_block(coords) == blocks["redstone-wire"].slot) if new_level > 0x0: new_level -= 1 world.set_metadata((x, y, z), new_level) return affected
def build(self, container): if container.x == -1 and container.z == -1 and container.y == 255: # Lala-land build packet. Discard it for now. return # Is the target being selected? bigx, smallx, bigz, smallz = split_coords(container.x, container.z) try: chunk = self.chunks[bigx, bigz] except KeyError: self.error("Couldn't select in chunk (%d, %d)!" % (bigx, bigz)) return # Try to open it first for hook in self.open_hooks: window = yield maybeDeferred(hook.open_hook, self, container, chunk.get_block((smallx, container.y, smallz))) if window: self.write_packet("window-open", wid=window.wid, type=window.identifier, title=window.title, slots=window.slots_num) packet = window.save_to_packet() self.transport.write(packet) # window opened return # Ignore clients that think -1 is placeable. if container.primary == -1: return # Special case when face is "noop": Update the status of the currently # held block rather than placing a new block. if container.face == "noop": return if container.primary in blocks: block = blocks[container.primary] elif container.primary in items: block = items[container.primary] else: log.err("Ignoring request to place unknown block %d" % container.primary) return # it's the top of the world, you can't build here if container.y == 127 and container.face == '+y': return # Run pre-build hooks. These hooks are able to interrupt the build # process. builddata = BuildData(block, 0x0, container.x, container.y, container.z, container.face) for hook in self.pre_build_hooks: cont, builddata, cancel = yield maybeDeferred(hook.pre_build_hook, self.player, builddata) if cancel: # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk) return if not cont: break # Run the build. try: yield maybeDeferred(self.run_build, builddata) except BuildError: return newblock = builddata.block.slot coords = adjust_coords_for_face( (builddata.x, builddata.y, builddata.z), builddata.face) # Run post-build hooks. These are merely callbacks which cannot # interfere with the build process, largely because the build process # already happened. for hook in self.post_build_hooks: yield maybeDeferred(hook.post_build_hook, self.player, coords, builddata.block) # Feed automatons. for automaton in self.factory.automatons: if newblock in automaton.blocks: automaton.feed(coords) # Re-send inventory. # XXX this could be optimized if/when inventories track damage. packet = self.inventory.save_to_packet() self.transport.write(packet) # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk)
def build(self, container): """ Handle a build packet. Several things must happen. First, the packet's contents need to be examined to ensure that the packet is valid. A check is done to see if the packet is opening a windowed object. If not, then a build is run. """ # Is the target within our purview? We don't do a very strict # containment check, but we *do* require that the chunk be loaded. bigx, smallx, bigz, smallz = split_coords(container.x, container.z) try: chunk = self.chunks[bigx, bigz] except KeyError: self.error("Couldn't select in chunk (%d, %d)!" % (bigx, bigz)) return target = blocks[chunk.get_block((smallx, container.y, smallz))] # If it's a chest, hax. if target.name == "chest": from bravo.policy.windows import Chest w = Chest() self.windows[self.wid] = w w.open() self.write_packet("window-open", wid=self.wid, type=w.identifier, title=w.title, slots=w.slots) self.wid += 1 return elif target.name == "workbench": from bravo.policy.windows import Workbench w = Workbench() self.windows[self.wid] = w w.open() self.write_packet("window-open", wid=self.wid, type=w.identifier, title=w.title, slots=w.slots) self.wid += 1 return # Try to open it first for hook in self.open_hooks: window = yield maybeDeferred(hook.open_hook, self, container, chunk.get_block((smallx, container.y, smallz))) if window: self.write_packet("window-open", wid=window.wid, type=window.identifier, title=window.title, slots=window.slots_num) packet = window.save_to_packet() self.transport.write(packet) # window opened return # Ignore clients that think -1 is placeable. if container.primary == -1: return # Special case when face is "noop": Update the status of the currently # held block rather than placing a new block. if container.face == "noop": return # If the target block is vanishable, then adjust our aim accordingly. if target.vanishes: container.face = "+y" container.y -= 1 if container.primary in blocks: block = blocks[container.primary] elif container.primary in items: block = items[container.primary] else: log.err("Ignoring request to place unknown block %d" % container.primary) return # Run pre-build hooks. These hooks are able to interrupt the build # process. builddata = BuildData(block, 0x0, container.x, container.y, container.z, container.face) for hook in self.pre_build_hooks: cont, builddata, cancel = yield maybeDeferred(hook.pre_build_hook, self.player, builddata) if cancel: # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk) return if not cont: break # Run the build. try: yield maybeDeferred(self.run_build, builddata) except BuildError: return newblock = builddata.block.slot coords = adjust_coords_for_face( (builddata.x, builddata.y, builddata.z), builddata.face) # Run post-build hooks. These are merely callbacks which cannot # interfere with the build process, largely because the build process # already happened. for hook in self.post_build_hooks: yield maybeDeferred(hook.post_build_hook, self.player, coords, builddata.block) # Feed automatons. for automaton in self.factory.automatons: if newblock in automaton.blocks: automaton.feed(coords) # Re-send inventory. # XXX this could be optimized if/when inventories track damage. packet = self.inventory.save_to_packet() self.transport.write(packet) # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk)
def build(self, container): if container.x == -1 and container.z == -1 and container.y == 255: # Lala-land build packet. Discard it for now. return # Is the target being selected? bigx, smallx, bigz, smallz = split_coords(container.x, container.z) try: chunk = self.chunks[bigx, bigz] except KeyError: self.error("Couldn't select in chunk (%d, %d)!" % (bigx, bigz)) return # Try to open it first for hook in self.open_hooks: window = yield maybeDeferred( hook.open_hook, self, container, chunk.get_block((smallx, container.y, smallz))) if window: self.write_packet("window-open", wid=window.wid, type=window.identifier, title=window.title, slots=window.slots_num) packet = window.save_to_packet() self.transport.write(packet) # window opened return # Ignore clients that think -1 is placeable. if container.primary == -1: return # Special case when face is "noop": Update the status of the currently # held block rather than placing a new block. if container.face == "noop": return if container.primary in blocks: block = blocks[container.primary] elif container.primary in items: block = items[container.primary] else: log.err("Ignoring request to place unknown block %d" % container.primary) return # it's the top of the world, you can't build here if container.y == 127 and container.face == '+y': return # Run pre-build hooks. These hooks are able to interrupt the build # process. builddata = BuildData(block, 0x0, container.x, container.y, container.z, container.face) for hook in self.pre_build_hooks: cont, builddata, cancel = yield maybeDeferred( hook.pre_build_hook, self.player, builddata) if cancel: # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk) return if not cont: break # Run the build. try: yield maybeDeferred(self.run_build, builddata) except BuildError: return newblock = builddata.block.slot coords = adjust_coords_for_face( (builddata.x, builddata.y, builddata.z), builddata.face) # Run post-build hooks. These are merely callbacks which cannot # interfere with the build process, largely because the build process # already happened. for hook in self.post_build_hooks: yield maybeDeferred(hook.post_build_hook, self.player, coords, builddata.block) # Feed automatons. for automaton in self.factory.automatons: if newblock in automaton.blocks: automaton.feed(coords) # Re-send inventory. # XXX this could be optimized if/when inventories track damage. packet = self.inventory.save_to_packet() self.transport.write(packet) # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk)
def pre_build_hook(self, player, builddata): item, metadata, x, y, z, face = builddata if item.slot == items["sign"].slot: # Buildin' a sign, puttin' it on a wall... builddata = builddata._replace(block=blocks["wall-sign"]) # Offset coords according to face. if face == "-x": builddata = builddata._replace(metadata=0x4) x -= 1 elif face == "+x": builddata = builddata._replace(metadata=0x5) x += 1 elif face == "-y": # Ceiling Sign is watching you read. returnValue((False, builddata, False)) elif face == "+y": # Put +Y signs on signposts. We're fancy that way. Also, # calculate the proper orientation based on player # orientation. # 180 degrees around to orient the signs correctly, and then # 23 degrees to get the sign to midpoint correctly. metadata = ((player.location.yaw + 180) * 16 // 360) % 0xf builddata = builddata._replace(block=blocks["signpost"], metadata=metadata) y += 1 elif face == "-z": builddata = builddata._replace(metadata=0x2) z -= 1 elif face == "+z": builddata = builddata._replace(metadata=0x3) z += 1 bigx, smallx, bigz, smallz = split_coords(x, z) # Let's build a sign! chunk = yield factory.world.request_chunk(bigx, bigz) s = Sign(smallx, y, smallz) chunk.tiles[smallx, y, smallz] = s elif item.slot in self.block_to_tile: x, y, z = adjust_coords_for_face((x, y, z), face) bigx, smallx, bigz, smallz = split_coords(x, z) if item.slot == blocks["chest"].slot: # Chests have some restrictions on building: # you cannot connect more than two chests. # (notchian) ccs = chestsAround(factory, (x, y, z)) ccn = len(ccs) if ccn == 1: # check gonna-be-connected chest is not connected already n = len(chestsAround(factory, ccs[0])) if n != 0: returnValue((False, builddata, True)) elif ccn > 1: # cannot build three or more connected chests returnValue((False, builddata, True)) elif item.slot == blocks["furnace"].slot: # the furnace cannot be oriented up or down if face == "-y" or face == "+y": orientation = ('+x', '+z', '-x', '-z')[((int(player.location.yaw) \ - 45 + 360) % 360) / 90] metadata = blocks["furnace"].orientation(orientation) builddata = builddata._replace(metadata=metadata) chunk = yield factory.world.request_chunk(bigx, bigz) # Not much to do, just tell the chunk about this tile. tileClass = self.block_to_tile[item.slot] tile = tileClass(smallx, y, smallz) chunk.tiles[smallx, y, smallz] = tile returnValue((True, builddata, False))
def test_adjust_plusx(self): coords = range(3) adjusted = adjust_coords_for_face(coords, "+x") self.assertEqual(adjusted, (1, 1, 2))
def build(self, container): """ Handle a build packet. Several things must happen. First, the packet's contents need to be examined to ensure that the packet is valid. A check is done to see if the packet is opening a windowed object. If not, then a build is run. """ # Is the target within our purview? We don't do a very strict # containment check, but we *do* require that the chunk be loaded. bigx, smallx, bigz, smallz = split_coords(container.x, container.z) try: chunk = self.chunks[bigx, bigz] except KeyError: self.error("Couldn't select in chunk (%d, %d)!" % (bigx, bigz)) return target = blocks[chunk.get_block((smallx, container.y, smallz))] # Attempt to open a window. from bravo.policy.windows import window_for_block window = window_for_block(target) if window is not None: # We have a window! self.windows[self.wid] = window identifier, title, slots = window.open() self.write_packet("window-open", wid=self.wid, type=identifier, title=title, slots=slots) self.wid += 1 return # Try to open it first for hook in self.open_hooks: window = yield maybeDeferred(hook.open_hook, self, container, chunk.get_block((smallx, container.y, smallz))) if window: self.write_packet("window-open", wid=window.wid, type=window.identifier, title=window.title, slots=window.slots_num) packet = window.save_to_packet() self.transport.write(packet) # window opened return # Ignore clients that think -1 is placeable. if container.primary == -1: return # Special case when face is "noop": Update the status of the currently # held block rather than placing a new block. if container.face == "noop": return # If the target block is vanishable, then adjust our aim accordingly. if target.vanishes: container.face = "+y" container.y -= 1 if container.primary in blocks: block = blocks[container.primary] elif container.primary in items: block = items[container.primary] else: log.err("Ignoring request to place unknown block 0x%x" % container.primary) return # Run pre-build hooks. These hooks are able to interrupt the build # process. builddata = BuildData(block, 0x0, container.x, container.y, container.z, container.face) for hook in self.pre_build_hooks: cont, builddata, cancel = yield maybeDeferred(hook.pre_build_hook, self.player, builddata) if cancel: # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk) return if not cont: break # Run the build. try: yield maybeDeferred(self.run_build, builddata) except BuildError: return newblock = builddata.block.slot coords = adjust_coords_for_face( (builddata.x, builddata.y, builddata.z), builddata.face) # Run post-build hooks. These are merely callbacks which cannot # interfere with the build process, largely because the build process # already happened. for hook in self.post_build_hooks: yield maybeDeferred(hook.post_build_hook, self.player, coords, builddata.block) # Feed automatons. for automaton in self.factory.automatons: if newblock in automaton.blocks: automaton.feed(coords) # Re-send inventory. # XXX this could be optimized if/when inventories track damage. packet = self.inventory.save_to_packet() self.transport.write(packet) # Flush damaged chunks. for chunk in self.chunks.itervalues(): self.factory.flush_chunk(chunk)