Handle door navigation and turnarounds
This commit is contained in:
+257
@@ -666,6 +666,8 @@ class LegacyGame:
|
||||
# migrated actions use the 26.3 packet classes imported above.
|
||||
class Game(LegacyGame):
|
||||
ARM_SWING_INTERVAL = 0.1
|
||||
DOOR_AUTO_OPEN_GRACE = 0.25
|
||||
DOOR_TURN_DURATION = 0.3
|
||||
|
||||
def __init__(self, global_state):
|
||||
self.g = global_state
|
||||
@@ -673,6 +675,9 @@ class Game(LegacyGame):
|
||||
self.break_deadline = None
|
||||
self.breaking_state = None
|
||||
self.next_swing_at = 0.0
|
||||
self.door_navigation = None
|
||||
self.g.yaw_target_override = None
|
||||
self.g.unopenable_doors = self.g.get('unopenable_doors') or set()
|
||||
|
||||
from mosfet.protocol.packets import SetHealthPacket
|
||||
# Health is a protocol-777 packet, not the legacy UpdateHealthPacket.
|
||||
@@ -725,6 +730,258 @@ class Game(LegacyGame):
|
||||
self.sequence += 1
|
||||
return self.sequence
|
||||
|
||||
def _door_info(self, position):
|
||||
state_id = self.g.world.block_at(*position)
|
||||
if state_id not in blocks.DOOR_IDS:
|
||||
return None
|
||||
properties = blocks.PROPS.get(state_id, {})
|
||||
lower = tuple(position)
|
||||
if properties.get('half') == 'upper':
|
||||
lower = utils.padd(lower, path.BLOCK_BELOW)
|
||||
lower_state = self.g.world.block_at(*lower)
|
||||
if lower_state in blocks.DOOR_IDS:
|
||||
state_id = lower_state
|
||||
properties = blocks.PROPS.get(state_id, {})
|
||||
return lower, state_id, properties
|
||||
|
||||
def _toggle_door(self, lower, state_id):
|
||||
position = utils.pint(self.g.pos)
|
||||
dx, dz = position[0] - lower[0], position[2] - lower[2]
|
||||
if abs(dx) > abs(dz):
|
||||
if dx >= 0:
|
||||
face, cursor = BlockFace.EAST, (0.95, 0.5, 0.5)
|
||||
else:
|
||||
face, cursor = BlockFace.WEST, (0.05, 0.5, 0.5)
|
||||
elif abs(dz) > 0:
|
||||
if dz >= 0:
|
||||
face, cursor = BlockFace.SOUTH, (0.5, 0.5, 0.95)
|
||||
else:
|
||||
face, cursor = BlockFace.NORTH, (0.5, 0.5, 0.05)
|
||||
else:
|
||||
face, cursor = BlockFace.TOP, (0.5, 0.95, 0.5)
|
||||
|
||||
boxes = blocks.COLLISION_BOXES.get(state_id, ())
|
||||
if boxes:
|
||||
box = boxes[0]
|
||||
cursor = ((box[0] + box[3]) / 2,
|
||||
(box[1] + box[4]) / 2,
|
||||
(box[2] + box[5]) / 2)
|
||||
self.place_block(lower, face, cursor=cursor)
|
||||
|
||||
def _look_at_with_yaw_separation(self, target, fallback=None):
|
||||
"""Keep target yaw far enough from the player to avoid the near-zero guard."""
|
||||
player = self.g.pos
|
||||
player_x = player.x if hasattr(player, 'x') else player[0]
|
||||
player_z = player.z if hasattr(player, 'z') else player[2]
|
||||
center = [target[0] + 0.5, target[1] + 0.5, target[2] + 0.5]
|
||||
dx, dz = player_x - center[0], player_z - center[2]
|
||||
distance = utils.hypot(dx, dz)
|
||||
if distance < 0.8:
|
||||
if distance < 1e-6 and fallback is not None:
|
||||
dx, dz = fallback
|
||||
distance = utils.hypot(dx, dz)
|
||||
if distance > 1e-6:
|
||||
scale = 0.8 / distance
|
||||
center[0] = player_x - dx * scale
|
||||
center[2] = player_z - dz * scale
|
||||
return tuple(value - 0.5 for value in center)
|
||||
|
||||
def _door_yaw_target(self, look_at):
|
||||
target = vector.Point3D(look_at)
|
||||
target.x += 0.5
|
||||
target.z += 0.5
|
||||
position = self.g.pos
|
||||
if not hasattr(position, 'x'):
|
||||
position = vector.Point3D(position)
|
||||
direction = position - target
|
||||
direction.y = 0
|
||||
if direction.length() <= 0.6:
|
||||
return self.g.yaw
|
||||
return direction.normalized().signedAngleDeg(
|
||||
other=bot.YAW_ANGLE_DIR, ref=bot.YAW_ANGLE_REF)
|
||||
|
||||
def _begin_door_turn(self, navigation, target, fallback=None):
|
||||
look_at = self._look_at_with_yaw_separation(target, fallback)
|
||||
yaw = self.g.yaw
|
||||
target_yaw = self._door_yaw_target(look_at)
|
||||
delta = (target_yaw - yaw + 180) % 360 - 180
|
||||
navigation['yaw_origin'] = yaw
|
||||
navigation['yaw_door_target'] = yaw + delta
|
||||
self.g.yaw_target_override = navigation['yaw_door_target']
|
||||
self.g.look_at = look_at
|
||||
|
||||
def _begin_door_return(self, navigation, now):
|
||||
navigation['phase'] = 'turning_back'
|
||||
navigation['deadline'] = now + self.DOOR_TURN_DURATION
|
||||
self.g.yaw_target_override = navigation.get('yaw_origin')
|
||||
target = navigation.get('forward_look_at')
|
||||
self.g.look_at = (self._look_at_with_yaw_separation(target)
|
||||
if target is not None else
|
||||
navigation['previous_look_at'])
|
||||
|
||||
def _restore_door_look(self, navigation):
|
||||
self.g.look_at = navigation['previous_look_at']
|
||||
self.g.yaw_target_override = None
|
||||
self.door_navigation = None
|
||||
|
||||
def handle_path_doors(self):
|
||||
"""Open closed wooden doors in the active route and close them after crossing."""
|
||||
now = time.monotonic()
|
||||
navigation = self.door_navigation
|
||||
if navigation is not None:
|
||||
lower = navigation['position']
|
||||
info = self._door_info(lower)
|
||||
is_open = bool(info and info[2].get('open') == 'true')
|
||||
|
||||
if navigation['phase'] == 'checking':
|
||||
if (info is None or
|
||||
info[1] not in blocks.OPENABLE_DOOR_IDS):
|
||||
self._restore_door_look(navigation)
|
||||
return False
|
||||
if is_open:
|
||||
navigation['phase'] = 'crossing'
|
||||
navigation['opened_by_bot'] = False
|
||||
navigation['saw_open'] = True
|
||||
self.g.look_at = navigation['previous_look_at']
|
||||
return False
|
||||
if now >= navigation['deadline']:
|
||||
navigation['phase'] = 'opening'
|
||||
navigation['opened_by_bot'] = True
|
||||
navigation['deadline'] = now + 2.0
|
||||
print('[door] opening on path at', lower, flush=True)
|
||||
self._toggle_door(lower, info[1])
|
||||
return True
|
||||
|
||||
if navigation['phase'] == 'opening':
|
||||
if not self.g.path:
|
||||
self._restore_door_look(navigation)
|
||||
return False
|
||||
if is_open:
|
||||
navigation['phase'] = 'crossing'
|
||||
navigation['saw_open'] = True
|
||||
self.g.look_at = navigation['previous_look_at']
|
||||
return False
|
||||
if now > navigation['deadline']:
|
||||
upper = utils.padd(lower, path.BLOCK_ABOVE)
|
||||
self.g.unopenable_doors.update((lower, upper))
|
||||
goal = navigation['goal']
|
||||
self._restore_door_look(navigation)
|
||||
current = utils.pint(self.g.pos)
|
||||
self.g.path = (self.g.world.path_to_place(current, goal)
|
||||
or [])
|
||||
print('Door did not open; routing around', lower,
|
||||
flush=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
if navigation['phase'] == 'crossing':
|
||||
if navigation['after'] is not None and \
|
||||
utils.pint(self.g.pos) == navigation['after']:
|
||||
if navigation.get('saw_open', False):
|
||||
navigation['phase'] = 'turning_to_close'
|
||||
navigation['deadline'] = (
|
||||
now + self.DOOR_TURN_DURATION)
|
||||
navigation['close_door'] = (
|
||||
navigation.get('opened_by_bot', False) and
|
||||
is_open and info is not None and
|
||||
info[2].get('powered') != 'true')
|
||||
after = navigation['after']
|
||||
fallback = ((after[0] - lower[0],
|
||||
after[2] - lower[2])
|
||||
if after is not None else None)
|
||||
self._begin_door_turn(navigation, lower, fallback)
|
||||
return True
|
||||
self._restore_door_look(navigation)
|
||||
return False
|
||||
if not is_open:
|
||||
# If a pressure plate or redstone opened it, wait briefly
|
||||
# for its state update rather than toggling it closed.
|
||||
if (info is not None and
|
||||
info[1] in blocks.OPENABLE_DOOR_IDS and
|
||||
lower in self.g.path):
|
||||
navigation['phase'] = 'checking'
|
||||
navigation['opened_by_bot'] = False
|
||||
navigation['deadline'] = (
|
||||
now + self.DOOR_AUTO_OPEN_GRACE)
|
||||
return True
|
||||
if (navigation.get('saw_open') and
|
||||
navigation['after'] is not None):
|
||||
return False
|
||||
self._restore_door_look(navigation)
|
||||
return False
|
||||
|
||||
if navigation['phase'] == 'turning_to_close':
|
||||
if now >= navigation['deadline']:
|
||||
if (navigation.get('close_door') and is_open and
|
||||
info is not None and
|
||||
info[2].get('powered') != 'true'):
|
||||
navigation['phase'] = 'closing'
|
||||
navigation['deadline'] = now + 2.0
|
||||
print('[door] closing after crossing at', lower,
|
||||
flush=True)
|
||||
self._toggle_door(lower, info[1])
|
||||
else:
|
||||
self._begin_door_return(navigation, now)
|
||||
return True
|
||||
|
||||
if navigation['phase'] == 'closing':
|
||||
if not is_open or now > navigation['deadline']:
|
||||
self._begin_door_return(navigation, now)
|
||||
return True
|
||||
|
||||
if navigation['phase'] == 'turning_back':
|
||||
if now >= navigation['deadline']:
|
||||
self._restore_door_look(navigation)
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.g.path:
|
||||
return False
|
||||
|
||||
current = utils.pint(self.g.pos)
|
||||
path_index = 0
|
||||
if (len(self.g.path) > 1 and
|
||||
tuple(self.g.path[0]) == current):
|
||||
path_index = 1
|
||||
if path_index >= len(self.g.path):
|
||||
return False
|
||||
info = self._door_info(tuple(self.g.path[path_index]))
|
||||
if info is None:
|
||||
return False
|
||||
lower, state_id, properties = info
|
||||
is_open = properties.get('open') == 'true'
|
||||
if (not is_open and state_id not in blocks.OPENABLE_DOOR_IDS) or \
|
||||
utils.phyp(current, lower) > 3:
|
||||
return False
|
||||
|
||||
after_index = path_index + 1
|
||||
after = (tuple(self.g.path[after_index])
|
||||
if after_index < len(self.g.path) else None)
|
||||
if after_index + 1 < len(self.g.path):
|
||||
forward_look_at = tuple(self.g.path[after_index + 1])
|
||||
elif after is not None:
|
||||
forward_look_at = tuple(
|
||||
after[i] + 2 * (after[i] - lower[i]) for i in range(3))
|
||||
else:
|
||||
forward_look_at = None
|
||||
navigation = {
|
||||
'position': lower,
|
||||
'after': after,
|
||||
'goal': tuple(self.g.path[-1]),
|
||||
'phase': 'crossing' if is_open else 'checking',
|
||||
'deadline': now + self.DOOR_AUTO_OPEN_GRACE,
|
||||
'opened_by_bot': False,
|
||||
'saw_open': is_open,
|
||||
'close_door': False,
|
||||
'previous_look_at': self.g.get('look_at'),
|
||||
'forward_look_at': forward_look_at,
|
||||
}
|
||||
self.door_navigation = navigation
|
||||
if not is_open:
|
||||
self.g.look_at = lower
|
||||
return True
|
||||
return False
|
||||
|
||||
def animate(self):
|
||||
"""Ask the 26.3 server to broadcast a main-hand punch animation."""
|
||||
self._send_action(PunchPacket())
|
||||
|
||||
Reference in New Issue
Block a user