Source

mccity / buildings.py

#! /usr/bin/env python

import abc
import random
import itertools

import pymclevel

__all__ = ['Buildings', 'Building', 'Skyscraper']

materials = pymclevel.namedMaterials['Alpha']

class Building(object):
    building_type_registry = []
    @classmethod
    def register_building_type(cls, bt):
        cls.building_type_registry.append(bt)
        return bt
    def __repr__(self):
        return "<Building {0} {1}>".format(self.__class__.__name__,
                                           self.bbox)
    def __init__(self, bbox, griditem, orientation=-1):
        self.bbox = pymclevel.mclevel.BoundingBox(bbox)
        self.griditem = griditem
        if orientation == -1:
            orientation = 1 #random.choice([0, 1, 2, 3])
        self.orientation = orientation
    # Actually lay down the building onto the map
    @abc.abstractmethod
    def make_building(self, level):
        raise NotImplementedError("Not implemented")
    # Sanity checker to determine if given the provided size a class can
    # properly generate buildings on it
    @staticmethod
    def can_use_here(width, height, depth):
        return True
    # Maximum number of times a building type can be used in a map generation
    # cycle
    max_uses = -1

@Building.register_building_type
class Park(Building):
    @staticmethod
    def can_use_here(width, height, depth):
        return 5 < height < 24
    def make_building(self, level):
        self.bbox.miny += 1
        self.bbox.maxy = self.bbox.miny + 1
        level.fillBlocks(self.bbox, materials.Flower)

@Building.register_building_type
class Skyscraper(Building):
    def __init__(self, bbox, griditem, orientation=-1):
        self.stone = random.choice([materials.Cobblestone,
                                    materials.Stone,
                                    materials.Obsidian,
                                    materials.Brick,
                                    materials.Clay,
                                    materials.Sandstone,
                                    materials.Wood,
                                    materials.BirchWood,
                                    materials.Ironwood,
                                    materials.WoodPlanks,
                                    materials.BlackWool,
                                    materials.BlueWool,
                                    materials.BrownWool,
                                    materials.CyanWool,
                                    materials.DarkGreenWool,
                                    materials.GrayWool,
                                    materials.LightBlueWool,
                                    materials.LightGrayWool,
                                    materials.LightGreenWool,
                                    materials.MagentaWool,
                                    materials.OrangeWool,
                                    materials.PinkWool,
                                    materials.PurpleWool,
                                    materials.RedWool,
                                    materials.WhiteWool,
                                    materials.YellowWool])
        self.flooring = random.choice([materials.Stone,
                                       materials.WoodPlanks])
        super(Skyscraper, self).__init__(bbox, griditem, orientation)
    @staticmethod
    def can_use_here(width, height, depth):
        return height > 5
    def make_building(self, level):
        Glass = materials.Glass
        Air = materials.Air
        Lighting = materials.Stone
        Torch = materials.Torch
        Stairs = (materials.StoneStairs
                        if self.flooring is materials.Stone
                        else materials.WoodenStairs)
        Door = materials.WoodenDoor
        Iron = materials.BlockofIron
        bbox = pymclevel.mclevel.BoundingBox(self.bbox)
        level.fillBlocks(bbox, Air)
        bbox.miny += 1 # Meet sidewalk level
        bbox.maxy -= (bbox.maxy - bbox.miny) % 4
        centerx = bbox.minx + ((bbox.maxx - bbox.minx) / 2)
        centerz = bbox.minz + ((bbox.maxz - bbox.minz) / 2)
        stories = (bbox.maxy - bbox.miny) / 4
        glasswall = pymclevel.mclevel.BoundingBox(bbox)
        glasswall.miny += 1
        northwall = pymclevel.mclevel.BoundingBox(glasswall)
        northwall.maxx = northwall.minx + 1
        southwall = pymclevel.mclevel.BoundingBox(glasswall)
        southwall.minx = southwall.maxx - 1
        eastwall = pymclevel.mclevel.BoundingBox(glasswall)
        eastwall.maxz = eastwall.minz + 1
        westwall = pymclevel.mclevel.BoundingBox(glasswall)
        westwall.minz = westwall.maxz - 1
        # Make glass box
        for glassbbox in (northwall, southwall, eastwall, westwall):
            level.fillBlocks(glassbbox, Glass)
        # Add a floor for each story
        for story in xrange(stories + 1):
            newbbox = pymclevel.mclevel.BoundingBox(bbox)
            newbbox.miny = bbox.miny + (4 * story)
            newbbox.maxy = newbbox.miny + 1
            level.fillBlocks(newbbox, self.stone)
            floorbox = pymclevel.mclevel.BoundingBox(newbbox)
            floorbox.minx += 1
            floorbox.maxx -= 1
            floorbox.minz += 1
            floorbox.maxz -= 1
            supportbox = pymclevel.mclevel.BoundingBox(floorbox)
            supportbox.miny -= 1
            level.fillBlocks(supportbox, Iron)
            supportbox.minx += 1
            supportbox.minz += 1
            supportbox.maxx -= 1
            supportbox.maxz -= 1
            level.fillBlocks(supportbox, Air)
            level.fillBlocks(floorbox, self.flooring)
            # Add lights to floor below, then put in stairs
            if story > 0:
                # Fill with lights
                lightbbox = pymclevel.mclevel.BoundingBox(floorbox)
                lightbbox.minz += 1
                lightbbox.maxz -= 1
                lightbbox.minx += 1
                lightbbox.maxx -= 1
                lightbbox.miny -= 1
                lightbbox.maxy = lightbbox.miny + 1
                yoffset = lightbbox.miny
                for xoffset in xrange(lightbbox.minx + 2, lightbbox.maxx - 2, 3):
                    for zoffset in xrange(lightbbox.minz + 2, lightbbox.maxz - 2, 3):
                        level.setBlockAt(xoffset, yoffset, zoffset, Lighting.ID)
                        level.setBlockLightAt(xoffset, yoffset, zoffset, 15)
                        level.setBlockAt(xoffset, yoffset, zoffset - 1, Torch.ID)
                        level.setBlockDataAt(xoffset, yoffset, zoffset - 1, 4)
                        level.setBlockAt(xoffset, yoffset, zoffset + 1, Torch.ID)
                        level.setBlockDataAt(xoffset, yoffset, zoffset + 1, 3)
                        level.setBlockAt(xoffset - 1, yoffset, zoffset, Torch.ID)
                        level.setBlockDataAt(xoffset - 1, yoffset, zoffset, 2)
                        level.setBlockAt(xoffset + 1, yoffset, zoffset, Torch.ID)
                        level.setBlockDataAt(xoffset + 1, yoffset, zoffset, 1)

                # Add upward stair
                offseta, offsetb = [0, 0, 0, 0], range(-2, 2)

                if self.orientation in (0, 2):
                    offsetb = reversed(offsetb)
                if self.orientation in (2, 3):
                    xo, zo = offseta, offsetb
                else:
                    zo, xo = offseta, offsetb

                for yo, (xoffset, zoffset) in enumerate(zip(xo, zo)):
                    for eyo in xrange(yo):
                        level.setBlockAt(centerx + xoffset,
                                         yoffset + 1 - eyo,
                                         centerz + zoffset,
                                         Air.ID)
                    level.setBlockAt(centerx + xoffset,
                                     yoffset - (yo - 1),
                                     centerz + zoffset,
                                     Stairs.ID)
                    level.setBlockDataAt(centerx + xoffset,
                                         yoffset - (yo - 1),
                                         centerz + zoffset,
                                         self.orientation)
                # Clear block right in front, add girders
                if xoffset < 0:
                    xoffset -= 1
                elif xoffset:
                    xoffset += 1
                if zoffset < 0:
                    zoffset -= 1
                elif zoffset:
                    zoffset += 1
                for eyo in range(3):
                    level.setBlockAt(centerx + xoffset,
                                     yoffset + 1 - eyo,
                                     centerz + zoffset,
                                     Air.ID)
                    for xpos, zpos in itertools.product((lightbbox.minx,
                                                         lightbbox.maxx),
                                                        (lightbbox.minz,
                                                         lightbbox.maxz)):
                        level.setBlockAt(xpos,
                                         yoffset + 1 - eyo,
                                         zpos,
                                         Iron.ID)

        northwall.minz = northwall.maxz - 1
        southwall.maxz = southwall.minz + 1
        eastwall.maxx = eastwall.minx + 1
        westwall.minx = westwall.maxx - 1
        for edge in (northwall, southwall, eastwall, westwall):
            level.fillBlocks(edge, self.stone)
        # Add a rooftop guard block
        newbbox.maxy += 1
        newbbox.miny += 1
        floorbox.miny += 1
        floorbox.maxy += 1
        level.fillBlocks(newbbox, self.stone)
        level.fillBlocks(floorbox, Air)
        # And an opening for the door
        if self.orientation in (2, 3):
            px, pz = centerx, bbox.minz if self.orientation == 2 else bbox.maxz - 1
        else:
            px, pz = bbox.minx if self.orientation == 0 else bbox.maxx - 1, centerz
        doordata = [1, 1, 1, 1]
        for yoffset in (1, 2):
            #print (px, bbox.miny + yoffset, pz, Lighting.ID)
            level.setBlockAt(px, bbox.miny + yoffset, pz, Door.ID)
            level.setBlockDataAt(px,
                                 bbox.miny + yoffset,
                                 pz,
                                 (8 if yoffset == 1 else 0) &
                                 doordata[self.orientation])

class Buildings(object):
    def __init__(self, grid, origin, node_length, level):
        self.grid = grid
        self.origin = origin
        self.node_length = node_length
        self.level = level
    def _bbox_for_building(self, griditem):
        origin_x, origin_z = self.origin
        x, z = griditem.position
        origin_x += 7
        origin_z += 7
        origin_x += (7 + self.node_length) * x
        origin_z += (7 + self.node_length) * z
        width, depth = griditem.dimensions
        widthadd = (width - 1) * 7
        depthadd = (depth - 1) * 7
        width *= self.node_length
        depth *= self.node_length
        width += widthadd
        depth += depthadd
        height = griditem.height
        return pymclevel.mclevel.BoundingBox(
                (origin_x, 63, origin_z),
                (width, height, depth))
    def put_buildings(self, noisy = False):
        for item in self.grid:
            bbox = self._bbox_for_building(item)
            building_types = [b for b in Building.building_type_registry
                              if b.can_use_here(*bbox.size)]
            if building_types:
                bt = random.choice(building_types)
                building = bt(bbox, item)
                if noisy:
                    print "    Building", building
                building.make_building(self.level)