From 9874e23aa61f26e1fd6cf45f4393442e1d5916b0 Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Fri, 23 Apr 2021 00:18:53 +0000 Subject: [PATCH] Move World class into its own file --- mosfet/bot.py | 3 +- mosfet/game.py | 306 ---------------------------------------------- mosfet/world.py | 314 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 316 insertions(+), 307 deletions(-) create mode 100644 mosfet/world.py diff --git a/mosfet/bot.py b/mosfet/bot.py index fd2b52f..2b77a85 100644 --- a/mosfet/bot.py +++ b/mosfet/bot.py @@ -26,6 +26,7 @@ from munch import Munch from mosfet import blocks from mosfet import game +from mosfet import world from mosfet import items from mosfet import job from mosfet import mcdata @@ -264,7 +265,7 @@ def bot(global_state): g.chat = ChatManager(g) g.game = game.Game(g) - g.world = game.MCWorld(g) + g.world = world.World(g) try: while not g.pos: diff --git a/mosfet/game.py b/mosfet/game.py index 5ae33f0..8f0021a 100644 --- a/mosfet/game.py +++ b/mosfet/game.py @@ -2,11 +2,9 @@ import re import time import importlib import random -import functools from math import hypot from itertools import count from munch import Munch -from copy import copy from minecraft.networking.packets import Packet, clientbound, serverbound from minecraft.networking.types import BlockFace @@ -34,310 +32,6 @@ from mosfet import mobs from mosfet import bot from mosfet import vector -class MCWorld: - def __init__(self, global_state): - self.g = global_state - - def block_at(self, x, y, z): - return self.g.chunks.get_block_at(x, y, z) - - def check_air_column(self, pos, distance): - for i in range(distance): - check = utils.padd(pos, (0, i, 0)) - if self.block_at(*check) not in blocks.NON_SOLID_IDS: - return False - return True - - def find_blocks_3d(self, center, block_ids, distance=0, y_limit=0): - for offset in utils.search_3d(distance, y_limit): - check = utils.padd(center, offset) - if self.block_at(*check) in block_ids: - yield check - - def find_blocks_indexed(self, center, block_ids, distance=0): - print('finding', block_ids) - index = [] - for bid in block_ids: - index.extend(self.g.chunks.index.get(bid, [])) - - print('index', index) - - result = [] - for block in index: - if self.block_at(*block) not in block_ids: - continue - if distance and utils.phyp(center, block) > distance: - continue - if block not in result: - result.append(block) - - result.sort(key=lambda x: utils.phyp(center, x)) - return result - - def find_blocks(self, center, distance, block_ids, limit=0): - # search in a spiral from center to all blocks with ID - result = [] - for n in count(): - offset = utils.spiral(n) - check = utils.padd(center, offset) - if self.block_at(*check) in block_ids: - if hypot(*offset) < distance: - result.append(check) - if limit and len(result) == limit: - return result - if offset[0] > distance: - return result - - def find_trees(self, center, distance): - found_trees = [] - for log in self.find_blocks_3d(center, blocks.LOG_IDS, distance, 15): - # crawl to the bottom log - while self.block_at(*utils.padd(log, path.BLOCK_BELOW)) in blocks.LOG_IDS: - log = utils.padd(log, path.BLOCK_BELOW) - base = log - - if base in found_trees: - continue - - # make sure we are on the ground - if self.block_at(*utils.padd(base, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS: - continue - - # crawl to the top log to count and check leaves - log_count = 1 - good_leaves = False - while self.block_at(*utils.padd(log, path.BLOCK_ABOVE)) in blocks.LOG_IDS: - log = utils.padd(log, path.BLOCK_ABOVE) - log_count += 1 - - for offset in path.CHECK_DIRECTIONS: - if self.block_at(*utils.padd(log, offset)) in blocks.LEAF_IDS: - good_leaves = True - - # make sure it's a good tree - if not good_leaves or log_count < 3: - continue - - found_trees.append(base) - - yield base - - def find_tree_openings(self, tree): - # returns coords in a cardinal direction where we can stand by tree - maze_solver = path.Pathfinder(self.g) - result = [] - - # TODO: make sure only non-solid and leaves between - # make sure traversable too and non-avoid - - for distance in range(5): - for direction in path.CHECK_DIRECTIONS: - offset = utils.pmul(direction, distance+1) - if maze_solver.check_traverse(tree, offset): - result.append(utils.padd(tree, offset)) - return result - - def path_to_place(self, start, place): - maze_solver = path.Pathfinder(self.g) - - try: - s = maze_solver.astar(start, place) - return list(s) if s else None - except path.AStarTimeout: - return None - - def find_bed_areas(self, center, distance): - bed_clearance = 9 # 5x5 area - clear_distance = 2 - - for a in self.find_blocks_3d(center, [0], distance, 50): - # check for air around the area - if len(self.find_blocks(a, clear_distance, [0], bed_clearance)) < bed_clearance: - continue - - # check for ground around the area - if len(self.find_blocks(utils.padd(a, path.BLOCK_BELOW), clear_distance, blocks.NON_SOLID_IDS, bed_clearance)): - continue - - # check for air above the area - if len(self.find_blocks(utils.padd(a, path.BLOCK_ABOVE), clear_distance, [0], bed_clearance)) < bed_clearance: - continue - - # ensure there's no monsters within 20 blocks - # can't sleep if they are within 10, good to have a buffer - if self.find_monsters(a, 20): - continue - - yield a - - def find_cache_areas(self, center, distance): - return self.find_bed_areas(center, distance) - - def sand_adjacent_safe(self, sand): - for direction in path.CHECK_DIRECTIONS: - if self.block_at(*utils.padd(sand, direction)) in blocks.AVOID_IDS: - return False - return True - - def find_sand(self, center, distance, player): - sand = [] - sand.extend(self.find_blocks(center, distance, [blocks.SAND], 25)) - - safe_sand = [] - for s in sand: - # make sure it has solid below - if self.block_at(*utils.padd(s, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS: - continue - # make sure it has solid two below - prevent hanging sand - if self.block_at(*utils.padd(s, path.BLOCK_BELOW2)) in blocks.NON_SOLID_IDS: - continue - - # and walkable air above - if self.block_at(*utils.padd(s, path.BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS: - continue - - if not self.sand_adjacent_safe(s): - continue - - safe_sand.append(s) - - safe_sand.sort(key=lambda x: utils.phyp(player, x)) - return safe_sand - - def check_sand_slice(self, center): - # checks if a 5x5x1 slice has sand in it - for i in range(9): - s = utils.padd(center, utils.spiral(i)) - if self.block_at(*s) != blocks.SAND: - continue - # make sure it has solid below - if self.block_at(*utils.padd(s, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS: - continue - # make sure it has solid two below - prevent hanging sand - if self.block_at(*utils.padd(s, path.BLOCK_BELOW2)) in blocks.NON_SOLID_IDS: - continue - # and walkable air above - if self.block_at(*utils.padd(s, path.BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS: - continue - if not self.sand_adjacent_safe(s): - continue - return True - - return False - - def find_sand_slice(self, center, distance, y_limit=0, bad_slices=[], prev_layer=0): - # returns the centre coord of the next 5x5x1 slice that still has - # diggable sand in it. lower slices are only valid if there's an - # adjacent slice farther at the same level. this should ensure an - # upside down pyramid gets excavated so the edges are still climbable - for v in count(prev_layer): - peak = utils.padd(center, (0, 10-v, 0)) - - slices = [] - layer = 0 - for step in count(): - offset = utils.spiral(step) - layer = max(layer, *offset) - offset = utils.pmul(offset, 3) - check = utils.padd(peak, offset) - check = utils.padd(check, (0, layer, 0)) - - if y_limit and check[1] - center[1] > y_limit: - break - if utils.phyp_king(center, check) > distance: - break - - if self.check_sand_slice(check) and check not in bad_slices: - slices.append(check) - - if len(slices): - return v, slices[-1] - elif v > 40: - return None, None - - - def find_bed_openings(self, area): - # returns coords in a cardinal direction where we can stand by bed - result = [] - - for direction in path.CHECK_DIRECTIONS: - result.append(utils.padd(area, direction)) - return result - - def find_cache_openings(self, area): - return self.find_bed_openings(area) - - def find_objects(self, object_ids): - result = [] - for eid, obj in copy(self.g.objects).items(): - if obj.get('item_id', None) in object_ids: - result.append(obj) - return result - - def find_leaves(self, center, distance): - for a in self.find_blocks_3d(center, blocks.LEAF_IDS, distance, 10): - yield a - - def find_monsters(self, center, distance): - # finds monsters within distance - result = [] - for eid, mob in copy(self.g.mobs).items(): - if mob.type not in mobs.EVIL_IDS: - continue - pos = utils.pint((mob.x, mob.y, mob.z)) - if utils.phyp(center, pos) > distance: - continue - result.append(mob) - return result - - def find_threats(self, center, distance): - # finds monsters on the surface within distance - monsters = self.find_monsters(center, distance) - result = [] - for mob in monsters: - pos = utils.pint((mob.x, mob.y, mob.z)) - # check distance number of blocks above, close enough? - if not self.check_air_column(pos, distance): - continue - result.append(mob) - return result - - def find_villagers(self, center, distance): - # finds villagers within distance - result = [] - for eid, mob in copy(self.g.mobs).items(): - type_name = mobs.MOB_NAMES[mob.type] - if type_name != 'villager' : continue - pos = utils.pint((mob.x, mob.y, mob.z)) - if utils.phyp(center, pos) > distance: - continue - result.append(mob) - return result - - def find_villager_openings(self, villager): - # returns coords in a cardinal direction where we can stand by a villager - maze_solver = path.Pathfinder(self.g) - result = [] - - for distance in range(3): - for direction in path.CHECK_DIRECTIONS: - offset = utils.pmul(direction, distance+1) - - if not maze_solver.check_traverse(villager, offset): - continue - - # check for line of sight - for check in range(distance+1): - offset2 = utils.pmul(direction, check+1) - offset2 = utils.padd(offset2, path.BLOCK_ABOVE) - check = utils.padd(villager, offset2) - if self.block_at(*check) not in blocks.NON_SOLID_IDS: - break - else: # for - result.append(utils.padd(villager, offset)) - return result - - class Game: def __init__(self, global_state): self.g = global_state diff --git a/mosfet/world.py b/mosfet/world.py new file mode 100644 index 0000000..4612fb9 --- /dev/null +++ b/mosfet/world.py @@ -0,0 +1,314 @@ +import re +import time +import random +from math import hypot +from itertools import count +from copy import copy + +from mosfet import utils +from mosfet import path +from mosfet import blocks +from mosfet import mobs + +class World: + def __init__(self, global_state): + self.g = global_state + + def block_at(self, x, y, z): + return self.g.chunks.get_block_at(x, y, z) + + def check_air_column(self, pos, distance): + for i in range(distance): + check = utils.padd(pos, (0, i, 0)) + if self.block_at(*check) not in blocks.NON_SOLID_IDS: + return False + return True + + def find_blocks_3d(self, center, block_ids, distance=0, y_limit=0): + for offset in utils.search_3d(distance, y_limit): + check = utils.padd(center, offset) + if self.block_at(*check) in block_ids: + yield check + + def find_blocks_indexed(self, center, block_ids, distance=0): + print('finding', block_ids) + index = [] + for bid in block_ids: + index.extend(self.g.chunks.index.get(bid, [])) + + print('index', index) + + result = [] + for block in index: + if self.block_at(*block) not in block_ids: + continue + if distance and utils.phyp(center, block) > distance: + continue + if block not in result: + result.append(block) + + result.sort(key=lambda x: utils.phyp(center, x)) + return result + + def find_blocks(self, center, distance, block_ids, limit=0): + # search in a spiral from center to all blocks with ID + result = [] + for n in count(): + offset = utils.spiral(n) + check = utils.padd(center, offset) + if self.block_at(*check) in block_ids: + if hypot(*offset) < distance: + result.append(check) + if limit and len(result) == limit: + return result + if offset[0] > distance: + return result + + def find_trees(self, center, distance): + found_trees = [] + for log in self.find_blocks_3d(center, blocks.LOG_IDS, distance, 15): + # crawl to the bottom log + while self.block_at(*utils.padd(log, path.BLOCK_BELOW)) in blocks.LOG_IDS: + log = utils.padd(log, path.BLOCK_BELOW) + base = log + + if base in found_trees: + continue + + # make sure we are on the ground + if self.block_at(*utils.padd(base, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS: + continue + + # crawl to the top log to count and check leaves + log_count = 1 + good_leaves = False + while self.block_at(*utils.padd(log, path.BLOCK_ABOVE)) in blocks.LOG_IDS: + log = utils.padd(log, path.BLOCK_ABOVE) + log_count += 1 + + for offset in path.CHECK_DIRECTIONS: + if self.block_at(*utils.padd(log, offset)) in blocks.LEAF_IDS: + good_leaves = True + + # make sure it's a good tree + if not good_leaves or log_count < 3: + continue + + found_trees.append(base) + + yield base + + def find_tree_openings(self, tree): + # returns coords in a cardinal direction where we can stand by tree + maze_solver = path.Pathfinder(self.g) + result = [] + + # TODO: make sure only non-solid and leaves between + # make sure traversable too and non-avoid + + for distance in range(5): + for direction in path.CHECK_DIRECTIONS: + offset = utils.pmul(direction, distance+1) + if maze_solver.check_traverse(tree, offset): + result.append(utils.padd(tree, offset)) + return result + + def path_to_place(self, start, place): + maze_solver = path.Pathfinder(self.g) + + try: + s = maze_solver.astar(start, place) + return list(s) if s else None + except path.AStarTimeout: + return None + + def find_bed_areas(self, center, distance): + bed_clearance = 9 # 5x5 area + clear_distance = 2 + + for a in self.find_blocks_3d(center, [0], distance, 50): + # check for air around the area + if len(self.find_blocks(a, clear_distance, [0], bed_clearance)) < bed_clearance: + continue + + # check for ground around the area + if len(self.find_blocks(utils.padd(a, path.BLOCK_BELOW), clear_distance, blocks.NON_SOLID_IDS, bed_clearance)): + continue + + # check for air above the area + if len(self.find_blocks(utils.padd(a, path.BLOCK_ABOVE), clear_distance, [0], bed_clearance)) < bed_clearance: + continue + + # ensure there's no monsters within 20 blocks + # can't sleep if they are within 10, good to have a buffer + if self.find_monsters(a, 20): + continue + + yield a + + def find_cache_areas(self, center, distance): + return self.find_bed_areas(center, distance) + + def sand_adjacent_safe(self, sand): + for direction in path.CHECK_DIRECTIONS: + if self.block_at(*utils.padd(sand, direction)) in blocks.AVOID_IDS: + return False + return True + + def find_sand(self, center, distance, player): + sand = [] + sand.extend(self.find_blocks(center, distance, [blocks.SAND], 25)) + + safe_sand = [] + for s in sand: + # make sure it has solid below + if self.block_at(*utils.padd(s, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS: + continue + # make sure it has solid two below - prevent hanging sand + if self.block_at(*utils.padd(s, path.BLOCK_BELOW2)) in blocks.NON_SOLID_IDS: + continue + + # and walkable air above + if self.block_at(*utils.padd(s, path.BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS: + continue + + if not self.sand_adjacent_safe(s): + continue + + safe_sand.append(s) + + safe_sand.sort(key=lambda x: utils.phyp(player, x)) + return safe_sand + + def check_sand_slice(self, center): + # checks if a 5x5x1 slice has sand in it + for i in range(9): + s = utils.padd(center, utils.spiral(i)) + if self.block_at(*s) != blocks.SAND: + continue + # make sure it has solid below + if self.block_at(*utils.padd(s, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS: + continue + # make sure it has solid two below - prevent hanging sand + if self.block_at(*utils.padd(s, path.BLOCK_BELOW2)) in blocks.NON_SOLID_IDS: + continue + # and walkable air above + if self.block_at(*utils.padd(s, path.BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS: + continue + if not self.sand_adjacent_safe(s): + continue + return True + + return False + + def find_sand_slice(self, center, distance, y_limit=0, bad_slices=[], prev_layer=0): + # returns the centre coord of the next 5x5x1 slice that still has + # diggable sand in it. lower slices are only valid if there's an + # adjacent slice farther at the same level. this should ensure an + # upside down pyramid gets excavated so the edges are still climbable + for v in count(prev_layer): + peak = utils.padd(center, (0, 10-v, 0)) + + slices = [] + layer = 0 + for step in count(): + offset = utils.spiral(step) + layer = max(layer, *offset) + offset = utils.pmul(offset, 3) + check = utils.padd(peak, offset) + check = utils.padd(check, (0, layer, 0)) + + if y_limit and check[1] - center[1] > y_limit: + break + if utils.phyp_king(center, check) > distance: + break + + if self.check_sand_slice(check) and check not in bad_slices: + slices.append(check) + + if len(slices): + return v, slices[-1] + elif v > 40: + return None, None + + + def find_bed_openings(self, area): + # returns coords in a cardinal direction where we can stand by bed + result = [] + + for direction in path.CHECK_DIRECTIONS: + result.append(utils.padd(area, direction)) + return result + + def find_cache_openings(self, area): + return self.find_bed_openings(area) + + def find_objects(self, object_ids): + result = [] + for eid, obj in copy(self.g.objects).items(): + if obj.get('item_id', None) in object_ids: + result.append(obj) + return result + + def find_leaves(self, center, distance): + for a in self.find_blocks_3d(center, blocks.LEAF_IDS, distance, 10): + yield a + + def find_monsters(self, center, distance): + # finds monsters within distance + result = [] + for eid, mob in copy(self.g.mobs).items(): + if mob.type not in mobs.EVIL_IDS: + continue + pos = utils.pint((mob.x, mob.y, mob.z)) + if utils.phyp(center, pos) > distance: + continue + result.append(mob) + return result + + def find_threats(self, center, distance): + # finds monsters on the surface within distance + monsters = self.find_monsters(center, distance) + result = [] + for mob in monsters: + pos = utils.pint((mob.x, mob.y, mob.z)) + # check distance number of blocks above, close enough? + if not self.check_air_column(pos, distance): + continue + result.append(mob) + return result + + def find_villagers(self, center, distance): + # finds villagers within distance + result = [] + for eid, mob in copy(self.g.mobs).items(): + type_name = mobs.MOB_NAMES[mob.type] + if type_name != 'villager' : continue + pos = utils.pint((mob.x, mob.y, mob.z)) + if utils.phyp(center, pos) > distance: + continue + result.append(mob) + return result + + def find_villager_openings(self, villager): + # returns coords in a cardinal direction where we can stand by a villager + maze_solver = path.Pathfinder(self.g) + result = [] + + for distance in range(3): + for direction in path.CHECK_DIRECTIONS: + offset = utils.pmul(direction, distance+1) + + if not maze_solver.check_traverse(villager, offset): + continue + + # check for line of sight + for check in range(distance+1): + offset2 = utils.pmul(direction, check+1) + offset2 = utils.padd(offset2, path.BLOCK_ABOVE) + check = utils.padd(villager, offset2) + if self.block_at(*check) not in blocks.NON_SOLID_IDS: + break + else: # for + result.append(utils.padd(villager, offset)) + return result