diff --git a/README.md b/README.md index 66cf0fea..6b1bd170 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,19 @@ For git: For optimal performance, you can compile the code to C using Cython. -To do so, you have to install Cython and Python dev files -(`sudo apt-get install cython python-dev` under Ubuntu), then run -`python setup.py build_ext --inplace`. You will have -to run this last command each time you update the game. +``` +pip install cython +python setup.py build_ext --inplace +``` +This will generate `.pyd` files, which Python will prefer to load instead of your `.py` files, +so you will need to rebuild or delete the `.pyd` each time you make changes. setup.py will also compile Pyglet using Cython, if you download the pyglet source code and put the *pyglet* folder inside the game repository. + +## Building Windows Executables +``` +pip install cython cx_Freeze +python setup.py build +``` +This builds Cython-optimized `.pyd`'s, and bundles together all libraries including Python itself, into `build/exe.win-amd64-3.7/` diff --git a/biome.pxd b/biome.pxd index a65f5f72..def0da12 100644 --- a/biome.pxd +++ b/biome.pxd @@ -5,7 +5,7 @@ cimport perlin #cython: wraparound=False #cython: cdivision=True -cdef class BiomeGenerator(object): +cdef class BiomeGenerator: cdef public: object temperature_gen, humidity_gen diff --git a/biome.py b/biome.py index 78c4cfba..87d99d50 100644 --- a/biome.py +++ b/biome.py @@ -8,7 +8,7 @@ __all__ = ("BiomeGenerator") -class BiomeGenerator(object): +class BiomeGenerator: def __init__(self, seed): self.temperature_gen = SimplexNoiseGen(seed + "97", zoom_level=0.01) self.humidity_gen = SimplexNoiseGen(seed + "147", zoom_level=0.01) diff --git a/biome_explorer.py b/biome_explorer.py index 87a626b7..382cebe6 100644 --- a/biome_explorer.py +++ b/biome_explorer.py @@ -3,7 +3,7 @@ from terrain import BiomeGenerator import globals as G -with open(os.path.join(G.game_dir, "world", "seed"), "rb") as f: +with open(os.path.join(G.game_dir, "world", "seed"), "r") as f: SEED = f.read() b = BiomeGenerator(SEED) @@ -13,10 +13,10 @@ xsize = 79 ysize = 28 -DESERT, PLAINS, MOUNTAINS, SNOW, FOREST = range(5) +DESERT, PLAINS, MOUNTAINS, SNOW, FOREST = list(range(5)) letters = ["D","P","M","S","F"] -print "Okay, click on the console window again, then use the arrow keys." +print("Okay, click on the console window again, then use the arrow keys.") while True: base = getch() @@ -38,4 +38,4 @@ for x in range(curx,curx+xsize): string += letters[b.get_biome_type(x,y)] string += "\n" - print string + "Current position: (%s-%s %s-%s)" % (curx*8, (curx+xsize)*8, cury*8, (cury+ysize)*8) + print(string + "Current position: (%s-%s %s-%s)" % (curx*8, (curx+xsize)*8, cury*8, (cury+ysize)*8)) diff --git a/blocks.py b/blocks.py index 46addd7c..e11cf548 100644 --- a/blocks.py +++ b/blocks.py @@ -3,7 +3,7 @@ # Imports, sorted alphabetically. # Future imports -from __future__ import unicode_literals + from math import floor # Python packages @@ -103,7 +103,7 @@ def unset_state(self): glDisable(self.texture.target) -class BlockID(object): +class BlockID: """ Datatype for Block and Item IDs @@ -121,7 +121,7 @@ class BlockID(object): def __init__(self, main, sub=0, icon_name=None): if isinstance(main, tuple): self.main, self.sub = main - elif isinstance(main, basestring): + elif isinstance(main, str): # Allow "35", "35.0", or "35,0" spl = main.split(".") if "." in main else main.split(",") if "," in main else (main, 0) if len(spl) == 2: @@ -148,30 +148,33 @@ def __repr__(self): def __hash__(self): return hash((self.main, self.sub)) - def __cmp__(self, other): + def __eq__(self, other): if isinstance(other, tuple): - return cmp((self.main, self.sub), other) + return (self.main, self.sub) == other if isinstance(other, float): - return cmp(float(repr(self.main)), other) + return float(repr(self.main)) == other if isinstance(other, int): - return cmp(self.main, other) + return self.main == other if isinstance(other, BlockID): - return cmp(self.main, other.main) or cmp(self.sub, other.sub) + return self.main == other.main and self.sub == other.sub + + def __ne__(self, other): + return not (self == other) - def __nonzero__(self): + def __bool__(self): """Checks whether it is an AirBlock.""" return self.main != 0 def is_item(self): - return self.main > 255 + return self.main >= G.ITEM_ID_MIN def filename(self): - if self.icon_name != None: return ["textures", "blocks" if self.main < G.ITEM_ID_MIN else "items", str(self.icon_name) + ".png"] + if self.icon_name != None: return ["textures", "items" if self.is_item() else "blocks", str(self.icon_name) + ".png"] if self.sub == 0: return ["textures", "icons", str(self.main) + ".png"] return ["textures", "icons", '%d.%d.png' % (self.main, self.sub)] -class Block(object): +class Block: id = None # Original minecraft id (also called data value). # Verify on http://www.minecraftwiki.net/wiki/Data_values # when creating a new "official" block. @@ -361,7 +364,7 @@ def on_neighbor_change(self, world, neighbor_pos, self_pos): def can_place_on(self, block_id): return False -class BlockColorizer(object): +class BlockColorizer: def __init__(self, filename): self.color_data = G.texture_pack_list.selected_texture_pack.load_texture(['misc', filename]) # if the texture is not available, don't colorize it @@ -378,7 +381,7 @@ def get_color(self, temperature, humidity): if self.color_data is None: return 1, 1, 1 pos = int(floor(humidity * 255) * BYTE_PER_LINE + 3 * floor((temperature) * 255)) - return float(ord(self.color_data[pos])) / 255, float(ord(self.color_data[pos + 1])) / 255, float(ord(self.color_data[pos + 2])) / 255 + return float(self.color_data[pos]) / 255, float(self.color_data[pos + 1]) / 255, float(self.color_data[pos + 2]) / 255 class AirBlock(Block): max_stack_size = 0 @@ -1393,7 +1396,7 @@ def growth_stage(self, value): def fertilize(self): if self.growth_stage == self.max_growth_stage: return False - G.CLIENT.update_tile_entity(self.entity.position, make_nbt_from_dict({'action'.encode(): 'fertilize'.encode()})) + G.CLIENT.update_tile_entity(self.entity.position, make_nbt_from_dict({'action': 'fertilize'})) return True def update_tile_entity(self, value): @@ -1717,7 +1720,7 @@ def can_place_on(self, block_id): return (block_id != 0) def set_metadata(self, metadata): - print 'metadata: ', metadata + print('metadata: ', metadata) if self.sub_id_as_metadata: self.id.sub = metadata @@ -1725,7 +1728,7 @@ def set_metadata(self, metadata): # not a real block, used to store crack texture data -class CrackTextureBlock(object): +class CrackTextureBlock: def __init__(self): self.crack_level = CRACK_LEVELS self.texture_data = [] diff --git a/cameras.pxd b/cameras.pxd index 7f73fb83..22c431b2 100644 --- a/cameras.pxd +++ b/cameras.pxd @@ -4,7 +4,7 @@ cdef extern from "math.h": float cosf(float theta) float sinf(float theta) -cdef class Camera3D(object): +cdef class Camera3D: cdef public: object target double x diff --git a/cameras.py b/cameras.py index 2a36bacb..043e6ae5 100644 --- a/cameras.py +++ b/cameras.py @@ -15,7 +15,7 @@ ) -class Camera3D(object): +class Camera3D: def __init__(self, target=None): self.target = target self.x = 0.0 diff --git a/client.pxd b/client.pxd deleted file mode 100644 index e69de29b..00000000 diff --git a/client.py b/client.py index c33dcf7b..ec52cf01 100644 --- a/client.py +++ b/client.py @@ -32,40 +32,40 @@ def run(self): except socket.error as e: if e[0] in (10053, 10054): #TODO: GUI tell the client they were disconnected - print "Disconnected from server." + print("Disconnected from server.") else: raise e self.controller.back_to_main_menu.set() # loop() is run once in its own thread def loop(self): - packetcache, packetsize = "", 0 + packetcache, packetsize = b"", 0 append_to_sector_packets = self.world.sector_packets.append while 1: resp = self.sock.recv(16384) if self._stop.isSet() or not resp: - print "Client PacketReceiver:",self._stop.isSet() and "Shutting down" or "We've been disconnected by the server" + print("Client PacketReceiver:",self._stop.isSet() and "Shutting down" or "We've been disconnected by the server") self.sock.shutdown(SHUT_RDWR) return # Sometimes data is sent in multiple pieces, just because sock.recv returned data doesn't mean its complete # So we write the datalength in the beginning of all messages, and don't look at the packet until we have enough data packetcache += resp - if not packetsize: + if not packetsize and len(packetcache) >= 4: packetsize = struct.unpack("i", packetcache[:4])[0] # Sometimes we get multiple packets in a single burst, so loop through packetcache until we lack the data while packetsize and len(packetcache) >= packetsize: #Once we've obtained the whole packet - packetid = struct.unpack("B",packetcache[4])[0] # Server Packet Type + packetid = struct.unpack_from("B",packetcache, 4)[0] # Server Packet Type packet = packetcache[5:packetsize] with self.lock: append_to_sector_packets((packetid, packet)) packetcache = packetcache[packetsize:] # Cut off the part we just read - packetsize = struct.unpack("i", packetcache[:4])[0] if packetcache else 0 # Get the next packet's size + packetsize = struct.unpack("i", packetcache[:4])[0] if len(packetcache) >= 4 else 0 # Get the next packet's size #=== The following functions are run by the Main Thread ===# @@ -81,9 +81,9 @@ def dequeue_packet(self): cx, cy, cz = sector_to_blockpos(secpos) fpos = 12 exposed_pos = fpos + 1024 - for x in xrange(cx, cx+8): - for y in xrange(cy, cy+8): - for z in xrange(cz, cz+8): + for x in range(cx, cx+8): + for y in range(cy, cy+8): + for z in range(cz, cz+8): read = packet[fpos:fpos+2] fpos += 2 unpacked = structuchar2.unpack(read) @@ -100,7 +100,7 @@ def dequeue_packet(self): blocks[position] = type(main_blk)() blocks[position].set_metadata(unpacked[-1]) sector.append(position) - if packet[exposed_pos] is "1": + if packet[exposed_pos:exposed_pos+1] == b"1": blocks.show_block(position) exposed_pos += 1 if secpos in self.world.sector_queue: @@ -122,14 +122,14 @@ def dequeue_packet(self): player = self.controller.player caret = 0 for inventory in (player.quick_slots.slots, player.inventory.slots, player.armor.slots): - for i in xrange(len(inventory)): + for i in range(len(inventory)): id_main, id_sub, amount = struct.unpack("HBB", packet[caret:caret+4]) caret += 4 if id_main == 0: continue durability = -1 if id_main >= G.ITEM_ID_MIN and (id_main, id_sub) not in G.ITEMS_DIR: #The subid must be durability - durability = id_sub * G.ITEMS_DIR[(id_main, 0)].durability / 255 + durability = id_sub * G.ITEMS_DIR[(id_main, 0)].durability // 255 id_sub = 0 inventory[i] = ItemStack(type=BlockID(id_main, id_sub), amount=amount, durability=durability) self.controller.item_list.update_items() @@ -161,31 +161,31 @@ def dequeue_packet(self): # Helper Functions for sending data to the Server def request_sector(self, sector): - self.sock.sendall("\1"+struct.pack("iii", *sector)) + self.sock.sendall(b"\1"+struct.pack("iii", *sector)) def add_block(self, position, block): - self.sock.sendall("\3"+struct.pack("iiiBB", *(position+(block.id.main, block.id.sub)))) + self.sock.sendall(b"\3"+struct.pack("iiiBB", *(position+(block.id.main, block.id.sub)))) def remove_block(self, position): - self.sock.sendall("\4"+struct.pack("iii", *position)) + self.sock.sendall(b"\4"+struct.pack("iii", *position)) def send_chat(self, msg): msg = msg.encode('utf-8') - self.sock.sendall("\5"+struct.pack("i", len(msg))+msg) + self.sock.sendall(b"\5"+struct.pack("i", len(msg))+msg) def request_spawnpos(self): name = G.USERNAME.encode('utf-8') self.sock.sendall(struct.pack("B", 255)+struct.pack("i",len(name)) + name) def send_player_inventory(self): - packet = "" + packet = b"" for item in (self.controller.player.quick_slots.slots + self.controller.player.inventory.slots + self.controller.player.armor.slots): if item: - packet += struct.pack("HBB", item.type.main, item.type.sub if item.max_durability == -1 else item.durability * 255 / item.max_durability, item.amount) + packet += struct.pack("HBB", item.type.main, item.type.sub if item.max_durability == -1 else item.durability * 255 // item.max_durability, item.amount) else: - packet += "\0\0\0\0" - self.sock.sendall("\6"+packet) + packet += b"\0\0\0\0" + self.sock.sendall(b"\6"+packet) def send_movement(self, momentum, position): - self.sock.sendall("\x08"+struct.pack("fff", *momentum) + struct.pack("ddd", *position)) + self.sock.sendall(b"\x08"+struct.pack("fff", *momentum) + struct.pack("ddd", *position)) def send_jump(self): - self.sock.sendall("\x09") + self.sock.sendall(b"\x09") def update_tile_entity(self, position, value): - self.sock.sendall("\x0A" + struct.pack("iii", *position) + struct.pack("i", len(value)) + value) + self.sock.sendall(b"\x0A" + struct.pack("iii", *position) + struct.pack("i", len(value)) + value) def stop(self): diff --git a/controllers.py b/controllers.py index 948bcd4d..17fc27f7 100644 --- a/controllers.py +++ b/controllers.py @@ -5,7 +5,7 @@ import time import datetime from functools import partial -from itertools import imap + from math import cos, sin, pi, fmod import operator import os @@ -39,7 +39,7 @@ ) -class Controller(object): +class Controller: def __init__(self, window): self.window = window self.current_view = None @@ -148,10 +148,10 @@ def update_sector(self, dt): def update_player(self, dt): m = 8 df = min(dt, 0.2) - for _ in xrange(m): + for _ in range(m): self.player.update(df / m, self) - for ply in self.player_ids.itervalues(): - for _ in xrange(m): + for ply in self.player_ids.values(): + for _ in range(m): ply.update(df / m, self) momentum = self.player.get_motion_vector(15 if self.player.flying else 5*self.player.current_density) if momentum != self.player.momentum_previous: @@ -224,18 +224,18 @@ def init_gl(self): def setup(self): if G.SINGLEPLAYER: try: - print 'Starting internal server...' + print('Starting internal server...') # TODO: create world menu G.SAVE_FILENAME = "world" start_server(internal=True) sock = socket.socket() sock.connect(("localhost", 1486)) except socket.error as e: - print "Socket Error:", e + print("Socket Error:", e) #Otherwise back to the main menu we go return False except Exception as e: - print 'Unable to start internal server' + print('Unable to start internal server') import traceback traceback.print_exc() return False @@ -247,7 +247,7 @@ def setup(self): sock = socket.socket() sock.connect((tuple(ipport))) except socket.error as e: - print "Socket Error:", e + print("Socket Error:", e) #Otherwise back to the main menu we go return False @@ -262,7 +262,7 @@ def setup(self): #else: # default_skybox = 'skybox.jpg' - print 'loading ' + default_skybox + print('loading ' + default_skybox) self.skydome = Skydome( 'resources/' + default_skybox, @@ -298,7 +298,7 @@ def setup(self): #We'll re-enable it when the server tells us where we should be self.player = Player(game_mode=G.GAME_MODE) - print('Game mode: ' + self.player.game_mode) + print(('Game mode: ' + self.player.game_mode)) self.item_list = ItemSelector(self, self.player, self.world) self.inventory_list = InventorySelector(self, self.player, self.world) self.item_list.on_resize(self.window.width, self.window.height) @@ -311,7 +311,7 @@ def setup(self): self.text_input.push_handlers(on_toggled=self.on_text_input_toggled, key_released=self.text_input_callback) self.chat_box = TextWidget(self.window, '', 0, self.text_input.y + self.text_input.height + 50, - self.window.width / 2, height=min(300, self.window.height / 3), + self.window.width // 2, height=min(300, self.window.height // 3), visible=False, multi_line=True, readonly=True, font_name=G.CHAT_FONT, text_color=(255, 255, 255, 255), @@ -432,15 +432,13 @@ def on_mouse_press_right(self, block, previous, x, y, button, modifiers): def put_block(self, previous): # FIXME - Better name... current_block = self.item_list.get_current_block() if current_block is not None: - # if current block is an item, - # call its on_right_click() method to handle this event - if current_block.id >= G.ITEM_ID_MIN: + if current_block.id.is_item(): if current_block.on_right_click(self.world, self.player): self.item_list.get_current_block_item().change_amount(-1) self.item_list.update_health() self.item_list.update_items() else: - localx, localy, localz = imap(operator.sub,previous,normalize(self.player.position)) + localx, localy, localz = map(operator.sub,previous,normalize(self.player.position)) if localx != 0 or localz != 0 or (localy != 0 and localy != -1): self.world.add_block(previous, current_block) self.item_list.remove_current_block() @@ -516,7 +514,7 @@ def on_resize(self, width, height): self.label.y = height - 10 self.text_input.resize(x=0, y=0, width=self.window.width) self.chat_box.resize(x=0, y=self.text_input.y + self.text_input.height + 50, - width=self.window.width / 2, height=min(300, self.window.height/3)) + width=self.window.width // 2, height=min(300, self.window.height // 3)) #self.debug_text.resize(0, self.window.height - 300, # 500, 300) @@ -553,7 +551,7 @@ def on_draw(self): self.crack_batch.draw() glDisable(GL_BLEND) self.draw_focused_block() - for ply in self.player_ids.itervalues(): + for ply in self.player_ids.values(): ply.model.draw() self.set_2d() if G.HUD_ENABLED: @@ -572,11 +570,11 @@ def on_draw(self): def show_cracks(self, hit_block, vertex_data): if self.block_damage: # also show the cracks crack_level = int(CRACK_LEVELS * self.block_damage - / hit_block.hardness) # range: [0, CRACK_LEVELS[ + // hit_block.hardness) # range: [0, CRACK_LEVELS[ if crack_level >= CRACK_LEVELS: return texture_data = crack_textures.texture_data[crack_level] - count = len(texture_data) / 2 + count = len(texture_data) // 2 if self.crack: self.crack.delete() self.crack = self.crack_batch.add(count, GL_QUADS, crack_textures.group, @@ -693,7 +691,7 @@ def on_close(self): def show_map(self): print("map called...") # taken from Nebual's biome_explorer, this is ment to be a full screen map that uses mini tiles to make a full 2d map. - with open(os.path.join(G.game_dir, "world", "seed"), "rb") as f: + with open(os.path.join(G.game_dir, "world", "seed"), "r") as f: SEED = f.read() b = BiomeGenerator(SEED) x, y, z = self.player.position @@ -703,7 +701,7 @@ def show_map(self): ysize = 28 pbatch = pyglet.graphics.Batch() pgroup = pyglet.graphics.OrderedGroup(1) - DESERT, PLAINS, MOUNTAINS, SNOW, FOREST = range(5) + DESERT, PLAINS, MOUNTAINS, SNOW, FOREST = list(range(5)) letters = ["D","P","M","S","F"] # temp background pic... @@ -721,7 +719,7 @@ def show_map(self): tmap = letters[b.get_biome_type(x,y)] tile_map = load_image('resources', 'textures', tmap +'.png') tile_map.anchor_x = x * 8 - tile_map.anchor_Y = y * 8 + tile_map.anchor_y = y * 8 sprite = pyglet.sprite.Sprite(tile_map, x=x * 8, y=y * 8, batch=pbatch) game_map = image_sprite(tile_map, pbatch, pgroup, x * 8, y * 8, 8, 8) game_map = pyglet.sprite.Sprite(image,x=G.WINDOW_WIDTH, y=G.WINDOW_HEIGHT,batch=pbatch, group=pgroup) diff --git a/crafting.py b/crafting.py index 6bbf4cb9..4037aff0 100644 --- a/crafting.py +++ b/crafting.py @@ -17,7 +17,7 @@ ) -class Recipe(object): +class Recipe: # ingre is a list that contains the ids of the ingredients # e.g. [[2, 2], [1, 1]] def __init__(self, ingre, output): @@ -29,7 +29,7 @@ def __init__(self, ingre, output): def __repr__(self): return 'Ingredients: ' + str(self.ingre) + '; Output: ' + str(self.output) -class Recipes(object): +class Recipes: def __init__(self): self.nr_recipes = 0 self.recipes = [] @@ -118,16 +118,16 @@ def craft(self, input_blocks): def dump(self): for recipe in self.recipes: - print recipe + print(recipe) -class SmeltingRecipe(object): +class SmeltingRecipe: def __init__(self, ingre, output): # what blocks are needed to craft this block/item self.ingre = ingre self.output = output -class SmeltingRecipes(object): +class SmeltingRecipes: def __init__(self): self.nr_recipes = 0 self.recipes = [] diff --git a/custom_types.py b/custom_types.py new file mode 100644 index 00000000..5eef37ca --- /dev/null +++ b/custom_types.py @@ -0,0 +1,4 @@ +from typing import Tuple + +iVector = Tuple[int, int, int] +fVector = Tuple[float, float, float] diff --git a/debug.py b/debug.py index 31437773..849ff7e5 100644 --- a/debug.py +++ b/debug.py @@ -21,7 +21,7 @@ def inner(*args, **kwargs): return func(*args, **kwargs) start = time.time() out = func(*args, **kwargs) - print('%s took %f seconds.' % (func.__name__, time.time() - start)) + print(('%s took %f seconds.' % (func.__name__, time.time() - start))) return out return inner @@ -33,7 +33,7 @@ def log(log_level, msg): now = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) thread = '[' + threading.currentThread().getName() + ']' level = '[' + log_levels[log_level] + ']' - print now + ' ' + thread + ' ' + level + ' ' + msg + print(now + ' ' + thread + ' ' + level + ' ' + msg) def log_debug(msg): log(G.LOG_DEBUG, msg) diff --git a/entity.py b/entity.py index abef92bf..6536b1f3 100644 --- a/entity.py +++ b/entity.py @@ -19,7 +19,7 @@ ) -class Entity(object): +class Entity: """ Base class for players, mobs, TNT and so on. """ @@ -51,9 +51,9 @@ def handle_message(self, msg_type, *args, **kwargs): # MSG_PICKUP, \ MSG_REDSTONE_ACTIVATE, MSG_REDSTONE_DEACTIVATE, \ - = range(3) + = list(range(3)) -class EntityManager(object): +class EntityManager: def __init__(self): self.last_id = 0 self.entities = {} diff --git a/globals.py b/globals.py index 6d256328..2f07d71f 100644 --- a/globals.py +++ b/globals.py @@ -9,7 +9,7 @@ # Imports, sorted alphabetically. # Python packages -from ConfigParser import ConfigParser, NoSectionError, NoOptionError +from configparser import ConfigParser, NoSectionError, NoOptionError import argparse import getpass from math import pi @@ -26,7 +26,7 @@ APP_NAME = 'pyCraft' # should I stay or should I go? APP_VERSION = 0.1 DEBUG = False -LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL = range(5) +LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL = list(range(5)) LOG_LEVEL = LOG_INFO IP_ADDRESS = "neb.nebtown.info" # The IP Address to connect to USERNAME = getpass.getuser() # Default to system username @@ -80,7 +80,7 @@ ESCAPE_KEY = 'ESCAPE' KEY_BINDINGS = dict( - (k.lower()[:-4], v) for k, v in locals().items() if k[-4:] == '_KEY' + (k.lower()[:-4], v) for k, v in list(locals().items()) if k[-4:] == '_KEY' ) # Saves @@ -148,10 +148,10 @@ GRASS_CHANCE = 0.05 # Biome -DESERT, PLAINS, MOUNTAINS, SNOW, FOREST, ISLAND, NETHER = range(7) +DESERT, PLAINS, MOUNTAINS, SNOW, FOREST, ISLAND, NETHER = list(range(7)) # Direction -EAST, SOUTH, WEST, NORTH = range(4) +EAST, SOUTH, WEST, NORTH = list(range(4)) # Graphical rendering FULLSCREEN = False @@ -193,9 +193,9 @@ EFFECT_VOLUME = 1 # Tool types -WOODEN_TOOL, STONE_TOOL, IRON_TOOL, DIAMOND_TOOL, GOLDEN_TOOL = range(5) -PICKAXE, AXE, SHOVEL, HOE, SWORD = range(5) -HELMET, CHESTPLATE, LEGGINGS, BOOTS = range(4) +WOODEN_TOOL, STONE_TOOL, IRON_TOOL, DIAMOND_TOOL, GOLDEN_TOOL = list(range(5)) +PICKAXE, AXE, SHOVEL, HOE, SWORD = list(range(5)) +HELMET, CHESTPLATE, LEGGINGS, BOOTS = list(range(4)) # Static aliases DEG_RAD = pi / 180.0 @@ -278,9 +278,9 @@ def get_or_update_config(section, option, default_value, conv=str, choices=()): return user_value def save_config(): - config.set("General","username", USERNAME.encode('utf-8')) + config.set("General","username", USERNAME) config.set("General","ip_address", IP_ADDRESS) - with open(config_file, 'wb') as handle: + with open(config_file, 'w') as handle: config.write(handle) def initialize_config(): @@ -295,7 +295,6 @@ def initialize_config(): general, 'debug', DEBUG, conv=bool) USERNAME = get_or_update_config( general, 'username', USERNAME, conv=str) - USERNAME = USERNAME.decode('utf-8') IP_ADDRESS = get_or_update_config( general, 'ip_address', IP_ADDRESS, conv=str) @@ -339,7 +338,7 @@ def initialize_config(): controls = 'Controls' # Adds missing keys to configuration file and converts to pyglet keys. - for control, default_key_name in KEY_BINDINGS.items(): + for control, default_key_name in list(KEY_BINDINGS.items()): key_name = get_or_update_config(controls, control, default_key_name) try: pyglet_key = get_key(key_name) diff --git a/gui.py b/gui.py index a63255e0..76c22379 100644 --- a/gui.py +++ b/gui.py @@ -31,7 +31,7 @@ ) -class Rectangle(object): +class Rectangle: def __init__(self, x, y, width, height): self.position = x, y self.size = width, height @@ -63,7 +63,7 @@ def size(self, size): @property def center(self): - return self.x + self.width / 2, self.y + self.height / 2 + return self.x + self.width // 2, self.y + self.height // 2 @property def min(self): @@ -220,7 +220,7 @@ def item(self, value): self._item.quickslots_x = self.icon.x self._item.quickslots_y = self.icon.y if self._item.max_durability != -1 and self._item.durability != -1: - self.icon.opacity = min(self._item.max_durability, self._item.durability + 1) * 255 / self._item.max_durability + self.icon.opacity = min(self._item.max_durability, self._item.durability + 1) * 255 // self._item.max_durability if self._item.amount > 1: self.amount_label = pyglet.text.Label( @@ -311,15 +311,14 @@ def __init__(self, parent, player, world, *args, **kwargs): for i in range(1, 10)] image = G.texture_pack_list.selected_texture_pack.load_texture(['gui', 'gui.png']) - image_scale = image.height / 256 + image_scale = image.height // 256 x_size = 182 * image_scale y_size = 22 * image_scale self.frame = image_sprite(image, self.batch, 0, y=image.height - y_size, height=y_size, x=0, width=x_size) self.frame.scale = (1.0 / image_scale) * 2 - self.frame.x = (self.parent.window.width - self.frame.width) / 2 + self.frame.x = (self.parent.window.width - self.frame.width) // 2 heart_image = load_image('resources', 'gui', 'heart.png') - frame_size = image.height / 2 x_size = 24 * image_scale y_size = 22 * image_scale @@ -367,7 +366,7 @@ def update_health(self): hearts_to_show = self.player.health showed_hearts = 0 for i, heart in enumerate(self.hearts): - heart.x = self.frame.x + i * (20 + 2) + (self.frame.width - hearts_to_show * (20 + 2)) / 2 + heart.x = self.frame.x + i * (20 + 2) + (self.frame.width - hearts_to_show * (20 + 2)) // 2 heart.y = self.icon_size * 1.0 + 12 heart.visible = True if showed_hearts >= hearts_to_show: @@ -427,7 +426,7 @@ def on_key_press(self, symbol, modifiers): return pyglet.event.EVENT_HANDLED def on_resize(self, width, height): - self.frame.x = (width - self.frame.width) / 2 + self.frame.x = (width - self.frame.width) // 2 self.frame.y = 0 self.active.y = self.frame.y + 2 slot_x = self.frame.x + 8 @@ -477,7 +476,7 @@ def __init__(self, parent, player, world, *args, **kwargs): # inventory slots - common # 9 items per row - rows = floor(self.max_items / 9) + rows = floor(self.max_items // 9) inventory_y = 0 inventory_height = (rows * (self.icon_size + 8)) + ((rows+1) * 3) slot_x = self.frame.x + 16 @@ -560,12 +559,12 @@ def switch_mode(self, mode=0, reset_mode=True): def change_image(self): image = G.texture_pack_list.selected_texture_pack.load_texture(['gui', 'inventory.png' if self.mode == 0 else 'crafting.png' if self.mode == 1 else 'furnace.png']) - image_scale = image.height / 256 + image_scale = image.height // 256 x_size = 176 * image_scale y_size = 166 * image_scale self.frame = image_sprite(image, self.batch[self.mode], 0, y=image.height - y_size, height=y_size, x=0, width=x_size) self.frame.scale = (1.0 / image_scale) * 2 - self.frame.x = (self.parent.window.width - self.frame.width) / 2 + self.frame.x = (self.parent.window.width - self.frame.width) // 2 self.frame.y = 74 def update_items(self): @@ -583,7 +582,7 @@ def update_items(self): for j, item in enumerate(items): self.slots[i].item = item if not item or item.get_object().id > 0: - crafting_ingredients[int(floor(j / (2 if self.mode == 0 else 3 if self.mode == 1 else 1)))].append(air_block if not item else item.get_object()) + crafting_ingredients[int(floor(j // (2 if self.mode == 0 else 3 if self.mode == 1 else 1)))].append(air_block if not item else item.get_object()) i += 1 if len(crafting_ingredients) > 0: @@ -617,7 +616,7 @@ def toggle(self, reset_mode=True): def set_furnace(self, furnace): self.furnace_panel = furnace crafting_rows = 2 - rows = floor(self.max_items / 9) + rows = floor(self.max_items // 9) inventory_y = 0 inventory_height = (rows * (self.icon_size + 8)) + ((rows+1) * 3) slot_x = self.frame.x + 63 @@ -657,8 +656,8 @@ def set_selected_item(self, item, x=0, y=0): self.selected_item_icon = image_sprite(img, self.batch[self.mode], self.group[self.mode]) image_scale = 0.8 / (img.width / self.icon_size) self.selected_item_icon.scale = image_scale - self.selected_item_icon.x = x - (self.selected_item_icon.width / 2) - self.selected_item_icon.y = y - (self.selected_item_icon.height / 2) + self.selected_item_icon.x = x - (self.selected_item_icon.width // 2) + self.selected_item_icon.y = y - (self.selected_item_icon.height // 2) def remove_selected_item(self): self.selected_item = None @@ -763,8 +762,8 @@ def on_mouse_motion(self, x, y, dx, dy): slot.highlighted = slot.hit_test(x, y) if self.selected_item_icon: - self.selected_item_icon.x = x - (self.selected_item_icon.width / 2) - self.selected_item_icon.y = y - (self.selected_item_icon.height / 2) + self.selected_item_icon.x = x - (self.selected_item_icon.width // 2) + self.selected_item_icon.y = y - (self.selected_item_icon.height // 2) return pyglet.event.EVENT_HANDLED def on_mouse_drag(self, x, y, dx, dy, button, modifiers): @@ -782,7 +781,7 @@ def on_key_press(self, symbol, modifiers): return pyglet.event.EVENT_HANDLED def on_resize(self, width, height): - self.frame.x = (width - self.frame.width) / 2 + self.frame.x = (width - self.frame.width) // 2 self.frame.y = 74 if self.visible: self.update_items() @@ -805,7 +804,7 @@ def _on_draw(self): def __run_iterator_fix(self, index): while index >= self.end and index > self.start: # condition has special case for 0-length run (fixes issue 471) - self.start, self.end, self.value = self.next() + self.start, self.end, self.value = next(self) return self.value from pyglet.text.runlist import RunIterator RunIterator.__getitem__ = __run_iterator_fix @@ -815,7 +814,13 @@ class TextWidget(Control): """ Variation of this example: http://www.pyglet.org/doc/programming_guide/text_input.py """ - def __init__(self, parent, text, x, y, width, height=None, multi_line=False, + batch: pyglet.graphics.Batch + x: int + y: int + width: int + height: int + + def __init__(self, parent, text, x: int, y: int, width: int, height: int = None, multi_line=False, font_size=12, font_name=G.DEFAULT_FONT, text_color=(0, 0, 0, 255), @@ -840,8 +845,10 @@ def __init__(self, parent, text, x, y, width, height=None, multi_line=False, if blank_text: self.clear() self.padding = 10 + self.x = x + self.y = y + self.width = width self.height = height or (font.ascent - font.descent) + self.padding - self.x, self.y, self.width = x, y, width self.multi_line = multi_line self.background_color = background_color @@ -958,7 +965,7 @@ def resize(self, x=None, y=None, width=None, height=None): self.width + self.padding, self.height + self.padding) # And reposition the text layout self.layout.x = self.x + self.padding - self.layout.y = (self.rectangle.y + (self.rectangle.height/2) - (self.height/2)) + self.layout.y = (self.rectangle.y + (self.rectangle.height//2) - (self.height//2)) self.layout.width = self.rectangle.width - self.padding self.layout.height = self.rectangle.height - self.padding if self.vertex_list: @@ -1121,7 +1128,7 @@ def move_to(self, x, y): @property def center(self): - return self.x + self.width / 2, self.y + self.height / 2 + return self.x + self.width // 2, self.y + self.height // 2 def _on_draw(self): self.background.draw() @@ -1131,7 +1138,7 @@ def _on_draw(self): def update_pos(self, new_pos): sb_space = self.width - self.sb_width if self.style == 1 else self.height - self.sb_height - offset = sb_space * new_pos / 100 + offset = sb_space * new_pos // 100 # vertical if self.style == 0: @@ -1150,7 +1157,7 @@ def update_pos(self, new_pos): def coord_to_pos(self, x, y): offset = (self.y + self.height - y) if self.style == 0 else (x - self.x) - return int(offset * 100 / (self.height if self.style == 0 else self.width)) + return int(offset * 100 // (self.height if self.style == 0 else self.width)) def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): if self.visible: @@ -1171,7 +1178,7 @@ def on_mouse_release(self, x, y, button, modifiers): def init_button_image(): gui_image = G.texture_pack_list.selected_texture_pack.load_texture(['gui', 'gui.png']) - image_scale = gui_image.height / 256 + image_scale = gui_image.height // 256 x_size = 200 * image_scale y_offset = 66 * image_scale y_size = 20 * image_scale @@ -1198,7 +1205,7 @@ def init_button_image(): def resize_button_image(image, old_width, new_width): if new_width == old_width: return image - new_width = int(image.width * new_width / old_width) + new_width = int(image.width * new_width // old_width) atlas = TextureAtlas(new_width, image.height) atlas.add(image.get_region(0, 0, new_width // 2, image.height).image_data) atlas.add(image.get_region(image.width - new_width // 2, 0, new_width // 2, image.height).image_data) diff --git a/inventory.py b/inventory.py index 5ebce9ba..79e2092c 100644 --- a/inventory.py +++ b/inventory.py @@ -15,7 +15,7 @@ ) -class Inventory(object): +class Inventory: sort_mode = 0 def __init__(self, slot_count = 27): @@ -93,11 +93,11 @@ def remove_unnecessary_stacks(self): def sort(self, reverse=True): if self.sort_mode == 0: - self.sort_with_key(key=lambda x: x.id if x != None else -sys.maxint - 1, reverse=True) + self.sort_with_key(key=lambda x: x.id if x != None else -sys.maxsize - 1, reverse=True) if self.sort_mode == 1: - self.sort_with_key(key=lambda x: x.amount if x != None else -sys.maxint - 1, reverse=True) + self.sort_with_key(key=lambda x: x.amount if x != None else -sys.maxsize - 1, reverse=True) elif self.sort_mode == 2: - self.sort_with_key(key=lambda x: x.amount if x != None else sys.maxint - 1, reverse=False) + self.sort_with_key(key=lambda x: x.amount if x != None else sys.maxsize - 1, reverse=False) def sort_with_key(self, key, reverse=True): self.slots = sorted(self.slots, key=key, reverse=reverse) diff --git a/items.py b/items.py index 47ff9cfe..6d3f62ac 100644 --- a/items.py +++ b/items.py @@ -29,16 +29,17 @@ def get_item(item_or_block_id): """ Get the Block or Item with the specified id, which must either be an instance of BlockID, or a string format BlockID knows how to parse. + :type item_or_block_id: Union[BlockID, int] """ if not isinstance(item_or_block_id, BlockID): item_or_block_id = BlockID(str(item_or_block_id)) - if item_or_block_id.main >= G.ITEM_ID_MIN: + if item_or_block_id.is_item(): return G.ITEMS_DIR[item_or_block_id] else: return G.BLOCKS_DIR[item_or_block_id] -class Item(object): +class Item: id = None max_stack_size = 0 amount_label_color = 255, 255, 255, 255 @@ -61,7 +62,13 @@ def on_right_click(self, world, player): def __repr__(self): return self.name -class ItemStack(object): +class ItemStack: + type: BlockID + amount: int + durability: int + max_durability: int + max_stack_size: int + def __init__(self, type = 0, amount = 1, durability = -1): if amount < 1: amount = 1 diff --git a/main.py b/main.py index 01251059..1c6c16b2 100755 --- a/main.py +++ b/main.py @@ -1,9 +1,9 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Imports, sorted alphabetically. # Python packages -from ConfigParser import NoSectionError, NoOptionError +from configparser import NoSectionError, NoOptionError import argparse import os import random @@ -93,7 +93,7 @@ def on_draw(self): def on_resize(self, width, height): if self.reticle: self.reticle.delete() - x, y = width / 2, height / 2 + x, y = width // 2, height // 2 n = 10 self.reticle = pyglet.graphics.vertex_list( 4, @@ -113,12 +113,12 @@ def main(options): setattr(G.LAUNCH_OPTIONS, name, val) if options.fast: - G.TIME_RATE /= 20 + G.TIME_RATE //= 20 if G.LANGUAGE != 'default': reload(sys) sys.setdefaultencoding('utf8') - gettext.install(True, localedir=None, unicode=1) + gettext.install(True, localedir=None, str=1) gettext.find(G.APP_NAME.lower(), 'locale') gettext.textdomain(G.APP_NAME.lower()) gettext.bind_textdomain_codeset(G.APP_NAME.lower(), 'utf8') @@ -138,9 +138,9 @@ def main(options): G.CLIENT.stop() if G.SERVER: - print 'Saving...' + print('Saving...') save_world(G.SERVER, "world") - print 'Shutting down internal server...' + print('Shutting down internal server...') G.main_timer.stop() G.SERVER._stop.set() G.SERVER.shutdown() diff --git a/model.py b/model.py index cbc97d26..d3fff124 100644 --- a/model.py +++ b/model.py @@ -26,7 +26,7 @@ def get_texture_coordinates(x, y, height, width, texture_height, texture_width): return x, y, x + width, y, x + width, y + height, x, y + height # not good at calculating coordinate things...there may be something wrong... -class BoxModel(object): +class BoxModel: # top bottom left right front back textures = [(-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1), (-1, -1)] texture_data = None @@ -103,7 +103,7 @@ def draw(self): LEG_LENGTH = BODY_WIDTH LEG_WIDTH = BODY_WIDTH -class PlayerModel(object): +class PlayerModel: def __init__(self, position): self.position = None image = load_image('resources', 'mob', 'char.png') diff --git a/nature.py b/nature.py index 4e245858..fc220c81 100644 --- a/nature.py +++ b/nature.py @@ -15,7 +15,7 @@ # -class SmallPlant(object): +class SmallPlant: block = None grows_on = grass_block, dirt_block @@ -24,7 +24,7 @@ def add_to_world(cls, world, position, sync=False): world.add_block(position, cls.block, sync=sync) -class Trunk(object): +class Trunk: block = None height_range = 4, 8 grows_on = () @@ -45,11 +45,11 @@ def __init__(self, position, block=None, height_range=None): @classmethod def add_to_world(cls, world, position, sync=False): trunk = cls(position) - for item in trunk.blocks.items(): + for item in list(trunk.blocks.items()): world.add_block(*item, sync=sync) -class Tree(object): +class Tree: trunk_block = None leaf_block = None trunk_height_range = 4, 8 @@ -60,7 +60,7 @@ def add_to_world(cls, world, position, sync=False): trunk = Trunk(position, block=cls.trunk_block, height_range=cls.trunk_height_range) - for item in trunk.blocks.items(): + for item in list(trunk.blocks.items()): world.add_block(*item, force=False, sync=sync) x, y, z = position @@ -68,7 +68,7 @@ def add_to_world(cls, world, position, sync=False): treetop = y + height # Leaves generation - d = height / 3 + 1 + d = height // 3 + 1 for xl in range(x - d, x + d): dx = abs(xl - x) for yl in range(treetop - d, treetop + d): diff --git a/noise.pxd b/noise.pxd index 8e8fb622..ec9cc0ce 100644 --- a/noise.pxd +++ b/noise.pxd @@ -5,7 +5,7 @@ cimport perlin #cython: wraparound=False #cython: cdivision=True -cdef class SimplexNoiseGen(object): +cdef class SimplexNoiseGen: cdef public: list perm object noise @@ -17,7 +17,7 @@ cdef class SimplexNoiseGen(object): @cython.locals(y=double, weight=double) cpdef double fBm(self, double x, double z) -cdef class PerlinNoise(object): +cdef class PerlinNoise: cdef public: list perm, weights double PERSISTENCE, H diff --git a/noise.py b/noise.py index 964ff194..931328e0 100644 --- a/noise.py +++ b/noise.py @@ -15,16 +15,16 @@ __all__ = ('SimplexNoiseGen', 'PerlinNoise') # Factory class utilizing perlin.SimplexNoise -class SimplexNoiseGen(object): +class SimplexNoiseGen: def __init__(self, seed, octaves=6, zoom_level=0.002): # octaves = 6, - perm = range(255) + perm = list(range(255)) random.Random(seed).shuffle(perm) self.noise = SimplexNoise(permutation_table=perm).noise2 self.PERSISTENCE = 2.1379201 # AKA lacunarity self.H = 0.836281 self.OCTAVES = octaves # Higher linearly increases calc time; increases apparent 'randomness' - self.weights = [self.PERSISTENCE ** (-self.H * n) for n in xrange(self.OCTAVES)] + self.weights = [self.PERSISTENCE ** (-self.H * n) for n in range(self.OCTAVES)] self.zoom_level = zoom_level # Smaller will create gentler, softer transitions. Larger is more mountainy @@ -41,7 +41,7 @@ def fBm(self,x,z): # Improved Perlin Noise based on Improved Noise reference implementation by Ken Perlin -class PerlinNoise(object): +class PerlinNoise: def __init__(self, seed): rand = FastRandom(seed) diff --git a/perlin.pxd b/perlin.pxd index c9769feb..38d90d2b 100644 --- a/perlin.pxd +++ b/perlin.pxd @@ -10,7 +10,7 @@ cdef tuple _GRAD3, _GRAD4, _SIMPLEX cdef double _F2, _G2, _F3, _G3 -cdef class BaseNoise(object): +cdef class BaseNoise: cdef public: tuple permutation int period diff --git a/perlin.py b/perlin.py index 0b4e6fad..8e6f08fe 100644 --- a/perlin.py +++ b/perlin.py @@ -63,7 +63,7 @@ _G3 = 1.0 / 6.0 -class BaseNoise(object): +class BaseNoise: """Noise abstract base class""" def __init__(self, period=None, permutation_table=None): @@ -129,7 +129,7 @@ def randomize(self, period=None): """ if period is not None: self.period = period - perm = range(self.period) + perm = list(range(self.period)) perm_right = self.period - 1 for i in list(perm): j = randint(0, perm_right) diff --git a/physics.py b/physics.py index 53c56997..3c3ff92a 100644 --- a/physics.py +++ b/physics.py @@ -16,7 +16,7 @@ PHYSICS_TIMER_INTERVAL = PHYSICS_TICK = 0.1 -class PhysicsTask(object): +class PhysicsTask: def __init__(self, position, accel, obj): self.accel = accel self.velocity = [0, 0, 0] @@ -25,7 +25,7 @@ def __init__(self, position, accel, obj): self.obj = obj self.position = list(position) -class PhysicsManager(object): +class PhysicsManager: def __init__(self): self.timer = Timer(PHYSICS_TIMER_INTERVAL, "physics_timer") self.started = False diff --git a/player.py b/player.py index 5cf78389..aeb154e8 100644 --- a/player.py +++ b/player.py @@ -208,13 +208,13 @@ def collide(self, parent, position, height): np = normalize(position) self.current_density = 1 # Reset it, incase we don't hit any water for face in FACES: # check all surrounding blocks - for i in xrange(3): # check each dimension independently + for i in range(3): # check each dimension independently if not face[i]: continue d = (p[i] - np[i]) * face[i] if d < pad: continue - for dy in xrange(height): # check each height + for dy in range(height): # check each height op = list(np) op[1] -= dy op[i] += face[i] diff --git a/savingsystem.pxd b/savingsystem.pxd deleted file mode 100644 index 758a8085..00000000 --- a/savingsystem.pxd +++ /dev/null @@ -1,40 +0,0 @@ -import cython - -@cython.locals(x=int, y=int, z=int) -cpdef str sector_to_filename(tuple secpos) - -cpdef str region_to_filename(tuple region) - -@cython.locals(x=int, y=int, z=int) -cpdef tuple sector_to_region(tuple secpos) - -@cython.locals(x=int, y=int, z=int) -cpdef int sector_to_offset(tuple secpos) - -@cython.locals(x=int, y=int, z=int) -cpdef tuple sector_to_blockpos(tuple secpos) - -@cython.locals(cx=int, cy=int, cz=int, fstr=str, x=int, y=int, z=int, blk=object) -cpdef str save_sector_to_string(object blocks, tuple secpos) - -cpdef object save_world(object server, str world) - -@cython.locals(secpos=tuple, f=object) -cpdef object save_blocks(object blocks, str world) - -cpdef object save_player(object player, str world) - -cpdef bint world_exists(str game_dir, world=?) - -cpdef object remove_world(str game_dir, world=?) - -cpdef bint sector_exists(tuple sector, world=?) - -@cython.locals(rx=int, ry=int, rz=int, cx=int, cy=int, cz=int, x=int, y=int, z=int, fstr=str, - fpos=int, read=str, position=tuple) -cpdef object load_region(object world, world_name=?, region=?, sector=?) - -@cython.locals(version=int) -cpdef object load_player(object player, str world) - -cpdef object open_world(object gamecontroller, str game_dir, world=?) diff --git a/savingsystem.py b/savingsystem.py index 561d9a8c..02f8fa79 100644 --- a/savingsystem.py +++ b/savingsystem.py @@ -1,17 +1,20 @@ # Imports, sorted alphabetically. # Python packages -import cPickle as pickle +import pickle as pickle import os import random import struct import time import sqlite3 +from typing import Optional # Third-party packages # Nothing for now... # Modules from this project +from custom_types import iVector + from blocks import BlockID from debug import performance_info import globals as G @@ -34,18 +37,22 @@ null1024 = null2*512 #1024 \0's air = G.BLOCKS_DIR[(0,0)] -def sector_to_filename(secpos): +def sector_to_filename(secpos: iVector) -> str: x,y,z = secpos - return "%i.%i.%i.pyr" % (x/4, y/4, z/4) -def region_to_filename(region): + return "%i.%i.%i.pyr" % (x//4, y//4, z//4) + +def region_to_filename(region: iVector) -> str: return "%i.%i.%i.pyr" % region -def sector_to_region(secpos): + +def sector_to_region(secpos: iVector) -> iVector: x,y,z = secpos - return (x/4, y/4, z/4) -def sector_to_offset(secpos): + return (x//4, y//4, z//4) + +def sector_to_offset(secpos: iVector) -> int: x,y,z = secpos return ((x % 4)*16 + (y % 4)*4 + (z % 4)) * 1024 -def sector_to_blockpos(secpos): + +def sector_to_blockpos(secpos: iVector) -> iVector: x,y,z = secpos return x*8, y*8, z*8 @@ -58,17 +65,23 @@ def connect_db(world=None): db = sqlite3.connect(os.path.join(world_dir, G.DB_NAME)) db.execute('create table players (id integer primary key autoincrement, version integer, ' + \ 'pos_x real, pos_y real, pos_z real, mom_x real, mom_y real, mom_z real, ' + \ - 'inventory varchar(160), name varchar(30) UNIQUE)'); + 'inventory blob, name varchar(30) UNIQUE)'); db.commit() return db return sqlite3.connect(os.path.join(world_dir, G.DB_NAME)) -def save_sector_to_string(blocks, secpos): + +def save_sector_to_bytes(blocks, secpos: iVector) -> bytes: + """ + + :type blocks: world_server.WorldServer + :type secpos: (int,int,int) + """ cx, cy, cz = sector_to_blockpos(secpos) - fstr = "" - for x in xrange(cx, cx+8): - for y in xrange(cy, cy+8): - for z in xrange(cz, cz+8): + fstr = b"" + for x in range(cx, cx+8): + for y in range(cy, cy+8): + for z in range(cz, cz+8): blk = blocks.get((x,y,z), air).id if blk is not air: #if isinstance(blk, int): # When does this occur? Its expensive and I don't see it triggering @@ -78,7 +91,12 @@ def save_sector_to_string(blocks, secpos): fstr += null2 return fstr -def save_world(server, world): +def save_world(server, world: str): + """ + + :type server: server.Server + :type world: str + """ #Non block related data #save = (4,window.player, window.time_of_day, G.SEED) #pickle.dump(save, open(os.path.join(game_dir, world, "save.pkl"), "wb")) @@ -87,7 +105,7 @@ def save_world(server, world): save_blocks(server.world, world) -def save_blocks(blocks, world): +def save_blocks(blocks, world: str): #blocks and sectors (window.world and window.world.sectors) #Saves individual sectors in region files (4x4x4 sectors) @@ -100,9 +118,14 @@ def save_blocks(blocks, world): f.truncate(64*1024) #Preallocate the file to be 64kb with open(file, "rb+") as f: #Load up the region file f.seek(sector_to_offset(secpos)) #Seek to the sector offset - f.write(save_sector_to_string(blocks, secpos)) + f.write(save_sector_to_bytes(blocks, secpos)) + +def save_player(player, world: str): + """ -def save_player(player, world): + :type player: server.ServerPlayer + :type world: str + """ db = connect_db(world) cur = db.cursor() cur.execute('insert or replace into players(version, pos_x, pos_y, pos_z, mom_x, mom_y, mom_z, inventory, name) ' + \ @@ -130,8 +153,7 @@ def sector_exists(sector, world=None): if world is None: world = "world" return os.path.lexists(os.path.join(G.game_dir, world, sector_to_filename(sector))) -def load_region(world, world_name=None, region=None, sector=None): - if world_name is None: world_name = "world" +def load_region(world, world_name: str = "world", region: Optional[iVector] = None, sector: Optional[iVector] = None): sectors = world.sectors blocks = world SECTOR_SIZE = G.SECTOR_SIZE @@ -141,16 +163,16 @@ def load_region(world, world_name=None, region=None, sector=None): rx,ry,rz = rx*32, ry*32, rz*32 with open(os.path.join(G.game_dir, world_name, region_to_filename(region)), "rb") as f: #Load every chunk in this region (4x4x4) - for cx in xrange(rx, rx+32, 8): - for cy in xrange(ry, ry+32, 8): - for cz in xrange(rz, rz+32, 8): + for cx in range(rx, rx+32, 8): + for cy in range(ry, ry+32, 8): + for cz in range(rz, rz+32, 8): #Now load every block in this chunk (8x8x8) fstr = f.read(1024) if fstr != null1024: fpos = 0 - for x in xrange(cx, cx+8): - for y in xrange(cy, cy+8): - for z in xrange(cz, cz+8): + for x in range(cx, cx+8): + for y in range(cy, cy+8): + for z in range(cz, cz+8): read = fstr[fpos:fpos+2] fpos += 2 if read != null2: @@ -168,10 +190,10 @@ def load_region(world, world_name=None, region=None, sector=None): blocks[position] = type(main_blk)() blocks[position].set_metadata(full_id[-1]) except KeyError as e: - print "load_region: Invalid Block", e - sectors[(x/SECTOR_SIZE, y/SECTOR_SIZE, z/SECTOR_SIZE)].append(position) + print("load_region: Invalid Block", e) + sectors[(x//SECTOR_SIZE, y//SECTOR_SIZE, z//SECTOR_SIZE)].append(position) -def load_player(player, world): +def load_player(player, world: str): db = connect_db(world) cur = db.cursor() cur.execute("select * from players where name='%s'" % player.username) @@ -179,11 +201,11 @@ def load_player(player, world): if data is None: # no such entry, set initial value player.position = None player.momentum = (0, 0, 0) - player.inventory = ''.join((struct.pack("HBB", 0, 0, 0)) * 40) + player.inventory = (struct.pack("HBB", 0, 0, 0)) * 40 else: player.position = list(data[i] for i in range(2, 5)) player.momentum = list(data[i] for i in range(5, 8)) - player.inventory = str(data[8]) + player.inventory = data[8] cur.close() db.close() @@ -199,13 +221,13 @@ def open_world(gamecontroller, game_dir, world=None): if isinstance(loaded_save[3], str): G.SEED = loaded_save[3] random.seed(G.SEED) - print('Loaded seed from save: ' + G.SEED) + print(('Loaded seed from save: ' + G.SEED)) elif loaded_save[0] == 3: #Version 3 if isinstance(loaded_save[1], Player): gamecontroller.player = loaded_save[1] if isinstance(loaded_save[2], float): gamecontroller.time_of_day = loaded_save[2] - G.SEED = str(long(time.time() * 256)) + G.SEED = str(int(time.time() * 256)) random.seed(G.SEED) - print('No seed in save, generated random seed: ' + G.SEED) + print(('No seed in save, generated random seed: ' + G.SEED)) #blocks and sectors (window.world and window.world.sectors) #Are loaded on the fly diff --git a/server.py b/server.py index 343601ae..7a999a26 100644 --- a/server.py +++ b/server.py @@ -1,20 +1,15 @@ -# Python packages +#!/usr/bin/env python3 + from _socket import SHUT_RDWR import socket import struct import time import timer - -try: # Python 3 - import socketserver -except ImportError: # Python 2 - import SocketServer as socketserver +import socketserver import threading -# Third-party packages -# Modules from this project import globals as G -from savingsystem import save_sector_to_string, save_blocks, save_world, load_player, save_player +from savingsystem import save_sector_to_bytes, save_blocks, save_world, load_player, save_player from world_server import WorldServer import blocks from text_commands import CommandParser, COMMAND_HANDLED, CommandException, COMMAND_ERROR_COLOR @@ -22,36 +17,37 @@ from mod import load_modules #This class is effectively a serverside "Player" object -class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): - inventory = "\0"*(4*40) # Currently, is serialized to be 4 bytes * (27 inv + 9 quickbar + 4 armor) = 160 bytes +class ServerPlayer(socketserver.BaseRequestHandler): + inventory = b"\0"*(4*40) # Currently, is serialized to be 4 bytes * (27 inv + 9 quickbar + 4 armor) = 160 bytes command_parser = CommandParser() operator = False - def sendpacket(self, size, packet): - self.request.sendall(struct.pack("i", 5+size)+packet) - def sendchat(self, txt, color=(255,255,255,255)): - txt = txt.encode('utf-8') - self.sendpacket(len(txt) + 4, "\5" + txt + struct.pack("BBBB", *color)) - def sendinfo(self, info, color=(255,255,255,255)): - info = info.encode('utf-8') - self.sendpacket(len(info) + 4, "\5" + info + struct.pack("BBBB", *color)) - def broadcast(self, txt): - for player in self.server.players.itervalues(): + def sendpacket(self, size: int, packet: bytes): + self.request.sendall(struct.pack("i", 5 + size) + packet) + + def sendchat(self, txt: str, color=(255,255,255,255)): + txt_bytes = txt.encode('utf-8') + self.sendpacket(len(txt_bytes) + 4, b"\5" + txt_bytes + struct.pack("BBBB", *color)) + def sendinfo(self, info: str, color=(255,255,255,255)): + info_bytes = info.encode('utf-8') + self.sendpacket(len(info_bytes) + 4, b"\5" + info_bytes + struct.pack("BBBB", *color)) + def broadcast(self, txt: str): + for player in self.server.players.values(): player.sendchat(txt) def sendpos(self, pos_bytes, mom_bytes): - self.sendpacket(38, "\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) + self.sendpacket(38, b"\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) def lookup_player(self, playername): # find player by name - for player in self.server.players.values(): + for player in list(self.server.players.values()): if player.username == playername: return player return None def handle(self): self.username = str(self.client_address) - print "Client connecting...", self.client_address + print("Client connecting...", self.client_address) self.server.players[self.client_address] = self self.server.player_ids.append(self) self.id = len(self.server.player_ids) - 1 @@ -61,7 +57,7 @@ def handle(self): if self.server._stop.isSet(): return # Socket error while shutting down doesn't matter if e[0] in (10053, 10054): - print "Client %s %s crashed." % (self.username, self.client_address) + print("Client %s %s crashed." % (self.username, self.client_address)) else: raise e @@ -81,10 +77,10 @@ def loop(self): if not world.sectors[sector]: #Empty sector, send packet 2 - self.sendpacket(12, "\2" + struct.pack("iii",*sector)) + self.sendpacket(12, b"\2" + struct.pack("iii",*sector)) else: - msg = struct.pack("iii",*sector) + save_sector_to_string(world, sector) + world.get_exposed_sector(sector) - self.sendpacket(len(msg), "\1" + msg) + msg = struct.pack("iii",*sector) + save_sector_to_bytes(world, sector) + world.get_exposed_sector(sector) + self.sendpacket(len(msg), b"\1" + msg) elif packettype == 3: # Add block positionbytes = self.request.recv(4*3) blockbytes = self.request.recv(2) @@ -96,7 +92,7 @@ def loop(self): for address in players: if address is self.client_address: continue # He told us, we don't need to tell him - players[address].sendpacket(14, "\3" + positionbytes + blockbytes) + players[address].sendpacket(14, b"\3" + positionbytes + blockbytes) elif packettype == 4: # Remove block positionbytes = self.request.recv(4*3) @@ -105,7 +101,7 @@ def loop(self): for address in players: if address is self.client_address: continue # He told us, we don't need to tell him - players[address].sendpacket(12, "\4" + positionbytes) + players[address].sendpacket(12, b"\4" + positionbytes) elif packettype == 5: # Receive chat text txtlen = struct.unpack("i", self.request.recv(4))[0] raw_txt = self.request.recv(txtlen).decode('utf-8') @@ -119,8 +115,8 @@ def loop(self): # Not a command, send the chat to all players for address in players: players[address].sendchat(txt) - print txt # May as well let console see it too - except CommandException, e: + print(txt) # May as well let console see it too + except CommandException as e: self.sendchat(str(e), COMMAND_ERROR_COLOR) elif packettype == 6: # Player Inventory Update self.inventory = self.request.recv(4*40) @@ -132,12 +128,12 @@ def loop(self): for address in players: if address is self.client_address: continue # He told us, we don't need to tell him #TODO: Only send to nearby players - players[address].sendpacket(38, "\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) + players[address].sendpacket(38, b"\x08" + struct.pack("H", self.id) + mom_bytes + pos_bytes) elif packettype == 9: # Player Jump for address in players: if address is self.client_address: continue # He told us, we don't need to tell him #TODO: Only send to nearby players - players[address].sendpacket(2, "\x09" + struct.pack("H", self.id)) + players[address].sendpacket(2, b"\x09" + struct.pack("H", self.id)) elif packettype == 10: # Update Tile Entity block_pos = struct.unpack("iii", self.request.recv(4*3)) ent_size = struct.unpack("i", self.request.recv(4))[0] @@ -148,47 +144,47 @@ def loop(self): self.position = None load_player(self, "world") - for player in self.server.players.itervalues(): + for player in self.server.players.values(): player.sendchat("$$y%s has connected." % self.username) - print "%s's username is %s" % (self.client_address, self.username) + print("%s's username is %s" % (self.client_address, self.username)) position = (0,self.server.world.terraingen.get_height(0,0)+2,0) if self.position is None: self.position = position # New player, set initial position # Send list of current players to the newcomer - for player in self.server.players.itervalues(): + for player in self.server.players.values(): if player is self: continue name = player.username.encode('utf-8') - self.sendpacket(2 + len(name), '\7' + struct.pack("H", player.id) + name) + self.sendpacket(2 + len(name), b'\7' + struct.pack("H", player.id) + name) # Send the newcomer's name to all current players name = self.username.encode('utf-8') - for player in self.server.players.itervalues(): + for player in self.server.players.values(): if player is self: continue - player.sendpacket(2 + len(name), '\7' + struct.pack("H", self.id) + name) + player.sendpacket(2 + len(name), b'\7' + struct.pack("H", self.id) + name) #Send them the sector under their feet first so they don't fall sector = sectorize(position) if sector not in world.sectors: with world.server_lock: world.open_sector(sector) - msg = struct.pack("iii",*sector) + save_sector_to_string(world, sector) + world.get_exposed_sector(sector) - self.sendpacket(len(msg), "\1" + msg) + msg = struct.pack("iii",*sector) + save_sector_to_bytes(world, sector) + world.get_exposed_sector(sector) + self.sendpacket(len(msg), b"\1" + msg) #Send them their spawn position and world seed(for client side biome generator) seed_packet = make_string_packet(G.SEED) self.sendpacket(12 + len(seed_packet), struct.pack("B",255) + struct.pack("iii", *position) + seed_packet) - self.sendpacket(4*40, "\6" + self.inventory) + self.sendpacket(4*40, b"\6" + self.inventory) else: - print "Received unknown packettype", packettype + print("Received unknown packettype", packettype) def finish(self): - print "Client disconnected,", self.client_address, self.username + print("Client disconnected,", self.client_address, self.username) try: del self.server.players[self.client_address] except KeyError: pass - for player in self.server.players.itervalues(): + for player in self.server.players.values(): player.sendchat("%s has disconnected." % self.username) # Send user list - for player in self.server.players.itervalues(): - player.sendpacket(2 + 1, '\7' + struct.pack("H", self.id) + '\0') + for player in self.server.players.values(): + player.sendpacket(2 + 1, b'\7' + struct.pack("H", self.id) + b'\0') save_player(self, "world") @@ -207,26 +203,28 @@ def __init__(self, *args, **kwargs): def show_block(self, position, block): blockid = block.id - for player in self.players.itervalues(): + for player in self.players.values(): #TODO: Only if they're in range - player.sendpacket(14, "\3" + struct.pack("iiiBB", *(position+(blockid.main, blockid.sub)))) + player.sendpacket(14, b"\3" + struct.pack("iiiBB", *(position+(blockid.main, blockid.sub)))) def hide_block(self, position): - for player in self.players.itervalues(): + for player in self.players.values(): #TODO: Only if they're in range - player.sendpacket(12, "\4" + struct.pack("iii", *position)) + player.sendpacket(12, b"\4" + struct.pack("iii", *position)) - def update_tile_entity(self, position, value): - for player in self.players.itervalues(): - player.sendpacket(12 + len(value), "\x0A" + struct.pack("iii", *position) + value) + def update_tile_entity(self, position, value: bytes): + for player in self.players.values(): + player.sendpacket(12 + len(value), b"\x0A" + struct.pack("iii", *position) + value) def start_server(internal=False): if internal: - server = Server(("localhost", 1486), ThreadedTCPRequestHandler) + localip = "localhost" else: - localip = [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][0] - server = Server((localip, 1486), ThreadedTCPRequestHandler) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 1)) # connect() for UDP doesn't send packets + localip = s.getsockname()[0] + server = Server((localip, 1486), ServerPlayer) G.SERVER = server server_thread = threading.Thread(target=server.serve_forever) server_thread.start() @@ -249,46 +247,46 @@ def start_server(internal=False): load_modules(server=True) server, server_thread = start_server() - print('Server loop running in thread: ' + server_thread.name) + print(('Server loop running in thread: ' + server_thread.name)) ip, port = server.server_address - print "Listening on",ip,port + print("Listening on",ip,port) helptext = "Available commands: " + ", ".join(["say", "stop", "save"]) while 1: - args = raw_input().replace(chr(13), "").split(" ") # On some systems CR is appended, gotta remove that + args = input().replace(chr(13), "").split(" ") # On some systems CR is appended, gotta remove that cmd = args.pop(0) if cmd == "say": msg = "Server: %s" % " ".join(args) - print msg - for player in server.players.itervalues(): + print(msg) + for player in server.players.values(): player.sendchat(msg, color=(180,180,180,255)) elif cmd == "help": - print helptext + print(helptext) elif cmd == "save": - print "Saving..." + print("Saving...") save_world(server, "world") - print "Done saving" + print("Done saving") elif cmd == "stop": server._stop.set() G.main_timer.stop() - print "Disconnecting clients..." + print("Disconnecting clients...") for address in server.players: try: server.players[address].request.shutdown(SHUT_RDWR) server.players[address].request.close() except socket.error: pass - print "Shutting down socket..." + print("Shutting down socket...") server.shutdown() - print "Saving..." + print("Saving...") save_world(server, "world") - print "Goodbye" + print("Goodbye") break else: - print "Unknown command '%s'." % cmd, helptext + print("Unknown command '%s'." % cmd, helptext) while len(threading.enumerate()) > 1: threads = threading.enumerate() threads.remove(threading.current_thread()) - print "Waiting on these threads to close:", threads + print("Waiting on these threads to close:", threads) time.sleep(1) diff --git a/setup.py b/setup.py index c7accb09..fb057e83 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,11 @@ -# Imports, sorted alphabetically. +import glob +import os +import sys -# Python packages from Cython.Distutils import build_ext -from distutils.core import setup +from distutils.util import get_platform from distutils.extension import Extension -import os -# Third-party packages -# Nothing for now... - -# Modules from this project import globals as G @@ -18,7 +14,11 @@ 'gui', 'views', 'controllers', - 'setup-py2exe', + 'mods', + 'build', + 'setup', + 'setup-py2exe', + 'setup-cx_Freeze', 'pyglet.gl.glext_arb', 'pyglet.gl.glext_nv', 'pyglet.image.codecs', @@ -27,9 +27,39 @@ 'pyglet.media.drivers.alsa.asound', 'pyglet.window', 'pyglet.window.xlib.xlib', + 'venv', +) + +excluded_includes = ( + 'main', + 'manager', ) - + +def find_files(source, target, patterns): + """Locates the specified data-files and returns the matches + in a data_files compatible format. + + source is the root of the source data tree. + Use '' or '.' for current directory. + target is the root of the target data tree. + Use '' or '.' for the distribution directory. + patterns is a sequence of glob-patterns for the + files you want to copy. + """ + if glob.has_magic(source) or glob.has_magic(target): + raise ValueError("Magic not allowed in src, target") + ret = {} + for pattern in patterns: + pattern = os.path.join(source, pattern) + for filename in glob.glob(pattern): + if os.path.isfile(filename): + targetpath = os.path.join(target, os.path.relpath(filename, source)) + path = os.path.dirname(targetpath) + ret.setdefault(path, []).append(filename) + return sorted(ret.items()) + + def get_modules(path=None): first = False if path is None: @@ -38,7 +68,7 @@ def get_modules(path=None): for f_or_d in os.listdir(path): if not first: f_or_d = os.path.join(path, f_or_d) - if os.path.isdir(f_or_d): + if os.path.isdir(f_or_d) and f_or_d not in excluded_modules: d = f_or_d for name, f in get_modules(d): yield name, f @@ -50,10 +80,47 @@ def get_modules(path=None): if name and name not in excluded_modules: yield name, f + ext_modules = [Extension(name, [f]) for name, f in get_modules()] +includes = [name for name, f in get_modules() if name not in excluded_includes] + +packages = ["pyglet", "sqlite3"] # force cx_Freeze to bundle these + +cython_output_dir = "build/lib.%s-%d.%d" % (get_platform(), *sys.version_info[:2]) +options = { + 'build_ext': { + # 'inplace': True, + 'cython_c_in_temp': True, + 'cython_directives': { + 'language_level': '3', + } + }, +} + +if len(sys.argv) > 1 and sys.argv[1] in ("build", "build_exe"): + from cx_Freeze import setup, Executable + + options['build_exe'] = { + 'packages': packages, + 'excludes': ['test', 'unittest'], + 'includes': includes, + 'include_files': ['resources/'], + "path": [cython_output_dir] + sys.path, + } + executables = [Executable("main.py", targetName=G.APP_NAME + (".exe" if sys.platform == 'win32' else '')), Executable("server.py")] + +else: + from distutils.core import setup + + executables = [] setup( name=G.APP_NAME, cmdclass={'build_ext': build_ext}, - ext_modules=ext_modules, requires=['pyglet', 'Cython'] + options=options, + requires=['pyglet', 'Cython'], + version="0.1", + description="A Minecraft demo clone in Python 3.6.x", + ext_modules=ext_modules, + executables=executables, ) diff --git a/skydome.py b/skydome.py index 823a24a1..7d706ba6 100644 --- a/skydome.py +++ b/skydome.py @@ -17,9 +17,9 @@ ) # radius of the sun (central angle) -SUN_RADIUS = pi / 6 +SUN_RADIUS: float = pi / 6 -class Skydome(object): +class Skydome: def __init__(self, filename, brightness=1.0, size=1.0, direction=0): self.direction = direction self.image = pyglet.image.load(filename) diff --git a/terrain.pxd b/terrain.pxd index 04dd379f..5626d744 100644 --- a/terrain.pxd +++ b/terrain.pxd @@ -1,5 +1,4 @@ import cython -cimport world cimport perlin #cython: boundscheck=False @@ -11,7 +10,7 @@ cdef int CHUNK_X_SIZE, CHUNK_Y_SIZE, CHUNK_Z_SIZE @cython.locals(xblks=dict, yblks=dict, zblks=dict, x=int, y=int, z=int) cpdef dict init_3d_list(int x_size, int y_size, int z_size) -cdef class Chunk(object): +cdef class Chunk: cdef public: int x_pos, y_pos, z_pos int x_size, y_size, z_size @@ -29,7 +28,7 @@ cdef class Chunk(object): cdef int SAMPLE_RATE_HOR, SAMPLE_RATE_VER -cdef class TerrainGeneratorBase(object): +cdef class TerrainGeneratorBase: cdef public object seed cpdef object generate_sector(self, sector) diff --git a/terrain.py b/terrain.py index 249e206e..4a370244 100644 --- a/terrain.py +++ b/terrain.py @@ -10,6 +10,7 @@ import random # Third-party packages +import savingsystem from perlin import SimplexNoise from noise import * @@ -34,17 +35,17 @@ def init_3d_list(x_size, y_size, z_size): # initialize block list xblks = {} - for x in xrange(x_size): + for x in range(x_size): yblks = {} - for y in xrange(y_size): + for y in range(y_size): zblks = {} - for z in xrange(z_size): + for z in range(z_size): zblks[z] = None yblks[y] = zblks xblks[x] = yblks return xblks -class Chunk(object): +class Chunk: def __init__(self, position, x_size=CHUNK_X_SIZE, y_size=CHUNK_Y_SIZE, z_size=CHUNK_Z_SIZE): self.x_pos, self.y_pos, self.z_pos = position self.x_size = x_size @@ -70,7 +71,7 @@ def world_block_zpos(self, z): SAMPLE_RATE_HOR = 4 SAMPLE_RATE_VER = 4 -class TerrainGeneratorBase(object): +class TerrainGeneratorBase: def __init__(self, seed): self.seed = seed @@ -236,7 +237,7 @@ def density(self, x, y, z): mountains = self.mount_density(x, y, z) hills = self.hill_density(x, y, z) - flatten = self._clamp(((CHUNK_Y_SIZE - 16) - y) / int(CHUNK_Y_SIZE * 0.10)) + flatten = self._clamp(((CHUNK_Y_SIZE - 16) - y) // int(CHUNK_Y_SIZE * 0.10)) return -y + (((32.0 + height * 32.0) * self._clamp(river + 0.25) * self._clamp(ocean + 0.25)) + mountains * 1024.0 + hills * 128.0) * flatten @@ -308,7 +309,7 @@ def __init__(self, world, seed): self.nether = (nether_block, soulsand_block, netherore_block, air_block) #self.nether = ((nether_block,) * 80 + (soulsand_block,) * 15 + (netherore_block,) * 5 + (air_block,) * 10) - self.weights = [self.PERSISTENCE ** (-self.H * n) for n in xrange(self.OCTAVES)] + self.weights = [self.PERSISTENCE ** (-self.H * n) for n in range(self.OCTAVES)] def _clamp(self, a): if a > 1: return 0.9999 #So int rounds down properly and keeps it within the right sector @@ -402,7 +403,7 @@ def generate_sector(self, sector): self.zoom_level = 0.01 world.sectors[sector] = [] # Precache it incase it ends up being solid air, so it doesn't get regenerated indefinitely - bx, by, bz = world.savingsystem.sector_to_blockpos(sector) + bx, by, bz = savingsystem.sector_to_blockpos(sector) if 0 <= by < (self.height_base + self.height_range): self.rand.seed(self.seed + "(%d,%d,%d)" % (bx, by, bz)) @@ -432,8 +433,8 @@ def generate_sector(self, sector): midlevel_ores = self.midlevel_ores lowlevel_ores = self.lowlevel_ores - for x in xrange(bx, bx + 8): - for z in xrange(bz, bz + 8): + for x in range(bx, bx + 8): + for z in range(bz, bz + 8): if by < height_base: # For sectors outside of the height_range, no point checking the heightmap y = height_base @@ -503,7 +504,7 @@ def generate_sector(self, sector): y -= 3 - for yy in xrange(by, y): + for yy in range(by, y): # ores and filler... if yy >= 32: blockset = highlevel_ores diff --git a/tests.py b/tests.py index d427c80c..092efa07 100644 --- a/tests.py +++ b/tests.py @@ -35,8 +35,8 @@ def test_find_empty_slot(self): def test_add_1(self): for size in [0, 9, random.randint(3, 100)]: inv = Inventory(slot_count=size) - item = random.choice(G.ITEMS_DIR.keys()) - block = random.choice(G.BLOCKS_DIR.keys()) + item = random.choice(list(G.ITEMS_DIR.keys())) + block = random.choice(list(G.BLOCKS_DIR.keys())) result = inv.add_item(item) result2 = inv.add_item(block) if size == 0: @@ -59,14 +59,14 @@ def test_add_1(self): def test_add_2(self): inv = Inventory(slot_count=20) - block = random.choice(G.BLOCKS_DIR.keys()) + block = random.choice(list(G.BLOCKS_DIR.keys())) max_items = G.BLOCKS_DIR[block].max_stack_size * 20 - for i in xrange(0, max_items): + for i in range(0, max_items): self.assertTrue(inv.add_item(block)) - item = random.choice(G.ITEMS_DIR.keys()) + item = random.choice(list(G.ITEMS_DIR.keys())) inv2 = Inventory(slot_count=20) max_items2 = G.ITEMS_DIR[item].max_stack_size * 20 - for i in xrange(0, max_items2): + for i in range(0, max_items2): self.assertTrue(inv2.add_item(item)) self.assertNotIn(None, inv.slots) self.assertNotIn(None, inv2.slots) @@ -79,20 +79,20 @@ def test_add_2(self): def test_remove(self): inv = Inventory(slot_count=20) - block = random.choice(G.BLOCKS_DIR.keys()) + block = random.choice(list(G.BLOCKS_DIR.keys())) max_items = G.BLOCKS_DIR[block].max_stack_size * 20 - for i in xrange(0, max_items): + for i in range(0, max_items): self.assertTrue(inv.add_item(block)) self.assertFalse(inv.remove_item(block, quantity=0)) - for i in xrange(0, 20): + for i in range(0, 20): self.assertTrue(inv.remove_item(block, quantity=G.BLOCKS_DIR[block].max_stack_size)) self.assertEqual(inv.slots, [None] * 20) - for i in xrange(0, max_items): + for i in range(0, max_items): self.assertTrue(inv.add_item(block)) - for i in xrange(0, 20): + for i in range(0, 20): self.assertTrue(inv.remove_by_index(i, quantity=G.BLOCKS_DIR[block].max_stack_size)) self.assertEqual(inv.slots, [None] * 20) - for i in xrange(0, 20): + for i in range(0, 20): inv.slots[i] = ItemStack(block, amount=1) inv.slots[i].change_amount(-1) inv.remove_unnecessary_stacks() @@ -108,24 +108,24 @@ def generate_random_recipe(self, characters='#@'): recipe3 = [] ingre = {} for character in characters: - ingre[character] = G.BLOCKS_DIR.values()[self.current_block_id] + ingre[character] = list(G.BLOCKS_DIR.values())[self.current_block_id] self.current_block_id += 1 - if self.current_block_id >= len(G.BLOCKS_DIR.values()): + if self.current_block_id >= len(list(G.BLOCKS_DIR.values())): self.current_block_id = 0 - for i in xrange(0, 3): - recipe.append(''.join(random.choice(characters) for x in xrange(3))) + for i in range(0, 3): + recipe.append(''.join(random.choice(characters) for x in range(3))) recipe2.append([]) for character in recipe[i]: recipe2[i].append(ingre[character]) recipe3.append(ingre[character]) - return recipe, ingre, ItemStack(random.choice(G.BLOCKS_DIR.values()).id, amount=random.randint(1, 20)), recipe2, recipe3 + return recipe, ingre, ItemStack(random.choice(list(G.BLOCKS_DIR.values())).id, amount=random.randint(1, 20)), recipe2, recipe3 def test_add_1(self, characters='#@'): self.recipes = Recipes() recipes = [] ingres = [] outputs = [] - for i in xrange(0, 50): + for i in range(0, 50): recipe, ingre, output, recipe2, recipe3 = self.generate_random_recipe(characters=characters) recipes.append(recipe2) ingres.append(ingre) @@ -140,7 +140,7 @@ def test_add_2(self, characters='#@'): recipes = [] ingres = [] outputs = [] - for i in xrange(0, 25): + for i in range(0, 25): recipe, ingre, output, recipe2, recipe3 = self.generate_random_recipe(characters=characters) recipes.append(recipe2) ingres.append(ingre) @@ -154,7 +154,7 @@ def test_add_3(self): recipes = [] ingres = [] outputs = [] - for i in xrange(0, 25): + for i in range(0, 25): shapeless = random.choice([True, False]) if shapeless: recipe, ingre, output, recipe2, recipe3 = self.generate_random_recipe() diff --git a/text_commands.py b/text_commands.py index 73eeb104..48987137 100644 --- a/text_commands.py +++ b/text_commands.py @@ -48,7 +48,7 @@ def __init__(self, command_text, *args, **kwargs): self.message = "$$rUnknown command. Try /help for help." -class CommandParser(object): +class CommandParser: """ Entry point for parsing and executing game commands. """ @@ -85,9 +85,9 @@ def execute(self, command_text, user=None, world=None): # ...but filter out "None" arguments. If commands # want optional arguments, they should use keyword arguments # in their execute methods. - args = filter(lambda a: a is not None, match.groups()) + args = [a for a in match.groups() if a is not None] kwargs = {} - for key, value in match.groupdict().iteritems(): + for key, value in match.groupdict().items(): if value is not None: kwargs[key] = value ret = command.execute(*args, **kwargs) @@ -101,7 +101,7 @@ def execute(self, command_text, user=None, world=None): return COMMAND_NOT_HANDLED -class Command(object): +class Command: command = None help_text = None diff --git a/textures.py b/textures.py index b9050d73..c443bccd 100644 --- a/textures.py +++ b/textures.py @@ -17,7 +17,7 @@ ) -class TexturePackImplementation(object): +class TexturePackImplementation: __texture_pack_id = None __texture_pack_file_name = "" __first_description_line = "" @@ -57,7 +57,7 @@ def load_texture(self, path): return fo except: - print 'Texture not found \'' + '/'.join(path) + '\'' + print('Texture not found \'' + '/'.join(path) + '\'') return None def load_description(self): diff --git a/timer.py b/timer.py index e36142ea..1c751507 100644 --- a/timer.py +++ b/timer.py @@ -17,7 +17,7 @@ ) -class TimerTask(object): +class TimerTask: def __init__(self, ticks, callback, speed): self.ticks = self.expire = ticks self.callback = callback diff --git a/utils.pxd b/utils.pxd deleted file mode 100644 index 5b2e7efe..00000000 --- a/utils.pxd +++ /dev/null @@ -1,23 +0,0 @@ -import cython - -#cython: boundscheck=False -#cython: wraparound=False -#cython: cdivision=True - - -cdef class FastRandom(object): - cdef public seed - - cpdef int randint(self) - - -@cython.locals(int_f=int) -cpdef int normalize_float(float f) - - -@cython.locals(x=float, y=float, z=float) -cpdef tuple normalize(tuple position) - - -@cython.locals(x=int, y=int, z=int) -cpdef tuple sectorize(tuple position) diff --git a/utils.py b/utils.py index 9402a497..95391914 100644 --- a/utils.py +++ b/utils.py @@ -3,6 +3,7 @@ # Python packages import os import struct +from typing import Tuple, List # Third-party packages import pyglet @@ -10,6 +11,7 @@ # Modules from this project import globals as G +from custom_types import iVector, fVector __all__ = ( @@ -26,7 +28,7 @@ def load_image(*args): path) else None -def image_sprite(image, batch, group, x=0, y=0, width=None, height=None): +def image_sprite(image, batch, group, x: int = 0, y: int = 0, width: int = None, height: int = None): if image is None or batch is None or group is None: return None width = width or image.width @@ -50,11 +52,12 @@ def vec(*args): # fast math algorithms -class FastRandom(object): +class FastRandom: + seed: int def __init__(self, seed): self.seed = seed - def randint(self): + def randint(self) -> int: self.seed = (214013 * self.seed + 2531011) return (self.seed >> 16) & 0x7FFF @@ -70,7 +73,7 @@ def init_font(filename, fontname): def get_block_icon(block, icon_size, world): - print block.id.filename() + print(block.id.filename()) block_icon = G.texture_pack_list.selected_texture_pack.load_texture(block.id.filename()) \ or (block.group or world.group).texture.get_region( int(block.texture_data[2 * 8] * G.TILESET_SIZE) * icon_size, @@ -80,7 +83,7 @@ def get_block_icon(block, icon_size, world): return block_icon -FACES = ( +FACES: Tuple[iVector, ...] = ( ( 0, 1, 0), ( 0, -1, 0), (-1, 0, 0), @@ -89,7 +92,7 @@ def get_block_icon(block, icon_size, world): ( 0, 0, -1), ) -FACES_WITH_DIAGONALS = FACES + ( +FACES_WITH_DIAGONALS: Tuple[iVector, ...] = FACES + ( (-1, -1, 0), (-1, 0, -1), ( 0, -1, -1), @@ -105,7 +108,7 @@ def get_block_icon(block, icon_size, world): ) -def normalize_float(f): +def normalize_float(f: float) -> int: """ This is faster than int(round(f)). Nearly two times faster. Since it is run at least 500,000 times during map generation, @@ -132,16 +135,16 @@ def normalize_float(f): return int_f - 1 -def normalize(position): +def normalize(position: fVector) -> fVector: x, y, z = position return normalize_float(x), normalize_float(y), normalize_float(z) -def sectorize(position): +def sectorize(position: iVector) -> iVector: x, y, z = normalize(position) - x, y, z = (x / G.SECTOR_SIZE, - y / G.SECTOR_SIZE, - z / G.SECTOR_SIZE) + x, y, z = (x // G.SECTOR_SIZE, + y // G.SECTOR_SIZE, + z // G.SECTOR_SIZE) return x, y, z @@ -160,29 +163,35 @@ def unset_state(self): glDisable(self.texture.target) # Named Binary Tag -def make_int_packet(i): +def make_int_packet(i: int) -> bytes: return struct.pack('i', i) -def extract_int_packet(packet): +def extract_int_packet(packet: bytes): + """ + :rtype: (bytes, int) + """ return packet[4:], struct.unpack('i', packet[:4])[0] -def make_string_packet(s): - return struct.pack('i', len(s)) + s +def make_string_packet(s: str) -> bytes: + return struct.pack('i', len(s)) + s.encode('utf-8') -def extract_string_packet(packet): +def extract_string_packet(packet: bytes): + """ + :rtype: (bytes, str) + """ strlen = struct.unpack('i', packet[:4])[0] packet = packet[4:] - s = packet [:strlen] + s = packet[:strlen].decode('utf-8') packet = packet[strlen:] return packet, s -def make_packet(obj): +def make_packet(obj) -> bytes: if type(obj) == int: return make_int_packet(obj) elif type(obj) == str: return make_string_packet(obj) else: - print('make_packet: unsupported type: ' + str(type(obj))) + print(('make_packet: unsupported type: ' + str(type(obj)))) return None def extract_packet(packet): @@ -192,7 +201,7 @@ def extract_packet(packet): elif tag == 1: return extract_string_packet(packet) -def type_tag(t): +def type_tag(t) -> bytes: tag = 0 if t == int: tag = 0 @@ -200,9 +209,9 @@ def type_tag(t): tag = 1 return struct.pack('B', tag) -def make_nbt_from_dict(d): - packet = '' - for key in d.keys(): +def make_nbt_from_dict(d: dict) -> bytes: + packet = b'' + for key in list(d.keys()): packet += make_string_packet(key) + type_tag(type(d[key])) + make_packet(d[key]) return packet diff --git a/views.py b/views.py index d9ed267b..d09c5a6b 100644 --- a/views.py +++ b/views.py @@ -27,7 +27,7 @@ 'View', 'MainMenuView', 'OptionsView', 'ControlsView', 'TexturesView', 'MultiplayerView' ) -class Layout(object): +class Layout: def __init__(self, x, y): self.components = [] self._position = x, y @@ -220,8 +220,9 @@ def on_draw(self): self.batch.draw() def on_resize(self, width, height): - self.frame.x, self.frame.y = (width - self.frame.width) / 2, (height - self.frame.height) / 2 - self.layout.position = (width - self.layout.width) / 2, self.frame.y + self.frame.x = (width - self.frame.width) // 2 + self.frame.y = (height - self.frame.height) // 2 + self.layout.position = (width - self.layout.width) // 2, self.frame.y class MainMenuView(MenuView): @@ -238,9 +239,10 @@ def setup(self): width, height = self.controller.window.width, self.controller.window.height - self.label = Label(G.APP_NAME, font_name='ChunkFive Roman', font_size=50, x=width/2, y=self.frame.y + self.frame.height, + self.label = Label(G.APP_NAME, font_name='ChunkFive Roman', font_size=50, x=width//2, y=self.frame.y + self.frame.height, anchor_x='center', anchor_y='top', color=(255, 255, 255, 255), batch=self.batch, group=self.labels_group) + self.label.width = self.label.content_width self.label.height = self.label.content_height self.layout.add(self.label) @@ -424,7 +426,7 @@ def draw_splash_text(self): def on_resize(self, width, height): MenuView.on_resize(self, width, height) self.label.y = self.frame.y + self.frame.height - 15 - self.label.x = width / 2 + self.label.x = width // 2 self.splash_text_label.x = self.label.x self.splash_text_label.y = self.label.y @@ -460,7 +462,7 @@ def text_input_callback(symbol, modifier): hl.add(sb) def change_sound_volume(pos): - print G.EFFECT_VOLUME + print(G.EFFECT_VOLUME) G.EFFECT_VOLUME = float(float(pos) / 100) sb = self.Scrollbar(x=0, y=0, width=300, height=40, sb_width=20, sb_height=40, caption="Sound", pos=int(G.EFFECT_VOLUME * 100), on_pos_change=change_sound_volume) hl.add(sb) @@ -479,7 +481,7 @@ def change_sound_volume(pos): self.layout.add(button) self.buttons.append(button) - self.label = Label('Options', font_name='ChunkFive Roman', font_size=25, x=width/2, y=self.frame.y + self.frame.height, + self.label = Label('Options', font_name='ChunkFive Roman', font_size=25, x=width//2, y=self.frame.y + self.frame.height, anchor_x='center', anchor_y='top', color=(255, 255, 255, 255), batch=self.batch, group=self.labels_group) @@ -487,7 +489,7 @@ def change_sound_volume(pos): def on_resize(self, width, height): MenuView.on_resize(self, width, height) - self.text_input.resize(x=self.frame.x + (self.frame.width - self.text_input.width) / 2 + 5, y=self.frame.y + (self.frame.height) / 2 + 75, width=150) + self.text_input.resize(x=self.frame.x + (self.frame.width - self.text_input.width) // 2 + 5, y=self.frame.y + self.frame.height // 2 + 75, width=150) class ControlsView(MenuView): @@ -512,9 +514,10 @@ def on_resize(self, width, height): self.background.scale = 1.0 self.background.scale = max(float(width) / self.background.width, float(height) / self.background.height) self.background.x, self.background.y = 0, 0 - self.frame.x, self.frame.y = (width - self.frame.width) / 2, (height - self.frame.height) / 2 + self.frame.x = (width - self.frame.width) // 2 + self.frame.y = (height - self.frame.height) // 2 default_button_x = button_x = self.frame.x + 30 - button_y = self.frame.y + (self.frame.height) / 2 + 10 + button_y = self.frame.y + self.frame.height // 2 + 10 i = 0 for button in self.key_buttons: button.position = button_x, button_y @@ -524,7 +527,7 @@ def on_resize(self, width, height): button_x = default_button_x button_y -= button.height + 20 i += 1 - button_x = self.frame.x + (self.frame.width - self.button_return.width) / 2 + button_x = self.frame.x + (self.frame.width - self.button_return.width) // 2 self.button_return.position = button_x, button_y def on_key_press(self, symbol, modifiers): @@ -580,7 +583,7 @@ def on_button_toggle(self): self.current_toggled = button G.config.set("Graphics", "texture_pack", button.id) G.TEXTURE_PACK = button.id - for block in G.BLOCKS_DIR.values(): + for block in list(G.BLOCKS_DIR.values()): block.update_texture() #Reload textures G.save_config() @@ -590,7 +593,8 @@ def on_resize(self, width, height): self.background.scale = 1.0 self.background.scale = max(float(width) / self.background.width, float(height) / self.background.height) self.background.x, self.background.y = 0, 0 - self.frame.x, self.frame.y = (width - self.frame.width) / 2, (height - self.frame.height) / 2 + self.frame.x = (width - self.frame.width) // 2 + self.frame.y = (height - self.frame.height) // 2 class MultiplayerView(MenuView): def setup(self): @@ -616,7 +620,7 @@ def text_input_callback(symbol, modifier): self.layout.add(button) self.buttons.append(button) - self.label = Label('Play Multiplayer', font_name='ChunkFive Roman', font_size=25, x=width/2, y=self.frame.y + self.frame.height, + self.label = Label('Play Multiplayer', font_name='ChunkFive Roman', font_size=25, x=width//2, y=self.frame.y + self.frame.height, anchor_x='center', anchor_y='top', color=(255, 255, 255, 255), batch=self.batch, group=self.labels_group) @@ -633,4 +637,4 @@ def launch_server(self): def on_resize(self, width, height): MenuView.on_resize(self, width, height) - self.text_input.resize(x=self.frame.x + (self.frame.width - self.text_input.width) / 2 + 5, y=self.frame.y + (self.frame.height) / 2 + 75, width=150) + self.text_input.resize(x=self.frame.x + (self.frame.width - self.text_input.width) // 2 + 5, y=self.frame.y + self.frame.height // 2 + 75, width=150) diff --git a/world.pxd b/world.pxd deleted file mode 100644 index 03317702..00000000 --- a/world.pxd +++ /dev/null @@ -1,100 +0,0 @@ -import cython -cimport client -#cython: boundscheck=False -#cython: wraparound=False -#cython: cdivision=True - - -@cython.locals(spreading_mutations=dict) -cdef class World(dict): - cdef public: - batch - transparency_batch - group - savingsystem - dict shown - dict _shown - sectors - urgent_queue, lazy_queue - sector_queue - generation_queue - terraingen - set before_set - spreading_mutable_blocks - double spreading_time - object packetreceiver - object sector_packets - object biome_generator - - cpdef object add_block(self, tuple position, object block, - bint sync=?, bint force=?) - - cpdef object _add_block(self, tuple position, object block) - - cpdef object remove_block(self, object player, tuple position, - bint sync=?, bint sound=?) - - @cython.locals(sector_position=tuple) - cpdef object _remove_block(self, tuple position, bint sync=?) - - # Generators are not handled by Cython for the moment. - # @cython.locals(x=float, y=float, z=float, - # dx=float, dy=float, dz=float) - # cpdef object neighbors_iterator(self, tuple position, - # tuple relative_neighbors_positions=?) - - @cython.locals(other_position=tuple, faces=tuple) - cpdef object check_neighbors(self, tuple position) - - @cython.locals(other_position=tuple) - cpdef bint has_neighbors(self, tuple position, - set is_in=?,bint diagonals=?, tuple faces=?) - - @cython.locals(other_position=tuple, x=double, y=double, z=double, fx=int, fy=int, fz=int) - cpdef bint is_exposed(self, tuple position) - - @cython.locals(m=int, _=int, - x=float, y=float, z=float, - dx=float, dy=float, dz=float, - previous=tuple, key=tuple) - cpdef tuple hit_test(self, tuple position, tuple vector, - int max_distance=?, bint hitwater=?) - - cpdef object hide_block(self, tuple position, bint immediate=?) - - cpdef object _hide_block(self, tuple position) - - @cython.locals(block=object) - cpdef object show_block(self, tuple position, bint immediate=?) - - @cython.locals(vertex_data=list, texture_data=list, - count=int, batch=object) - cpdef object _show_block(self, tuple position, object block) - - @cython.locals(position=tuple) - cpdef object _show_sector(self, tuple sector) - - @cython.locals(position=tuple) - cpdef object _hide_sector(self, tuple sector) - - cpdef object enqueue_sector(self, bint state, tuple sector) - - @cython.locals(state=bint, sector=tuple) - cpdef object dequeue_sector(self) - - @cython.locals(before_set=set, after_set=set, pad=int, - dx=int, dy=int, dz=int, - x=int, y=int, z=int, - show=set, hide=set, sector=tuple) - cpdef object change_sectors(self, tuple after) - - @cython.locals(queue=object) - cpdef object dequeue(self) - - @cython.locals(stoptime=double) - cpdef object process_queue(self, double dt) - - cpdef object process_entire_queue(self) - - @cython.locals(deload=int, plysector=tuple, px=double, py=double, pz=double, x=int, y=int, z=int) - cpdef object hide_sectors(self, double dt, object player) \ No newline at end of file diff --git a/world.py b/world.py index 1e1564a4..9afe6fd0 100644 --- a/world.py +++ b/world.py @@ -5,10 +5,12 @@ import os from time import time import warnings +from typing import Tuple, Set, Optional, Any, Dict, Deque, DefaultDict # Third-party packages import pyglet from pyglet.gl import * +from pyglet.graphics import Batch # Modules from this project from blocks import * @@ -16,6 +18,7 @@ import globals as G from client import PacketReceiver from entity import TileEntity +from custom_types import iVector, fVector __all__ = ( @@ -25,6 +28,19 @@ #The Client's world class World(dict): + batch: Batch + transparency_batch: Batch + group: TextureGroup + shown: Dict[iVector, Any] + _shown: Dict[iVector, Any] + sectors: DefaultDict[iVector, list] + before_set: Set[iVector] + urgent_queue: Deque[Any] + lazy_queue: Deque[Any] + sector_queue: Dict[iVector, bool] + packetreceiver: None + sector_packets: Deque[Any] + biome_generator: None spreading_mutations = { dirt_block: grass_block, } @@ -49,13 +65,13 @@ def __init__(self): self.biome_generator = None # Add the block clientside, then tell the server about the new block - def add_block(self, position, block, sync=True, force=True): + def add_block(self, position: iVector, block, sync: bool = True, force: bool = True): self._add_block(position, block) # For Prediction if sync: self.packetreceiver.add_block(position, block) # Clientside, add the block - def _add_block(self, position, block): + def _add_block(self, position: iVector, block): if position in self: self._remove_block(position, sync=True) if hasattr(block, 'entity_type'): @@ -76,7 +92,7 @@ def _add_block(self, position, block): self.show_block(position) self.inform_neighbors_of_block_change(position) - def remove_block(self, player, position, sync=True, sound=True): + def remove_block(self, player, position: iVector, sync: bool = True, sound: bool = True): if sound and player is not None: self[position].play_break_sound(player, position) self._remove_block(position, sync=sync) @@ -84,7 +100,7 @@ def remove_block(self, player, position, sync=True, sound=True): self.packetreceiver.remove_block(position) # Clientside, delete the block - def _remove_block(self, position, sync=True): + def _remove_block(self, position: iVector, sync: bool = True): del self[position] sector_position = sectorize(position) try: @@ -99,7 +115,7 @@ def _remove_block(self, position, sync=True): self.check_neighbors(position) self.inform_neighbors_of_block_change(position) - def is_exposed(self, position): + def is_exposed(self, position: iVector) -> bool: x, y, z = position for fx,fy,fz in FACES: other_position = (fx+x, fy+y, fz+z) @@ -107,12 +123,12 @@ def is_exposed(self, position): return True return False - def neighbors_iterator(self, position, relative_neighbors_positions=FACES): + def neighbors_iterator(self, position: iVector, relative_neighbors_positions: Tuple[iVector, ...] = FACES): x, y, z = position for dx, dy, dz in relative_neighbors_positions: yield x + dx, y + dy, z + dz - def check_neighbors(self, position): + def check_neighbors(self, position: iVector): for other_position in self.neighbors_iterator(position): if other_position not in self: continue @@ -123,32 +139,32 @@ def check_neighbors(self, position): if other_position in self.shown: self.hide_block(other_position) - def has_neighbors(self, position, is_in=None, diagonals=False, - faces=None): + def has_neighbors(self, position: iVector, + is_in: Optional[Set[iVector]] = None, + diagonals: bool = False, + faces: Optional[Tuple[iVector, ...]] = None) -> bool: if faces is None: faces = FACES_WITH_DIAGONALS if diagonals else FACES for other_position in self.neighbors_iterator( - position, relative_neighbors_positions=faces): + position, relative_neighbors_positions=faces): if other_position in self: if is_in is None or self[other_position] in is_in: return True return False - def inform_neighbors_of_block_change(self, position): + def inform_neighbors_of_block_change(self, position: iVector): for neighbor in self.neighbors_iterator(position): if neighbor not in self: continue self[neighbor].on_neighbor_change(self, position, neighbor) - #self.hide_block(neighbor) - #self.show_block(neighbor) - def hit_test(self, position, vector, max_distance=8, hitwater=False): + def hit_test(self, position: fVector, vector: fVector, max_distance: int = 8, hitwater: bool = False): m = 8 x, y, z = position dx, dy, dz = vector dx, dy, dz = dx / m, dy / m, dz / m - previous = () - for _ in xrange(max_distance * m): + previous: fVector = (0.0, 0.0, 0.0) + for _ in range(max_distance * m): key = normalize((x, y, z)) if key != previous and key in self and (self[key].density != 0.5 or hitwater): return key, previous @@ -156,17 +172,17 @@ def hit_test(self, position, vector, max_distance=8, hitwater=False): x, y, z = x + dx, y + dy, z + dz return None, None - def hide_block(self, position, immediate=True): + def hide_block(self, position: iVector, immediate: bool = True): del self.shown[position] if immediate: self._hide_block(position) else: self.enqueue(self._hide_block, position) - def _hide_block(self, position): + def _hide_block(self, position: iVector): self._shown.pop(position).delete() - def show_block(self, position, immediate=True): + def show_block(self, position: iVector, immediate: bool = True): block = self[position] self.shown[position] = block if immediate: @@ -174,9 +190,8 @@ def show_block(self, position, immediate=True): else: self.enqueue(self._show_block, position, block) - def _show_block(self, position, block): + def _show_block(self, position: iVector, block): # only show exposed faces - index = 0 vertex_data = list(block.get_vertices(*position)) texture_data = list(block.texture_data) color_data = None @@ -184,7 +199,6 @@ def _show_block(self, position, block): temp = self.biome_generator.get_temperature(position[0], position[-1]) humidity = self.biome_generator.get_humidity(position[0], position[-1]) color_data = block.get_color(temp, humidity) - count = len(texture_data) / 2 # FIXME: Do something of what follows. #for neighbor in self.neighbors_iterator(position): # if neighbor in self: @@ -198,7 +212,7 @@ def _show_block(self, position, block): # else: # index += 1 - count = len(texture_data) / 2 + count = len(texture_data) // 2 # create vertex list batch = self.transparency_batch if block.transparent else self.batch if color_data is not None: @@ -211,7 +225,7 @@ def _show_block(self, position, block): ('v3f/static', vertex_data), ('t2f/static', texture_data)) - def show_sector(self, sector): + def show_sector(self, sector: iVector): if sector in self.sectors: self._show_sector(sector) else: @@ -219,12 +233,12 @@ def show_sector(self, sector): self.packetreceiver.request_sector(sector) #Clientside, show a sector we've downloaded - def _show_sector(self, sector): + def _show_sector(self, sector: iVector): for position in self.sectors[sector]: if position not in self.shown and self.is_exposed(position): self.show_block(position) - def _hide_sector(self, sector): + def _hide_sector(self, sector: iVector): if sector in self.sectors: for position in self.sectors[sector]: if position in self: del self[position] @@ -232,17 +246,17 @@ def _hide_sector(self, sector): self.hide_block(position) del self.sectors[sector] - def change_sectors(self, after): + def change_sectors(self, after: iVector): before_set = self.before_set after_set = set() pad = G.VISIBLE_SECTORS_RADIUS x, y, z = after - for distance in xrange(0, pad + 1): - for dx in xrange(-distance, distance + 1): - for dz in xrange(-distance, distance + 1): + for distance in range(0, pad + 1): + for dx in range(-distance, distance + 1): + for dz in range(-distance, distance + 1): if abs(dx) != distance and abs(dz) != distance: continue - for dy in xrange(-4, 4): + for dy in range(-4, 4): if dx ** 2 + dy ** 2 + dz ** 2 > (pad + 1) ** 2: continue after_set.add((x + dx, y + dy, z + dz)) @@ -253,7 +267,7 @@ def change_sectors(self, after): self.enqueue_sector(True, sector) self.before_set = after_set - def enqueue_sector(self, state, sector): #State=True to show, False to hide + def enqueue_sector(self, state: bool, sector: iVector): #State=True to show, False to hide self.sector_queue[sector] = state def dequeue_sector(self): diff --git a/world_server.pxd b/world_server.pxd deleted file mode 100644 index 8bd2ef14..00000000 --- a/world_server.pxd +++ /dev/null @@ -1,59 +0,0 @@ -import cython - -#cython: boundscheck=False -#cython: wraparound=False -#cython: cdivision=True - -@cython.locals(spreading_mutations=dict) -cdef class WorldServer(dict): - cdef public: - sectors - savingsystem - dict exposed_cache - - urgent_queue - lazy_queue - sector_queue - generation_queue - spreading_mutable_blocks - - server_lock - server - - terraingen - - cpdef object add_block(self, tuple position, object block, - bint sync=?, bint force=?, bint check_spread=?) - - cpdef object init_block(self, tuple position, object block) - - cpdef object remove_block(self, tuple position, - bint sync=?, bint check_spread=?) - - @cython.locals(x=int, y=int, z=int, fx=int, fy=int, fz=int, - other_position=tuple) - cpdef bint is_exposed(self, tuple position) - - @cython.locals(x=int, y=int, z=int, cx=int, cy=int, cz=int) - cpdef object get_exposed_sector_cached(self, tuple sector) - - cpdef object get_exposed_sector(self, tuple sector) - - @cython.locals(other_position=tuple) - cpdef object check_neighbors(self, tuple position) - - @cython.locals(x=int, y=int, z=int, above_position=tuple) - cpdef object check_spreading_mutable(self, tuple position, object block) - - cpdef bint has_neighbors(self, tuple position, object is_in=?, object diagonals=?, - object faces=?) - - cpdef object generate_seed(self) - - cpdef object open_sector(self, tuple sector) - - cpdef object hide_sector(self, tuple sector) - - cpdef object content_update(self) - - cpdef object generate_vegetation(self, tuple position, vegetation_class) \ No newline at end of file diff --git a/world_server.py b/world_server.py index 62481ebb..9bf40dea 100644 --- a/world_server.py +++ b/world_server.py @@ -1,17 +1,17 @@ -# Python packages from binascii import hexlify from collections import deque, defaultdict, OrderedDict import os import threading import time import warnings - -# Third-party packages - -# Modules from this project import datetime +from sqlite3.dbapi2 import Connection +from threading import Lock +from typing import Any, Dict, DefaultDict, Deque, Tuple, Optional, Set + from blocks import * -from savingsystem import sector_to_blockpos +from custom_types import iVector +import savingsystem from utils import FACES, FACES_WITH_DIAGONALS, normalize_float, normalize, sectorize, TextureGroup import globals as G from nature import TREES, TREE_BLOCKS @@ -19,23 +19,24 @@ class WorldServer(dict): + sectors: DefaultDict[iVector, list] + exposed_cache: Dict[iVector, bytes] + spreading_mutable_blocks: Deque[iVector] + server_lock: Lock + server: Any + db: Connection + terraingen: terrain.TerrainGeneratorSimple spreading_mutations = { dirt_block: grass_block, } def __init__(self, server): super(WorldServer, self).__init__() - import savingsystem #This module doesn't like being imported at modulescope - self.savingsystem = savingsystem if not os.path.lexists(os.path.join(G.game_dir, "world", "players")): os.makedirs(os.path.join(G.game_dir, "world", "players")) self.sectors = defaultdict(list) self.exposed_cache = dict() - self.urgent_queue = deque() - self.lazy_queue = deque() - self.sector_queue = OrderedDict() - self.generation_queue = deque() self.spreading_mutable_blocks = deque() self.server_lock = threading.Lock() @@ -45,17 +46,16 @@ def __init__(self, server): if os.path.exists(os.path.join(G.game_dir, G.SAVE_FILENAME, "seed")): with open(os.path.join(G.game_dir, G.SAVE_FILENAME, "seed"), "rb") as f: - G.SEED = f.read() + G.SEED = f.read().decode('utf-8') else: if not os.path.exists(os.path.join(G.game_dir, G.SAVE_FILENAME)): os.makedirs(os.path.join(G.game_dir, G.SAVE_FILENAME)) with open(os.path.join(G.game_dir, G.SAVE_FILENAME, "seed"), "wb") as f: - f.write(self.generate_seed()) + f.write(self.generate_seed().encode('utf-8')) self.terraingen = terrain.TerrainGeneratorSimple(self, G.SEED) def __del__(self): self.db.close() - super(WorldServer, self).__del__() def __delitem__(self, position): super(WorldServer, self).__delitem__(position) @@ -68,7 +68,7 @@ def __delitem__(self, position): 'spreading mutations; your save is probably ' 'corrupted' % repr(position)) - def add_block(self, position, block, sync=True, force=True, check_spread=True): + def add_block(self, position: iVector, block, sync: bool = True, force: bool = True, check_spread: bool = True): if position in self: if not force: return @@ -89,10 +89,10 @@ def add_block(self, position, block, sync=True, force=True, check_spread=True): self.check_spreading_mutable(position, block) self.check_neighbors(position) - def init_block(self, position, block): + def init_block(self, position: iVector, block): self.add_block(position, block, sync=False, force=False, check_spread=False) - def remove_block(self, position, sync=True, check_spread=True): + def remove_block(self, position: iVector, sync: bool = True, check_spread: bool = True): del self[position] sector_position = sectorize(position) try: @@ -106,7 +106,7 @@ def remove_block(self, position, sync=True, check_spread=True): if check_spread: self.check_neighbors(position) - def is_exposed(self, position): + def is_exposed(self, position: iVector) -> bool: x, y, z = position for fx,fy,fz in FACES: other_position = (fx+x, fy+y, fz+z) @@ -114,31 +114,27 @@ def is_exposed(self, position): return True return False - def get_exposed_sector_cached(self, sector): + def get_exposed_sector_cached(self, sector: iVector) -> bytes: """ Cached. Returns a 512 length string of 0's and 1's if blocks are exposed """ - if sector in self.exposed_cache: - return self.exposed_cache[sector] - cx,cy,cz = sector_to_blockpos(sector) - #Most ridiculous list comprehension ever, but this is 25% faster than using appends - self.exposed_cache[sector] = "".join([(x,y,z) in self and self.is_exposed((x,y,z)) and "1" or "0" - for x in xrange(cx, cx+8) for y in xrange(cy, cy+8) for z in xrange(cz, cz+8)]) + if sector not in self.exposed_cache: + self.exposed_cache[sector] = self.get_exposed_sector(sector) return self.exposed_cache[sector] - def get_exposed_sector(self, sector): + def get_exposed_sector(self, sector: iVector) -> bytes: """ Returns a 512 length string of 0's and 1's if blocks are exposed """ - cx,cy,cz = sector_to_blockpos(sector) + cx,cy,cz = savingsystem.sector_to_blockpos(sector) #Most ridiculous list comprehension ever, but this is 25% faster than using appends - return "".join([(x,y,z) in self and self.is_exposed((x,y,z)) and "1" or "0" - for x in xrange(cx, cx+8) for y in xrange(cy, cy+8) for z in xrange(cz, cz+8)]) + return b"".join([(x,y,z) in self and self.is_exposed((x,y,z)) and b"1" or b"0" + for x in range(cx, cx+8) for y in range(cy, cy+8) for z in range(cz, cz+8)]) - def neighbors_iterator(self, position, relative_neighbors_positions=FACES): + def neighbors_iterator(self, position: iVector, relative_neighbors_positions: Tuple[iVector, ...] = FACES): x, y, z = position for dx, dy, dz in relative_neighbors_positions: yield x + dx, y + dy, z + dz - def check_neighbors(self, position): + def check_neighbors(self, position: iVector): for other_position in self.neighbors_iterator(position): if other_position not in self: continue @@ -146,9 +142,9 @@ def check_neighbors(self, position): self.check_spreading_mutable(other_position, self[other_position]) - def check_spreading_mutable(self, position, block): + def check_spreading_mutable(self, position: iVector, block): x, y, z = position - above_position = x, y + 1, z + above_position: iVector = (x, y + 1, z) if above_position in self\ or position in self.spreading_mutable_blocks\ or not self.is_exposed(position): @@ -159,8 +155,10 @@ def check_spreading_mutable(self, position, block): diagonals=True): self.spreading_mutable_blocks.appendleft(position) - def has_neighbors(self, position, is_in=None, diagonals=False, - faces=None): + def has_neighbors(self, position: iVector, + is_in: Optional[Set[iVector]] = None, + diagonals: bool = False, + faces: Optional[Tuple[iVector, ...]] = None) -> bool: if faces is None: faces = FACES_WITH_DIAGONALS if diagonals else FACES for other_position in self.neighbors_iterator( @@ -175,13 +173,13 @@ def generate_seed(self): if seed is None: # Generates pseudo-random number. try: - seed = long(hexlify(os.urandom(16)), 16) + seed = int(hexlify(os.urandom(16)), 16) except NotImplementedError: - seed = long(time.time() * 256) # use fractional seconds + seed = int(time.time() * 256) # use fractional seconds # Then convert it to a string so all seeds have the same type. seed = str(seed) - print('No seed set, generated random seed: ' + seed) + print(('No seed set, generated random seed: ' + seed)) G.SEED = seed with open(os.path.join(G.game_dir, 'seeds.txt'), 'a') as seeds: @@ -189,26 +187,26 @@ def generate_seed(self): seeds.write('%s\n\n' % seed) return seed - def open_sector(self, sector): + def open_sector(self, sector: iVector): #The sector is not in memory, load or create it - if self.savingsystem.sector_exists(sector): + if savingsystem.sector_exists(sector): #If its on disk, load it - self.savingsystem.load_region(self, sector=sector) + savingsystem.load_region(self, sector=sector) else: #The sector doesn't exist yet, generate it! - bx, by, bz = self.savingsystem.sector_to_blockpos(sector) - rx, ry, rz = bx/32*32, by/32*32, bz/32*32 + bx, by, bz = savingsystem.sector_to_blockpos(sector) + rx, ry, rz = bx//32*32, by//32*32, bz//32*32 #For ease of saving/loading, queue up generation of a whole region (4x4x4 sectors) at once - yiter, ziter = xrange(ry/8,ry/8+4), xrange(rz/8,rz/8+4) - for secx in xrange(rx/8,rx/8+4): + yiter, ziter = range(ry//8,ry//8+4), range(rz//8,rz//8+4) + for secx in range(rx//8,rx//8+4): for secy in yiter: for secz in ziter: self.terraingen.generate_sector((secx,secy,secz)) #Generate the requested sector immediately, so the following show_block's work #self.terraingen.generate_sector(sector) - def hide_sector(self, sector): + def hide_sector(self, sector: iVector): #TODO: remove from memory; save #for position in self.sectors.get(sector, ()): # if position in self.shown: @@ -229,7 +227,7 @@ def content_update(self): self.add_block(position, self.spreading_mutations[self[position]], check_spread=False) - def generate_vegetation(self, position, vegetation_class): + def generate_vegetation(self, position: iVector, vegetation_class): if position in self: return