Compare commits

..

9 Commits

10 changed files with 160 additions and 88 deletions

View File

@@ -65,21 +65,19 @@ def tick(global_state):
target = None target = None
# make sure current chunks are loaded for physics # make sure current chunks are loaded for physics
if not g.chunks.check_loaded(p, 288): if not g.chunks.check_loaded(g.info.render_distance):
if not g.chunks.loading: if not g.chunks.loading:
print('Loading chunks', end='', flush=True) print('Loading chunks', end='', flush=True)
g.chunks.loading = True g.chunks.loading = time.time()
packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True) packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True)
g.connection.write_packet(packet, force=True) g.connection.write_packet(packet, force=True)
return return
else: else:
if g.chunks.loading: if g.chunks.loading:
print() print()
print('Chunks loaded.') print('Chunks loaded in', round(time.time() - g.chunks.loading, 2), 's')
g.chunks.loading = False g.chunks.loading = False
g.chunks.unload_chunks(p)
########## object physics ########## ########## object physics ##########
# note: it's possible the chunk data is out of date when this runs # note: it's possible the chunk data is out of date when this runs

View File

@@ -1,5 +1,6 @@
import re import re
import time import time
from datetime import datetime, timedelta
import random import random
from itertools import count from itertools import count
from munch import Munch from munch import Munch
@@ -22,7 +23,7 @@ class Commands:
def handle_chat(self, message): def handle_chat(self, message):
source, sender, text = message source, sender, text = message
reply = None reply = ''
private = False private = False
for_me = False for_me = False
authed = sender == '0c123cfa-1697-4427-9413-4b645dee7ec0' authed = sender == '0c123cfa-1697-4427-9413-4b645dee7ec0'
@@ -140,7 +141,10 @@ class Commands:
## !time - replies with Minecraft world time ## !time - replies with Minecraft world time
if command == 'time': if command == 'time':
reply = str(self.g.time) seconds = self.g.time * 3.6
start = datetime(2000, 1, 1, hour=6)
mctime = start + timedelta(seconds=seconds)
reply = str(self.g.time) + ' - ' + mctime.strftime('%I:%M %p')
## !count [id] - counts the number of items with that id ## !count [id] - counts the number of items with that id
if command == 'count' and data: if command == 'count' and data:
@@ -149,7 +153,9 @@ class Commands:
## !loaded - replies with the current loaded area ## !loaded - replies with the current loaded area
if command == 'loaded': if command == 'loaded':
reply = str(self.g.chunks.get_loaded_area()) l = self.g.chunks.get_loaded_area()
reply = '{}, {}, {} to {}, {}, {}'.format(*l[0], *l[1])
reply += ' - ' + str(len(self.g.chunks.chunks)//16) + ' chunks'
## !players - prints the current players ## !players - prints the current players
## !players clear - clears the current player list ## !players clear - clears the current player list
@@ -238,23 +244,67 @@ class Commands:
tree = next(self.g.world.find_trees(pos, 50)) tree = next(self.g.world.find_trees(pos, 50))
reply = str(tree)[1:-1] reply = str(tree)[1:-1]
## !block x y z - replies what block is at (x, y, z) ## !info [query] - replies with info on a coordinate, block, item, or player
if command == 'block': if command == 'info':
try:
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x1, y1, z1 = [int(x) for x in data.split()]
except (AttributeError, ValueError):
reply = 'usage: !block x1 y1 z1'
if not reply: if not reply:
try:
check = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x1, y1, z1 = [int(x) for x in check.split()]
coord = (x1, y1, z1) coord = (x1, y1, z1)
block = self.g.world.block_at(*coord) block = self.g.world.block_at(*coord)
if not reply and block is None: if not reply and block is None:
reply = 'first coord out of range' reply = 'coord out of range'
if not reply: if not reply:
reply = blocks.BLOCKS[block] + ':' + str(block) reply = 'Block: ' + blocks.BLOCKS[block] + ':' + str(block)
if blocks.PROPS[block]:
reply += ' - ' + ', '.join(['{}:{}'.format(k, v) for k, v in blocks.PROPS[block].items()])
except (AttributeError, ValueError):
pass
if not reply:
try:
check = int(data)
if check in blocks.BLOCKS:
block = check
reply += 'Block: ' + blocks.BLOCKS[block] + ':' + str(block)
if blocks.PROPS[block]:
reply += ' - ' + ', '.join(['{}:{}'.format(k, v) for k, v in blocks.PROPS[block].items()])
if check in blocks.BLOCKS and check in items.ITEM_NAMES:
reply += ' / '
if check in items.ITEM_NAMES:
item = check
reply += 'Item: ' + items.ITEM_NAMES[item] + ':' + str(item)
except ValueError:
pass
check = data.lower()
if not reply and check in self.g.player_names:
uuid = self.g.player_names[check]
for p in self.g.players.values():
if p.player_uuid == uuid:
player = p
break
else: # for
reply = 'player out of range'
if not reply:
reply += 'Player: '
results = []
for k, v in player.items():
try:
results.append('{}:{}'.format(k, int(v)))
except ValueError:
results.append('{}:{}'.format(k, str(v)))
reply += ', '.join(results)
################# Specific commands ########################## ################# Specific commands ##########################

View File

@@ -15,7 +15,7 @@ from mosfet.protocol.packets import (
ClientWindowConfirmationPacket, EntityMetadataPacket, ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket, SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket, EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
SelectTradePacket, DisconnectPacket, SelectTradePacket, DisconnectPacket, UnloadChunkPacket,
) )
from mosfet.protocol.types import Slot from mosfet.protocol.types import Slot
@@ -49,6 +49,7 @@ class Game:
register(self.handle_spawn_living, SpawnLivingEntityPacket) register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket) register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
register(self.handle_entity_position_rotation, EntityPositionRotationPacket) register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
register(self.handle_entity_look, clientbound.play.EntityLookPacket)
register(self.handle_destroy_entities, DestroyEntitiesPacket) register(self.handle_destroy_entities, DestroyEntitiesPacket)
register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket) register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket)
register(self.handle_respawn, clientbound.play.RespawnPacket) register(self.handle_respawn, clientbound.play.RespawnPacket)
@@ -58,6 +59,7 @@ class Game:
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket) #register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
register(self.handle_trade_list, TradeListPacket) register(self.handle_trade_list, TradeListPacket)
register(self.handle_disconnect, DisconnectPacket) register(self.handle_disconnect, DisconnectPacket)
register(self.handle_unload_chunk, UnloadChunkPacket)
#register(self.handle_packet, Packet, early=True) #register(self.handle_packet, Packet, early=True)
@@ -441,8 +443,14 @@ class Game:
player.x += packet.delta_x / 4096.0 player.x += packet.delta_x / 4096.0
player.y += packet.delta_y / 4096.0 player.y += packet.delta_y / 4096.0
player.z += packet.delta_z / 4096.0 player.z += packet.delta_z / 4096.0
player.yaw = packet.yaw
player.pitch = packet.pitch
#if player.player_uuid == '0c123cfa-1697-4427-9413-4b645dee7ec0': print(packet) def handle_entity_look(self, packet):
player = self.g.players.get(packet.entity_id, None)
if player:
player.yaw = packet.yaw
player.pitch = packet.pitch
def handle_entity_teleport(self, packet): def handle_entity_teleport(self, packet):
mob = self.g.mobs.get(packet.entity_id, None) mob = self.g.mobs.get(packet.entity_id, None)
@@ -486,12 +494,14 @@ class Game:
def handle_respawn(self, packet): def handle_respawn(self, packet):
print(packet) print(packet)
self.g.dimension = packet.world_name.replace('minecraft:', '') self.g.dimension = packet.world_name.replace('minecraft:', '')
self.g.chunks.unload_all_chunks()
def handle_player_list(self, packet): def handle_player_list(self, packet):
for action in packet.actions: for action in packet.actions:
if isinstance(action, packet.AddPlayerAction): if isinstance(action, packet.AddPlayerAction):
self.g.player_names[action.uuid] = action.name self.g.player_names[action.uuid] = action.name
self.g.player_names[action.name] = action.uuid # porque no los dos? self.g.player_names[action.name] = action.uuid
self.g.player_names[action.name.lower()] = action.uuid # porque no los dos?
def handle_update_health(self, packet): def handle_update_health(self, packet):
print(packet) print(packet)
@@ -532,6 +542,9 @@ class Game:
import os import os
os._exit(1) os._exit(1)
def handle_unload_chunk(self, packet):
self.g.chunks.unload_chunk(packet.chunk_x, packet.chunk_z)
def tick(self): def tick(self):
if self.g.breaking: if self.g.breaking:
self.animate() self.animate()

View File

@@ -15,6 +15,11 @@ for name, data in JSON_BLOCKS.items():
for state in data['states']: for state in data['states']:
BLOCKS[state['id']] = name.replace('minecraft:', '') BLOCKS[state['id']] = name.replace('minecraft:', '')
PROPS = {}
for name, data in JSON_BLOCKS.items():
for state in data['states']:
PROPS[state['id']] = state.get('properties', {})
BREAK_DISTANCE = 6 BREAK_DISTANCE = 6
AIR = 0 AIR = 0

View File

@@ -72,14 +72,6 @@ class GatherCropStates:
def break_crop(self): def break_crop(self):
self.g.game.break_block(self.crop) self.g.game.break_block(self.crop)
self.wait_time = 0.5
self.state = self.wait
def wait(self):
# wait for the item
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.select_seed self.state = self.select_seed
def select_seed(self): def select_seed(self):
@@ -91,18 +83,17 @@ class GatherCropStates:
blocks.MATURE_CARROT_ID: items.CARROT_ID, blocks.MATURE_CARROT_ID: items.CARROT_ID,
blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID, blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID,
} }
self.target_seed = crop_seeds[self.type_id]
if self.g.game.select_item([crop_seeds[self.type_id]]): if self.g.game.select_item([self.target_seed]):
self.state = self.wait_select self.state = self.wait_select
self.wait_time = 0.5
else: else:
print('Aborting planting, no crop') print('Havent picked up seed yet')
self.state = self.cleanup return
def wait_select(self): def wait_select(self):
# wait a bit to select if self.target_seed != self.g.holding:
if self.wait_time > 0: return
self.wait_time -= utils.TICK
else: else:
self.state = self.place_crop self.state = self.place_crop
@@ -111,12 +102,11 @@ class GatherCropStates:
self.g.game.place_block(p, BlockFace.TOP) self.g.game.place_block(p, BlockFace.TOP)
print('Placed crop') print('Placed crop')
self.state = self.wait_place self.state = self.wait_place
self.wait_time = 0.5
def wait_place(self): def wait_place(self):
# wait a bit for chunk data to update w = self.g.world
if self.wait_time > 0: if w.block_at(*self.crop) == blocks.AIR:
self.wait_time -= utils.TICK return
else: else:
self.state = self.cleanup self.state = self.cleanup
@@ -134,8 +124,8 @@ class GatherCropStates:
self.crop = None self.crop = None
self.type_id = None self.type_id = None
self.target_seed = None
self.bad_crops = [] self.bad_crops = []
self.wait_time = 0
def run(self): def run(self):
self.state() self.state()

View File

@@ -139,6 +139,14 @@ class SleepWithBedStates:
print('Placing bed') print('Placing bed')
self.g.game.place_block(self.area, BlockFace.TOP) self.g.game.place_block(self.area, BlockFace.TOP)
self.my_bed = True self.my_bed = True
self.wait_time = 0.5
self.state = self.wait_use
def wait_use(self):
# wait to use the bed
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.use_bed self.state = self.use_bed
def use_bed(self): def use_bed(self):

View File

@@ -20,6 +20,7 @@ def get_packets(old_get_packets):
mc_packets.add(packets.EntityTeleport) mc_packets.add(packets.EntityTeleport)
mc_packets.add(packets.TradeListPacket) mc_packets.add(packets.TradeListPacket)
mc_packets.add(packets.DisconnectPacket) mc_packets.add(packets.DisconnectPacket)
mc_packets.add(packets.UnloadChunkPacket)
return mc_packets return mc_packets

View File

@@ -69,11 +69,10 @@ class ChunksManager:
for item_id, locations in chunk.sub_index.items(): for item_id, locations in chunk.sub_index.items():
if item_id not in self.index: if item_id not in self.index:
self.index[item_id] = [] self.index[item_id] = set()
for l in locations: for l in locations:
coords = (dx + l%16, dy + l//256, dz + l%256//16) coords = (dx + l%16, dy + l//256, dz + l%256//16)
self.index[item_id].append(coords) self.index[item_id].add(coords)
#self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME #self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME
if self.loading: if self.loading:
@@ -119,31 +118,24 @@ class ChunksManager:
if block in blocks.INDEXED_IDS: if block in blocks.INDEXED_IDS:
if block not in self.index: if block not in self.index:
self.index[block] = [] self.index[block] = set()
self.index[block].append((x, y, z)) self.index[block].add((x, y, z))
def check_loaded(self, position, steps=1): def check_loaded(self, chunk_distance):
x, y, z = utils.pint(position) num = (chunk_distance * 2 + 1) ** 2
player_chunk = (x//16, 1, z//16) num_subchunks = num * 16
for i in range(steps): # TODO: base off render_distance? return len(self.chunks) >= num_subchunks
offset = utils.spiral(i)
check = utils.padd(player_chunk, offset)
if check not in self.chunks: def unload_chunk(self, x, z):
return False for y in range(16):
try:
return True del self.chunks[(x, y, z)]
except KeyError:
def unload_chunks(self, position): pass
x, y, z = utils.pint(position)
player_chunk = (x//16, 0, z//16)
loaded_chunks = list(self.chunks.keys())
for chunk in loaded_chunks:
check = (chunk[0], 0, chunk[2])
if utils.phyp_king(player_chunk, check) > 20:
del self.chunks[chunk]
def unload_all_chunks(self):
self.chunks = {}
self.index = {}
class ChunkNotLoadedException(Exception): class ChunkNotLoadedException(Exception):
def __str__(self): def __str__(self):

View File

@@ -31,10 +31,10 @@ class ChunkDataPacket(Packet):
self.biomes.append(VarInt.read(file_object)) self.biomes.append(VarInt.read(file_object))
size = VarInt.read(file_object) size = VarInt.read(file_object)
self.data = file_object.read(size) self.data = file_object.read(size)
size_entities = VarInt.read(file_object) #size_entities = VarInt.read(file_object)
self.entities = [] #self.entities = []
for i in range(size_entities): #for i in range(size_entities):
self.entities.append(Nbt.read(file_object)) # self.entities.append(Nbt.read(file_object))
self.decode_chunk_data() self.decode_chunk_data()
@@ -49,9 +49,9 @@ class ChunkDataPacket(Packet):
Integer.send(self.biomes[i], packet_buffer) Integer.send(self.biomes[i], packet_buffer)
VarInt.send(len(self.data), packet_buffer) VarInt.send(len(self.data), packet_buffer)
packet_buffer.send(self.data) packet_buffer.send(self.data)
VarInt.send(len(self.entities), packet_buffer) #VarInt.send(len(self.entities), packet_buffer)
for e in self.entities: #for e in self.entities:
Nbt.send(e, packet_buffer) # Nbt.send(e, packet_buffer)
def decode_chunk_data(self): def decode_chunk_data(self):
packet_data = PacketBuffer() packet_data = PacketBuffer()
@@ -64,9 +64,9 @@ class ChunkDataPacket(Packet):
if self.bit_mask_y & (1 << i): if self.bit_mask_y & (1 << i):
self.chunks[i].read(packet_data) self.chunks[i].read(packet_data)
for e in self.entities: #for e in self.entities:
y = e['y'] # y = e['y']
self.chunks[floor(y/16)].entities.append(e) # self.chunks[floor(y/16)].entities.append(e)
class Chunk: class Chunk:
position = multi_attribute_alias(Vector, 'x', 'y', 'z') position = multi_attribute_alias(Vector, 'x', 'y', 'z')
@@ -458,3 +458,15 @@ class DisconnectPacket(Packet):
definition = [ definition = [
{'reason': String}, {'reason': String},
] ]
class UnloadChunkPacket(Packet):
# Tells the client to unload a chunk column
# https://wiki.vg/Protocol#Unload_Chunk
id = 0x1C
packet_name = 'unload chunk'
definition = [
{'chunk_x': Integer},
{'chunk_z': Integer},
]

View File

@@ -280,12 +280,12 @@ class World:
def check_bed_occupied(self, bed): def check_bed_occupied(self, bed):
# returns true if the bed is occupied by a player # returns true if the bed is occupied by a player
print('Checking bed occupancy:', bed) bid = self.g.chunks.get_block_at(*bed)
for player in self.g.players.values(): if blocks.PROPS[bid]['occupied'] == 'true':
ppos = utils.pint((player.x, player.y, player.z)) print('Checking bed occupancy:', bed, '-> occupied')
if utils.phyp(bed, ppos) <= 1 and player.y - int(player.y) == 0.6875:
print('Bed is occupied by:', player, self.g.player_names[player.player_uuid])
return True return True
else:
print('Checking bed occupancy:', bed, '-> free')
return False return False
def find_cache_openings(self, area): def find_cache_openings(self, area):
@@ -312,6 +312,7 @@ class World:
if utils.phyp(center, pos) > distance: if utils.phyp(center, pos) > distance:
continue continue
result.append(mob) result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result return result
def find_threats(self, center, distance): def find_threats(self, center, distance):
@@ -324,6 +325,7 @@ class World:
if not self.check_air_column(pos, distance): if not self.check_air_column(pos, distance):
continue continue
result.append(mob) result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result return result
def find_villagers(self, center, distance): def find_villagers(self, center, distance):
@@ -336,6 +338,7 @@ class World:
if utils.phyp(center, pos) > distance: if utils.phyp(center, pos) > distance:
continue continue
result.append(mob) result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result return result
def find_villager_openings(self, villager): def find_villager_openings(self, villager):