You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
365 lines
13 KiB
365 lines
13 KiB
import collections |
|
import re |
|
import time |
|
import random |
|
from itertools import count |
|
from copy import copy |
|
|
|
from mosfet import utils |
|
from mosfet import path |
|
from mosfet.info import blocks |
|
from mosfet.info 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, thru_air=False): |
|
to_visit = collections.deque([(0, 0, 0)]) |
|
visited = set() |
|
|
|
while to_visit: |
|
cur = to_visit.pop() |
|
if cur in visited: |
|
continue |
|
if y_limit and abs(cur[1]) > y_limit: |
|
continue |
|
if distance and utils.hypot(*cur) > distance: |
|
continue |
|
|
|
check = utils.padd(center, cur) |
|
|
|
if not thru_air or self.block_at(*check) in blocks.NON_SOLID_IDS: |
|
for neighbor in utils.get_neighbors_3d(*cur): |
|
to_visit.appendleft(neighbor) |
|
|
|
visited.add(cur) |
|
|
|
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 utils.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 path_to_place_faked(self, start, place): |
|
# same as above, but adds a fake block below and air before pathfinding |
|
# so that the pathfinder can actually make it to the block |
|
c = self.g.chunks |
|
above = utils.padd(place, path.BLOCK_ABOVE) |
|
below = utils.padd(place, path.BLOCK_BELOW) |
|
|
|
tmp = c.get_block_at(*place) |
|
tmp2 = c.get_block_at(*above) |
|
tmp3 = c.get_block_at(*below) |
|
c.set_block_at(*place, blocks.AIR) |
|
c.set_block_at(*above, blocks.AIR) |
|
c.set_block_at(*below, blocks.STONE) |
|
navpath = self.path_to_place(start, place) |
|
c.set_block_at(*place, tmp) |
|
c.set_block_at(*above, tmp2) |
|
c.set_block_at(*below, tmp3) |
|
|
|
return navpath |
|
|
|
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 check_bed_occupied(self, bed): |
|
# returns true if the bed is occupied by a player |
|
bid = self.g.chunks.get_block_at(*bed) |
|
if blocks.PROPS[bid]['occupied'] == 'true': |
|
print('Checking bed occupancy:', bed, '-> occupied') |
|
return True |
|
else: |
|
print('Checking bed occupancy:', bed, '-> free') |
|
return False |
|
|
|
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) |
|
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z))) |
|
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) |
|
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z))) |
|
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) |
|
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z))) |
|
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
|
|
|