parent
221d497204
commit
9874e23aa6
3 changed files with 316 additions and 307 deletions
@ -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 |
Loading…
Reference in new issue