Find chest openings within interaction range
This commit is contained in:
+190
-2
@@ -3,6 +3,7 @@ import json
|
||||
import re
|
||||
import time
|
||||
import random
|
||||
from math import floor
|
||||
from itertools import count
|
||||
from copy import copy
|
||||
|
||||
@@ -11,6 +12,10 @@ from mosfet import path
|
||||
from mosfet.info import blocks
|
||||
from mosfet.info import items
|
||||
from mosfet.info import mobs
|
||||
from minecraft.networking.types import BlockFace
|
||||
|
||||
CHEST_OPENING_RADIUS = 3
|
||||
|
||||
|
||||
class World:
|
||||
def __init__(self, global_state):
|
||||
@@ -625,8 +630,191 @@ class World:
|
||||
self._bed_occupancy_states[bed] = occupied
|
||||
return occupied
|
||||
|
||||
def find_cache_openings(self, area):
|
||||
return self.find_bed_openings(area)
|
||||
@staticmethod
|
||||
def _is_chest_state(state_id):
|
||||
name = blocks.BLOCKS.get(state_id)
|
||||
return (name is not None and name != 'ender_chest' and
|
||||
(name == 'chest' or name.endswith('_chest')))
|
||||
|
||||
def _cache_opening_standable(self, position):
|
||||
at_feet = self.block_at(*position)
|
||||
above = self.block_at(*utils.padd(position, path.BLOCK_ABOVE))
|
||||
below = self.block_at(*utils.padd(position, path.BLOCK_BELOW))
|
||||
return (at_feet in blocks.NON_SOLID_IDS and
|
||||
at_feet not in blocks.AVOID_IDS and
|
||||
above in blocks.NON_SOLID_IDS and
|
||||
above not in blocks.AVOID_IDS and
|
||||
below is not None and
|
||||
below not in blocks.NON_SOLID_IDS and
|
||||
below not in blocks.AVOID_IDS)
|
||||
|
||||
@staticmethod
|
||||
def _segment_intersects_aabb(start, end, minimum, maximum):
|
||||
lower, upper = 0.0, 1.0
|
||||
for axis in range(3):
|
||||
origin = start[axis]
|
||||
delta = end[axis] - origin
|
||||
if abs(delta) < 1e-9:
|
||||
if origin < minimum[axis] or origin > maximum[axis]:
|
||||
return False
|
||||
continue
|
||||
first = (minimum[axis] - origin) / delta
|
||||
second = (maximum[axis] - origin) / delta
|
||||
if first > second:
|
||||
first, second = second, first
|
||||
lower = max(lower, first)
|
||||
upper = min(upper, second)
|
||||
if lower > upper:
|
||||
return False
|
||||
return upper >= 0.0 and lower <= 1.0
|
||||
|
||||
def _has_cache_line_of_sight(self, start, end, target):
|
||||
first = tuple(floor(min(start[i], end[i])) for i in range(3))
|
||||
last = tuple(floor(max(start[i], end[i])) for i in range(3))
|
||||
for x in range(first[0], last[0] + 1):
|
||||
for y in range(first[1], last[1] + 1):
|
||||
for z in range(first[2], last[2] + 1):
|
||||
position = (x, y, z)
|
||||
if position == target:
|
||||
continue
|
||||
if not self._segment_intersects_aabb(
|
||||
start, end, position, utils.padd(position, (1, 1, 1))):
|
||||
continue
|
||||
state_id = self.block_at(*position)
|
||||
if state_id is None:
|
||||
return False
|
||||
for box in blocks.COLLISION_BOXES.get(state_id, ()):
|
||||
minimum = (x + box[0], y + box[1], z + box[2])
|
||||
maximum = (x + box[3], y + box[4], z + box[5])
|
||||
if self._segment_intersects_aabb(
|
||||
start, end, minimum, maximum):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _chest_interaction_points(target, boxes):
|
||||
points = []
|
||||
for box in boxes:
|
||||
x0, y0, z0, x1, y1, z1 = box
|
||||
xm, zm = (x0 + x1) / 2, (z0 + z1) / 2
|
||||
for fraction in (0.2, 0.5, 0.8):
|
||||
y = y0 + (y1 - y0) * fraction
|
||||
points.extend((
|
||||
(target[0] + xm, target[1] + y, target[2] + z0),
|
||||
(target[0] + xm, target[1] + y, target[2] + z1),
|
||||
(target[0] + x0, target[1] + y, target[2] + zm),
|
||||
(target[0] + x1, target[1] + y, target[2] + zm),
|
||||
))
|
||||
for x_fraction, z_fraction in (
|
||||
(0.25, 0.25), (0.25, 0.75),
|
||||
(0.75, 0.25), (0.75, 0.75), (0.5, 0.5)):
|
||||
points.append((
|
||||
target[0] + x0 + (x1 - x0) * x_fraction,
|
||||
target[1] + y1,
|
||||
target[2] + z0 + (z1 - z0) * z_fraction,
|
||||
))
|
||||
return points
|
||||
|
||||
@staticmethod
|
||||
def _ray_chest_surface_hit(start, end, target, boxes):
|
||||
negative_faces = (BlockFace.WEST, BlockFace.BOTTOM, BlockFace.NORTH)
|
||||
positive_faces = (BlockFace.EAST, BlockFace.TOP, BlockFace.SOUTH)
|
||||
delta = tuple(end[i] - start[i] for i in range(3))
|
||||
hits = []
|
||||
for box in boxes:
|
||||
minimum = tuple(target[i] + box[i] for i in range(3))
|
||||
maximum = tuple(target[i] + box[i + 3] for i in range(3))
|
||||
enter, leave = float('-inf'), float('inf')
|
||||
entering_face = None
|
||||
for axis in range(3):
|
||||
if abs(delta[axis]) < 1e-9:
|
||||
if (start[axis] < minimum[axis] or
|
||||
start[axis] > maximum[axis]):
|
||||
break
|
||||
continue
|
||||
near = (minimum[axis] - start[axis]) / delta[axis]
|
||||
far = (maximum[axis] - start[axis]) / delta[axis]
|
||||
face = negative_faces[axis]
|
||||
if near > far:
|
||||
near, far = far, near
|
||||
face = positive_faces[axis]
|
||||
if near > enter:
|
||||
enter = near
|
||||
entering_face = face
|
||||
leave = min(leave, far)
|
||||
if enter > leave:
|
||||
break
|
||||
else:
|
||||
if entering_face is None or enter < 0 or enter > 1:
|
||||
continue
|
||||
hit_point = tuple(start[i] + delta[i] * enter
|
||||
for i in range(3))
|
||||
cursor = tuple(hit_point[i] - target[i] for i in range(3))
|
||||
hits.append((enter, entering_face, cursor, hit_point))
|
||||
if not hits:
|
||||
return None
|
||||
_distance, face, cursor, hit_point = min(hits, key=lambda hit: hit[0])
|
||||
return face, cursor, hit_point
|
||||
|
||||
def find_cache_openings(self, area, center=None, radius=None):
|
||||
"""Find standable nearby spots with a clear ray to a chest surface."""
|
||||
area = tuple(area)
|
||||
center = tuple(center if center is not None else area)
|
||||
radius = CHEST_OPENING_RADIUS if radius is None else radius
|
||||
state_id = self.block_at(*area)
|
||||
targets = (self._double_chest_positions(area)
|
||||
if self._is_chest_state(state_id) else (area,))
|
||||
default_chest = min(blocks.CHEST_IDS)
|
||||
interactions = {}
|
||||
|
||||
for target in targets:
|
||||
target_state = self.block_at(*target)
|
||||
boxes = blocks.COLLISION_BOXES.get(target_state) or \
|
||||
blocks.COLLISION_BOXES[default_chest]
|
||||
hit_points = self._chest_interaction_points(target, boxes)
|
||||
for horizontal in utils.search_2d(radius + 1):
|
||||
for dy in range(-radius - 1, radius + 2):
|
||||
offset = (horizontal[0], dy, horizontal[2])
|
||||
opening = utils.padd(target, offset)
|
||||
if (opening in targets or
|
||||
not self._cache_opening_standable(opening)):
|
||||
continue
|
||||
eye = (opening[0] + 0.5, opening[1] + 1.62,
|
||||
opening[2] + 0.5)
|
||||
for aim_point in hit_points:
|
||||
if not self._has_cache_line_of_sight(
|
||||
eye, aim_point, target):
|
||||
continue
|
||||
hit = self._ray_chest_surface_hit(
|
||||
eye, aim_point, target, boxes)
|
||||
if hit is None:
|
||||
continue
|
||||
face, cursor, hit_point = hit
|
||||
hit_distance = utils.phyp(eye, hit_point)
|
||||
if hit_distance > radius:
|
||||
continue
|
||||
interaction = {
|
||||
'position': opening,
|
||||
'target': target,
|
||||
'face': face,
|
||||
'cursor': cursor,
|
||||
'hit_point': hit_point,
|
||||
'look_at': tuple(value - 0.5
|
||||
for value in hit_point),
|
||||
}
|
||||
distance = utils.phyp(center, opening)
|
||||
interaction['distance'] = distance
|
||||
interaction['hit_distance'] = hit_distance
|
||||
previous = interactions.get(opening)
|
||||
if (previous is None or
|
||||
interaction['hit_distance'] <
|
||||
previous['hit_distance']):
|
||||
interactions[opening] = interaction
|
||||
break
|
||||
|
||||
return sorted(interactions.values(),
|
||||
key=lambda result: (result['distance'],
|
||||
result['position']))
|
||||
|
||||
def find_objects(self, object_ids):
|
||||
result = []
|
||||
|
||||
Reference in New Issue
Block a user