From 93c20acce5a87feaa5477b642f81db7ed499d282 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 18 Nov 2015 20:08:29 +0000 Subject: [PATCH 01/42] Do not store slice positions within chunk files --- saves.py | 111 ++++++++++++++++++++++-------------------------------- server.py | 3 +- 2 files changed, 47 insertions(+), 67 deletions(-) diff --git a/saves.py b/saves.py index 079820e..6302c0d 100644 --- a/saves.py +++ b/saves.py @@ -24,7 +24,7 @@ SAVES_DIR = 'saves' CHUNK_EXT = '.chunk' -SLICE_SEP = '' +CHUNK_SIZE = world_gen['chunk_size'] * (world_gen['height'] + 1) save_path = lambda save, filename='': os.path.join(SAVES_DIR, save, filename) meta_path = lambda save: save_path(save, 'meta.json') @@ -73,30 +73,6 @@ def load_meta(save): return meta -def load_chunk(save, chunk): - try: - map_ = parse_slices(get_chunk(save, chunk)) - except FileNotFoundError: - map_ = {} - - valid_map = {} - for pos, slice_ in map_.items(): - if pos in range(chunk, chunk + world_gen['chunk_size']): - - if not len(slice_) == world_gen['height']: - if len(slice_) < world_gen['height']: - # Extend slice height - slice_ = [' '] * (world_gen['height'] - len(slice_)) + slice_ - elif len(slice_) > world_gen['height']: - # Truncate slice height - slice_ = slice_[len(slice_) - world_gen['height']:] - - valid_map[pos] = slice_ - - save_map(save, map_) - return map_ - - def get_meta(save): with open(meta_path(save)) as f: data = json.load(f) @@ -104,18 +80,6 @@ def get_meta(save): return data -def get_chunk(save, chunk): - data = [] - - chunk_file = save_path(save, str(chunk) + CHUNK_EXT) - - if os.path.isfile(chunk_file): - with open(chunk_file) as f: - data = f.readlines() - - return data - - def check_meta(meta): # Create meta items if needed for key, default in default_meta.items(): @@ -130,54 +94,71 @@ def check_meta(meta): return meta -def parse_slices(data): - slices = {} +def save_meta(save, meta): + # Save meta file + with open(meta_path(save), 'w') as f: + json.dump(meta, f) + + +def chunk_file_name(save, chunk): + return save_path(save, str(chunk) + CHUNK_EXT) - for line in data: - # Parses map file - key, slice_ = line.split(SLICE_SEP) - slice_ = list(slice_) - # Removes new line char if it exists - slices[key] = slice_ if not slice_[-1] == '\n' else slice_[:-1] +def load_chunk(save, chunk): + map_ = {} + chunk_pos = chunk * world_gen['chunk_size'] - return slices + try: + with open(chunk_file_name(save, chunk)) as data: + for d_pos, slice_ in enumerate(data): + # Truncate to correct size + slice_ = slice_[:world_gen['height']] -def save_meta(save, meta): - # Save meta file - with open(meta_path(save), 'w') as f: - json.dump(meta, f) + height_error = world_gen['height'] - len(slice_) + if not height_error == 0: + # Extend slice height + slice_ = (' ' * height_error) + slice_ + + map_[str(chunk_pos + d_pos)] = list(slice_) + + except FileNotFoundError: + pass + + return map_ def save_map(save, new_slices): # Group slices by chunk chunks = {} for pos, slice_ in new_slices.items(): + chunk_pos = chunk_num(pos) + rel_pos = int(pos) % world_gen['chunk_size'] + try: - chunks[chunk_num(pos)].update({pos: slice_}) + chunks[chunk_pos].update({rel_pos: slice_}) except KeyError: - chunks[chunk_num(pos)] = {pos: slice_} + chunks[chunk_pos] = {rel_pos: slice_} debug('saving slices', new_slices.keys()) debug('saving chunks', chunks.keys()) # Update chunk files for num, chunk in chunks.items(): - chunk_file = save_path(save, str(num) + CHUNK_EXT) - # Update slices in chunk file with new slices - try: - with open(chunk_file) as f: - slices = parse_slices(f.readlines()) - except (OSError, IOError): - slices = {} - slices.update(chunk) - - # Write slices back to file - with open(chunk_file, 'w') as f: - for pos, slice_ in slices.items(): - f.write(str(pos) + SLICE_SEP + ''.join(slice_) + '\n') + filename = chunk_file_name(save, num) + if os.path.isfile(filename): + mode = 'r+' + else: + mode = 'w' + + with open(filename, mode) as file_: + + file_.truncate(CHUNK_SIZE) + for pos, slice_ in chunk.items(): + + file_pos = file_.seek(int(pos) * (world_gen['height'] + 1)) + file_.write(''.join(slice_) + '\n') def set_blocks(map_, blocks): diff --git a/server.py b/server.py index 7d9c0f5..4284bc9 100644 --- a/server.py +++ b/server.py @@ -179,7 +179,7 @@ def get_chunks(self, chunk_list): for chunk_num in chunk_list: chunk = saves.load_chunk(self._save, chunk_num) for i in range(chunk_size): - pos = i + chunk_num * chunk_size + pos = i + (chunk_num * chunk_size) if not str(pos) in chunk: slice_ = terrain.gen_slice(pos, self._meta) chunk[str(pos)] = slice_ @@ -191,7 +191,6 @@ def get_chunks(self, chunk_list): # Save generated terrain to file if gen_slices: - debug('saving slices', gen_slices.keys()) saves.save_map(self._save, gen_slices) self._map.update(new_slices) From 15de098c788fa50a70cb04febe3e19b6b7827f60 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 18 Nov 2015 20:49:29 +0000 Subject: [PATCH 02/42] Dummy generation by chunk implementation --- saves.py | 8 +++++++- server.py | 22 ++++++++++------------ terrain.py | 10 ++++++++++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/saves.py b/saves.py index 6302c0d..4306b99 100644 --- a/saves.py +++ b/saves.py @@ -128,7 +128,13 @@ def load_chunk(save, chunk): return map_ -def save_map(save, new_slices): +def save_chunk(save, chunk_pos, chunk): + # TODO: Write this function, so when we generate chunks we don't have to + # save them slowly + pass + + +def save_slices(save, new_slices): # Group slices by chunk chunks = {} for pos, slice_ in new_slices.items(): diff --git a/server.py b/server.py index 4284bc9..5dabdca 100644 --- a/server.py +++ b/server.py @@ -8,8 +8,6 @@ from console import debug -chunk_size = terrain.world_gen['chunk_size'] - SUN_TICK = radians(1/32) TPS = 10 # Ticks @@ -176,29 +174,29 @@ def get_chunks(self, chunk_list): debug('loading chunks', chunk_list) # Generates new terrain - for chunk_num in chunk_list: - chunk = saves.load_chunk(self._save, chunk_num) - for i in range(chunk_size): - pos = i + (chunk_num * chunk_size) - if not str(pos) in chunk: - slice_ = terrain.gen_slice(pos, self._meta) - chunk[str(pos)] = slice_ - gen_slices[str(pos)] = slice_ + for chunk_pos in chunk_list: + + chunk = saves.load_chunk(self._save, chunk_pos) + if not chunk: + chunk = terrain.gen_chunk(chunk_pos, self._meta) + gen_slices.update(chunk) new_slices.update(chunk) + debug(new_slices.keys(), trunc=False) debug('generated slices', gen_slices.keys()) debug('new slices', new_slices.keys()) # Save generated terrain to file if gen_slices: - saves.save_map(self._save, gen_slices) + # TODO: Use save_chunk once it is written. + saves.save_slices(self._save, gen_slices) self._map.update(new_slices) return new_slices def set_blocks(self, blocks): self._map, new_slices = saves.set_blocks(self._map, blocks) - saves.save_map(self._save, new_slices) + saves.save_slices(self._save, new_slices) return blocks def set_player(self, name, player): diff --git a/terrain.py b/terrain.py index 591f36b..47c13c2 100644 --- a/terrain.py +++ b/terrain.py @@ -153,6 +153,16 @@ def add_tall_grass(slice_, pos, meta, slice_height_): return slice_ +def gen_chunk(chunk_pos, meta): + chunk = {} + for d_pos in range(world_gen['chunk_size']): + pos = (chunk_pos * world_gen['chunk_size']) + d_pos + + chunk.update({str(pos): gen_slice(pos, meta)}) + + return chunk + + def gen_slice(pos, meta): slice_height_ = slice_height(pos, meta) From 0f301263851c3a211110bcb6f7e68337805f7a7d Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 18 Nov 2015 21:04:15 +0000 Subject: [PATCH 03/42] Create save_chunk function for directly saving a whole chunk --- saves.py | 43 ++++++++++++++++++++++--------------------- server.py | 10 +--------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/saves.py b/saves.py index 4306b99..a94e338 100644 --- a/saves.py +++ b/saves.py @@ -129,42 +129,43 @@ def load_chunk(save, chunk): def save_chunk(save, chunk_pos, chunk): - # TODO: Write this function, so when we generate chunks we don't have to - # save them slowly - pass + """ Updates slices within one chunk. """ + + filename = chunk_file_name(save, chunk_pos) + if os.path.isfile(filename): + mode = 'r+' + else: + mode = 'w' + + with open(filename, mode) as file_: + + file_.truncate(CHUNK_SIZE) + for pos, slice_ in chunk.items(): + rel_pos = int(pos) % world_gen['chunk_size'] + + file_.seek(int(rel_pos) * (world_gen['height'] + 1)) + file_.write(''.join(slice_) + '\n') def save_slices(save, new_slices): + """ Updates slices anywhere in the world. """ + # Group slices by chunk chunks = {} for pos, slice_ in new_slices.items(): chunk_pos = chunk_num(pos) - rel_pos = int(pos) % world_gen['chunk_size'] try: - chunks[chunk_pos].update({rel_pos: slice_}) + chunks[chunk_pos].update({pos: slice_}) except KeyError: - chunks[chunk_pos] = {rel_pos: slice_} + chunks[chunk_pos] = {pos: slice_} debug('saving slices', new_slices.keys()) debug('saving chunks', chunks.keys()) # Update chunk files - for num, chunk in chunks.items(): - - filename = chunk_file_name(save, num) - if os.path.isfile(filename): - mode = 'r+' - else: - mode = 'w' - - with open(filename, mode) as file_: - - file_.truncate(CHUNK_SIZE) - for pos, slice_ in chunk.items(): - - file_pos = file_.seek(int(pos) * (world_gen['height'] + 1)) - file_.write(''.join(slice_) + '\n') + for chunk_pos, chunk in chunks.items(): + save_chunk(save, chunk_pos, chunk) def set_blocks(map_, blocks): diff --git a/server.py b/server.py index 5dabdca..d9f6f1f 100644 --- a/server.py +++ b/server.py @@ -169,7 +169,6 @@ def __init__(self, save): def get_chunks(self, chunk_list): new_slices = {} - gen_slices = {} debug('loading chunks', chunk_list) @@ -179,18 +178,11 @@ def get_chunks(self, chunk_list): chunk = saves.load_chunk(self._save, chunk_pos) if not chunk: chunk = terrain.gen_chunk(chunk_pos, self._meta) - gen_slices.update(chunk) + saves.save_chunk(self._save, chunk_pos, chunk) new_slices.update(chunk) - debug(new_slices.keys(), trunc=False) - debug('generated slices', gen_slices.keys()) debug('new slices', new_slices.keys()) - # Save generated terrain to file - if gen_slices: - # TODO: Use save_chunk once it is written. - saves.save_slices(self._save, gen_slices) - self._map.update(new_slices) return new_slices From d8234896bd0fd30e692aca61f04e7b2434cb276b Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 19 Nov 2015 23:10:49 +0000 Subject: [PATCH 04/42] First pass on feature caching: creates the cache, but it's not used. --- terrain.py | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/terrain.py b/terrain.py index 47c13c2..de73d54 100644 --- a/terrain.py +++ b/terrain.py @@ -1,4 +1,5 @@ import random +from collections import OrderedDict from math import ceil import render @@ -153,7 +154,120 @@ def add_tall_grass(slice_, pos, meta, slice_height_): return slice_ +class Cache(OrderedDict): + """ Implements a Dict with a size limit. + Beyond which it replaces the oldest item. """ + + def __init__(self, *args, **kwds): + self._limit = kwds.pop("limit", None) + OrderedDict.__init__(self, *args, **kwds) + self._check_limit() + + def __setitem__(self, key, value): + OrderedDict.__setitem__(self, key, value) + self._check_limit() + + def _check_limit(self): + if self._limit is not None: + while len(self) > self._limit: + self.popitem(last=False) + + +features = Cache(limit=(world_gen['chunk_size'] * 4)) # Magic number! + + +def gen_features(features, range_, meta): + """ Ensures the features within `range` exist in `features` """ + + for x in range_: + if features.get(str(x)) is None: + + # Init to empty, so 'no features' is cached. + features[str(x)] = [] + + # Biomes + random.seed(str(meta['seed']) + str(x) + 'biome') + if random.random() <= .05: + + attrs = {} + attrs['tree_chance'] = random.choice(world_gen['biome_tree_weights']) + + if str(x) not in features: + features[str(x)] = [] + + features[str(x)].append({ + 'type': 'biome', + 'attrs': attrs + }) + + # Hills + random.seed(str(meta['seed']) + str(x) + 'hill') + if random.random() <= 0.05: + + attrs = {} + attrs['gradient_l'] = random.randint(1, world_gen['min_grad']), + attrs['gradient_r'] = random.randint(1, world_gen['min_grad']), + attrs['height'] = random.randint(0, world_gen['max_hill']) + + if str(x) not in features: + features[str(x)] = [] + + features[str(x)].append({ + 'type': 'hill', + 'attrs': attrs + }) + + # Trees + + # TODO: `tree_chance` depends on the biome...? + tree_chance = 0 + + random.seed(str(meta['seed']) + str(x) + 'tree') + if random.random() <= tree_chance: + + attrs = {} + attrs['type'] = random.choice(world_gen['trees']) + + # Centre tree slice (contains trunk) + center_leaves = tree[int(len(tree)/2)] + trunk_depth = next(i for i, leaf in enumerate(center_leaves[::-1]) if leaf) + attrs['height'] = random.randint(2, air_height - len(center_leaves) + trunk_depth) + + features[str(x)].append({ + 'type': 'tree', + 'attrs': attrs + }) + + # Ores + # NOTE: Ores seem to be the way to model the generalisation of the + # rest of the features after + for ore in world_gen['ores'].values(): + + random.seed(str(meta['seed']) + str(x) + ore['char']) + if random.random() <= ore['chance']: + + attrs = {} + attrs['type'] = ore['char'] + attrs['ore_root_height'] = random.randint(ore['lower'], ore['upper']) + + features[str(x)].append({ + 'type': 'ore_root', + 'attrs': attrs + }) + + # TODO: Grass? + + def gen_chunk(chunk_pos, meta): + # First generate all the features we will need + # for all the slice is in this chunk, + + MAX_FEATURE_RANGE = 0 # TODO: Temp! + + feature_range = (chunk_pos - MAX_FEATURE_RANGE, + chunk_pos + world_gen['chunk_size'] + MAX_FEATURE_RANGE) + gen_features(features, feature_range, meta) + chunk = {} for d_pos in range(world_gen['chunk_size']): pos = (chunk_pos * world_gen['chunk_size']) + d_pos From 02fe04dd92a80cb28deb049cd64b8665c140dc7d Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Fri, 20 Nov 2015 16:33:27 +0000 Subject: [PATCH 05/42] Split features into 'per slice' and 'per chunk', store features as map not list This means biomes can be stored per chunk, and accessed without regenerating them for every slice. --- data.py | 15 +++++++++++- terrain.py | 71 +++++++++++++++++++++++++++--------------------------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/data.py b/data.py index c339065..429e8d3 100644 --- a/data.py +++ b/data.py @@ -320,8 +320,21 @@ 'ground_height': 10, 'chunk_size': 16, 'max_biome_size': 50, - 'biome_tree_weights': [0]*2 + [.05]*2 + [.2], 'tall_grass_rate': .25, + 'biomes': { + 'plains': { + 'chance': .4, + 'trees': 0 + }, + 'normal': { + 'chance': .4, + 'trees': .05 + }, + 'forest': { + 'chance': .2, + 'trees': .2 + } + }, 'ores': { 'coal': { 'char': 'x', diff --git a/terrain.py b/terrain.py index de73d54..8057df6 100644 --- a/terrain.py +++ b/terrain.py @@ -110,8 +110,8 @@ def biome(pos, meta): random.seed(str(meta['seed']) + str(biome_x) + 'biome') # Generate a biome marker with a 5% chance - if random.random() <= .05: - biome_type.append(random.choice(world_gen['biome_tree_weights'])) + # if random.random() <= .05: + # biome_type.append(random.choice(world_gen['biome_tree_weights'])) # If not plains or forest, it's normal return max(set(biome_type), key=biome_type.count) if biome_type else .05 @@ -173,32 +173,44 @@ def _check_limit(self): self.popitem(last=False) -features = Cache(limit=(world_gen['chunk_size'] * 4)) # Magic number! +# TODO: This probably shouldn't stay here... +n_chunks_in_cache = 4 +features = { + 'chunks': Cache(limit=n_chunks_in_cache), + 'slices': Cache(limit=n_chunks_in_cache * world_gen['chunk_size']) +} def gen_features(features, range_, meta): """ Ensures the features within `range` exist in `features` """ for x in range_: - if features.get(str(x)) is None: + chunk = x // world_gen['chunk_size'] + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. + + if features['chunks'].get(str(chunk)) is None: # Init to empty, so 'no features' is cached. - features[str(x)] = [] + features['chunks'][str(chunk)] = {} - # Biomes - random.seed(str(meta['seed']) + str(x) + 'biome') - if random.random() <= .05: + # TODO: ATM using basic per chunk biomes, see #71 + random.seed(str(meta['seed']) + str(chunk) + 'biome') - attrs = {} - attrs['tree_chance'] = random.choice(world_gen['biome_tree_weights']) + biomes_population = [] + for name, data in world_gen['biomes'].items(): + biomes_population.extend([name] * int(data['chance'] * 100)) + + attrs = {} + attrs['type'] = random.choice(biomes_population) + + features['chunks'][str(chunk)]['biome'] = attrs - if str(x) not in features: - features[str(x)] = [] + current_chunk_biome = features['chunks'][str(chunk)]['biome']['type'] - features[str(x)].append({ - 'type': 'biome', - 'attrs': attrs - }) + if features['slices'].get(str(x)) is None: + # Init to empty, so 'no features' is cached. + features['slices'][str(x)] = {} # Hills random.seed(str(meta['seed']) + str(x) + 'hill') @@ -209,34 +221,24 @@ def gen_features(features, range_, meta): attrs['gradient_r'] = random.randint(1, world_gen['min_grad']), attrs['height'] = random.randint(0, world_gen['max_hill']) - if str(x) not in features: - features[str(x)] = [] - - features[str(x)].append({ - 'type': 'hill', - 'attrs': attrs - }) + features['slices'][str(x)]['hill'] = attrs # Trees - - # TODO: `tree_chance` depends on the biome...? - tree_chance = 0 - random.seed(str(meta['seed']) + str(x) + 'tree') + tree_chance = world_gen['biomes'][current_chunk_biome]['trees'] if random.random() <= tree_chance: attrs = {} attrs['type'] = random.choice(world_gen['trees']) # Centre tree slice (contains trunk) + # TODO: This calculation could be done on start-up, and stored + # with each tree type. center_leaves = tree[int(len(tree)/2)] trunk_depth = next(i for i, leaf in enumerate(center_leaves[::-1]) if leaf) attrs['height'] = random.randint(2, air_height - len(center_leaves) + trunk_depth) - features[str(x)].append({ - 'type': 'tree', - 'attrs': attrs - }) + features['slices'][str(x)]['tree'] = attrs # Ores # NOTE: Ores seem to be the way to model the generalisation of the @@ -247,13 +249,10 @@ def gen_features(features, range_, meta): if random.random() <= ore['chance']: attrs = {} - attrs['type'] = ore['char'] attrs['ore_root_height'] = random.randint(ore['lower'], ore['upper']) - features[str(x)].append({ - 'type': 'ore_root', - 'attrs': attrs - }) + feature_name = ore['char'] + '_ore_root' + features['slices'][str(x)][feature_name] = attrs # TODO: Grass? From d43a4d07c4c01c008f1cf5f969f7985e1c69206e Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Fri, 20 Nov 2015 20:05:17 +0000 Subject: [PATCH 06/42] Use the features to generate the terrain (Not debugged yet) --- data.py | 3 +- terrain.py | 225 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 170 insertions(+), 58 deletions(-) diff --git a/data.py b/data.py index 429e8d3..27e84dc 100644 --- a/data.py +++ b/data.py @@ -379,7 +379,8 @@ 'lower': 1 } }, - 'trees': ( + 'trees': ( # TODO: Preprocessing should be done on these, to give the data + # the terrain gen needs. ((0, 1, 1), (1, 1, 0), (0, 1, 1)), diff --git a/terrain.py b/terrain.py index 8057df6..7cbd399 100644 --- a/terrain.py +++ b/terrain.py @@ -180,119 +180,230 @@ def _check_limit(self): 'slices': Cache(limit=n_chunks_in_cache * world_gen['chunk_size']) } +RAD = 16 -def gen_features(features, range_, meta): - """ Ensures the features within `range` exist in `features` """ +# # TODO: Use this for are the other functions! +# def gen_features(generator, features, feature_group_name, chunk_pos, meta): +# """ Ensures the features within `range` exist in `features` """ - for x in range_: - chunk = x // world_gen['chunk_size'] +# feature_cache = features[feature_group_name] - # TODO: Each of these `if` blocks should be abstracted into a function - # which just returns the `attrs` object. +# for x in range(chunk_pos - RAD, chunk_pos + world_gen['chunk_size'] + RAD): +# if feature_cache.get(str(chunk_pos)) is None: - if features['chunks'].get(str(chunk)) is None: - # Init to empty, so 'no features' is cached. - features['chunks'][str(chunk)] = {} +# # Init to empty, so 'no features' is cached. +# feature_cache[str(chunk_pos)] = {} + +# random.seed(str(meta['seed']) + str(chunk_pos) + feature_group_name) +# feature_cache[str(chunk_pos)]['biome'] = generator() + + +def gen_biome_features(features, chunk_pos, meta): + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. + + if features['chunks'].get(str(chunk_pos)) is None: + # Init to empty, so 'no features' is cached. + features['chunks'][str(chunk_pos)] = {} - # TODO: ATM using basic per chunk biomes, see #71 - random.seed(str(meta['seed']) + str(chunk) + 'biome') + # If it is not None, it has all ready been generated. + if features['chunks'][str(chunk_pos)].get('biome') is None: - biomes_population = [] - for name, data in world_gen['biomes'].items(): - biomes_population.extend([name] * int(data['chance'] * 100)) + # TODO: ATM using basic per chunk biomes, see #71 + random.seed(str(meta['seed']) + str(chunk_pos) + 'biome') - attrs = {} - attrs['type'] = random.choice(biomes_population) + biomes_population = [] + for name, data in world_gen['biomes'].items(): + biomes_population.extend([name] * int(data['chance'] * 100)) - features['chunks'][str(chunk)]['biome'] = attrs + attrs = {} + attrs['type'] = random.choice(biomes_population) - current_chunk_biome = features['chunks'][str(chunk)]['biome']['type'] + features['chunks'][str(chunk_pos)]['biome'] = attrs + + +def gen_hill_features(features, rad, chunk_pos, meta): + for x in range(chunk_pos - rad, chunk_pos + world_gen['chunk_size'] + rad): + + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. if features['slices'].get(str(x)) is None: # Init to empty, so 'no features' is cached. features['slices'][str(x)] = {} - # Hills + # If it is not None, it has all ready been generated. + if features['slices'][str(x)].get('hill') is None: + random.seed(str(meta['seed']) + str(x) + 'hill') if random.random() <= 0.05: attrs = {} - attrs['gradient_l'] = random.randint(1, world_gen['min_grad']), - attrs['gradient_r'] = random.randint(1, world_gen['min_grad']), + attrs['gradient_l'] = random.randint(1, world_gen['min_grad']) + attrs['gradient_r'] = random.randint(1, world_gen['min_grad']) attrs['height'] = random.randint(0, world_gen['max_hill']) features['slices'][str(x)]['hill'] = attrs - # Trees + +def gen_tree_features(features, ground_heights, chunk_pos, meta): + current_chunk_biome = features['chunks'][str(chunk_pos)]['biome']['type'] + + for x in range(chunk_pos - RAD, chunk_pos + world_gen['chunk_size'] + RAD): + + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. + + if features['slices'].get(str(x)) is None: + # Init to empty, so 'no features' is cached. + features['slices'][str(x)] = {} + + # If it is not None, it has all ready been generated. + if features['slices'][str(x)].get('tree') is None: + random.seed(str(meta['seed']) + str(x) + 'tree') tree_chance = world_gen['biomes'][current_chunk_biome]['trees'] if random.random() <= tree_chance: attrs = {} - attrs['type'] = random.choice(world_gen['trees']) + attrs['type'] = random.randint(0, len(world_gen['trees'])) # Centre tree slice (contains trunk) # TODO: This calculation could be done on start-up, and stored # with each tree type. - center_leaves = tree[int(len(tree)/2)] - trunk_depth = next(i for i, leaf in enumerate(center_leaves[::-1]) if leaf) + tree = world_gen['trees'][attrs['type']] + center_leaves = tree[int(len(tree) / 2)] + attrs['trunk_depth'] = next(i for i, leaf in enumerate(center_leaves[::-1]) if leaf) + + # Get space above ground + air_height = world_gen['height'] - ground_heights[str(x)] attrs['height'] = random.randint(2, air_height - len(center_leaves) + trunk_depth) features['slices'][str(x)]['tree'] = attrs - # Ores - # NOTE: Ores seem to be the way to model the generalisation of the - # rest of the features after - for ore in world_gen['ores'].values(): - random.seed(str(meta['seed']) + str(x) + ore['char']) +def gen_ore_features(features, ground_heights, chunk_pos, meta): + for x in range(chunk_pos - RAD, chunk_pos + world_gen['chunk_size'] + RAD): + + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. + + if features['slices'].get(str(x)) is None: + # Init to empty, so 'no features' is cached. + features['slices'][str(x)] = {} + + # Ores + # NOTE: Ores seem to be the way to model the generalisation of the + # rest of the features after + for name, ore in world_gen['ores'].items(): + feature_name = name + '_ore_root' + + # If it is not None, it has all ready been generated. + if features['slices'][str(x)].get(feature_name) is None: + + random.seed(str(meta['seed']) + str(x) + feature_name) if random.random() <= ore['chance']: attrs = {} - attrs['ore_root_height'] = random.randint(ore['lower'], ore['upper']) + attrs['root_height'] = random.randint( + ore['lower'], + min(ore['upper'], (ground_heights[str(x)] - 1)) # -1 for grass. + ) - feature_name = ore['char'] + '_ore_root' features['slices'][str(x)][feature_name] = attrs - # TODO: Grass? - def gen_chunk(chunk_pos, meta): + # First generate all the features we will need - # for all the slice is in this chunk, + # for all the slice is in this chunk + + gen_biome_features(features, chunk_pos, meta) + gen_hill_features(features, RAD * 2, chunk_pos, meta) - MAX_FEATURE_RANGE = 0 # TODO: Temp! + # Insert hills because the trees and ores depend on the ground height. + ground_heights = {} + for feature_x, slice_features in features['slices'].items(): + feature_x = int(feature_x) - feature_range = (chunk_pos - MAX_FEATURE_RANGE, - chunk_pos + world_gen['chunk_size'] + MAX_FEATURE_RANGE) - gen_features(features, feature_range, meta) + if slice_features.get('hill'): + hill = slice_features['hill'] + for d_x in range(-hill['height'] * hill['gradient_l'], + hill['height'] * hill['gradient_r']): + abs_pos = feature_x + d_x + + gradient = hill['gradient_l'] if feature_x < 0 else hill['gradient_r'] + hill_height = hill['height'] - (abs(d_x) / gradient) + + # Make top of hill flat + if d_x == 0: + hill_height -= 1 + + ground_height = world_gen['ground_height'] + hill_height + + old_height = ground_heights.get(str(abs_pos), 0) + ground_heights[str(abs_pos)] = max(ground_height, old_height) + + # We have to generate the ground heights before we can calculate the + # features which depend on them + + gen_tree_features(features, ground_heights, chunk_pos, meta) + gen_ore_features(features, ground_heights, chunk_pos, meta) + + # Build slices chunk = {} - for d_pos in range(world_gen['chunk_size']): - pos = (chunk_pos * world_gen['chunk_size']) + d_pos + for x in range(chunk_pos, chunk_pos + world_gen['chunk_size']): - chunk.update({str(pos): gen_slice(pos, meta)}) + # Form slice of sky, grass, stone, bedrock + chunk[str(x)] = ( + [' '] * (world_gen['height'] - ground_heights[x]) + + ['-'] + + ['#'] * (ground_heights[x] - 2) + # 2 for grass and bedrock + ['_'] + ) - return chunk + # Insert trees and ores + for feature_x, slice_features in features['slices'].items(): + feature_x = int(feature_x) + # Tree + if slice_features.get('tree'): + tree = slice_features['tree'] + leaves = world_gen['trees'][tree['type']] + half_leaves = int(len(leaves) / 2) -def gen_slice(pos, meta): - slice_height_ = slice_height(pos, meta) + for leaf_dx, leaf_slice in enumerate(leaves): + leaf_x = feature_x + (leaf_dx - half_leaves) - # Form slice of sky, grass, stone, bedrock - slice_ = ( - [' '] * (world_gen['height'] - slice_height_) + - ['-'] + - ['#'] * (slice_height_ - 2) + # 2 for grass and bedrock - ['_'] - ) + air_height = world_gen['height'] - ground_heights[str(leaf_x)] + leaf_height = air_height - tree['tree_height'] - len(leaf_slice) + tree['trunk_depth'] - # TODO: Combine loops in each of these functions? - slice_ = add_tree(slice_, pos, meta) - slice_ = add_ores(slice_, pos, meta, slice_height_) - slice_ = add_tall_grass(slice_, pos, meta, slice_height_) + # Add leaves to slice + for leaf_dy, leaf in enumerate(leaf_slice): + if leaf: + leaf_y = leaf_height + leaf_dy + chunk[str(leaf_x)][leaf_y] = spawn_hierarchy(('@', chunk[str(leaf_x)][leaf_y])) - return slice_ + # Add trunk to slice + air_height = world_gen['height'] - ground_heights[str(feature_x)] + for trunk_y in range(air_height - tree['tree_height'], air_height): + chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) + + # All ores + for name, ore in world_gen['ores'].items(): + feature_name = name + '_ore_root' + + if slice_features.get(feature_name): + ore = slice_features[feature_name] + + for d_x in range(-hill['height'] * hill['gradient_l'], + hill['height'] * hill['gradient_r']): + abs_pos = x + d_x + + pass + + return chunk def detect_edges(map_, edges): From c3493550d6cc80925c670d81e8e20580cda1c6c7 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Mon, 23 Nov 2015 18:43:39 +0000 Subject: [PATCH 07/42] Fix comment spelling --- terrain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terrain.py b/terrain.py index 7cbd399..ca1cf95 100644 --- a/terrain.py +++ b/terrain.py @@ -124,7 +124,7 @@ def add_ores(slice_, pos, meta, slice_height_): # Set seed for random numbers based on position and ore random.seed(str(meta['seed']) + str(x) + ore['char']) - # Gernerate a ore with a probability + # Generate an ore with a probability if random.random() <= ore['chance']: root_ore_height = random.randint(ore['lower'], ore['upper']) @@ -146,7 +146,7 @@ def add_tall_grass(slice_, pos, meta, slice_height_): # Set seed for random numbers based on position and grass random.seed(str(meta['seed']) + str(pos) + 'grass') - # Gernerate a grass with a probability + # Generate a grass with a probability if random.random() <= world_gen['tall_grass_rate']: sy = world_gen['height'] - slice_height_ - 1 slice_[sy] = spawn_hierarchy(('v', slice_[sy])) From 9a89b64112320e639e196f2806f24d2cd746aa7e Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Mon, 23 Nov 2015 18:50:06 +0000 Subject: [PATCH 08/42] Fixed some typo bugs --- terrain.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/terrain.py b/terrain.py index ca1cf95..da6ab4c 100644 --- a/terrain.py +++ b/terrain.py @@ -266,7 +266,7 @@ def gen_tree_features(features, ground_heights, chunk_pos, meta): if random.random() <= tree_chance: attrs = {} - attrs['type'] = random.randint(0, len(world_gen['trees'])) + attrs['type'] = random.randint(0, len(world_gen['trees'])-1) # Centre tree slice (contains trunk) # TODO: This calculation could be done on start-up, and stored @@ -277,7 +277,7 @@ def gen_tree_features(features, ground_heights, chunk_pos, meta): # Get space above ground air_height = world_gen['height'] - ground_heights[str(x)] - attrs['height'] = random.randint(2, air_height - len(center_leaves) + trunk_depth) + attrs['height'] = random.randint(2, air_height - len(center_leaves) + attrs['trunk_depth']) features['slices'][str(x)]['tree'] = attrs @@ -334,7 +334,7 @@ def gen_chunk(chunk_pos, meta): abs_pos = feature_x + d_x gradient = hill['gradient_l'] if feature_x < 0 else hill['gradient_r'] - hill_height = hill['height'] - (abs(d_x) / gradient) + hill_height = hill['height'] - int(abs(d_x) / gradient) # Make top of hill flat if d_x == 0: @@ -377,7 +377,7 @@ def gen_chunk(chunk_pos, meta): leaf_x = feature_x + (leaf_dx - half_leaves) air_height = world_gen['height'] - ground_heights[str(leaf_x)] - leaf_height = air_height - tree['tree_height'] - len(leaf_slice) + tree['trunk_depth'] + leaf_height = air_height - tree['height'] - len(leaf_slice) + tree['trunk_depth'] # Add leaves to slice for leaf_dy, leaf in enumerate(leaf_slice): @@ -387,7 +387,7 @@ def gen_chunk(chunk_pos, meta): # Add trunk to slice air_height = world_gen['height'] - ground_heights[str(feature_x)] - for trunk_y in range(air_height - tree['tree_height'], air_height): + for trunk_y in range(air_height - tree['height'], air_height): chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) # All ores From d6ee7ea5673f3b7d0c08efc173055a2f11af6d2d Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Mon, 23 Nov 2015 19:29:42 +0000 Subject: [PATCH 09/42] Fix inconsitancies with `chunk`, `chunk_n` and `chunk_pos` variables --- saves.py | 14 +++++++------- server.py | 8 ++++---- terrain.py | 10 +++++++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/saves.py b/saves.py index a94e338..7b86ac9 100644 --- a/saves.py +++ b/saves.py @@ -100,16 +100,16 @@ def save_meta(save, meta): json.dump(meta, f) -def chunk_file_name(save, chunk): - return save_path(save, str(chunk) + CHUNK_EXT) +def chunk_file_name(save, chunk_n): + return save_path(save, str(chunk_n) + CHUNK_EXT) -def load_chunk(save, chunk): +def load_chunk(save, chunk_n): map_ = {} - chunk_pos = chunk * world_gen['chunk_size'] + chunk_pos = chunk_n * world_gen['chunk_size'] try: - with open(chunk_file_name(save, chunk)) as data: + with open(chunk_file_name(save, chunk_n)) as data: for d_pos, slice_ in enumerate(data): # Truncate to correct size @@ -128,10 +128,10 @@ def load_chunk(save, chunk): return map_ -def save_chunk(save, chunk_pos, chunk): +def save_chunk(save, chunk_n, chunk): """ Updates slices within one chunk. """ - filename = chunk_file_name(save, chunk_pos) + filename = chunk_file_name(save, chunk_n) if os.path.isfile(filename): mode = 'r+' else: diff --git a/server.py b/server.py index d9f6f1f..97a2c38 100644 --- a/server.py +++ b/server.py @@ -173,12 +173,12 @@ def get_chunks(self, chunk_list): debug('loading chunks', chunk_list) # Generates new terrain - for chunk_pos in chunk_list: + for chunk_n in chunk_list: - chunk = saves.load_chunk(self._save, chunk_pos) + chunk = saves.load_chunk(self._save, chunk_n) if not chunk: - chunk = terrain.gen_chunk(chunk_pos, self._meta) - saves.save_chunk(self._save, chunk_pos, chunk) + chunk = terrain.gen_chunk(chunk_n, self._meta) + saves.save_chunk(self._save, chunk_n, chunk) new_slices.update(chunk) debug('new slices', new_slices.keys()) diff --git a/terrain.py b/terrain.py index da6ab4c..ec87344 100644 --- a/terrain.py +++ b/terrain.py @@ -222,8 +222,11 @@ def gen_biome_features(features, chunk_pos, meta): features['chunks'][str(chunk_pos)]['biome'] = attrs -def gen_hill_features(features, rad, chunk_pos, meta): - for x in range(chunk_pos - rad, chunk_pos + world_gen['chunk_size'] + rad): +def gen_hill_features(features, chunk_pos, meta): + max_hill_rad = world_gen['max_hill'] * world_gen['min_grad'] + + for x in range(chunk_pos - max_hill_rad, + chunk_pos + world_gen['chunk_size'] + max_hill_rad): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. @@ -313,7 +316,8 @@ def gen_ore_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)][feature_name] = attrs -def gen_chunk(chunk_pos, meta): +def gen_chunk(chunk_n, meta): + chunk_pos = chunk_n * world_gen['chunk_size'] # First generate all the features we will need # for all the slice is in this chunk From 8c2f9de3c2ebecf3908d9a042649d7baae1bd3ab Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Mon, 23 Nov 2015 19:30:41 +0000 Subject: [PATCH 10/42] Generate all ground heights in gen_chunk loop --- terrain.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/terrain.py b/terrain.py index ec87344..fb27d59 100644 --- a/terrain.py +++ b/terrain.py @@ -358,12 +358,14 @@ def gen_chunk(chunk_n, meta): # Build slices chunk = {} for x in range(chunk_pos, chunk_pos + world_gen['chunk_size']): + # Add missing ground heights + ground_heights.setdefault(str(x), world_gen['ground_height']) # Form slice of sky, grass, stone, bedrock chunk[str(x)] = ( - [' '] * (world_gen['height'] - ground_heights[x]) + + [' '] * (world_gen['height'] - ground_heights[str(x)]) + ['-'] + - ['#'] * (ground_heights[x] - 2) + # 2 for grass and bedrock + ['#'] * (ground_heights[str(x)] - 2) + # 2 for grass and bedrock ['_'] ) From c0c1b3177a8e896c9beda9e34ed3ba48b6640b58 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Mon, 23 Nov 2015 22:49:32 +0000 Subject: [PATCH 11/42] Fix hill generation --- terrain.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/terrain.py b/terrain.py index 6f3c69b..216bd1b 100644 --- a/terrain.py +++ b/terrain.py @@ -323,7 +323,8 @@ def gen_chunk(chunk_n, meta): # for all the slice is in this chunk gen_biome_features(features, chunk_pos, meta) - gen_hill_features(features, RAD * 2, chunk_pos, meta) + gen_hill_features(features, chunk_pos, meta) + # Insert hills because the trees and ores depend on the ground height. ground_heights = {} @@ -337,18 +338,15 @@ def gen_chunk(chunk_n, meta): hill['height'] * hill['gradient_r']): abs_pos = feature_x + d_x - gradient = hill['gradient_l'] if feature_x < 0 else hill['gradient_r'] + gradient = hill['gradient_l'] if d_x < 0 else hill['gradient_r'] hill_height = hill['height'] - int(abs(d_x) / gradient) - # Make top of hill flat - if d_x == 0: - hill_height -= 1 - ground_height = world_gen['ground_height'] + hill_height old_height = ground_heights.get(str(abs_pos), 0) ground_heights[str(abs_pos)] = max(ground_height, old_height) + # We have to generate the ground heights before we can calculate the # features which depend on them From 2290e9eead4773ad7da2764182459c4d8bda3a96 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Mon, 23 Nov 2015 22:52:20 +0000 Subject: [PATCH 12/42] Make feature cache big enough to work --- terrain.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/terrain.py b/terrain.py index 216bd1b..442a7c1 100644 --- a/terrain.py +++ b/terrain.py @@ -154,7 +154,7 @@ def add_tall_grass(slice_, pos, meta, slice_height_): return slice_ -class Cache(OrderedDict): +class TerrainCache(OrderedDict): """ Implements a Dict with a size limit. Beyond which it replaces the oldest item. """ @@ -174,13 +174,16 @@ def _check_limit(self): # TODO: This probably shouldn't stay here... -n_chunks_in_cache = 4 -features = { - 'chunks': Cache(limit=n_chunks_in_cache), - 'slices': Cache(limit=n_chunks_in_cache * world_gen['chunk_size']) -} - -RAD = 16 +features = {} +def init_features(): + global features + cache_size = 2 * ((world_gen['max_hill'] * world_gen['min_grad'] * 2) + world_gen['chunk_size']) + features = { + 'chunks': TerrainCache(limit=(cache_size // world_gen['chunk_size']) + 1), + 'slices': TerrainCache(limit=cache_size) + } + +init_features() # # TODO: Use this for are the other functions! # def gen_features(generator, features, feature_group_name, chunk_pos, meta): From ffb6ebf373853acddbfc2fcb29892cba117455c5 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Mon, 23 Nov 2015 22:54:09 +0000 Subject: [PATCH 13/42] Correct commented comment spelling --- terrain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index 442a7c1..db8f75c 100644 --- a/terrain.py +++ b/terrain.py @@ -185,7 +185,8 @@ def init_features(): init_features() -# # TODO: Use this for are the other functions! + +# # TODO: Use this for the other functions! # def gen_features(generator, features, feature_group_name, chunk_pos, meta): # """ Ensures the features within `range` exist in `features` """ From cf03c3a7097ec62136ff84dc58abb6fd2d0805e8 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Tue, 24 Nov 2015 18:01:31 +0000 Subject: [PATCH 14/42] Fix hill generation to be compatible with the old version --- terrain.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/terrain.py b/terrain.py index db8f75c..d7fbde4 100644 --- a/terrain.py +++ b/terrain.py @@ -343,14 +343,16 @@ def gen_chunk(chunk_n, meta): abs_pos = feature_x + d_x gradient = hill['gradient_l'] if d_x < 0 else hill['gradient_r'] - hill_height = hill['height'] - int(abs(d_x) / gradient) + hill_height = int(hill['height'] - (abs(d_x) / gradient)) + + if d_x == 0: + hill_height -= 1 ground_height = world_gen['ground_height'] + hill_height old_height = ground_heights.get(str(abs_pos), 0) ground_heights[str(abs_pos)] = max(ground_height, old_height) - # We have to generate the ground heights before we can calculate the # features which depend on them From 84535033d77519ec135cb5a8780fca83dac7c331 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 25 Nov 2015 22:14:02 +0000 Subject: [PATCH 15/42] Chunk boundary display in debug --- render.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/render.py b/render.py index e8f1f20..960c2c5 100644 --- a/render.py +++ b/render.py @@ -2,7 +2,7 @@ import terrain from colors import * -from console import CLS, CLS_END, CLS_END_LN, REDRAW, POS_STR, supported_chars +from console import DEBUG, CLS, CLS_END, CLS_END_LN, REDRAW, POS_STR, supported_chars import data from data import world_gen @@ -35,7 +35,7 @@ def render_map(map_, objects, sun, lights, time, last_frame): # [2, '## ']] # Separates the pos and data - map_ = tuple(zip(*map_))[1] + world_positions, map_ = tuple(zip(*map_)) # Orientates the data map_ = zip(*map_) @@ -49,6 +49,10 @@ def render_map(map_, objects, sun, lights, time, last_frame): for x, pixel in enumerate(row): pixel_out = calc_pixel(x, y, pixel, objects, sun, lights, time) + + if DEBUG and y == 1 and world_positions[x] % world_gen['chunk_size'] == 0: + pixel_out = colorStr('*', bg=RED, fg=YELLOW) + this_frame[-1].append(pixel_out) try: From 1f6f0a463ce9216c5a02d529681ac4c6b2a958ef Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 16 Dec 2015 21:43:18 +0000 Subject: [PATCH 16/42] Fix gen_tree_features loop radius --- terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index d7fbde4..b2fc898 100644 --- a/terrain.py +++ b/terrain.py @@ -256,7 +256,7 @@ def gen_hill_features(features, chunk_pos, meta): def gen_tree_features(features, ground_heights, chunk_pos, meta): current_chunk_biome = features['chunks'][str(chunk_pos)]['biome']['type'] - for x in range(chunk_pos - RAD, chunk_pos + world_gen['chunk_size'] + RAD): + for x in range(chunk_pos - MAX_HALF_TREE, chunk_pos + world_gen['chunk_size'] + MAX_HALF_TREE): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. From 54899675de322874df9658d7387de35b115a5e1f Mon Sep 17 00:00:00 2001 From: Geraint White Date: Wed, 13 Jan 2016 22:01:10 +0000 Subject: [PATCH 17/42] Terrain gen debugging --- render.py | 2 +- terrain.py | 11 ++++++++++- tester.py | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/render.py b/render.py index 960c2c5..eb436a4 100644 --- a/render.py +++ b/render.py @@ -2,7 +2,7 @@ import terrain from colors import * -from console import DEBUG, CLS, CLS_END, CLS_END_LN, REDRAW, POS_STR, supported_chars +from console import DEBUG, CLS, CLS_END, CLS_END_LN, REDRAW, POS_STR, supported_chars, log import data from data import world_gen diff --git a/terrain.py b/terrain.py index b2fc898..238bb78 100644 --- a/terrain.py +++ b/terrain.py @@ -4,6 +4,7 @@ import render from data import world_gen +from console import log # Maximum width of half a tree @@ -255,6 +256,7 @@ def gen_hill_features(features, chunk_pos, meta): def gen_tree_features(features, ground_heights, chunk_pos, meta): current_chunk_biome = features['chunks'][str(chunk_pos)]['biome']['type'] + log(current_chunk_biome, m=1) for x in range(chunk_pos - MAX_HALF_TREE, chunk_pos + world_gen['chunk_size'] + MAX_HALF_TREE): @@ -357,7 +359,12 @@ def gen_chunk(chunk_n, meta): # features which depend on them gen_tree_features(features, ground_heights, chunk_pos, meta) - gen_ore_features(features, ground_heights, chunk_pos, meta) + # gen_ore_features(features, ground_heights, chunk_pos, meta) + + log('chunk_pos', chunk_pos, m=1) + tree_features = list(filter(lambda f: f[1].get('tree'), features['slices'].items())) + log('trees in cache\n', [str(f[0]) for f in tree_features], m=1, trunc=0) + log('trees in range', [str(f[0]) for f in tree_features if (chunk_pos <= int(f[0]) < chunk_pos + world_gen['chunk_size'])], m=1, trunc=0) # Build slices chunk = {} @@ -413,6 +420,8 @@ def gen_chunk(chunk_n, meta): pass + log('trees gen:', trees_gen, m=1) + return chunk diff --git a/tester.py b/tester.py index b3343ca..9cbd2c1 100644 --- a/tester.py +++ b/tester.py @@ -2,6 +2,7 @@ def main(): + pycraft.log('\n\n', m=1) save = pycraft.saves.new_save({'name': 'test', 'seed': 'This is a test!'}) pycraft.game(pycraft.server_interface.LocalInterface('tester', save, 0)) From afce778f78c809e1970e02ad1d468fdcf8e88c45 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Wed, 13 Jan 2016 22:01:29 +0000 Subject: [PATCH 18/42] Comments --- terrain.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/terrain.py b/terrain.py index 238bb78..b3cc314 100644 --- a/terrain.py +++ b/terrain.py @@ -129,6 +129,18 @@ def add_ores(slice_, pos, meta, slice_height_): if random.random() <= ore['chance']: root_ore_height = random.randint(ore['lower'], ore['upper']) + # TODO: Use this for vain gen! + """ + # Generates ore at random position around root ore + # TODO: Do we need a `vain_density` value per ore type? + + pot_vain_blocks = ore['vain_size'] ** 2 + # The bits describe the shape of the vain, + # top to bottom, left to right. + vain_shape = random.getkrandbits(pot_vain_blocks) + this_ores = vain_shape[] + """ + # Generates ore at random position around root ore random.seed(str(meta['seed']) + str(pos) + ore['char']) ore_height = (root_ore_height + @@ -435,6 +447,7 @@ def detect_edges(map_, edges): def spawn_hierarchy(tests): + # TODO: Use argument expansion for tests return max(tests, key=lambda block: blocks[block]['hierarchy']) From 38517a858a5b0361195be189760119e1eff6d288 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Wed, 13 Jan 2016 22:01:40 +0000 Subject: [PATCH 19/42] Fix incorrect biome issue --- terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index b3cc314..f87b7f2 100644 --- a/terrain.py +++ b/terrain.py @@ -234,7 +234,7 @@ def gen_biome_features(features, chunk_pos, meta): biomes_population.extend([name] * int(data['chance'] * 100)) attrs = {} - attrs['type'] = random.choice(biomes_population) + attrs['type'] = random.choice(sorted(biomes_population)) features['chunks'][str(chunk_pos)]['biome'] = attrs From a33a8eef5434b7e1b0c9db6417e238cbec5b7cd0 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 13 Jan 2016 22:03:56 +0000 Subject: [PATCH 20/42] Fix tree generation --- terrain.py | 31 ++++++++++++++++++------------- ui.py | 2 +- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/terrain.py b/terrain.py index f87b7f2..21986fb 100644 --- a/terrain.py +++ b/terrain.py @@ -343,7 +343,6 @@ def gen_chunk(chunk_n, meta): gen_biome_features(features, chunk_pos, meta) gen_hill_features(features, chunk_pos, meta) - # Insert hills because the trees and ores depend on the ground height. ground_heights = {} for feature_x, slice_features in features['slices'].items(): @@ -392,6 +391,8 @@ def gen_chunk(chunk_n, meta): ['_'] ) + trees_gen = 0 + # Insert trees and ores for feature_x, slice_features in features['slices'].items(): feature_x = int(feature_x) @@ -405,19 +406,23 @@ def gen_chunk(chunk_n, meta): for leaf_dx, leaf_slice in enumerate(leaves): leaf_x = feature_x + (leaf_dx - half_leaves) - air_height = world_gen['height'] - ground_heights[str(leaf_x)] - leaf_height = air_height - tree['height'] - len(leaf_slice) + tree['trunk_depth'] - - # Add leaves to slice - for leaf_dy, leaf in enumerate(leaf_slice): - if leaf: - leaf_y = leaf_height + leaf_dy - chunk[str(leaf_x)][leaf_y] = spawn_hierarchy(('@', chunk[str(leaf_x)][leaf_y])) + if chunk_pos <= leaf_x < chunk_pos + world_gen['chunk_size']: + air_height = world_gen['height'] - ground_heights[str(leaf_x)] + leaf_height = air_height - tree['height'] - len(leaf_slice) + tree['trunk_depth'] - # Add trunk to slice - air_height = world_gen['height'] - ground_heights[str(feature_x)] - for trunk_y in range(air_height - tree['height'], air_height): - chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) + # Add leaves to slice + for leaf_dy, leaf in enumerate(leaf_slice): + if leaf: + leaf_y = leaf_height + leaf_dy + chunk[str(leaf_x)][leaf_y] = spawn_hierarchy(('@', chunk[str(leaf_x)][leaf_y])) + + # Add trunk if in chunk + if chunk_pos <= feature_x < chunk_pos + world_gen['chunk_size']: + trees_gen += 1 + air_height = world_gen['height'] - ground_heights[str(feature_x)] + for trunk_y in range(air_height - tree['height'], air_height): + chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) + # chunk[str(feature_x)][air_height - 1] = spawn_hierarchy(('|', chunk[str(feature_x)][air_height - 1])) # All ores for name, ore in world_gen['ores'].items(): diff --git a/ui.py b/ui.py index 38b6886..90dffe1 100644 --- a/ui.py +++ b/ui.py @@ -157,7 +157,7 @@ def add_save(): save = saves.new_save(meta) if save is None: - return error('Error creating save') + error('Error creating save') else: return {'local': True, 'save': save} From 5546f876ec2667c88cb3c4ef81815283eb59b610 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Wed, 13 Jan 2016 22:13:30 +0000 Subject: [PATCH 21/42] Fix leaf slice height --- terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index 21986fb..99c8cc8 100644 --- a/terrain.py +++ b/terrain.py @@ -407,7 +407,7 @@ def gen_chunk(chunk_n, meta): leaf_x = feature_x + (leaf_dx - half_leaves) if chunk_pos <= leaf_x < chunk_pos + world_gen['chunk_size']: - air_height = world_gen['height'] - ground_heights[str(leaf_x)] + air_height = world_gen['height'] - ground_heights[str(feature_x)] leaf_height = air_height - tree['height'] - len(leaf_slice) + tree['trunk_depth'] # Add leaves to slice From 06c83cad1b52890e11944c264dd4e1a94e52b0b7 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 00:04:01 +0000 Subject: [PATCH 22/42] Reimplement ores with multiple y-levels! --- terrain.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/terrain.py b/terrain.py index 99c8cc8..de06a10 100644 --- a/terrain.py +++ b/terrain.py @@ -8,7 +8,12 @@ # Maximum width of half a tree +# TODO: Do the two half ranges like ores MAX_HALF_TREE = int(len(max(world_gen['trees'], key=lambda tree: len(tree))) / 2) + +largest_ore = max(map(lambda ore: world_gen['ores'][ore]['vain_size'], world_gen['ores'])) +MAX_ORE_RANGE = (int((largest_ore - 1) / 2), (int(largest_ore / 2) + 1)) + EMPTY_SLICE = [' ' for y in range(world_gen['height'])] get_chunk_list = lambda slice_list: list(set(int(i) // world_gen['chunk_size'] for i in slice_list)) @@ -304,7 +309,7 @@ def gen_tree_features(features, ground_heights, chunk_pos, meta): def gen_ore_features(features, ground_heights, chunk_pos, meta): - for x in range(chunk_pos - RAD, chunk_pos + world_gen['chunk_size'] + RAD): + for x in range(chunk_pos - MAX_ORE_RANGE[0], chunk_pos + world_gen['chunk_size'] + MAX_ORE_RANGE[1]): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. @@ -326,11 +331,19 @@ def gen_ore_features(features, ground_heights, chunk_pos, meta): if random.random() <= ore['chance']: attrs = {} - attrs['root_height'] = random.randint( + attrs['root_height'] = world_gen['height'] - random.randint( ore['lower'], min(ore['upper'], (ground_heights[str(x)] - 1)) # -1 for grass. ) + # Generates ore at random position around root ore + # TODO: Do we need a `vain_density` value per ore type? + pot_vain_blocks = ore['vain_size'] ** 2 + + # The bits describe the shape of the vain, + # top to bottom, left to right. + attrs['vain_shape'] = random.getrandbits(pot_vain_blocks) + features['slices'][str(x)][feature_name] = attrs @@ -370,7 +383,7 @@ def gen_chunk(chunk_n, meta): # features which depend on them gen_tree_features(features, ground_heights, chunk_pos, meta) - # gen_ore_features(features, ground_heights, chunk_pos, meta) + gen_ore_features(features, ground_heights, chunk_pos, meta) log('chunk_pos', chunk_pos, m=1) tree_features = list(filter(lambda f: f[1].get('tree'), features['slices'].items())) @@ -422,20 +435,28 @@ def gen_chunk(chunk_n, meta): air_height = world_gen['height'] - ground_heights[str(feature_x)] for trunk_y in range(air_height - tree['height'], air_height): chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) - # chunk[str(feature_x)][air_height - 1] = spawn_hierarchy(('|', chunk[str(feature_x)][air_height - 1])) # All ores for name, ore in world_gen['ores'].items(): feature_name = name + '_ore_root' if slice_features.get(feature_name): - ore = slice_features[feature_name] + ore_feature = slice_features[feature_name] + + for block_pos in range(ore['vain_size'] ** 2): + if ore_feature['vain_shape'] & (1 << block_pos): + + # Centre on root ore + block_dx = (block_pos % ore['vain_size']) - int((ore['vain_size'] - 1) / 2) + block_dy = int(block_pos / ore['vain_size']) - int((ore['vain_size'] - 1) / 2) - for d_x in range(-hill['height'] * hill['gradient_l'], - hill['height'] * hill['gradient_r']): - abs_pos = x + d_x + block_x = block_dx + feature_x + block_y = block_dy + ore_feature['root_height'] - pass + # Won't allow ore above surface + if chunk_pos <= block_x < chunk_pos + world_gen['chunk_size']: + if world_gen['height'] > block_y > world_gen['height'] - ground_heights[str(block_x)]: + chunk[str(block_x)][block_y] = spawn_hierarchy((ore['char'], chunk[str(block_x)][block_y])) log('trees gen:', trees_gen, m=1) From 9f2d6d64d8c980807203d3f35148cb6792d12cfb Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 00:17:02 +0000 Subject: [PATCH 23/42] Add density attributes to ore generation --- data.py | 9 ++++++++- terrain.py | 7 +++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/data.py b/data.py index 511cf64..17f19e9 100644 --- a/data.py +++ b/data.py @@ -335,10 +335,12 @@ 'trees': .2 } }, + # TODO: Densities need tuning. 'ores': { 'coal': { 'char': 'x', 'vain_size': 4, + 'vain_density': .4, 'chance': 0.05, 'upper': 30, 'lower': 1 @@ -346,6 +348,7 @@ 'iron': { 'char': '+', 'vain_size': 3, + 'vain_density': .3, 'chance': 0.03, 'upper': 15, 'lower': 1 @@ -353,6 +356,7 @@ 'redstone': { 'char': ':', 'vain_size': 4, + 'vain_density': .6, 'chance': 0.03, 'upper': 7, 'lower': 1 @@ -360,13 +364,15 @@ 'gold': { 'char': '"', 'vain_size': 2, + 'vain_density': .3, 'chance': 0.02, 'upper': 10, 'lower': 1 }, 'diamond': { 'char': 'o', - 'vain_size': 1, + 'vain_size': 2, + 'vain_density': .5, 'chance': 0.01, 'upper': 5, 'lower': 1 @@ -374,6 +380,7 @@ 'emerald': { 'char': '.', 'vain_size': 1, + 'vain_density': 1, 'chance': 0.002, 'upper': 7, 'lower': 1 diff --git a/terrain.py b/terrain.py index de06a10..aeee9ec 100644 --- a/terrain.py +++ b/terrain.py @@ -337,12 +337,11 @@ def gen_ore_features(features, ground_heights, chunk_pos, meta): ) # Generates ore at random position around root ore - # TODO: Do we need a `vain_density` value per ore type? pot_vain_blocks = ore['vain_size'] ** 2 - # The bits describe the shape of the vain, + # Describes the shape of the vain, # top to bottom, left to right. - attrs['vain_shape'] = random.getrandbits(pot_vain_blocks) + attrs['vain_shape'] = [b / 100 for b in random.sample(range(0, 100), pot_vain_blocks)] features['slices'][str(x)][feature_name] = attrs @@ -444,7 +443,7 @@ def gen_chunk(chunk_n, meta): ore_feature = slice_features[feature_name] for block_pos in range(ore['vain_size'] ** 2): - if ore_feature['vain_shape'] & (1 << block_pos): + if ore_feature['vain_shape'][block_pos] < ore['vain_density']: # Centre on root ore block_dx = (block_pos % ore['vain_size']) - int((ore['vain_size'] - 1) / 2) From 14ac704879ae65503b877c69f52d606efce8a132 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 00:31:54 +0000 Subject: [PATCH 24/42] Remove old terrain gen functions --- terrain.py | 182 ++++++++--------------------------------------------- 1 file changed, 26 insertions(+), 156 deletions(-) diff --git a/terrain.py b/terrain.py index aeee9ec..aca7ac0 100644 --- a/terrain.py +++ b/terrain.py @@ -29,147 +29,26 @@ def move_map(map_, edges): return slices -def slice_height(pos, meta): - slice_height_ = world_gen['ground_height'] - - # Check surrounding slices for a hill with min gradient - for x in range(pos - world_gen['max_hill'] * world_gen['min_grad'], - pos + world_gen['max_hill'] * world_gen['min_grad']): - # Set seed for random numbers based on position - random.seed(str(meta['seed']) + str(x) + 'hill') - - # Generate a hill with a 5% chance - if random.random() <= 0.05: - - # Get gradient for left, or right side of hill - gradient_l = random.randint(1, world_gen['min_grad']) - gradient_r = random.randint(1, world_gen['min_grad']) - - gradient = gradient_r if x < pos else gradient_l - - # Height is distance from hill with gradient - d_hill_height = abs(pos-x) / gradient - - # Cut off anything that would not be a part of the hill assuming - # flat ground. - if d_hill_height < world_gen['max_hill']: - - hill_height = (world_gen['ground_height'] + - random.randint(0, world_gen['max_hill']) - d_hill_height) - # Make top of hill flat - hill_height -= 1 if pos == x else 0 - - slice_height_ = max(slice_height_, hill_height) - - return int(slice_height_) - - -def add_tree(slice_, pos, meta): - for x in range(pos - MAX_HALF_TREE, pos + MAX_HALF_TREE + 1): - tree_chance = biome(x, meta) - - # Set seed for random numbers based on position - random.seed(str(meta['seed']) + str(x) + 'tree') - - # Generate a tree with a chance dependent on the biome - if random.random() <= tree_chance: - tree = random.choice(world_gen['trees']) - - # Get height above ground - air_height = world_gen['height'] - slice_height(x, meta) - - # Centre tree slice (contains trunk) - center_leaves = tree[int(len(tree)/2)] - trunk_depth = next(i for i, leaf in enumerate(center_leaves[::-1]) - if leaf) - tree_height = random.randint(2, air_height - - len(center_leaves) + trunk_depth) - - # Find leaves of current tree - for i, leaf_slice in enumerate(tree): - leaf_pos = x + (i - int(len(tree) / 2)) - if leaf_pos == pos: - leaf_height = air_height - tree_height - (len(leaf_slice) - trunk_depth) - - # Add leaves to slice - for j, leaf in enumerate(leaf_slice): - if leaf: - sy = leaf_height + j - slice_[sy] = spawn_hierarchy(('@', slice_[sy])) - - if x == pos: - # Add trunk to slice - for i in range(air_height - tree_height, - air_height): - slice_[i] = spawn_hierarchy(('|', slice_[i])) - - return slice_ - - -def biome(pos, meta): - biome_type = [] - - # Check surrounding slices for a biome marker - for biome_x in range(pos - int(world_gen['max_biome_size'] / 2), - pos + int(world_gen['max_biome_size'] / 2)): - # Set seed for random numbers based on position - random.seed(str(meta['seed']) + str(biome_x) + 'biome') - - # Generate a biome marker with a 5% chance - # if random.random() <= .05: - # biome_type.append(random.choice(world_gen['biome_tree_weights'])) - - # If not plains or forest, it's normal - return max(set(biome_type), key=biome_type.count) if biome_type else .05 - - -def add_ores(slice_, pos, meta, slice_height_): - for ore in world_gen['ores'].values(): - for x in range(pos - int(ore['vain_size'] / 2), - pos + ceil(ore['vain_size'] / 2)): - # Set seed for random numbers based on position and ore - random.seed(str(meta['seed']) + str(x) + ore['char']) - - # Generate an ore with a probability - if random.random() <= ore['chance']: - root_ore_height = random.randint(ore['lower'], ore['upper']) - - # TODO: Use this for vain gen! - """ - # Generates ore at random position around root ore - # TODO: Do we need a `vain_density` value per ore type? - - pot_vain_blocks = ore['vain_size'] ** 2 - # The bits describe the shape of the vain, - # top to bottom, left to right. - vain_shape = random.getkrandbits(pot_vain_blocks) - this_ores = vain_shape[] - """ +def detect_edges(map_, edges): + slices = [] + for pos in range(*edges): + if not str(pos) in map_: + slices.append(pos) - # Generates ore at random position around root ore - random.seed(str(meta['seed']) + str(pos) + ore['char']) - ore_height = (root_ore_height + - random.randint(-int(ore['vain_size'] / 2), - ceil(ore['vain_size'] / 2))) + return slices - # Won't allow ore above surface - if ore['lower'] < ore_height < min(ore['upper'], slice_height_): - sy = world_gen['height'] - ore_height - slice_[sy] = spawn_hierarchy((ore['char'], slice_[sy])) - return slice_ +def spawn_hierarchy(tests): + # TODO: Use argument expansion for tests + return max(tests, key=lambda block: blocks[block]['hierarchy']) -def add_tall_grass(slice_, pos, meta, slice_height_): - # Set seed for random numbers based on position and grass - random.seed(str(meta['seed']) + str(pos) + 'grass') +def is_solid(block): + return blocks[block]['solid'] - # Generate a grass with a probability - if random.random() <= world_gen['tall_grass_rate']: - sy = world_gen['height'] - slice_height_ - 1 - slice_[sy] = spawn_hierarchy(('v', slice_[sy])) - return slice_ +def ground_height(slice_): + return next(i for i, block in enumerate(slice_) if blocks[block]['solid']) class TerrainCache(OrderedDict): @@ -346,6 +225,19 @@ def gen_ore_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)][feature_name] = attrs +# TODO: Convert to new terrain system +def add_tall_grass(slice_, pos, meta, slice_height_): + # Set seed for random numbers based on position and grass + random.seed(str(meta['seed']) + str(pos) + 'grass') + + # Generate a grass with a probability + if random.random() <= world_gen['tall_grass_rate']: + sy = world_gen['height'] - slice_height_ - 1 + slice_[sy] = spawn_hierarchy(('v', slice_[sy])) + + return slice_ + + def gen_chunk(chunk_n, meta): chunk_pos = chunk_n * world_gen['chunk_size'] @@ -460,25 +352,3 @@ def gen_chunk(chunk_n, meta): log('trees gen:', trees_gen, m=1) return chunk - - -def detect_edges(map_, edges): - slices = [] - for pos in range(*edges): - if not str(pos) in map_: - slices.append(pos) - - return slices - - -def spawn_hierarchy(tests): - # TODO: Use argument expansion for tests - return max(tests, key=lambda block: blocks[block]['hierarchy']) - - -def is_solid(block): - return blocks[block]['solid'] - - -def ground_height(slice_): - return next(i for i, block in enumerate(slice_) if blocks[block]['solid']) From 6ab6c2e5d15a85d15fc85ada294a3ba7336565b5 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 13:53:49 +0000 Subject: [PATCH 25/42] Reimplelment tall grass --- terrain.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/terrain.py b/terrain.py index aca7ac0..18a7ee3 100644 --- a/terrain.py +++ b/terrain.py @@ -225,17 +225,26 @@ def gen_ore_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)][feature_name] = attrs -# TODO: Convert to new terrain system -def add_tall_grass(slice_, pos, meta, slice_height_): - # Set seed for random numbers based on position and grass - random.seed(str(meta['seed']) + str(pos) + 'grass') +def gen_grass_features(features, ground_heights, chunk_pos, meta): + for x in range(chunk_pos, chunk_pos + world_gen['chunk_size']): + + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. + + if features['slices'].get(str(x)) is None: + # Init to empty, so 'no features' is cached. + features['slices'][str(x)] = {} + + # If it is not None, it has all ready been generated. + if features['slices'][str(x)].get('grass') is None: - # Generate a grass with a probability - if random.random() <= world_gen['tall_grass_rate']: - sy = world_gen['height'] - slice_height_ - 1 - slice_[sy] = spawn_hierarchy(('v', slice_[sy])) + random.seed(str(meta['seed']) + str(x) + 'grass') + if random.random() <= world_gen['tall_grass_rate']: - return slice_ + attrs = {} + attrs['y'] = ground_heights[str(x)] + + features['slices'][str(x)]['grass'] = attrs def gen_chunk(chunk_n, meta): @@ -275,6 +284,7 @@ def gen_chunk(chunk_n, meta): gen_tree_features(features, ground_heights, chunk_pos, meta) gen_ore_features(features, ground_heights, chunk_pos, meta) + gen_grass_features(features, ground_heights, chunk_pos, meta) log('chunk_pos', chunk_pos, m=1) tree_features = list(filter(lambda f: f[1].get('tree'), features['slices'].items())) @@ -349,6 +359,12 @@ def gen_chunk(chunk_n, meta): if world_gen['height'] > block_y > world_gen['height'] - ground_heights[str(block_x)]: chunk[str(block_x)][block_y] = spawn_hierarchy((ore['char'], chunk[str(block_x)][block_y])) + if slice_features.get('grass'): + if chunk_pos <= feature_x < chunk_pos + world_gen['chunk_size']: + grass_feature = slice_features['grass'] + grass_y = world_gen['height'] - ground_heights[str(feature_x)] - 1 + chunk[str(feature_x)][grass_y] = spawn_hierarchy(('v', chunk[str(feature_x)][grass_y])) + log('trees gen:', trees_gen, m=1) return chunk From 59b25dd477dcf331f33a522f429e00392791cda0 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 14:00:08 +0000 Subject: [PATCH 26/42] Extend terrain debugging --- terrain.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/terrain.py b/terrain.py index 18a7ee3..d51a634 100644 --- a/terrain.py +++ b/terrain.py @@ -306,6 +306,8 @@ def gen_chunk(chunk_n, meta): ) trees_gen = 0 + grass_gen = 0 + ores_gen = 0 # Insert trees and ores for feature_x, slice_features in features['slices'].items(): @@ -337,11 +339,19 @@ def gen_chunk(chunk_n, meta): for trunk_y in range(air_height - tree['height'], air_height): chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) + if slice_features.get('grass'): + if chunk_pos <= feature_x < chunk_pos + world_gen['chunk_size']: + grass_gen += 1 + grass_feature = slice_features['grass'] + grass_y = world_gen['height'] - ground_heights[str(feature_x)] - 1 + chunk[str(feature_x)][grass_y] = spawn_hierarchy(('v', chunk[str(feature_x)][grass_y])) + # All ores for name, ore in world_gen['ores'].items(): feature_name = name + '_ore_root' if slice_features.get(feature_name): + ores_gen += 1 ore_feature = slice_features[feature_name] for block_pos in range(ore['vain_size'] ** 2): @@ -359,12 +369,8 @@ def gen_chunk(chunk_n, meta): if world_gen['height'] > block_y > world_gen['height'] - ground_heights[str(block_x)]: chunk[str(block_x)][block_y] = spawn_hierarchy((ore['char'], chunk[str(block_x)][block_y])) - if slice_features.get('grass'): - if chunk_pos <= feature_x < chunk_pos + world_gen['chunk_size']: - grass_feature = slice_features['grass'] - grass_y = world_gen['height'] - ground_heights[str(feature_x)] - 1 - chunk[str(feature_x)][grass_y] = spawn_hierarchy(('v', chunk[str(feature_x)][grass_y])) - log('trees gen:', trees_gen, m=1) + log('grass gen:', grass_gen, m=1) + log('ores gen:', ores_gen, m=1) return chunk From ce475eafb86c986d8f346dd8696cf947e73262da Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 14:29:31 +0000 Subject: [PATCH 27/42] Moved feature drawing into functions --- terrain.py | 116 ++++++++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/terrain.py b/terrain.py index d51a634..fe34a72 100644 --- a/terrain.py +++ b/terrain.py @@ -247,6 +247,63 @@ def gen_grass_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)]['grass'] = attrs +def in_chunk(pos, chunk_pos): + return chunk_pos <= pos < chunk_pos + world_gen['chunk_size'] + + +def build_tree(chunk, chunk_pos, x, tree_feature, ground_heights): + """ Adds a tree feature at x to the chunk. """ + + # Add trunk + if in_chunk(x, chunk_pos): + air_height = world_gen['height'] - ground_heights[str(x)] + for trunk_y in range(air_height - tree_feature['height'], air_height): + chunk[str(x)][trunk_y] = spawn_hierarchy(('|', chunk[str(x)][trunk_y])) + + # Add leaves + leaves = world_gen['trees'][tree_feature['type']] + half_leaves = int(len(leaves) / 2) + + for leaf_dx, leaf_slice in enumerate(leaves): + leaf_x = x + (leaf_dx - half_leaves) + + if in_chunk(leaf_x, chunk_pos): + air_height = world_gen['height'] - ground_heights[str(x)] + leaf_height = air_height - tree_feature['height'] - len(leaf_slice) + tree_feature['trunk_depth'] + + for leaf_dy, leaf in enumerate(leaf_slice): + if leaf: + leaf_y = leaf_height + leaf_dy + chunk[str(leaf_x)][leaf_y] = spawn_hierarchy(('@', chunk[str(leaf_x)][leaf_y])) + + +def build_grass(chunk, chunk_pos, x, grass_feature, ground_heights): + """ Adds a grass feature at x to the chunk. """ + + if in_chunk(x, chunk_pos): + grass_y = world_gen['height'] - ground_heights[str(x)] - 1 + chunk[str(x)][grass_y] = spawn_hierarchy(('v', chunk[str(x)][grass_y])) + + +def build_ore(chunk, chunk_pos, x, ore_feature, ore, ground_heights): + """ Adds an ore feature at x to the chunk. """ + + for block_pos in range(ore['vain_size'] ** 2): + if ore_feature['vain_shape'][block_pos] < ore['vain_density']: + + # Centre on root ore + block_dx = (block_pos % ore['vain_size']) - int((ore['vain_size'] - 1) / 2) + block_dy = int(block_pos / ore['vain_size']) - int((ore['vain_size'] - 1) / 2) + + block_x = block_dx + x + block_y = block_dy + ore_feature['root_height'] + + # Won't allow ore above surface + if chunk_pos <= block_x < chunk_pos + world_gen['chunk_size']: + if world_gen['height'] > block_y > world_gen['height'] - ground_heights[str(block_x)]: + chunk[str(block_x)][block_y] = spawn_hierarchy((ore['char'], chunk[str(block_x)][block_y])) + + def gen_chunk(chunk_n, meta): chunk_pos = chunk_n * world_gen['chunk_size'] @@ -305,72 +362,23 @@ def gen_chunk(chunk_n, meta): ['_'] ) - trees_gen = 0 - grass_gen = 0 - ores_gen = 0 - # Insert trees and ores for feature_x, slice_features in features['slices'].items(): feature_x = int(feature_x) - # Tree if slice_features.get('tree'): - tree = slice_features['tree'] - leaves = world_gen['trees'][tree['type']] - half_leaves = int(len(leaves) / 2) - - for leaf_dx, leaf_slice in enumerate(leaves): - leaf_x = feature_x + (leaf_dx - half_leaves) - - if chunk_pos <= leaf_x < chunk_pos + world_gen['chunk_size']: - air_height = world_gen['height'] - ground_heights[str(feature_x)] - leaf_height = air_height - tree['height'] - len(leaf_slice) + tree['trunk_depth'] - - # Add leaves to slice - for leaf_dy, leaf in enumerate(leaf_slice): - if leaf: - leaf_y = leaf_height + leaf_dy - chunk[str(leaf_x)][leaf_y] = spawn_hierarchy(('@', chunk[str(leaf_x)][leaf_y])) - - # Add trunk if in chunk - if chunk_pos <= feature_x < chunk_pos + world_gen['chunk_size']: - trees_gen += 1 - air_height = world_gen['height'] - ground_heights[str(feature_x)] - for trunk_y in range(air_height - tree['height'], air_height): - chunk[str(feature_x)][trunk_y] = spawn_hierarchy(('|', chunk[str(feature_x)][trunk_y])) + tree_feature = slice_features['tree'] + build_tree(chunk, chunk_pos, feature_x, tree_feature, ground_heights) if slice_features.get('grass'): - if chunk_pos <= feature_x < chunk_pos + world_gen['chunk_size']: - grass_gen += 1 - grass_feature = slice_features['grass'] - grass_y = world_gen['height'] - ground_heights[str(feature_x)] - 1 - chunk[str(feature_x)][grass_y] = spawn_hierarchy(('v', chunk[str(feature_x)][grass_y])) + grass_feature = slice_features['grass'] + build_grass(chunk, chunk_pos, feature_x, grass_feature, ground_heights) - # All ores for name, ore in world_gen['ores'].items(): feature_name = name + '_ore_root' if slice_features.get(feature_name): - ores_gen += 1 ore_feature = slice_features[feature_name] - - for block_pos in range(ore['vain_size'] ** 2): - if ore_feature['vain_shape'][block_pos] < ore['vain_density']: - - # Centre on root ore - block_dx = (block_pos % ore['vain_size']) - int((ore['vain_size'] - 1) / 2) - block_dy = int(block_pos / ore['vain_size']) - int((ore['vain_size'] - 1) / 2) - - block_x = block_dx + feature_x - block_y = block_dy + ore_feature['root_height'] - - # Won't allow ore above surface - if chunk_pos <= block_x < chunk_pos + world_gen['chunk_size']: - if world_gen['height'] > block_y > world_gen['height'] - ground_heights[str(block_x)]: - chunk[str(block_x)][block_y] = spawn_hierarchy((ore['char'], chunk[str(block_x)][block_y])) - - log('trees gen:', trees_gen, m=1) - log('grass gen:', grass_gen, m=1) - log('ores gen:', ores_gen, m=1) + build_ore(chunk, chunk_pos, feature_x, ore_feature, ore, ground_heights) return chunk From 6af6dcdcef33256a13c466bf24347ba2b2c3b031 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 14:37:23 +0000 Subject: [PATCH 28/42] Simplify slice_features branching --- terrain.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/terrain.py b/terrain.py index fe34a72..a638652 100644 --- a/terrain.py +++ b/terrain.py @@ -298,8 +298,8 @@ def build_ore(chunk, chunk_pos, x, ore_feature, ore, ground_heights): block_x = block_dx + x block_y = block_dy + ore_feature['root_height'] - # Won't allow ore above surface - if chunk_pos <= block_x < chunk_pos + world_gen['chunk_size']: + if in_chunk(block_x, chunk_pos): + # Won't allow ore above surface if world_gen['height'] > block_y > world_gen['height'] - ground_heights[str(block_x)]: chunk[str(block_x)][block_y] = spawn_hierarchy((ore['char'], chunk[str(block_x)][block_y])) @@ -307,6 +307,8 @@ def build_ore(chunk, chunk_pos, x, ore_feature, ore, ground_heights): def gen_chunk(chunk_n, meta): chunk_pos = chunk_n * world_gen['chunk_size'] + # TODO: Allow more than one feature per x in features? + # First generate all the features we will need # for all the slice is in this chunk @@ -366,19 +368,19 @@ def gen_chunk(chunk_n, meta): for feature_x, slice_features in features['slices'].items(): feature_x = int(feature_x) - if slice_features.get('tree'): - tree_feature = slice_features['tree'] - build_tree(chunk, chunk_pos, feature_x, tree_feature, ground_heights) + for feature_name, feature in slice_features.items(): - if slice_features.get('grass'): - grass_feature = slice_features['grass'] - build_grass(chunk, chunk_pos, feature_x, grass_feature, ground_heights) + if feature_name == 'tree': + build_tree(chunk, chunk_pos, feature_x, feature, ground_heights) - for name, ore in world_gen['ores'].items(): - feature_name = name + '_ore_root' + elif feature_name == 'grass': + build_grass(chunk, chunk_pos, feature_x, feature, ground_heights) + + else: + for name, ore in world_gen['ores'].items(): + ore_name = name + '_ore_root' - if slice_features.get(feature_name): - ore_feature = slice_features[feature_name] - build_ore(chunk, chunk_pos, feature_x, ore_feature, ore, ground_heights) + if feature_name == ore_name: + build_ore(chunk, chunk_pos, feature_x, feature, ore, ground_heights) return chunk From a9b7ebc2624564d11f81badc21af94197b256c6f Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 15:59:51 +0000 Subject: [PATCH 29/42] Add gaps at bottom of trees in debug mode --- terrain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terrain.py b/terrain.py index a638652..6c02cd2 100644 --- a/terrain.py +++ b/terrain.py @@ -4,7 +4,7 @@ import render from data import world_gen -from console import log +from console import log, DEBUG # Maximum width of half a tree @@ -257,7 +257,7 @@ def build_tree(chunk, chunk_pos, x, tree_feature, ground_heights): # Add trunk if in_chunk(x, chunk_pos): air_height = world_gen['height'] - ground_heights[str(x)] - for trunk_y in range(air_height - tree_feature['height'], air_height): + for trunk_y in range(air_height - tree_feature['height'], air_height - (DEBUG * 3)): chunk[str(x)][trunk_y] = spawn_hierarchy(('|', chunk[str(x)][trunk_y])) # Add leaves From 46d2887ee4c1e1278b7872a04b25171cf94fea02 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 16:00:25 +0000 Subject: [PATCH 30/42] Fix loop in terrain building --- terrain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/terrain.py b/terrain.py index 6c02cd2..edbc795 100644 --- a/terrain.py +++ b/terrain.py @@ -382,5 +382,6 @@ def gen_chunk(chunk_n, meta): if feature_name == ore_name: build_ore(chunk, chunk_pos, feature_x, feature, ore, ground_heights) + break return chunk From 39c9c696d7860a4a6b727d8834693ddc02fb003b Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 16:30:11 +0000 Subject: [PATCH 31/42] Fix debug for trees --- terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index edbc795..b204a26 100644 --- a/terrain.py +++ b/terrain.py @@ -257,7 +257,7 @@ def build_tree(chunk, chunk_pos, x, tree_feature, ground_heights): # Add trunk if in_chunk(x, chunk_pos): air_height = world_gen['height'] - ground_heights[str(x)] - for trunk_y in range(air_height - tree_feature['height'], air_height - (DEBUG * 3)): + for trunk_y in range(air_height - tree_feature['height'], air_height - (bool(DEBUG) * 3)): chunk[str(x)][trunk_y] = spawn_hierarchy(('|', chunk[str(x)][trunk_y])) # Add leaves From fefd5a49818285a7da346de653d0478e8a740c16 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 19:36:02 +0000 Subject: [PATCH 32/42] Clean up changes in get_pos_delta --- player.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/player.py b/player.py index 6eff8e3..6cc2fdd 100644 --- a/player.py +++ b/player.py @@ -24,16 +24,14 @@ def get_pos_delta(char, map_, x, y, jump): dy = 0 dx = 0 - is_solid = lambda block: terrain.is_solid(block) - # Calculate change in x pos for left and right movement - for test_char, dir_, func in (('a', -1, left_slice), ('d', 1, right_slice)): + for test_char, dir_, next_slice in (('a', -1, left_slice), ('d', 1, right_slice)): if ( char in test_char - and not is_solid( func[head_y] )): + and not terrain.is_solid( next_slice[head_y] )): - if is_solid( func[feet_y] ): - if ( not is_solid( func[above_y] ) - and not is_solid( player_slice[above_y] )): + if terrain.is_solid( next_slice[feet_y] ): + if ( not terrain.is_solid( next_slice[above_y] ) + and not terrain.is_solid( player_slice[above_y] )): dy = -1 dx = dir_ @@ -42,8 +40,8 @@ def get_pos_delta(char, map_, x, y, jump): # Jumps if up pressed, block below, no block above if ( char in 'w' and y > 1 - and not is_solid( player_slice[above_y] ) - and ( is_solid( player_slice[below_y] ) + and not terrain.is_solid( player_slice[above_y] ) + and ( terrain.is_solid( player_slice[below_y] ) or player_slice[feet_y] == '=' )): dy = -1 From 955cfa394bd312d1ef8698eaf2cd0ff2771e5122 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 20:26:10 +0000 Subject: [PATCH 33/42] Clean up --- terrain.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/terrain.py b/terrain.py index b204a26..ffe5afb 100644 --- a/terrain.py +++ b/terrain.py @@ -8,7 +8,6 @@ # Maximum width of half a tree -# TODO: Do the two half ranges like ores MAX_HALF_TREE = int(len(max(world_gen['trees'], key=lambda tree: len(tree))) / 2) largest_ore = max(map(lambda ore: world_gen['ores'][ore]['vain_size'], world_gen['ores'])) @@ -47,10 +46,6 @@ def is_solid(block): return blocks[block]['solid'] -def ground_height(slice_): - return next(i for i, block in enumerate(slice_) if blocks[block]['solid']) - - class TerrainCache(OrderedDict): """ Implements a Dict with a size limit. Beyond which it replaces the oldest item. """ @@ -74,7 +69,7 @@ def _check_limit(self): features = {} def init_features(): global features - cache_size = 2 * ((world_gen['max_hill'] * world_gen['min_grad'] * 2) + world_gen['chunk_size']) + cache_size = (world_gen['max_hill'] * world_gen['min_grad'] * 2) + world_gen['chunk_size'] features = { 'chunks': TerrainCache(limit=(cache_size // world_gen['chunk_size']) + 1), 'slices': TerrainCache(limit=cache_size) From 6874262a6f9fac74ffe5740d45c9dc06c140c28b Mon Sep 17 00:00:00 2001 From: Geraint White Date: Thu, 14 Jan 2016 20:49:05 +0000 Subject: [PATCH 34/42] Initialise ground_heights, fixes #77 --- terrain.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/terrain.py b/terrain.py index b204a26..cbd2e84 100644 --- a/terrain.py +++ b/terrain.py @@ -18,6 +18,9 @@ get_chunk_list = lambda slice_list: list(set(int(i) // world_gen['chunk_size'] for i in slice_list)) +MAX_HILL_RAD = world_gen['max_hill'] * world_gen['min_grad'] +hill_range = lambda chunk_pos: range(chunk_pos - MAX_HILL_RAD, chunk_pos + world_gen['chunk_size'] + MAX_HILL_RAD) + blocks = render.blocks @@ -74,7 +77,7 @@ def _check_limit(self): features = {} def init_features(): global features - cache_size = 2 * ((world_gen['max_hill'] * world_gen['min_grad'] * 2) + world_gen['chunk_size']) + cache_size = (world_gen['max_hill'] * world_gen['min_grad'] * 2) + world_gen['chunk_size'] features = { 'chunks': TerrainCache(limit=(cache_size // world_gen['chunk_size']) + 1), 'slices': TerrainCache(limit=cache_size) @@ -124,10 +127,7 @@ def gen_biome_features(features, chunk_pos, meta): def gen_hill_features(features, chunk_pos, meta): - max_hill_rad = world_gen['max_hill'] * world_gen['min_grad'] - - for x in range(chunk_pos - max_hill_rad, - chunk_pos + world_gen['chunk_size'] + max_hill_rad): + for x in hill_range(chunk_pos): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. @@ -316,7 +316,7 @@ def gen_chunk(chunk_n, meta): gen_hill_features(features, chunk_pos, meta) # Insert hills because the trees and ores depend on the ground height. - ground_heights = {} + ground_heights = {str(x): world_gen['ground_height'] for x in hill_range(chunk_pos)} for feature_x, slice_features in features['slices'].items(): feature_x = int(feature_x) @@ -338,6 +338,12 @@ def gen_chunk(chunk_n, meta): old_height = ground_heights.get(str(abs_pos), 0) ground_heights[str(abs_pos)] = max(ground_height, old_height) + int_x = list(map(int, ground_heights.keys())) + log('chunk', chunk_pos, m=1) + log('max', max(int_x), m=1) + log('min', min(int_x), m=1) + log('gh diff', set(hill_range(chunk_pos)) - set(int_x), m=1, trunc=False) + # We have to generate the ground heights before we can calculate the # features which depend on them From 8f0ad9a374a5e99e60b70ed817423f10456c5dd5 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Thu, 14 Jan 2016 21:14:29 +0000 Subject: [PATCH 35/42] Move in_chunk function to top --- terrain.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terrain.py b/terrain.py index ffe5afb..a8830c2 100644 --- a/terrain.py +++ b/terrain.py @@ -46,6 +46,10 @@ def is_solid(block): return blocks[block]['solid'] +def in_chunk(pos, chunk_pos): + return chunk_pos <= pos < chunk_pos + world_gen['chunk_size'] + + class TerrainCache(OrderedDict): """ Implements a Dict with a size limit. Beyond which it replaces the oldest item. """ @@ -242,10 +246,6 @@ def gen_grass_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)]['grass'] = attrs -def in_chunk(pos, chunk_pos): - return chunk_pos <= pos < chunk_pos + world_gen['chunk_size'] - - def build_tree(chunk, chunk_pos, x, tree_feature, ground_heights): """ Adds a tree feature at x to the chunk. """ From d02ed8406ecc12b0f608ccaf54b9146d7fd810ce Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Sun, 17 Jan 2016 19:49:37 +0000 Subject: [PATCH 36/42] Rewrite biome generation to behave like trees, ref #71 --- data.py | 19 ++++---- terrain.py | 124 +++++++++++++++++++++++++++++------------------------ 2 files changed, 80 insertions(+), 63 deletions(-) diff --git a/data.py b/data.py index 17f19e9..b40cd35 100644 --- a/data.py +++ b/data.py @@ -319,20 +319,23 @@ 'min_grad': 5, 'ground_height': 10, 'chunk_size': 16, - 'max_biome_size': 50, - 'tall_grass_rate': .25, + 'min_biome': 16, + 'max_biome': 64, 'biomes': { 'plains': { - 'chance': .4, - 'trees': 0 + 'chance': .3, + 'trees': 0, + 'grass': .15 }, 'normal': { - 'chance': .4, - 'trees': .05 + 'chance': .3, + 'trees': .05, + 'grass': .1 }, 'forest': { - 'chance': .2, - 'trees': .2 + 'chance': .3, + 'trees': .2, + 'grass': 0 } }, # TODO: Densities need tuning. diff --git a/terrain.py b/terrain.py index 0056577..ac9cbf4 100644 --- a/terrain.py +++ b/terrain.py @@ -102,27 +102,31 @@ def init_features(): def gen_biome_features(features, chunk_pos, meta): - # TODO: Each of these `if` blocks should be abstracted into a function - # which just returns the `attrs` object. + for x in range(chunk_pos - world_gen['max_biome'], chunk_pos + world_gen['chunk_size'] + world_gen['max_biome']): - if features['chunks'].get(str(chunk_pos)) is None: - # Init to empty, so 'no features' is cached. - features['chunks'][str(chunk_pos)] = {} + # TODO: Each of these `if` blocks should be abstracted into a function + # which just returns the `attrs` object. + + if features['slices'].get(str(x)) is None: + # Init to empty, so 'no features' is cached. + features['slices'][str(x)] = {} - # If it is not None, it has all ready been generated. - if features['chunks'][str(chunk_pos)].get('biome') is None: + # If it is not None, it has all ready been generated. + if features['slices'][str(x)].get('biome') is None: - # TODO: ATM using basic per chunk biomes, see #71 - random.seed(str(meta['seed']) + str(chunk_pos) + 'biome') + random.seed(str(meta['seed']) + str(x) + 'biome') + if random.random() <= 0.05: - biomes_population = [] - for name, data in world_gen['biomes'].items(): - biomes_population.extend([name] * int(data['chance'] * 100)) + # TODO: Move outside function + biomes_population = [] + for name, data in world_gen['biomes'].items(): + biomes_population.extend([name] * int(data['chance'] * 100)) - attrs = {} - attrs['type'] = random.choice(sorted(biomes_population)) + attrs = {} + attrs['type'] = random.choice(sorted(biomes_population)) + attrs['radius'] = random.randint(world_gen['min_biome'], world_gen['max_biome']) - features['chunks'][str(chunk_pos)]['biome'] = attrs + features['slices'][str(x)]['biome'] = attrs def gen_hill_features(features, chunk_pos, meta): @@ -149,10 +153,7 @@ def gen_hill_features(features, chunk_pos, meta): features['slices'][str(x)]['hill'] = attrs -def gen_tree_features(features, ground_heights, chunk_pos, meta): - current_chunk_biome = features['chunks'][str(chunk_pos)]['biome']['type'] - log(current_chunk_biome, m=1) - +def gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta): for x in range(chunk_pos - MAX_HALF_TREE, chunk_pos + world_gen['chunk_size'] + MAX_HALF_TREE): # TODO: Each of these `if` blocks should be abstracted into a function @@ -165,8 +166,10 @@ def gen_tree_features(features, ground_heights, chunk_pos, meta): # If it is not None, it has all ready been generated. if features['slices'][str(x)].get('tree') is None: + biome_data = world_gen['biomes'][slices_biome[str(x)][0]] + tree_chance = biome_data['trees'] + random.seed(str(meta['seed']) + str(x) + 'tree') - tree_chance = world_gen['biomes'][current_chunk_biome]['trees'] if random.random() <= tree_chance: attrs = {} @@ -186,7 +189,7 @@ def gen_tree_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)]['tree'] = attrs -def gen_ore_features(features, ground_heights, chunk_pos, meta): +def gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta): for x in range(chunk_pos - MAX_ORE_RANGE[0], chunk_pos + world_gen['chunk_size'] + MAX_ORE_RANGE[1]): # TODO: Each of these `if` blocks should be abstracted into a function @@ -224,7 +227,7 @@ def gen_ore_features(features, ground_heights, chunk_pos, meta): features['slices'][str(x)][feature_name] = attrs -def gen_grass_features(features, ground_heights, chunk_pos, meta): +def gen_grass_features(features, ground_heights, slices_biome, chunk_pos, meta): for x in range(chunk_pos, chunk_pos + world_gen['chunk_size']): # TODO: Each of these `if` blocks should be abstracted into a function @@ -237,8 +240,11 @@ def gen_grass_features(features, ground_heights, chunk_pos, meta): # If it is not None, it has all ready been generated. if features['slices'][str(x)].get('grass') is None: + biome_data = world_gen['biomes'][slices_biome[str(x)][0]] + grass_chance = biome_data['grass'] + random.seed(str(meta['seed']) + str(x) + 'grass') - if random.random() <= world_gen['tall_grass_rate']: + if random.random() <= grass_chance: attrs = {} attrs['y'] = ground_heights[str(x)] @@ -310,54 +316,47 @@ def gen_chunk(chunk_n, meta): gen_biome_features(features, chunk_pos, meta) gen_hill_features(features, chunk_pos, meta) - # Insert hills because the trees and ores depend on the ground height. + # Generate hill heights and biomes map for the tree and ore generation. ground_heights = {str(x): world_gen['ground_height'] for x in hill_range(chunk_pos)} + # Store feature_x with the value for calculating precedence. + slices_biome = {str(x): ('normal', None) for x in range(chunk_pos - world_gen['max_biome'], chunk_pos + world_gen['chunk_size'] + world_gen['max_biome'])} + for feature_x, slice_features in features['slices'].items(): feature_x = int(feature_x) - if slice_features.get('hill'): - hill = slice_features['hill'] + for feature_name, feature in slice_features.items(): - for d_x in range(-hill['height'] * hill['gradient_l'], - hill['height'] * hill['gradient_r']): - abs_pos = feature_x + d_x + if feature_name == 'hill': - gradient = hill['gradient_l'] if d_x < 0 else hill['gradient_r'] - hill_height = int(hill['height'] - (abs(d_x) / gradient)) + for d_x in range(-feature['height'] * feature['gradient_l'], + feature['height'] * feature['gradient_r']): + x = feature_x + d_x - if d_x == 0: - hill_height -= 1 + gradient = feature['gradient_l'] if d_x < 0 else feature['gradient_r'] + hill_height = int(feature['height'] - (abs(d_x) / gradient)) - ground_height = world_gen['ground_height'] + hill_height + if d_x == 0: + hill_height -= 1 - old_height = ground_heights.get(str(abs_pos), 0) - ground_heights[str(abs_pos)] = max(ground_height, old_height) + ground_height = world_gen['ground_height'] + hill_height - int_x = list(map(int, ground_heights.keys())) - log('chunk', chunk_pos, m=1) - log('max', max(int_x), m=1) - log('min', min(int_x), m=1) - log('gh diff', set(hill_range(chunk_pos)) - set(int_x), m=1, trunc=False) + old_height = ground_heights.get(str(x), 0) + ground_heights[str(x)] = max(ground_height, old_height) - # We have to generate the ground heights before we can calculate the - # features which depend on them + elif feature_name == 'biome': - gen_tree_features(features, ground_heights, chunk_pos, meta) - gen_ore_features(features, ground_heights, chunk_pos, meta) - gen_grass_features(features, ground_heights, chunk_pos, meta) + for d_x in range(-feature['radius'], feature['radius']): + x = feature_x + d_x - log('chunk_pos', chunk_pos, m=1) - tree_features = list(filter(lambda f: f[1].get('tree'), features['slices'].items())) - log('trees in cache\n', [str(f[0]) for f in tree_features], m=1, trunc=0) - log('trees in range', [str(f[0]) for f in tree_features if (chunk_pos <= int(f[0]) < chunk_pos + world_gen['chunk_size'])], m=1, trunc=0) + if str(x) in slices_biome: + previous_slice_biome_feature_x = slices_biome[str(x)][1] + + if (previous_slice_biome_feature_x is None or + previous_slice_biome_feature_x < feature_x): + slices_biome[str(x)] = (feature['type'], feature_x) - # Build slices chunk = {} for x in range(chunk_pos, chunk_pos + world_gen['chunk_size']): - # Add missing ground heights - ground_heights.setdefault(str(x), world_gen['ground_height']) - - # Form slice of sky, grass, stone, bedrock chunk[str(x)] = ( [' '] * (world_gen['height'] - ground_heights[str(x)]) + ['-'] + @@ -365,6 +364,21 @@ def gen_chunk(chunk_n, meta): ['_'] ) + int_x = list(map(int, ground_heights.keys())) + log('chunk', chunk_pos, m=1) + log('max', max(int_x), m=1) + log('min', min(int_x), m=1) + log('gh diff', set(hill_range(chunk_pos)) - set(int_x), m=1, trunc=False) + + gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta) + gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta) + gen_grass_features(features, ground_heights, slices_biome, chunk_pos, meta) + + log('chunk_pos', chunk_pos, m=1) + tree_features = list(filter(lambda f: f[1].get('tree'), features['slices'].items())) + log('trees in cache\n', [str(f[0]) for f in tree_features], m=1, trunc=0) + log('trees in range', [str(f[0]) for f in tree_features if (chunk_pos <= int(f[0]) < chunk_pos + world_gen['chunk_size'])], m=1, trunc=0) + # Insert trees and ores for feature_x, slice_features in features['slices'].items(): feature_x = int(feature_x) From b88fb93329e97fd522233b23714d693e75c4a529 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Sun, 17 Jan 2016 20:19:29 +0000 Subject: [PATCH 37/42] Remove hill_range for olls --- terrain.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/terrain.py b/terrain.py index ac9cbf4..e909e57 100644 --- a/terrain.py +++ b/terrain.py @@ -18,7 +18,6 @@ get_chunk_list = lambda slice_list: list(set(int(i) // world_gen['chunk_size'] for i in slice_list)) MAX_HILL_RAD = world_gen['max_hill'] * world_gen['min_grad'] -hill_range = lambda chunk_pos: range(chunk_pos - MAX_HILL_RAD, chunk_pos + world_gen['chunk_size'] + MAX_HILL_RAD) blocks = render.blocks @@ -130,7 +129,7 @@ def gen_biome_features(features, chunk_pos, meta): def gen_hill_features(features, chunk_pos, meta): - for x in hill_range(chunk_pos): + for x in range(chunk_pos - MAX_HILL_RAD, chunk_pos + world_gen['chunk_size'] + MAX_HILL_RAD): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. @@ -317,7 +316,7 @@ def gen_chunk(chunk_n, meta): gen_hill_features(features, chunk_pos, meta) # Generate hill heights and biomes map for the tree and ore generation. - ground_heights = {str(x): world_gen['ground_height'] for x in hill_range(chunk_pos)} + ground_heights = {str(x): world_gen['ground_height'] for x in range(chunk_pos - MAX_HILL_RAD, chunk_pos + world_gen['chunk_size'] + MAX_HILL_RAD)} # Store feature_x with the value for calculating precedence. slices_biome = {str(x): ('normal', None) for x in range(chunk_pos - world_gen['max_biome'], chunk_pos + world_gen['chunk_size'] + world_gen['max_biome'])} @@ -368,7 +367,7 @@ def gen_chunk(chunk_n, meta): log('chunk', chunk_pos, m=1) log('max', max(int_x), m=1) log('min', min(int_x), m=1) - log('gh diff', set(hill_range(chunk_pos)) - set(int_x), m=1, trunc=False) + log('gh diff', set(range(chunk_pos - MAX_HILL_RAD, chunk_pos + world_gen['chunk_size'] + MAX_HILL_RAD)) - set(int_x), m=1, trunc=False) gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta) gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta) From a9ba16eee1c256cf71e620446081e87d7810ae18 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Sun, 17 Jan 2016 21:18:06 +0000 Subject: [PATCH 38/42] Add start position debugging option --- saves.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saves.py b/saves.py index 8f964fa..678f971 100644 --- a/saves.py +++ b/saves.py @@ -17,7 +17,7 @@ } default_player = { - 'player_x': 0, + 'player_x': int(os.getenv('PYCRAFT_START_X')) or 0, 'player_y': 1, 'inv': [] } From a8a14f5bcaba4b9a6014d019b70d700377065c2f Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Sun, 17 Jan 2016 21:20:11 +0000 Subject: [PATCH 39/42] Add leaf debugging --- terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index ac9cbf4..d833c45 100644 --- a/terrain.py +++ b/terrain.py @@ -273,7 +273,7 @@ def build_tree(chunk, chunk_pos, x, tree_feature, ground_heights): leaf_height = air_height - tree_feature['height'] - len(leaf_slice) + tree_feature['trunk_depth'] for leaf_dy, leaf in enumerate(leaf_slice): - if leaf: + if (bool(DEBUG) and leaf_dy == 0) or (not bool(DEBUG) and leaf): leaf_y = leaf_height + leaf_dy chunk[str(leaf_x)][leaf_y] = spawn_hierarchy(('@', chunk[str(leaf_x)][leaf_y])) From ac3bd55cbb5d410c2b03554d5c5ab4112daff4a0 Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Sun, 17 Jan 2016 21:20:31 +0000 Subject: [PATCH 40/42] Fix cache size for biomes --- terrain.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terrain.py b/terrain.py index d833c45..a6dcc47 100644 --- a/terrain.py +++ b/terrain.py @@ -76,7 +76,7 @@ def _check_limit(self): features = {} def init_features(): global features - cache_size = (world_gen['max_hill'] * world_gen['min_grad'] * 2) + world_gen['chunk_size'] + cache_size = (world_gen['max_biome'] * 2) + world_gen['chunk_size'] features = { 'chunks': TerrainCache(limit=(cache_size // world_gen['chunk_size']) + 1), 'slices': TerrainCache(limit=cache_size) @@ -369,6 +369,7 @@ def gen_chunk(chunk_n, meta): log('max', max(int_x), m=1) log('min', min(int_x), m=1) log('gh diff', set(hill_range(chunk_pos)) - set(int_x), m=1, trunc=False) + log('slices_biome', list(filter(lambda slice_: (int(slice_[0])%16 == 0) or (int(slice_[0])+1)%16 == 0, sorted(slices_biome.items()))), m=1, trunc=False) gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta) gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta) From 3af071f4710a8ea34ab4eec2cb62428580aeae35 Mon Sep 17 00:00:00 2001 From: Geraint White Date: Sun, 17 Jan 2016 21:28:42 +0000 Subject: [PATCH 41/42] Fix PYCRAFT_START_X int --- saves.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saves.py b/saves.py index 678f971..5ab3e45 100644 --- a/saves.py +++ b/saves.py @@ -17,7 +17,7 @@ } default_player = { - 'player_x': int(os.getenv('PYCRAFT_START_X')) or 0, + 'player_x': int(os.getenv('PYCRAFT_START_X') or 0), 'player_y': 1, 'inv': [] } From 488628b41ee45d9ecf2e07f55aee9f515f3890ea Mon Sep 17 00:00:00 2001 From: Oliver Faircliff Date: Sun, 17 Jan 2016 21:28:43 +0000 Subject: [PATCH 42/42] Remove chunk cache --- terrain.py | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/terrain.py b/terrain.py index 12bce42..b82a124 100644 --- a/terrain.py +++ b/terrain.py @@ -72,14 +72,11 @@ def _check_limit(self): # TODO: This probably shouldn't stay here... -features = {} +features = None def init_features(): global features cache_size = (world_gen['max_biome'] * 2) + world_gen['chunk_size'] - features = { - 'chunks': TerrainCache(limit=(cache_size // world_gen['chunk_size']) + 1), - 'slices': TerrainCache(limit=cache_size) - } + features = TerrainCache(limit=cache_size) init_features() @@ -106,12 +103,12 @@ def gen_biome_features(features, chunk_pos, meta): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. - if features['slices'].get(str(x)) is None: + if features.get(str(x)) is None: # Init to empty, so 'no features' is cached. - features['slices'][str(x)] = {} + features[str(x)] = {} # If it is not None, it has all ready been generated. - if features['slices'][str(x)].get('biome') is None: + if features[str(x)].get('biome') is None: random.seed(str(meta['seed']) + str(x) + 'biome') if random.random() <= 0.05: @@ -125,7 +122,7 @@ def gen_biome_features(features, chunk_pos, meta): attrs['type'] = random.choice(sorted(biomes_population)) attrs['radius'] = random.randint(world_gen['min_biome'], world_gen['max_biome']) - features['slices'][str(x)]['biome'] = attrs + features[str(x)]['biome'] = attrs def gen_hill_features(features, chunk_pos, meta): @@ -134,12 +131,12 @@ def gen_hill_features(features, chunk_pos, meta): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. - if features['slices'].get(str(x)) is None: + if features.get(str(x)) is None: # Init to empty, so 'no features' is cached. - features['slices'][str(x)] = {} + features[str(x)] = {} # If it is not None, it has all ready been generated. - if features['slices'][str(x)].get('hill') is None: + if features[str(x)].get('hill') is None: random.seed(str(meta['seed']) + str(x) + 'hill') if random.random() <= 0.05: @@ -149,7 +146,7 @@ def gen_hill_features(features, chunk_pos, meta): attrs['gradient_r'] = random.randint(1, world_gen['min_grad']) attrs['height'] = random.randint(0, world_gen['max_hill']) - features['slices'][str(x)]['hill'] = attrs + features[str(x)]['hill'] = attrs def gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta): @@ -158,12 +155,12 @@ def gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. - if features['slices'].get(str(x)) is None: + if features.get(str(x)) is None: # Init to empty, so 'no features' is cached. - features['slices'][str(x)] = {} + features[str(x)] = {} # If it is not None, it has all ready been generated. - if features['slices'][str(x)].get('tree') is None: + if features[str(x)].get('tree') is None: biome_data = world_gen['biomes'][slices_biome[str(x)][0]] tree_chance = biome_data['trees'] @@ -185,7 +182,7 @@ def gen_tree_features(features, ground_heights, slices_biome, chunk_pos, meta): air_height = world_gen['height'] - ground_heights[str(x)] attrs['height'] = random.randint(2, air_height - len(center_leaves) + attrs['trunk_depth']) - features['slices'][str(x)]['tree'] = attrs + features[str(x)]['tree'] = attrs def gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta): @@ -194,9 +191,9 @@ def gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. - if features['slices'].get(str(x)) is None: + if features.get(str(x)) is None: # Init to empty, so 'no features' is cached. - features['slices'][str(x)] = {} + features[str(x)] = {} # Ores # NOTE: Ores seem to be the way to model the generalisation of the @@ -205,7 +202,7 @@ def gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta): feature_name = name + '_ore_root' # If it is not None, it has all ready been generated. - if features['slices'][str(x)].get(feature_name) is None: + if features[str(x)].get(feature_name) is None: random.seed(str(meta['seed']) + str(x) + feature_name) if random.random() <= ore['chance']: @@ -223,7 +220,7 @@ def gen_ore_features(features, ground_heights, slices_biome, chunk_pos, meta): # top to bottom, left to right. attrs['vain_shape'] = [b / 100 for b in random.sample(range(0, 100), pot_vain_blocks)] - features['slices'][str(x)][feature_name] = attrs + features[str(x)][feature_name] = attrs def gen_grass_features(features, ground_heights, slices_biome, chunk_pos, meta): @@ -232,12 +229,12 @@ def gen_grass_features(features, ground_heights, slices_biome, chunk_pos, meta): # TODO: Each of these `if` blocks should be abstracted into a function # which just returns the `attrs` object. - if features['slices'].get(str(x)) is None: + if features.get(str(x)) is None: # Init to empty, so 'no features' is cached. - features['slices'][str(x)] = {} + features[str(x)] = {} # If it is not None, it has all ready been generated. - if features['slices'][str(x)].get('grass') is None: + if features[str(x)].get('grass') is None: biome_data = world_gen['biomes'][slices_biome[str(x)][0]] grass_chance = biome_data['grass'] @@ -248,7 +245,7 @@ def gen_grass_features(features, ground_heights, slices_biome, chunk_pos, meta): attrs = {} attrs['y'] = ground_heights[str(x)] - features['slices'][str(x)]['grass'] = attrs + features[str(x)]['grass'] = attrs def build_tree(chunk, chunk_pos, x, tree_feature, ground_heights): @@ -320,7 +317,7 @@ def gen_chunk(chunk_n, meta): # Store feature_x with the value for calculating precedence. slices_biome = {str(x): ('normal', None) for x in range(chunk_pos - world_gen['max_biome'], chunk_pos + world_gen['chunk_size'] + world_gen['max_biome'])} - for feature_x, slice_features in features['slices'].items(): + for feature_x, slice_features in features.items(): feature_x = int(feature_x) for feature_name, feature in slice_features.items(): @@ -375,12 +372,12 @@ def gen_chunk(chunk_n, meta): gen_grass_features(features, ground_heights, slices_biome, chunk_pos, meta) log('chunk_pos', chunk_pos, m=1) - tree_features = list(filter(lambda f: f[1].get('tree'), features['slices'].items())) + tree_features = list(filter(lambda f: f[1].get('tree'), features.items())) log('trees in cache\n', [str(f[0]) for f in tree_features], m=1, trunc=0) log('trees in range', [str(f[0]) for f in tree_features if (chunk_pos <= int(f[0]) < chunk_pos + world_gen['chunk_size'])], m=1, trunc=0) # Insert trees and ores - for feature_x, slice_features in features['slices'].items(): + for feature_x, slice_features in features.items(): feature_x = int(feature_x) for feature_name, feature in slice_features.items():