Compare commits

...

4 Commits

27 changed files with 471 additions and 441 deletions

View File

@ -24,16 +24,17 @@ from mosfet.protocol.managers import DataManager, ChunksManager, ChatManager, Ch
from munch import Munch from munch import Munch
from mosfet import blocks
from mosfet import game from mosfet import game
from mosfet import items
from mosfet import job from mosfet import job
from mosfet import mcdata
from mosfet import mobs
from mosfet import path from mosfet import path
from mosfet import print_help from mosfet import print_help
from mosfet import utils from mosfet import utils
from mosfet import vector from mosfet import vector
from mosfet import world
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
for module in [ for module in [
blocks, blocks,
@ -46,6 +47,7 @@ for module in [
print_help, print_help,
utils, utils,
vector, vector,
world,
]: ]:
importlib.reload(module) importlib.reload(module)
@ -264,7 +266,7 @@ def bot(global_state):
g.chat = ChatManager(g) g.chat = ChatManager(g)
g.game = game.Game(g) g.game = game.Game(g)
g.world = game.MCWorld(g) g.world = world.World(g)
try: try:
while not g.pos: while not g.pos:

View File

@ -2,11 +2,9 @@ import re
import time import time
import importlib import importlib
import random import random
import functools
from math import hypot from math import hypot
from itertools import count from itertools import count
from munch import Munch from munch import Munch
from copy import copy
from minecraft.networking.packets import Packet, clientbound, serverbound from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@ -27,316 +25,12 @@ from mosfet.protocol.types import Slot
from mosfet import print_help from mosfet import print_help
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet import bot from mosfet import bot
from mosfet import vector from mosfet import vector
from mosfet.info import blocks
class MCWorld: from mosfet.info import items
def __init__(self, global_state): from mosfet.info import mcdata
self.g = global_state from mosfet.info import mobs
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: class Game:
def __init__(self, global_state): def __init__(self, global_state):
@ -377,8 +71,8 @@ class Game:
self.g.name = packet.Username self.g.name = packet.Username
def handle_join_game(self, packet): def handle_join_game(self, packet):
print('Received join game packet')
print('Connected.') print('Connected.')
print(packet)
self.g.info = packet self.g.info = packet
self.g.eid = packet.entity_id self.g.eid = packet.entity_id
self.g.dimension = packet.world_name.replace('minecraft:', '') self.g.dimension = packet.world_name.replace('minecraft:', '')
@ -947,8 +641,8 @@ class Game:
elif g.window: elif g.window:
g.window.contents[packet.slot] = packet.slot_data g.window.contents[packet.slot] = packet.slot_data
if packet.window_id >= 0 and not packet.slot_data.present: if g.item_lock and packet.window_id >= 0 and not packet.slot_data.present:
print('unlocking item lock') print('Unlocking item lock')
g.item_lock = False g.item_lock = False
def break_block(self, location): def break_block(self, location):
@ -1301,6 +995,12 @@ class Game:
self.g.health = packet.health self.g.health = packet.health
self.g.food = packet.food self.g.food = packet.food
if packet.health == 0:
print('Died, stopping')
print('Use 1respawn to respawn the bot')
self.close_window()
bot.init(self.g)
def use_item(self, hand): def use_item(self, hand):
packet = serverbound.play.UseItemPacket() packet = serverbound.play.UseItemPacket()
packet.hand = hand packet.hand = hand

View File

@ -1,7 +1,7 @@
import json import json
import importlib import importlib
from mosfet import mcdata from mosfet.info import mcdata
MCD_BLOCKS = {} MCD_BLOCKS = {}
for d in mcdata.mcd.blocks.values(): for d in mcdata.mcd.blocks.values():

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
from mosfet.jobs import ( from mosfet.jobs import (
cache_items, cache_items,

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class CacheItemsStates: class CacheItemsStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class CheckThreatsStates: class CheckThreatsStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class ClearLeavesStates: class ClearLeavesStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class EatFoodStates: class EatFoodStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class FillBlocksStates: class FillBlocksStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class FindGappleStates: class FindGappleStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GatherCropStates: class GatherCropStates:
def idle(self): def idle(self):
@ -35,8 +35,7 @@ class GatherCropStates:
blocks.MATURE_BEETROOT_ID, blocks.MATURE_BEETROOT_ID,
] ]
for crop in w.find_blocks_3d(p, mature_crops, 50, 20): for crop in w.find_blocks_3d(p, mature_crops, 50, 20, True):
print('Found crop:', crop)
if crop not in self.bad_crops: if crop not in self.bad_crops:
break break
else: # for else: # for
@ -44,6 +43,7 @@ class GatherCropStates:
self.state = self.cleanup self.state = self.cleanup
return return
print('Found crop:', crop)
self.crop = crop self.crop = crop
self.type_id = w.block_at(*crop) self.type_id = w.block_at(*crop)
self.state = self.nav_to_crop self.state = self.nav_to_crop
@ -55,10 +55,12 @@ class GatherCropStates:
navpath = w.path_to_place(p, self.crop) navpath = w.path_to_place(p, self.crop)
if navpath: if navpath:
print('Going to crop', self.crop)
self.g.path = navpath self.g.path = navpath
self.g.look_at = utils.padd(self.crop, path.BLOCK_BELOW) self.g.look_at = utils.padd(self.crop, path.BLOCK_BELOW)
self.state = self.going_to_crop self.state = self.going_to_crop
else: else:
print('Cant get to it, blacklisting')
self.bad_crops.append(self.crop) self.bad_crops.append(self.crop)
self.wait_time = 0.5 self.wait_time = 0.5
self.state = self.wait_to_restart self.state = self.wait_to_restart

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GatherSandStates: class GatherSandStates:
def bair(self, p): def bair(self, p):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GatherWartStates: class GatherWartStates:
def idle(self): def idle(self):
@ -29,8 +29,7 @@ class GatherWartStates:
p = utils.pint(self.g.pos) p = utils.pint(self.g.pos)
mature_wart = max(blocks.NETHERWART_IDS) mature_wart = max(blocks.NETHERWART_IDS)
for wart in w.find_blocks_3d(p, [mature_wart], 50, 20): for wart in w.find_blocks_3d(p, [mature_wart], 50, 20, True):
print('Found wart:', wart)
if wart not in self.bad_warts: if wart not in self.bad_warts:
break break
else: # for else: # for
@ -38,6 +37,7 @@ class GatherWartStates:
self.state = self.cleanup self.state = self.cleanup
return return
print('Found wart:', wart)
self.wart = wart self.wart = wart
self.state = self.nav_to_wart self.state = self.nav_to_wart
@ -48,11 +48,21 @@ class GatherWartStates:
navpath = w.path_to_place(p, self.wart) navpath = w.path_to_place(p, self.wart)
if navpath: if navpath:
print('Going to wart', self.wart)
self.g.path = navpath self.g.path = navpath
self.g.look_at = utils.padd(self.wart, path.BLOCK_BELOW) self.g.look_at = utils.padd(self.wart, path.BLOCK_BELOW)
self.state = self.going_to_wart self.state = self.going_to_wart
else: else:
print('Cant get to it, blacklisting')
self.bad_warts.append(wart) self.bad_warts.append(wart)
self.wait_time = 0.5
self.state = self.wait_to_restart
def wait_to_restart(self):
# prevent timeouts
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.find_new_wart self.state = self.find_new_wart
def going_to_wart(self): def going_to_wart(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GatherWoodStates: class GatherWoodStates:
def bair(self, p): def bair(self, p):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GrabSandStates: class GrabSandStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GrabSaplingStates: class GrabSaplingStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class GrabSuppliesStates: class GrabSuppliesStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class PlantTreeStates: class PlantTreeStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class SellToVillagerStates: class SellToVillagerStates:
def idle(self): def idle(self):

View File

@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import blocks from mosfet.info import blocks
from mosfet import items from mosfet.info import items
from mosfet import mcdata from mosfet.info import mcdata
from mosfet import mobs from mosfet.info import mobs
class SleepWithBedStates: class SleepWithBedStates:
def idle(self): def idle(self):

View File

@ -5,7 +5,7 @@ from math import hypot, sqrt
from astar import AStar from astar import AStar
from mosfet import blocks from mosfet.info import blocks
from mosfet import utils from mosfet import utils
class AStarTimeout(Exception): class AStarTimeout(Exception):

View File

@ -10,7 +10,7 @@ from minecraft.networking.types import (
from .types import Nbt, Slot, Entry, Trade from .types import Nbt, Slot, Entry, Trade
from mosfet import blocks from mosfet.info import blocks
class ChunkDataPacket(Packet): class ChunkDataPacket(Packet):

View File

@ -2,8 +2,8 @@ import importlib
import collections import collections
from math import floor, ceil, sqrt, hypot from math import floor, ceil, sqrt, hypot
from mosfet import blocks from mosfet.info import blocks
from mosfet import mcdata from mosfet.info import mcdata
TICK = 0.05 TICK = 0.05
@ -133,49 +133,32 @@ def search_2d(distance=0):
visited.add(cur) visited.add(cur)
yield cur yield cur
def search_3d(distance=0, y_limit=0): def get_neighbors_3d(x,y,z):
def get_neighbors(x,y,z): return [
return [ #(x+1, y+1, z+0),
(x+1, y+1, z+0), #(x+1, y-1, z+0),
(x+1, y-1, z+0), #(x+1, y+1, z+1),
(x+1, y+1, z+1), #(x+1, y+0, z+1),
(x+1, y+0, z+1), #(x+1, y-1, z+1),
(x+1, y-1, z+1), #(x+1, y+1, z-1),
(x+1, y+1, z-1), #(x+1, y+0, z-1),
(x+1, y+0, z-1), #(x+1, y-1, z-1),
(x+1, y-1, z-1), (x+1, y+0, z+0),
(x+1, y+0, z+0), (x+0, y+1, z+0),
(x+0, y+1, z+0), (x+0, y-1, z+0),
(x+0, y-1, z+0), #(x+0, y+1, z+1),
(x+0, y+1, z+1), (x+0, y+0, z+1),
(x+0, y+0, z+1), #(x+0, y-1, z+1),
(x+0, y-1, z+1), #(x+0, y+1, z-1),
(x+0, y+1, z-1), (x+0, y+0, z-1),
(x+0, y+0, z-1), #(x+0, y-1, z-1),
(x+0, y-1, z-1), #(x-1, y+1, z+0),
(x-1, y+1, z+0), #(x-1, y-1, z+0),
(x-1, y-1, z+0), #(x-1, y+1, z+1),
(x-1, y+1, z+1), #(x-1, y+0, z+1),
(x-1, y+0, z+1), #(x-1, y-1, z+1),
(x-1, y-1, z+1), #(x-1, y+1, z-1),
(x-1, y+1, z-1), #(x-1, y+0, z-1),
(x-1, y+0, z-1), #(x-1, y-1, z-1),
(x-1, y-1, z-1), (x-1, y+0, z+0),
(x-1, y+0, z+0), ]
]
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 hypot(*cur) > distance:
continue
for neighbor in get_neighbors(*cur):
to_visit.appendleft(neighbor)
visited.add(cur)
yield cur

333
mosfet/world.py Normal file
View File

@ -0,0 +1,333 @@
import collections
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.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 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 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