Compare commits

...

50 Commits

Author SHA1 Message Date
bbe2d9f99c Fix farm crop seed pick up bug 2021-06-19 22:17:52 +00:00
2f77c7b990 Sort mobs by distance 2021-06-19 21:30:55 +00:00
3540e5580e Remove delays from gather_crop job 2021-06-19 21:30:41 +00:00
261684ea90 Handle entity rotation packets 2021-05-07 04:13:36 +00:00
09b09b3f96 Improve !info command 2021-05-07 04:02:51 +00:00
4d2d358175 Use block state id to check bed occupancy 2021-05-07 04:02:26 +00:00
324ad41bf7 Print Minecraft time in 12h format 2021-05-07 00:30:29 +00:00
343268af24 Use a set for the block index to limit size 2021-05-07 00:29:41 +00:00
e5fd062a4a Handle loading / unloading chunks better 2021-05-07 00:14:25 +00:00
5184d4a173 Reply in-game with inventory 2021-05-06 09:29:26 +00:00
396c1e2e33 Update README 2021-05-06 07:13:41 +00:00
217bb0dae0 Remove extra picture 2021-05-06 07:12:34 +00:00
f7400a1225 Make bot look where he's going while trading 2021-05-03 02:59:23 +00:00
3c31c41acd Try different ports for web interface 2021-05-02 01:47:40 +00:00
15d4dd7922 Add check for missing email 2021-05-02 01:40:15 +00:00
8f0d2eb417 Add check for square brackets 2021-05-02 01:38:26 +00:00
6315f8c309 Add docs about grabbing supplies 2021-05-02 01:26:06 +00:00
2fc08be581 Add link to trapped chest minecraft wiki 2021-05-02 00:35:19 +00:00
c39a8c6d93 Add info about caching items 2021-05-02 00:32:03 +00:00
43e8bf97a0 Add picture of trapped chest 2021-05-02 00:15:13 +00:00
b59c260e90 Begin cache items page 2021-05-01 23:44:33 +00:00
a41d9f2793 Switch USERNAME to EMAIL to avoid collisions 2021-05-01 23:34:43 +00:00
0d2be1378f Improve README 2021-05-01 23:27:34 +00:00
0d23dbaf9f Add burner account credentials to use by default 2021-05-01 23:22:43 +00:00
a87cc85eab Improve trading villager selection based on profession 2021-05-01 21:31:21 +00:00
080895421d Improve parsing chat commands 2021-04-29 02:18:35 +00:00
e4ea9aeaa0 Update README 2021-04-29 02:18:35 +00:00
ad6c412802 Add support for Python 3.6 (thanks sose) 2021-04-28 02:12:15 -06:00
aec057c89c Use our own hypot() for older Python support 2021-04-28 07:12:30 +00:00
sose
af9cc4b546 allow connection to offline mode servers without a password 2021-04-26 20:31:00 -07:00
sose
666435ddae added requirements, updated install command 2021-04-26 20:29:31 -07:00
1c0824a167 Fix README typo 2021-04-26 03:26:07 +00:00
47c2fca00a Print web interface port 2021-04-26 03:25:42 +00:00
76cdf90b44 Commit web interface build and serve statically 2021-04-26 03:07:06 +00:00
33145cb3f2 Display some basic stats on web 2021-04-26 02:37:43 +00:00
963b3d9736 Begin web interface hello world 2021-04-25 23:43:55 +00:00
a49caaedf5 Sleep 0.1 s after failing to pathfind to prevent timeouts 2021-04-25 23:39:57 +00:00
7caa51f011 Add function for faking blocks while pathing 2021-04-25 21:25:15 +00:00
e588c8fa1a Remove script delays 2021-04-25 21:24:58 +00:00
650398255b Detect being forced to wake up 2021-04-25 00:59:43 +00:00
d69f9cf09e Don't sleep in occupied beds 2021-04-25 00:33:55 +00:00
8b85de2b2a Limit reply length so bot doesn't get kicked 2021-04-24 23:50:52 +00:00
ce9613c00c Index blocks on change 2021-04-24 23:37:11 +00:00
09b9002c58 Fix bug where needed and wanted items werent updating 2021-04-24 22:59:22 +00:00
4026b410f4 Set sand origin when command is given 2021-04-24 22:34:25 +00:00
a5642409d2 Move command processing to separate file 2021-04-23 07:01:00 +00:00
ae2b0f4875 Move blocks, items, mcdata, mobs to info/ folder 2021-04-23 01:41:01 +00:00
23891066c0 Improve searching for crops 2021-04-23 01:25:44 +00:00
9874e23aa6 Move World class into its own file 2021-04-23 00:18:53 +00:00
221d497204 Reset the bot on death 2021-04-23 00:05:56 +00:00
58 changed files with 13219 additions and 1172 deletions

1
.gitignore vendored
View File

@@ -8,7 +8,6 @@ __pycache__/
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/

View File

@@ -1,10 +1,16 @@
# Mosfet Minecraft Bot
A general-purpose Minecraft 1.16 bot written in Python.
A general-purpose Minecraft 1.16 bot written in Python that uses an actual
Minecraft account to play.
Mosfet is able to farm wood by cutting trees, gather sand, gather netherwart,
and trade with villagers to get emeralds. He can eat, sleep, and flee from
threats.
Mosfet is able to farm wood by cutting trees, farm crops, gather sand, farm
netherwart, and trade with villagers to get emeralds. He can eat, sleep, and
flee from threats.
## Requirements
- Python >= 3.6
- pip, virtualenv, git, wget, unzip
## Linux Setup
@@ -12,7 +18,7 @@ Assuming Debian / Ubuntu based distro:
```
$ sudo apt update
$ sudo apt install build-essential python3 python3-dev python3-pip python3-virtualenv git wget unzip
$ sudo apt install build-essential python3 python3-dev python3-pip virtualenv git wget unzip
$ git clone https://git.tannercollin.com/tanner/minecraft-bot.git
$ cd minecraft-bot/
@@ -20,15 +26,38 @@ $ cd minecraft-bot/
## Running
If you want to use the built-in burner account (Minecraft name `mattstack`):
```
$ USERNAME=you@domain.com PASSWORD=supersecret SERVER=example.com ./run-linux.sh
$ SERVER=minecraft.example.com ./run_linux.sh
```
Or, use `PORT` to specify a custom port to connect to:
```
$ SERVER=localhost PORT=12345 ./run_linux.sh
```
Or, if you have your own alt account for the bot:
```
$ EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh
```
## Usage
[How to Cache Items](docs/cache_items.md)
[How to Grab Supplies](docs/grab_supplies.md)
## Commands
The command prefix character is the last character of the bot's name. For
example, if the bot's name is `mosfet1`, then you would issue commands like
`1farm wood` or `1pos`. This lets you run multiple bots on the same server.
example, if the bot's name is `mattstack`, then you would issue commands like
`kfarm wood` or `kpos`. This lets you run multiple bots on the same server.
In the following examples, we'll assume the bot's name is `mosfet1`, so commands
would be ran like `1farm wood` or `1pos`.
The exception are the below public commands, they can optionally be prefixed with `!`
and all bots will run the command.
@@ -59,7 +88,7 @@ These can be ran by anyone, all bots will reply.
`!error` - raises an error
`!inv` - prints current inventory
`!inv` - replies and prints current inventory
`!time` - replies with Minecraft world time
@@ -95,7 +124,7 @@ These can be ran by anyone, all bots will reply.
### Bot-specific Commands
These will only run for the bot they are addressed to.
These will only run for the bot they are addressed to. Replace `1` with the last character of the bot's name.
`1respawn` - respawns the bot if it's dead
@@ -160,4 +189,4 @@ put to use.
## Acknowledgements
Thanks to Isaia and the devs behind pyCraft.
Thanks to Isaia, sose, and the devs behind pyCraft.

28
docs/cache_items.md Normal file
View File

@@ -0,0 +1,28 @@
# Mosfet Minecraft Bot
## How to Cache Items
![a single trapped chest with three normal double chests under it](media/cache_items.png)
Mosfet will automatically dump items into the closest [trapped
chest](https://minecraft.fandom.com/wiki/Trapped_Chest#Crafting) once his
inventory gets full enough. Normally this is when the number of used inventory
slots is >= 27.
If Mosfet can't find a trapped chest nearby, he will place down a normal chest
if he has one in his inventory. Then he will store the items in it.
Mosfet won't cache items related to his current job. For example, if he's
farming wood, he will keep axes, beds, chests, and food.
### Advanced Details
The global `g.minimum_cache_slots` in `bot.py` controls the minimum amount of
used slots needed to trigger item caching.
In `jobs.py`, each job calls `items.set_wanted()` and `items.set_needed()` to
control which items Mosfet shouldn't cache. No items in the "needed" list will
be cached and all items in the "wanted" list—except for one stack of each—will
be cached. The "needed" list is meant for essentials like tools, food, and beds,
while the "wanted" list is for stuff that's good to have a stack of like seeds
and saplings.

30
docs/grab_supplies.md Normal file
View File

@@ -0,0 +1,30 @@
# Mosfet Minecraft Bot
## How to Grab Supplies
![a single barrel with two double chests above it](media/grab_supplies.png)
Mosfet will automatically grab supplies he needs to do his job from nearby
[barrels](https://minecraft.fandom.com/wiki/Barrel#Crafting). Before starting
the job, he will check each barrel for related supplies and grab some depending
on the job. He will recheck the barrels for supplies periodically to try and get
more.
If he can't find any barrels, he will try to continue doing the job anyway. If
his inventory is too full, he will skip grabbing supplies.
### Advanced Details
The global `g.maximum_supply_slots` in `bot.py` controls the maximum amount of
used slots allowed while grabbing supplies.
In `jobs.py`, each job sets the `grab_supplies_states.supplies` dictionary to
specify what items to grab and how many. Here's an example:
```
self.grab_supplies_states.supplies = {
tuple([items.PUMPKIN_ID]): (64, 3),
}
```
This tells Mosfet to grab three stacks of pumpkins if he ever has less than 64.

BIN
docs/media/cache_items.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

37
main.py
View File

@@ -4,8 +4,15 @@ import time
import traceback
import json
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
from flask import Flask
app = Flask(__name__)
from flask_cors import CORS
build_folder = 'web_interface/build'
app = Flask(__name__, template_folder=build_folder, static_folder=build_folder, static_url_path='')
CORS(app)
from munch import Munch
from watchdog.observers import Observer
@@ -20,6 +27,7 @@ g.name = None
g.mcdata = False
g.pos = False
g.dimension = None
g.item_lock = False
g.inv = {}
g.objects = {}
g.mobs = {}
@@ -32,9 +40,10 @@ g.holding = 0
g.afk = False
g.health = 20
g.food = 20
g.sand_origin = None
@app.route('/')
def hello_world():
@app.route('/api/global')
def api_global():
data = json.dumps(g, default=lambda o: str(o), indent=4)
response = app.response_class(
@@ -44,6 +53,10 @@ def hello_world():
)
return response
@app.route('/')
def root():
return app.send_static_file('index.html')
reload_timeout = time.time()
def main():
@@ -58,7 +71,7 @@ def main():
event_handler.on_any_event = reload_bot
observer = Observer()
observer.schedule(event_handler, '.', recursive=True)
observer.schedule(event_handler, 'mosfet', recursive=True)
observer.start()
try:
@@ -78,8 +91,22 @@ def main():
observer.stop()
observer.join()
def run_api():
host = '0.0.0.0'
port = 3300
while True:
print('Trying to run web interface on port:', port)
print('If it works, go to http://localhost:' + str(port))
try:
app.run(host=host, port=port)
except OSError:
print()
print('Error: Port already taken.')
port += 1
if __name__ == '__main__':
threading.Thread(target=app.run).start()
threading.Thread(target=run_api).start()
time.sleep(1)
main()

View File

@@ -3,16 +3,12 @@ if __name__ == '__main__':
exit(1)
import os
import sys
import time
import importlib
from math import floor, ceil
from copy import copy
USERNAME = os.environ['USERNAME']
PASSWORD = os.environ['PASSWORD']
SERVER = os.environ['SERVER']
PORT = int(os.environ.get('PORT', 25565))
from . import monkey_patch # must be before any possible pyCraft imports
from minecraft import authentication
@@ -24,19 +20,22 @@ from mosfet.protocol.managers import DataManager, ChunksManager, ChatManager, Ch
from munch import Munch
from mosfet import blocks
from mosfet import commands
from mosfet import game
from mosfet import items
from mosfet import job
from mosfet import mcdata
from mosfet import mobs
from mosfet import path
from mosfet import print_help
from mosfet import utils
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 [
blocks,
commands,
game,
items,
job,
@@ -46,6 +45,7 @@ for module in [
print_help,
utils,
vector,
world,
]:
importlib.reload(module)
@@ -65,21 +65,19 @@ def tick(global_state):
target = None
# 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:
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)
g.connection.write_packet(packet, force=True)
return
else:
if g.chunks.loading:
print()
print('Chunks loaded.')
print('Chunks loaded in', round(time.time() - g.chunks.loading, 2), 's')
g.chunks.loading = False
g.chunks.unload_chunks(p)
########## object physics ##########
# note: it's possible the chunk data is out of date when this runs
@@ -202,8 +200,8 @@ def tick(global_state):
packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=g.pitch, yaw=g.yaw, on_ground=(not in_air))
g.connection.write_packet(packet)
g.game.tick()
g.job.tick()
g.game.tick() # order important for correction_count
def init(global_state):
@@ -240,20 +238,57 @@ def init(global_state):
g.maximum_supply_slots = 33
def bot(global_state):
EMAIL = os.getenv('EMAIL')
PASSWORD = os.getenv('PASSWORD')
SERVER = os.getenv('SERVER')
PORT = int(os.environ.get('PORT', 25565))
g = global_state
if not g.mcdata:
g.mcdata = DataManager('./minecraft_data')
if not g.connection:
if not SERVER:
print()
print('You must specify a server to connect to. For example:')
print('SERVER=minecraft.example.com ./run_linux.sh')
print('SERVER=localhost PORT=12345 ./run_linux.sh')
print()
print('If you want to use your own account:')
print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh')
os._exit(0)
elif not g.connection:
if EMAIL and PASSWORD:
auth_token = authentication.AuthenticationToken()
try:
auth_token.authenticate(USERNAME, PASSWORD)
auth_token.authenticate(EMAIL, PASSWORD)
except YggdrasilError as e:
print(e)
sys.exit()
os._exit(0)
print("Logged in as %s..." % auth_token.username)
g.connection = Connection(SERVER, PORT, auth_token=auth_token)
elif EMAIL:
print('No password provided, attempting to connect in offline mode...')
g.connection = Connection(SERVER, PORT, username=EMAIL)
elif PASSWORD:
print('')
print('Did you forget to specify an email?')
print('If you want to use your own account:')
print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh')
os._exit(0)
else:
print('No username or password provided, using burner minecraft account...')
EMAIL = 'moc.liamg@monortem'[::-1]
PASSWORD = '!8891anteR'[::-1]
auth_token = authentication.AuthenticationToken()
try:
auth_token.authenticate(EMAIL, PASSWORD)
except YggdrasilError as e:
print(e)
os._exit(0)
print("Logged in as %s..." % auth_token.username)
g.connection = Connection(SERVER, PORT, auth_token=auth_token)
g.chunks = ChunksManager(g.mcdata)
@@ -264,7 +299,8 @@ def bot(global_state):
g.chat = ChatManager(g)
g.game = game.Game(g)
g.world = game.MCWorld(g)
g.world = world.World(g)
g.commands = commands.Commands(g)
try:
while not g.pos:

585
mosfet/commands.py Normal file
View File

@@ -0,0 +1,585 @@
import re
import time
from datetime import datetime, timedelta
import random
from itertools import count
from munch import Munch
from mosfet.protocol.types import Slot
from mosfet import print_help
from mosfet import utils
from mosfet import path
from mosfet import bot
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mobs
class Commands:
def __init__(self, global_state):
self.g = global_state
self.g.chat.set_handler(self.handle_chat)
def handle_chat(self, message):
source, sender, text = message
reply = ''
private = False
for_me = False
authed = sender == '0c123cfa-1697-4427-9413-4b645dee7ec0'
bot_num = self.g.name[-1]
if source == 'SYSTEM':
self.g.command_lock = False
if text == 'You are now AFK.':
self.g.afk = True
elif text == 'You are no longer AFK.':
self.g.afk = False
text = text.replace('zzz', '!zzz')
match = re.match(r'(.*\W+)\s+(['+bot_num+'|!])(\w+) ?(.*)', text)
if match:
meta, prefix, command, data = match.groups()
else:
return
if '-> me' in meta:
private = True
if prefix == bot_num:
for_me = True
if data.startswith('[') and data.endswith(']'):
command = 'nosquarebrackets'
try:
## ### Public Commands
## These can be ran by anyone, all bots will reply.
## !help - prints this whole help message to console
## !help [command] - replies in-game explaining command
if command == 'help':
if data:
for line in print_help.HELP_LINES:
if line[1:].startswith(data) or line[1:].startswith(data[1:]):
reply = 'command ' + line
break
else: # for
reply = 'command not found'
else:
print()
print()
for line in print_help.HELP_LINES:
print(line)
reply = 'check console'
## !ping - replies with "pong"
if command == 'ping':
reply = 'pong'
## !echo [data] - replies with "data"
if command == 'echo' and data:
reply = data
if command == 'nosquarebrackets':
reply = 'don\'t literally put the [ ]'
## !pos - replies with position and dimension
if command == 'pos':
reply = str(utils.pint(self.g.pos))[1:-1] + ', ' + self.g.dimension
## !afk - goes AFK with /afk
if command == 'afk':
if not self.g.afk:
reply = '/afk'
## !unafk - goes not AFK with /afk
if command == 'unafk':
if self.g.afk:
reply = '/afk'
## !error - raises an error
if command == 'error':
reply = 'ok'
raise
## !inv - replies and prints current inventory
if command == 'inv' or command == 'inventory':
inv_list = []
uniq_item_counts = {}
for i in self.g.inv.values():
if i.present:
inv_list.append((items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count))
if i.item_id not in uniq_item_counts:
uniq_item_counts[i.item_id] = 0
uniq_item_counts[i.item_id] += i.item_count
inv_list.sort()
console_result = '\n'.join(['{}:{} x {}'.format(*x) for x in inv_list])
if not console_result:
print('Empty')
reply = 'empty'
else:
print(console_result)
reply_result_1 = ', '.join(['{}:{} x {}'.format(*x) for x in inv_list])
reply_result_2 = ', '.join(['{}:{} x {}'.format(items.ITEM_NAMES[k], str(k), v) for k,v in uniq_item_counts.items()])
reply_result_3 = ', '.join(['{}:{}x{}'.format(items.ITEM_NAMES[k], str(k), v) for k,v in uniq_item_counts.items()])
reply_result_4 = ', '.join(['{}:{}x{}'.format(re.sub(r'[aeiou]', '', items.ITEM_NAMES[k]), str(k), v) for k,v in uniq_item_counts.items()])
reply_result_5 = ' '.join(['{}{}x{}'.format(re.sub(r'[aeiou]', '', items.ITEM_NAMES[k]), str(k), v) for k,v in uniq_item_counts.items()])
for r in [reply_result_1, reply_result_2, reply_result_3, reply_result_4, reply_result_5]:
reply = r
if len(reply) < 256:
break
## !time - replies with Minecraft world time
if command == '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
if command == 'count' and data:
item = int(data)
reply = str(self.g.game.count_items([item]))
## !loaded - replies with the current loaded area
if command == 'loaded':
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 clear - clears the current player list
if command == 'players':
if data == 'clear':
self.g.players = {}
reply = 'ok'
else:
for k, v in self.g.players.items():
print(str(k) + ':', v, self.g.player_names[v.player_uuid])
## !objects - prints the current items on ground
## !objects clear - clears the current object list
if command == 'objects':
if data == 'clear':
self.g.objects = {}
reply = 'ok'
else:
for k, v in self.g.objects.items():
if data and v.item_id != int(data): continue
print(str(k) + ':', v, items.ITEM_NAMES[v.item_id])
## !mobs - prints the current mobs
## !mobs clear - clears the current mob list
if command == 'mobs':
if data == 'clear':
self.g.mobs = {}
reply = 'ok'
else:
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
for k, v in all_mobs:
if data and v.type != int(data): continue
print(str(k) + ':', v, mobs.MOB_NAMES[v.type])
reply = str(len(all_mobs)) + ' mobs'
## !monsters - prints the current monsters
if command == 'monsters':
monsters = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
count = 0
for k, v in monsters:
if v.type not in mobs.EVIL_IDS: continue
if data and v.type != int(data): continue
count += 1
print(str(k) + ':', v, mobs.MOB_NAMES[v.type])
reply = str(count) + ' monsters'
## !villagers - prints the current villagers
if command == 'villagers':
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
count = 0
for k, v in all_mobs:
type_name = mobs.MOB_NAMES[v.type]
if type_name != 'villager' : continue
count += 1
print(str(k) + ':', v, type_name)
reply = str(count) + ' villagers'
## !threats - prints the dangerous monsters within 20 blocks
## !threats [num] - prints the dangerous monsters within num blocks
if command == 'threats':
distance = int(data) if data else 20
p = utils.pint(self.g.pos)
threats = self.g.world.find_threats(p, distance)
for t in threats:
print(str(t.entity_id) + ':', t, mobs.MOB_NAMES[t.type])
reply = str(len(threats)) + ' threats'
if command == 'spiral' and data:
for i in range(int(data)):
print(utils.spiral(i))
if command == 'sand_slice':
result = self.g.world.find_sand_slice(utils.pint(self.g.pos), 50)
reply = str(result)
## "zzz" or !zzz - bot does /afk to let others sleep
if command == 'zzz':
if not self.g.afk and self.g.dimension == 'overworld':
reply = '/afk'
self.g.afk_timeout = 5.0
## !tree - replies with the closest tree
if command == 'tree':
pos = utils.pint(self.g.pos)
tree = next(self.g.world.find_trees(pos, 50))
reply = str(tree)[1:-1]
## !info [query] - replies with info on a coordinate, block, item, or player
if command == 'info':
if not reply:
try:
check = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x1, y1, z1 = [int(x) for x in check.split()]
coord = (x1, y1, z1)
block = self.g.world.block_at(*coord)
if not reply and block is None:
reply = 'coord out of range'
if not reply:
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 ##########################
## ### Bot-specific Commands
## These will only run for the bot they are addressed to.
if for_me:
## 1respawn - respawns the bot if it's dead
if command == 'respawn':
self.g.game.respawn()
reply = 'ok'
## 1gather wood - gathers wood from the world
## 1gather sand - gathers sand from the world
if command == 'gather' and data:
if data == 'wood':
self.g.job.state = self.g.job.gather_wood
reply = 'ok'
elif data == 'sand':
if not self.g.sand_origin or not self.g.chunks.check_loaded(self.g.sand_origin):
self.g.sand_origin = utils.pint(self.g.pos)
self.g.job.state = self.g.job.gather_sand
reply = 'ok'
if reply:
for i in self.g.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
## 1farm wood - farms wood from a certain area
## 1farm sand - farms sand from a certain area
## 1farm wart - farms netherwart from a certain area
## 1farm crop - farms mature crops from a certain area
if command == 'farm' and data:
if data == 'wood':
self.g.job.state = self.g.job.farm_wood
reply = 'ok'
elif data == 'sand':
if not self.g.sand_origin or not self.g.chunks.check_loaded(self.g.sand_origin):
self.g.sand_origin = utils.pint(self.g.pos)
self.g.job.state = self.g.job.farm_sand
reply = 'ok'
elif data == 'wart':
self.g.job.state = self.g.job.farm_wart
reply = 'ok'
elif data.startswith('crop'):
self.g.job.state = self.g.job.farm_crop
reply = 'ok'
if reply and self.g.dimension == 'overworld':
for i in self.g.inv.values():
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
## 1loiter - stands still but eats, sleeps, and flees
if command == 'loiter':
self.g.job.state = self.g.job.loiter
reply = 'ok'
## 1trade - sells items to villagers to get emeralds
if command == 'trade':
self.g.job.state = self.g.job.trade
reply = 'ok'
## 1stop - stops the current job and resets bot
if command == 'stop':
self.g.game.close_window()
bot.init(self.g)
reply = 'ok'
## 1drop - drops the current stack its holding
if command == 'drop':
self.g.game.drop_stack()
## 1select [id] - moves item with id into main hand
if command == 'select' and data:
item = int(data)
if self.g.game.select_item([item]):
reply = 'ok'
else:
reply = 'not found'
## 1dump [id] - drops all items matching id
if command == 'dump' and data:
item = int(data)
if self.g.game.count_items([item]):
self.g.dumping = item
reply = 'ok'
else:
reply = 'not found'
## 1drain - drops all items in inventory
if command == 'drain':
self.g.draining = True
reply = 'ok'
if command == 'gapple':
self.g.job.state = self.g.job.find_gapple
if data:
self.g.job.find_gapple_states.count = int(data)
reply = 'ok'
if command == 'cache':
self.g.job.state = self.g.job.cache_items
self.g.job.cache_items_states.minimum = 0
self.g.job.cache_items_states.silent = True
reply = 'ok'
## 1fill [x] [y] [z] [x] [y] [z] - fills the cuboid with the block at the first coordinate
if command == 'fill':
try:
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x1, y1, z1, x2, y2, z2 = [int(x) for x in data.split()]
except (AttributeError, ValueError):
reply = 'usage: !fill x1 y1 z1 x2 y2 z2'
if not reply:
coord1 = (x1, y1, z1)
coord2 = (x2, y2, z2)
block = self.g.world.block_at(*coord1)
if not reply and y1 > y2:
reply = 'can only fill upwards'
if not reply and block is None:
reply = 'first coord out of range'
if not reply and block == 0:
reply = 'can\'t fill with air'
if not reply:
self.g.filling = Munch(coord1=coord1, coord2=coord2, block=block)
self.g.job.state = self.g.job.fill_blocks
reply = 'filling ' + str(utils.pvolume(coord1, coord2)) + ' with ' + blocks.BLOCKS[block]
## 1here - bot comes to your location
if command == 'here':
if not reply:
for p in self.g.players.values():
if p.player_uuid == sender:
player = p
break
else: # for
reply = 'can\'t find you'
if not reply:
pos = utils.pint(self.g.pos)
goal = utils.pint((p.x, p.y, p.z))
start = time.time()
navpath = self.g.world.path_to_place(pos, goal)
if navpath:
self.g.path = navpath
if self.g.job:
self.g.job.stop()
print(len(navpath))
print(navpath)
print(round(time.time() - start, 3), 'seconds')
if self.g.job:
self.g.job.stop()
self.g.look_at = None
reply = 'ok'
else:
reply = 'no path'
## 1goto [x] [y] [z] - sends the bot to coordinate (x, y, z)
if command == 'goto':
try:
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x2, y2, z2 = [int(x) for x in data.split()]
except (AttributeError, ValueError):
reply = 'usage: !goto x y z'
if not reply:
pos = utils.pint(self.g.pos)
goal = utils.pint((x2, y2, z2))
start = time.time()
navpath = self.g.world.path_to_place(pos, goal)
if navpath:
self.g.path = navpath
if self.g.job:
self.g.job.stop()
print(len(navpath))
print(navpath)
print(round(time.time() - start, 3), 'seconds')
if self.g.job:
self.g.job.stop()
self.g.look_at = None
reply = 'ok'
else:
reply = 'no path'
if command == 'break':
self.g.game.break_block(blocks.TEST_BLOCK)
reply = 'ok'
if command == 'open':
self.g.game.open_container(blocks.TEST_BLOCK)
## 1close - closes the current Minecraft window
if command == 'close':
if self.g.window:
self.g.game.close_window()
reply = 'ok'
else:
reply = 'nothing open'
## 1click [slot] [button] [mode] - clicks the current window
if command == 'click' and data:
if self.g.window:
slot, button, mode = [int(x) for x in data.split(' ')]
try:
item = self.g.window.contents[slot]
except KeyError:
item = Slot(present=False)
print(item)
self.g.game.click_window(slot, button, mode, item)
else:
reply = 'nothing open'
## 1use - use the item it's currently holding
if command == 'use':
self.g.game.use_item(0)
## 1interact [entity id] - interacts with that entity
if command == 'interact' and data:
self.g.game.interact(int(data))
if command == 'test':
reply = 'ok'
r = self.g.world.find_villager_openings((615, 78, 493))
print(r)
################# Authorized commands ##########################
## ### Authorized Commands
## These dangerous commands can only be ran by the bot owner.
if authed:
## 1print [expression] - replies with Python eval(expression)
if command == 'print':
data = data.replace('`', '.')
reply = str(eval(data))
## 1exit - exits the program
if command == 'exit':
import os
os._exit(0)
except BaseException as e:
import traceback
print(traceback.format_exc())
reply = 'Error: {} - {}\n'.format(e.__class__.__name__, e)
pass
if reply:
print('Reply:', reply)
if len(reply) >= 256:
reply = 'reply too long, check console'
if private and not reply.startswith('/'):
self.g.chat.send('/r ' + reply)
else:
self.g.chat.send(reply)

View File

@@ -1,12 +1,8 @@
import re
import time
import importlib
import random
import functools
from math import hypot
from itertools import count
from munch import Munch
from copy import copy
from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.networking.types import BlockFace
@@ -19,324 +15,19 @@ from mosfet.protocol.packets import (
ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
SelectTradePacket, DisconnectPacket,
SelectTradePacket, DisconnectPacket, UnloadChunkPacket,
)
from mosfet.protocol.types import Slot
from mosfet import print_help
from mosfet import utils
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 vector
class MCWorld:
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
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class Game:
def __init__(self, global_state):
@@ -358,6 +49,7 @@ class Game:
register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
register(self.handle_entity_look, clientbound.play.EntityLookPacket)
register(self.handle_destroy_entities, DestroyEntitiesPacket)
register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket)
register(self.handle_respawn, clientbound.play.RespawnPacket)
@@ -367,18 +59,17 @@ class Game:
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
register(self.handle_trade_list, TradeListPacket)
register(self.handle_disconnect, DisconnectPacket)
register(self.handle_unload_chunk, UnloadChunkPacket)
#register(self.handle_packet, Packet, early=True)
self.g.chat.set_handler(self.handle_chat)
def handle_login_success(self, packet):
print(packet)
self.g.name = packet.Username
def handle_join_game(self, packet):
print('Received join game packet')
print('Connected.')
print(packet)
self.g.info = packet
self.g.eid = packet.entity_id
self.g.dimension = packet.world_name.replace('minecraft:', '')
@@ -432,510 +123,6 @@ class Game:
if new_path:
self.g.path = new_path
def handle_chat(self, message):
source, text = message
reply = None
private = False
for_me = False
authed = False
if source == 'SYSTEM':
self.g.command_lock = False
if text == 'You are now AFK.':
self.g.afk = True
elif text == 'You are no longer AFK.':
self.g.afk = False
match1 = re.match(r'<?(\w+)> (.*)', text)
match2 = re.match(r'\[(\w+) -> me] (.*)', text)
if match1:
sender, text = match1.groups()
elif match2:
sender, text = match2.groups()
private = True
else:
return
if sender == 'tanner6':
authed = True
if text.startswith('zzz'):
text = '!zzz'
bot_num = self.g.name[-1]
if text.startswith(bot_num):
text = text[1:]
for_me = True
elif text.startswith('! '):
text = text[2:]
elif text.startswith('!'):
text = text[1:]
else:
return
if ' ' in text:
command = text.split(' ', 1)[0]
data = text.split(' ', 1)[1]
else:
command = text
data = None
try:
## ### Public Commands
## These can be ran by anyone, all bots will reply.
## !help - prints this whole help message to console
## !help [command] - replies in-game explaining command
if command == 'help':
if data:
for line in print_help.HELP_LINES:
if line[1:].startswith(data) or line[1:].startswith(data[1:]):
reply = 'command ' + line
break
else: # for
reply = 'command not found'
else:
print()
print()
for line in print_help.HELP_LINES:
print(line)
reply = 'check console'
## !ping - replies with "pong"
if command == 'ping':
reply = 'pong'
## !echo [data] - replies with "data"
if command == 'echo' and data:
reply = data
## !pos - replies with position and dimension
if command == 'pos':
reply = str(utils.pint(self.g.pos))[1:-1] + ', ' + self.g.dimension
## !afk - goes AFK with /afk
if command == 'afk':
if not self.g.afk:
reply = '/afk'
## !unafk - goes not AFK with /afk
if command == 'unafk':
if self.g.afk:
reply = '/afk'
## !error - raises an error
if command == 'error':
reply = 'ok'
raise
## !inv - prints current inventory
if command == 'inv':
inv_list = []
for i in self.g.inv.values():
if i.present:
inv_list.append('{}:{} x {}'.format(items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count))
inv_list.sort()
result = '\n'.join(inv_list)
print(result or 'Empty')
## !time - replies with Minecraft world time
if command == 'time':
reply = str(self.g.time)
## !count [id] - counts the number of items with that id
if command == 'count' and data:
item = int(data)
reply = str(self.count_items([item]))
## !loaded - replies with the current loaded area
if command == 'loaded':
reply = str(self.g.chunks.get_loaded_area())
## !players - prints the current players
## !players clear - clears the current player list
if command == 'players':
if data == 'clear':
self.g.players = {}
reply = 'ok'
else:
for k, v in self.g.players.items():
print(str(k) + ':', v, self.g.player_names[v.player_uuid])
## !objects - prints the current items on ground
## !objects clear - clears the current object list
if command == 'objects':
if data == 'clear':
self.g.objects = {}
reply = 'ok'
else:
for k, v in self.g.objects.items():
if data and v.item_id != int(data): continue
print(str(k) + ':', v, items.ITEM_NAMES[v.item_id])
## !mobs - prints the current mobs
## !mobs clear - clears the current mob list
if command == 'mobs':
if data == 'clear':
self.g.mobs = {}
reply = 'ok'
else:
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
for k, v in all_mobs:
if data and v.type != int(data): continue
print(str(k) + ':', v, mobs.MOB_NAMES[v.type])
reply = str(len(all_mobs)) + ' mobs'
## !monsters - prints the current monsters
if command == 'monsters':
monsters = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
count = 0
for k, v in monsters:
if v.type not in mobs.EVIL_IDS: continue
if data and v.type != int(data): continue
count += 1
print(str(k) + ':', v, mobs.MOB_NAMES[v.type])
reply = str(count) + ' monsters'
## !villagers - prints the current villagers
if command == 'villagers':
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type)
count = 0
for k, v in all_mobs:
type_name = mobs.MOB_NAMES[v.type]
if type_name != 'villager' : continue
count += 1
print(str(k) + ':', v, type_name)
reply = str(count) + ' villagers'
## !threats - prints the dangerous monsters within 20 blocks
## !threats [num] - prints the dangerous monsters within num blocks
if command == 'threats':
distance = int(data) if data else 20
p = utils.pint(self.g.pos)
threats = self.g.world.find_threats(p, distance)
for t in threats:
print(str(t.entity_id) + ':', t, mobs.MOB_NAMES[t.type])
reply = str(len(threats)) + ' threats'
if command == 'spiral' and data:
for i in range(int(data)):
print(utils.spiral(i))
if command == 'sand_slice':
result = self.g.world.find_sand_slice(utils.pint(self.g.pos), 50)
reply = str(result)
## "zzz" or !zzz - bot does /afk to let others sleep
if command == 'zzz':
if not self.g.afk and self.g.dimension == 'overworld':
reply = '/afk'
self.g.afk_timeout = 5.0
## !tree - replies with the closest tree
if command == 'tree':
pos = utils.pint(self.g.pos)
tree = next(self.g.world.find_trees(pos, 50))
reply = str(tree)[1:-1]
## !block x y z - replies what block is at (x, y, z)
if command == 'block':
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:
coord = (x1, y1, z1)
block = self.g.world.block_at(*coord)
if not reply and block is None:
reply = 'first coord out of range'
if not reply:
reply = blocks.BLOCKS[block] + ':' + str(block)
################# Specific commands ##########################
## ### Bot-specific Commands
## These will only run for the bot they are addressed to.
if for_me:
pass
## 1respawn - respawns the bot if it's dead
if command == 'respawn':
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
self.g.connection.write_packet(packet)
reply = 'ok'
## 1gather wood - gathers wood from the world
## 1gather sand - gathers sand from the world
if command == 'gather' and data:
if data == 'wood':
self.g.job.state = self.g.job.gather_wood
reply = 'ok'
elif data == 'sand':
self.g.job.state = self.g.job.gather_sand
reply = 'ok'
if reply:
for i in self.g.inv.values():
print(i.item_id)
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
## 1farm wood - farms wood from a certain area
## 1farm sand - farms sand from a certain area
## 1farm wart - farms netherwart from a certain area
## 1farm crop - farms mature crops from a certain area
if command == 'farm' and data:
if data == 'wood':
self.g.job.state = self.g.job.farm_wood
reply = 'ok'
elif data == 'sand':
self.g.job.state = self.g.job.farm_sand
reply = 'ok'
elif data == 'wart':
self.g.job.state = self.g.job.farm_wart
reply = 'ok'
elif data.startswith('crop'):
self.g.job.state = self.g.job.farm_crop
reply = 'ok'
if reply and self.g.dimension == 'overworld':
for i in self.g.inv.values():
if i.item_id in items.BED_IDS:
break
else:
reply += ', I need a bed'
## 1loiter - stands still but eats, sleeps, and flees
if command == 'loiter':
self.g.job.state = self.g.job.loiter
reply = 'ok'
## 1trade - sells items to villagers to get emeralds
if command == 'trade':
self.g.job.state = self.g.job.trade
reply = 'ok'
## 1stop - stops the current job and resets bot
if command == 'stop':
self.close_window()
bot.init(self.g)
reply = 'ok'
## 1drop - drops the current stack its holding
if command == 'drop':
self.drop_stack()
## 1select [id] - moves item with id into main hand
if command == 'select' and data:
item = int(data)
if self.select_item([item]):
reply = 'ok'
else:
reply = 'not found'
## 1dump [id] - drops all items matching id
if command == 'dump' and data:
item = int(data)
if self.count_items([item]):
self.g.dumping = item
reply = 'ok'
else:
reply = 'not found'
## 1drain - drops all items in inventory
if command == 'drain':
self.g.draining = True
reply = 'ok'
if command == 'gapple':
self.g.job.state = self.g.job.find_gapple
if data:
self.g.job.find_gapple_states.count = int(data)
reply = 'ok'
if command == 'cache':
self.g.job.state = self.g.job.cache_items
self.g.job.cache_items_states.minimum = 0
self.g.job.cache_items_states.silent = True
reply = 'ok'
## 1fill [x] [y] [z] [x] [y] [z] - fills the cuboid with the block at the first coordinate
if command == 'fill':
try:
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x1, y1, z1, x2, y2, z2 = [int(x) for x in data.split()]
except (AttributeError, ValueError):
reply = 'usage: !fill x1 y1 z1 x2 y2 z2'
if not reply:
coord1 = (x1, y1, z1)
coord2 = (x2, y2, z2)
block = self.g.world.block_at(*coord1)
if not reply and y1 > y2:
reply = 'can only fill upwards'
if not reply and block is None:
reply = 'first coord out of range'
if not reply and block == 0:
reply = 'can\'t fill with air'
if not reply:
self.g.filling = Munch(coord1=coord1, coord2=coord2, block=block)
self.g.job.state = self.g.job.fill_blocks
reply = 'filling ' + str(utils.pvolume(coord1, coord2)) + ' with ' + blocks.BLOCKS[block]
## 1here - bot comes to your location
if command == 'here':
try:
sender_uuid = self.g.player_names[sender]
except KeyError:
reply = 'can\'t find your uuid'
if not reply:
for p in self.g.players.values():
if p.player_uuid == sender_uuid:
player = p
break
else: # for
reply = 'can\'t find you'
if not reply:
pos = utils.pint(self.g.pos)
goal = utils.pint((p.x, p.y, p.z))
start = time.time()
navpath = self.g.world.path_to_place(pos, goal)
if navpath:
self.g.path = navpath
if self.g.job:
self.g.job.stop()
print(len(navpath))
print(navpath)
print(round(time.time() - start, 3), 'seconds')
if self.g.job:
self.g.job.stop()
self.g.look_at = None
reply = 'ok'
else:
reply = 'no path'
## 1goto [x] [y] [z] - sends the bot to coordinate (x, y, z)
if command == 'goto':
try:
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
x2, y2, z2 = [int(x) for x in data.split()]
except (AttributeError, ValueError):
reply = 'usage: !goto x y z'
if not reply:
pos = utils.pint(self.g.pos)
goal = utils.pint((x2, y2, z2))
start = time.time()
navpath = self.g.world.path_to_place(pos, goal)
if navpath:
self.g.path = navpath
if self.g.job:
self.g.job.stop()
print(len(navpath))
print(navpath)
print(round(time.time() - start, 3), 'seconds')
if self.g.job:
self.g.job.stop()
self.g.look_at = None
reply = 'ok'
else:
reply = 'no path'
if command == 'break':
self.break_block(blocks.TEST_BLOCK)
reply = 'ok'
if command == 'open':
self.open_container(blocks.TEST_BLOCK)
## 1close - closes the current Minecraft window
if command == 'close':
if self.g.window:
self.close_window()
reply = 'ok'
else:
reply = 'nothing open'
## 1click [slot] [button] [mode] - clicks the current window
if command == 'click' and data:
if self.g.window:
slot, button, mode = [int(x) for x in data.split(' ')]
try:
item = self.g.window.contents[slot]
except KeyError:
item = Slot(present=False)
print(item)
self.click_window(slot, button, mode, item)
else:
reply = 'nothing open'
## 1use - use the item it's currently holding
if command == 'use':
self.use_item(0)
## 1interact [entity id] - interacts with that entity
if command == 'interact' and data:
self.interact(int(data))
if command == 'test':
reply = 'ok'
r = self.g.world.find_villager_openings((615, 78, 493))
print(r)
################# Authorized commands ##########################
## ### Authorized Commands
## These dangerous commands can only be ran by the bot owner.
if authed:
## 1print [expression] - replies with Python eval(expression)
if command == 'print':
data = data.replace('`', '.')
reply = str(eval(data))
## 1exit - exits the program
if command == 'exit':
import os
os._exit(0)
except BaseException as e:
import traceback
print(traceback.format_exc())
reply = 'Error: {} - {}\n'.format(e.__class__.__name__, e)
pass
if reply:
print(reply)
if private and not reply.startswith('/'):
self.g.chat.send('/m ' + sender + ' ' + reply)
else:
self.g.chat.send(reply)
def handle_time_update(self, packet):
self.g.time = packet.time_of_day % 24000
@@ -947,8 +134,8 @@ class Game:
elif g.window:
g.window.contents[packet.slot] = packet.slot_data
if packet.window_id >= 0 and not packet.slot_data.present:
print('unlocking item lock')
if g.item_lock and packet.window_id >= 0 and not packet.slot_data.present:
print('Unlocking item lock')
g.item_lock = False
def break_block(self, location):
@@ -989,6 +176,11 @@ class Game:
#print(packet)
return
def respawn(self):
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
self.g.connection.write_packet(packet)
def animate(self):
packet = serverbound.play.AnimationPacket()
packet.hand = packet.HAND_MAIN
@@ -1203,6 +395,13 @@ class Game:
obj.item_id = entry.value.item_id
obj.item_count = entry.value.item_count
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
for entry in packet.metadata:
if mob.type == mobs.VILLAGER:
if entry.index == 17:
mob.profession = entry.value[1]
player = self.g.players.get(packet.entity_id, None)
if player:
return
@@ -1244,8 +443,14 @@ class Game:
player.x += packet.delta_x / 4096.0
player.y += packet.delta_y / 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):
mob = self.g.mobs.get(packet.entity_id, None)
@@ -1289,18 +494,26 @@ class Game:
def handle_respawn(self, packet):
print(packet)
self.g.dimension = packet.world_name.replace('minecraft:', '')
self.g.chunks.unload_all_chunks()
def handle_player_list(self, packet):
for action in packet.actions:
if isinstance(action, packet.AddPlayerAction):
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):
print(packet)
self.g.health = packet.health
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):
packet = serverbound.play.UseItemPacket()
packet.hand = hand
@@ -1329,6 +542,9 @@ class Game:
import os
os._exit(1)
def handle_unload_chunk(self, packet):
self.g.chunks.unload_chunk(packet.chunk_x, packet.chunk_z)
def tick(self):
if self.g.breaking:
self.animate()

View File

@@ -1,7 +1,7 @@
import json
import importlib
from mosfet import mcdata
from mosfet.info import mcdata
MCD_BLOCKS = {}
for d in mcdata.mcd.blocks.values():
@@ -15,6 +15,11 @@ for name, data in JSON_BLOCKS.items():
for state in data['states']:
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
AIR = 0

View File

@@ -89,6 +89,9 @@ for item_name, item in ITEMS.items():
def get_id(name):
return ITEMS['minecraft:' + name]['protocol_id']
def get_name(idx):
return ITEM_NAMES[idx]
CHEST_ID = get_id('chest')
GAPPLE_ID = get_id('enchanted_golden_apple')
SAND_ID = get_id('sand')
@@ -100,6 +103,7 @@ WHEAT_ID = get_id('wheat')
WHEAT_SEEDS_ID = get_id('wheat_seeds')
BEETROOT_SEEDS_ID = get_id('beetroot_seeds')
PUMPKIN_ID = get_id('pumpkin')
BEETROOT_ID = get_id('beetroot')
EMERALD_ID = get_id('emerald')
BERRIES_ID = get_id('sweet_berries')
@@ -115,8 +119,10 @@ INIT_WANTED_ITEMS = set()
WANTED_ITEMS = INIT_WANTED_ITEMS
def set_needed(items):
global NEEDED_ITEMS
NEEDED_ITEMS = INIT_NEEDED_ITEMS | items
def set_wanted(items):
global WANTED_ITEMS
WANTED_ITEMS = INIT_WANTED_ITEMS | items

View File

@@ -1,8 +1,33 @@
from mosfet.info import items
import json
with open('minecraft_data/registries.json') as f:
MOBS = json.load(f)['minecraft:entity_type']['entries']
VILLAGER = 93
ARMORER = 1
BUTCHER = 2
CARTOGRAPHER = 3
CLERIC = 4
FARMER = 5
FISHERMAN = 6
FLETCHER = 7
LEATHERWORKER = 8
LIBRARIAN = 9
MASON = 10
NITWIT = 11
SHEPHERD = 12
TOOLSMITH = 13
WEAPONSMITH = 14
TRADES = {
ARMORER: [items.IRON_INGOT_ID],
BUTCHER: [items.BERRIES_ID],
FARMER: [items.PUMPKIN_ID, items.WHEAT_ID, items.POTATO_ID, items.CARROT_ID, items.BEETROOT_ID],
TOOLSMITH: [items.IRON_INGOT_ID],
WEAPONSMITH: [items.IRON_INGOT_ID],
}
EVIL = [
'blaze',
'cave_spider',

View File

@@ -3,7 +3,6 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from minecraft.networking.types import BlockFace
@@ -11,10 +10,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
from mosfet.jobs import (
cache_items,
@@ -236,6 +235,8 @@ class JobStates:
tuple([items.IRON_INGOT_ID]): (64, 3),
tuple([items.WHEAT_ID]): (64, 3),
tuple([items.POTATO_ID]): (64, 3),
tuple([items.CARROT_ID]): (64, 3),
tuple([items.BEETROOT_ID]): (64, 3),
}
items.set_needed(set([
@@ -244,6 +245,8 @@ class JobStates:
items.IRON_INGOT_ID,
items.WHEAT_ID,
items.POTATO_ID,
items.CARROT_ID,
items.BEETROOT_ID,
]))
return machines

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class CacheItemsStates:
def idle(self):
@@ -54,11 +54,7 @@ class CacheItemsStates:
chest = self.trapped_chests[0]
tmp = c.get_block_at(*chest)
c.set_block_at(*chest, blocks.AIR)
navpath = w.path_to_place(p, chest)
c.set_block_at(*chest, tmp)
navpath = w.path_to_place_faked(p, chest)
print('navpath:', navpath)
if navpath:
@@ -69,6 +65,7 @@ class CacheItemsStates:
return
else:
self.trapped_chests.pop(0)
time.sleep(0.1)
def going_to_trapped_chest(self):
if utils.pint(self.g.pos) == self.opening:

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class CheckThreatsStates:
def idle(self):
@@ -64,6 +64,7 @@ class CheckThreatsStates:
return
else:
print('Cant get to safety', self.safety)
time.sleep(0.1)
print('Cant get to safety, aborting')
self.state = self.cleanup

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class ClearLeavesStates:
def idle(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class EatFoodStates:
def idle(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class FillBlocksStates:
def idle(self):
@@ -40,7 +40,7 @@ class FillBlocksStates:
b1, b2 = utils.pboundingbox(f.coord1, f.coord2)
box = utils.psub(b2, b1)
xz_distance = hypot(box[0]+1, box[2]+1)
xz_distance = utils.hypot(box[0]+1, box[2]+1)
y_start = f.coord1[1]
y_end = f.coord2[1]
@@ -84,7 +84,7 @@ class FillBlocksStates:
b1, b2 = utils.pboundingbox(f.coord1, f.coord2)
box = utils.psub(b2, b1)
xz_distance = hypot(box[0]+1, box[2]+1)
xz_distance = utils.hypot(box[0]+1, box[2]+1)
y_start = f.coord1[1]
y_end = f.coord2[1]
@@ -139,8 +139,7 @@ class FillBlocksStates:
else:
print('Cant get to that block')
self.state = self.cleanup
#self.bad_sand.append(self.sand)
#self.state = self.find_new_sand
time.sleep(0.1)
def going_to_block(self):
if not len(self.g.path):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class FindGappleStates:
def idle(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherCropStates:
def idle(self):
@@ -35,8 +35,7 @@ class GatherCropStates:
blocks.MATURE_BEETROOT_ID,
]
for crop in w.find_blocks_3d(p, mature_crops, 50, 20):
print('Found crop:', crop)
for crop in w.find_blocks_3d(p, mature_crops, 50, 20, True):
if crop not in self.bad_crops:
break
else: # for
@@ -44,6 +43,7 @@ class GatherCropStates:
self.state = self.cleanup
return
print('Found crop:', crop)
self.crop = crop
self.type_id = w.block_at(*crop)
self.state = self.nav_to_crop
@@ -55,19 +55,14 @@ class GatherCropStates:
navpath = w.path_to_place(p, self.crop)
if navpath:
print('Going to crop', self.crop)
self.g.path = navpath
self.g.look_at = utils.padd(self.crop, path.BLOCK_BELOW)
self.state = self.going_to_crop
else:
print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_crops.append(self.crop)
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_crop
def going_to_crop(self):
@@ -77,14 +72,6 @@ class GatherCropStates:
def break_crop(self):
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
def select_seed(self):
@@ -96,18 +83,17 @@ class GatherCropStates:
blocks.MATURE_CARROT_ID: items.CARROT_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.wait_time = 0.5
else:
print('Aborting planting, no crop')
self.state = self.cleanup
print('Havent picked up seed yet')
return
def wait_select(self):
# wait a bit to select
if self.wait_time > 0:
self.wait_time -= utils.TICK
if self.target_seed != self.g.holding:
return
else:
self.state = self.place_crop
@@ -116,12 +102,11 @@ class GatherCropStates:
self.g.game.place_block(p, BlockFace.TOP)
print('Placed crop')
self.state = self.wait_place
self.wait_time = 0.5
def wait_place(self):
# wait a bit for chunk data to update
if self.wait_time > 0:
self.wait_time -= utils.TICK
w = self.g.world
if w.block_at(*self.crop) == blocks.AIR:
return
else:
self.state = self.cleanup
@@ -139,8 +124,8 @@ class GatherCropStates:
self.crop = None
self.type_id = None
self.target_seed = None
self.bad_crops = []
self.wait_time = 0
def run(self):
self.state()

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherSandStates:
def bair(self, p):
@@ -27,6 +27,9 @@ class GatherSandStates:
return None
def init(self):
if not self.g.sand_origin or not self.g.chunks.check_loaded(self.g.sand_origin):
self.g.sand_origin = utils.pint(self.g.pos)
self.state = self.select_shovel
def select_shovel(self):
@@ -36,10 +39,11 @@ class GatherSandStates:
def find_new_slice(self):
print('Finding new slice...')
w = self.g.world
origin = self.g.sand_origin
print('using origin', self.origin)
print('using origin', self.g.sand_origin)
start = time.time()
self.prev_layer, s = w.find_sand_slice(self.origin, 200, 10, self.bad_slices, self.prev_layer)
self.prev_layer, s = w.find_sand_slice(self.g.sand_origin, 200, 10, self.bad_slices, self.prev_layer)
print('Found slice:', s, 'in', time.time() - start, 'seconds')
if s:
@@ -77,16 +81,15 @@ class GatherSandStates:
p = utils.pint(self.g.pos)
c = self.g.chunks
tmp = c.get_block_at(*self.sand)
c.set_block_at(*self.sand, blocks.AIR)
navpath = w.path_to_place(p, self.sand)
c.set_block_at(*self.sand, tmp)
navpath = w.path_to_place_faked(p, self.sand)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.state = self.going_to_sand
else:
print('Cant get to that sand')
time.sleep(0.1)
self.bad_sand.append(self.sand)
self.state = self.find_new_sand
@@ -117,8 +120,6 @@ class GatherSandStates:
self.g = global_state
self.state = self.idle
self.origin = utils.pint(self.g.pos)
self.origin = (2019, 64, 238)
self.slice = None
self.bad_slices = []
self.prev_layer = 0

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherWartStates:
def idle(self):
@@ -29,8 +29,7 @@ class GatherWartStates:
p = utils.pint(self.g.pos)
mature_wart = max(blocks.NETHERWART_IDS)
for wart in w.find_blocks_3d(p, [mature_wart], 50, 20):
print('Found wart:', wart)
for wart in w.find_blocks_3d(p, [mature_wart], 50, 20, True):
if wart not in self.bad_warts:
break
else: # for
@@ -38,6 +37,7 @@ class GatherWartStates:
self.state = self.cleanup
return
print('Found wart:', wart)
self.wart = wart
self.state = self.nav_to_wart
@@ -48,10 +48,13 @@ class GatherWartStates:
navpath = w.path_to_place(p, self.wart)
if navpath:
print('Going to wart', self.wart)
self.g.path = navpath
self.g.look_at = utils.padd(self.wart, path.BLOCK_BELOW)
self.state = self.going_to_wart
else:
print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_warts.append(wart)
self.state = self.find_new_wart

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherWoodStates:
def bair(self, p):
@@ -80,6 +80,7 @@ class GatherWoodStates:
self.state = self.going_to_tree
else:
self.openings.pop(0)
time.sleep(0.1)
def going_to_tree(self):
if utils.pint(self.g.pos) == self.openings[0]:
@@ -126,6 +127,7 @@ class GatherWoodStates:
else:
self.openings.pop(0)
self.state = self.choose_opening
time.sleep(0.1)
def going_to_trunk_base(self):
if utils.pint(self.g.pos) == self.tree:

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GrabSandStates:
def idle(self):
@@ -61,6 +61,7 @@ class GrabSandStates:
return
else:
print('Cant get to sand', self.sand)
time.sleep(0.1)
print('Cant get to any more sand, aborting')
self.state = self.cleanup

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GrabSaplingStates:
def idle(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GrabSuppliesStates:
def idle(self):
@@ -61,7 +61,7 @@ class GrabSuppliesStates:
w = self.g.world
p = utils.pint(self.g.pos)
self.barrels = w.find_blocks_indexed(p, blocks.BARREL_IDS)
self.barrels = w.find_blocks_indexed(p, blocks.BARREL_IDS, 80)
print('Found:', self.barrels)
self.state = self.choose_barrel
@@ -73,6 +73,7 @@ class GrabSuppliesStates:
if barrel in self.bad_barrels:
continue
print('Chose:', barrel)
self.barrel = barrel
self.state = self.path_to_barrel
return
@@ -86,26 +87,24 @@ class GrabSuppliesStates:
p = utils.pint(self.g.pos)
c = self.g.chunks
barrel = self.barrel
tmp = c.get_block_at(*barrel)
c.set_block_at(*barrel, blocks.AIR)
navpath = w.path_to_place(p, barrel)
c.set_block_at(*barrel, tmp)
navpath = w.path_to_place_faked(p, self.barrel)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.opening = self.g.path[-1]
self.checked_barrels.append(barrel)
self.area = barrel
self.checked_barrels.append(self.barrel)
self.area = self.barrel
self.state = self.going_to_barrel
self.checked_supplies = []
return
else:
print('No path, blacklisting barrel')
self.bad_barrels.append(barrel)
time.sleep(0.1)
self.bad_barrels.append(self.barrel)
if len(self.bad_barrels) > 3:
self.state = self.cleanup
else:
self.state = self.choose_barrel
def going_to_barrel(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class PlantTreeStates:
def idle(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class SellToVillagerStates:
def idle(self):
@@ -31,7 +31,28 @@ class SellToVillagerStates:
for v in w.find_villagers(p, 100):
print('Found villager:', v)
if v not in self.bad_villagers and v not in self.spent_villagers:
if v in self.bad_villagers:
print('In bad villager list')
continue
if v in self.spent_villagers:
print('In spent villager list')
continue
if 'profession' not in v:
print('Villager has unknown profession')
continue
if v.profession not in mobs.TRADES:
print('Villager doesnt sell stuff we collect')
continue
trades = mobs.TRADES[v.profession]
if not self.g.game.count_items(trades):
print('We dont have anything to sell him')
continue
break
else: # for
print('No good villagers left, aborting.')
@@ -59,7 +80,7 @@ class SellToVillagerStates:
if self.villager not in self.good_villagers:
self.bad_villagers.append(self.villager)
print('Added to bad villager list')
self.state = self.cleanup
self.state = self.find_villager
return
navpath = w.path_to_place(p, self.openings[0])
@@ -67,8 +88,10 @@ class SellToVillagerStates:
if navpath:
self.g.path = navpath
self.state = self.going_to_villager
self.g.look_at = None
else:
self.openings.pop(0)
time.sleep(0.1)
def going_to_villager(self):
if utils.pint(self.g.pos) == self.openings[0]:
@@ -143,8 +166,7 @@ class SellToVillagerStates:
print('Villager has been spent, aborting')
self.g.game.close_window()
self.spent_villagers.append(self.villager)
self.state = self.wait
self.wait_time = 10
self.state = self.find_villager
return
def click_trade(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib
import random
from itertools import count
from math import hypot, floor
from math import floor
from minecraft.networking.types import BlockFace
@@ -11,10 +11,10 @@ from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet import blocks
from mosfet import items
from mosfet import mcdata
from mosfet import mobs
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class SleepWithBedStates:
def idle(self):
@@ -38,11 +38,16 @@ class SleepWithBedStates:
w = self.g.world
p = utils.pint(self.g.pos)
result = w.find_blocks_indexed(p, blocks.BED_IDS)
result = w.find_blocks_indexed(p, blocks.BED_IDS, 80)
self.beds = []
for bed in result:
if bed not in self.bad_beds:
if bed in self.bad_beds:
continue
if w.check_bed_occupied(bed):
continue
self.beds.append(bed)
print('Found:', self.beds)
@@ -62,11 +67,7 @@ class SleepWithBedStates:
bed = self.beds[0]
print('Chose:', bed)
tmp = c.get_block_at(*bed)
c.set_block_at(*bed, blocks.AIR)
navpath = w.path_to_place(p, bed)
c.set_block_at(*bed, tmp)
navpath = w.path_to_place_faked(p, bed)
print('navpath:', navpath)
if navpath:
@@ -80,10 +81,12 @@ class SleepWithBedStates:
self.beds.pop(0)
self.bad_beds.append(bed)
print('Cant get to bed, blacklisting')
time.sleep(0.1)
self.state = self.select_bed
def going_to_bed(self):
if utils.pint(self.g.pos) == self.opening:
print('At existing bed')
self.my_bed = False
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.use_bed
@@ -128,17 +131,36 @@ class SleepWithBedStates:
def going_to_area(self):
if utils.pint(self.g.pos) == self.opening:
print('At bed area')
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.place_bed
def place_bed(self):
print('Placing bed')
self.g.game.place_block(self.area, BlockFace.TOP)
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
def use_bed(self):
if self.g.time > 12550:
print('Sleeping')
w = self.g.world
if self.g.time < 12550:
return
if w.check_bed_occupied(self.area):
print('Bed occupied, aborting.')
self.state = self.cleanup
return
print('Using bed')
self.g.game.place_block(self.area, BlockFace.TOP)
if not self.silent:
self.g.chat.send('zzz')
@@ -147,16 +169,19 @@ class SleepWithBedStates:
def sleep_bed(self):
w = self.g.world
p = utils.pint(self.g.pos)
threats = w.find_threats(p, 10)
threats = w.find_threats(p, 30)
if threats:
print('Waking up due to threats:')
print(threats)
self.g.game.leave_bed()
self.state = self.break_bed
self.state = self.cleanup
elif self.g.time < 100:
print('Woke up time')
self.state = self.break_bed
elif self.g.correction_count:
print('Woke up by movement')
self.state = self.break_bed
def break_bed(self):
if self.my_bed:
@@ -165,7 +190,6 @@ class SleepWithBedStates:
else:
self.state = self.cleanup
def collect_bed(self):
if not self.g.breaking:
self.g.path = [utils.padd(self.area, utils.spiral(n)) for n in range(9)]

View File

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

View File

@@ -1,11 +1,10 @@
import importlib
import functools
import time
from math import hypot, sqrt
from astar import AStar
from mosfet import blocks
from mosfet.info import blocks
from mosfet import utils
class AStarTimeout(Exception):
@@ -306,7 +305,7 @@ class Pathfinder(AStar):
def distance_between(self, n1, n2):
(x1, y1, z1) = n1
(x2, y2, z2) = n2
return hypot(x2-x1, y2-y1, z2-z1)
return utils.hypot(x2-x1, y2-y1, z2-z1)
def heuristic_cost_estimate(self, n1, n2):
(x1, y1, z1) = n1

View File

@@ -1,6 +1,6 @@
HELP_LINES = []
with open('mosfet/game.py', 'r') as f:
with open('mosfet/commands.py', 'r') as f:
for line in f.readlines():
if line.strip().startswith('## '):
HELP_LINES.append(line.strip()[3:])

View File

@@ -7,6 +7,7 @@ from minecraft.networking.packets import clientbound, serverbound
from mosfet.protocol import packets
from mosfet import utils
from mosfet.info import blocks
class DataManager:
def __init__(self, directory):
@@ -42,7 +43,7 @@ class ChunksManager:
def __init__(self, data_manager):
self.data = data_manager
self.chunks = {}
self.biomes = {}
#self.biomes = {}
self.index = {}
self.loading = False
@@ -68,13 +69,12 @@ class ChunksManager:
for item_id, locations in chunk.sub_index.items():
if item_id not in self.index:
self.index[item_id] = []
self.index[item_id] = set()
for l in locations:
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:
print('.', end='', flush=True)
@@ -116,28 +116,26 @@ class ChunksManager:
if not c: return None
c.set_block_at(x%16, y%16, z%16, block)
def check_loaded(self, position, steps):
x, y, z = utils.pint(position)
player_chunk = (x//16, 1, z//16)
for i in range(steps): # TODO: base off render_distance?
offset = utils.spiral(i)
check = utils.padd(player_chunk, offset)
if block in blocks.INDEXED_IDS:
if block not in self.index:
self.index[block] = set()
self.index[block].add((x, y, z))
if check not in self.chunks:
return False
def check_loaded(self, chunk_distance):
num = (chunk_distance * 2 + 1) ** 2
num_subchunks = num * 16
return len(self.chunks) >= num_subchunks
return True
def unload_chunks(self, position):
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_chunk(self, x, z):
for y in range(16):
try:
del self.chunks[(x, y, z)]
except KeyError:
pass
def unload_all_chunks(self):
self.chunks = {}
self.index = {}
class ChunkNotLoadedException(Exception):
def __str__(self):
@@ -155,24 +153,25 @@ class ChatManager:
def translate_chat(self, data):
if isinstance(data, str):
return data
result = data.get('text', '')
result += data.get('translate', '')
if 'with' in data:
result += ' ' + ' '.join([self.translate_chat(x) for x in data['with']])
elif 'extra' in data:
return ''.join([self.translate_chat(x) for x in data['extra']])
elif 'text' in data:
return data['text']
elif 'with' in data:
if len(data['with']) >= 2:
return '<{}> {}'.format(*[self.translate_chat(x) for x in data['with']])
else:
return self.translate_chat(data['with'][0])
elif 'translate' in data:
return data['translate']
else:
print(data)
return '?'
result += ''.join([self.translate_chat(x) for x in data['extra']])
return result
def print_chat(self, chat_packet):
#print(chat_packet)
#print(chat_packet.json_data)
#import json
#print(json.dumps(json.loads(chat_packet.json_data), indent=4))
try:
source = chat_packet.field_string('position')
sender = chat_packet.sender
text = self.translate_chat(json.loads(chat_packet.json_data))
print('[%s] %s'%(source, text))
except Exception as ex:
@@ -180,7 +179,7 @@ class ChatManager:
return
if self.handler:
self.handler((source, text))
self.handler((source, sender, text))
def set_handler(self, func):
self.handler = func

View File

@@ -10,7 +10,7 @@ from minecraft.networking.types import (
from .types import Nbt, Slot, Entry, Trade
from mosfet import blocks
from mosfet.info import blocks
class ChunkDataPacket(Packet):
@@ -31,10 +31,10 @@ class ChunkDataPacket(Packet):
self.biomes.append(VarInt.read(file_object))
size = VarInt.read(file_object)
self.data = file_object.read(size)
size_entities = VarInt.read(file_object)
self.entities = []
for i in range(size_entities):
self.entities.append(Nbt.read(file_object))
#size_entities = VarInt.read(file_object)
#self.entities = []
#for i in range(size_entities):
# self.entities.append(Nbt.read(file_object))
self.decode_chunk_data()
@@ -49,9 +49,9 @@ class ChunkDataPacket(Packet):
Integer.send(self.biomes[i], packet_buffer)
VarInt.send(len(self.data), packet_buffer)
packet_buffer.send(self.data)
VarInt.send(len(self.entities), packet_buffer)
for e in self.entities:
Nbt.send(e, packet_buffer)
#VarInt.send(len(self.entities), packet_buffer)
#for e in self.entities:
# Nbt.send(e, packet_buffer)
def decode_chunk_data(self):
packet_data = PacketBuffer()
@@ -64,9 +64,9 @@ class ChunkDataPacket(Packet):
if self.bit_mask_y & (1 << i):
self.chunks[i].read(packet_data)
for e in self.entities:
y = e['y']
self.chunks[floor(y/16)].entities.append(e)
#for e in self.entities:
# y = e['y']
# self.chunks[floor(y/16)].entities.append(e)
class Chunk:
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
@@ -304,7 +304,7 @@ class EntityMetadataPacket(Packet):
self.metadata = []
for _ in range(99):
entry = Entry.read(file_object, self.context)
if not entry: break
if not entry: break # hit an unimplemented type, stops parsing
self.metadata.append(entry)
@@ -458,3 +458,15 @@ class DisconnectPacket(Packet):
definition = [
{'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

@@ -141,7 +141,7 @@ class Slot(Type):
class Entry(Type):
types = {
simple_types = {
0: Byte,
1: VarInt,
2: Float,
@@ -169,12 +169,19 @@ class Entry(Type):
index = UnsignedByte.read(file_object)
if index == 0xff: return None
type = VarInt.read(file_object)
if type == 10: # optional position
present = Boolean.read(file_object)
value = Position.read_with_context(file_object, context) if present else None
elif type == 16: # villager data
value = (VarInt.read(file_object), VarInt.read(file_object), VarInt.read(file_object))
else:
try:
value = Entry.types[type].read(file_object)
value = Entry.simple_types[type].read(file_object)
except TypeError:
value = Entry.types[type].read_with_context(file_object, context)
value = Entry.simple_types[type].read_with_context(file_object, context)
except KeyError:
return None
return None # unimplemented data type, stops parsing entries
return Entry(index, type, value)

View File

@@ -1,12 +1,16 @@
import importlib
import collections
from math import floor, ceil, sqrt, hypot
from math import floor, ceil, sqrt
from mosfet import blocks
from mosfet import mcdata
from mosfet.info import blocks
from mosfet.info import mcdata
TICK = 0.05
def hypot(*coordinates):
# python's 3D hypot is too new, so we'll use our own
return sqrt(sum(x**2 for x in coordinates))
def padd(p1, p2):
return (p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2])
@@ -133,49 +137,32 @@ def search_2d(distance=0):
visited.add(cur)
yield cur
def search_3d(distance=0, y_limit=0):
def get_neighbors(x,y,z):
def get_neighbors_3d(x,y,z):
return [
(x+1, y+1, z+0),
(x+1, y-1, z+0),
(x+1, y+1, 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-1),
(x+1, y-1, z-1),
#(x+1, y+1, z+0),
#(x+1, y-1, z+0),
#(x+1, y+1, 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-1),
#(x+1, y-1, z-1),
(x+1, y+0, 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-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-1, z-1),
(x-1, y+1, z+0),
(x-1, y-1, z+0),
(x-1, y+1, 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-1),
(x-1, 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+1),
#(x-1, y+0, z+1),
#(x-1, y-1, z+1),
#(x-1, y+1, z-1),
#(x-1, y+0, z-1),
#(x-1, y-1, z-1),
(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

View File

@@ -1,4 +1,5 @@
import math
from mosfet import utils
class Vector3D:
def __init__(self, vector):
@@ -17,7 +18,7 @@ class Vector3D:
return self.tuple()[key]
def length(self):
return math.hypot(self.x, self.y, self.z)
return utils.hypot(self.x, self.y, self.z)
def normalized(self):
x = self.x / self.length()

365
mosfet/world.py Normal file
View File

@@ -0,0 +1,365 @@
import collections
import re
import time
import random
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 utils.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 utils.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 path_to_place_faked(self, start, place):
# same as above, but adds a fake block below and air before pathfinding
# so that the pathfinder can actually make it to the block
c = self.g.chunks
above = utils.padd(place, path.BLOCK_ABOVE)
below = utils.padd(place, path.BLOCK_BELOW)
tmp = c.get_block_at(*place)
tmp2 = c.get_block_at(*above)
tmp3 = c.get_block_at(*below)
c.set_block_at(*place, blocks.AIR)
c.set_block_at(*above, blocks.AIR)
c.set_block_at(*below, blocks.STONE)
navpath = self.path_to_place(start, place)
c.set_block_at(*place, tmp)
c.set_block_at(*above, tmp2)
c.set_block_at(*below, tmp3)
return navpath
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 check_bed_occupied(self, bed):
# returns true if the bed is occupied by a player
bid = self.g.chunks.get_block_at(*bed)
if blocks.PROPS[bid]['occupied'] == 'true':
print('Checking bed occupancy:', bed, '-> occupied')
return True
else:
print('Checking bed occupancy:', bed, '-> free')
return False
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)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
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)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
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)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
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

View File

@@ -1,39 +1,45 @@
appdirs==1.4.3
appdirs==1.4.4
astar==0.93
CacheControl==0.12.6
certifi==2019.11.28
certifi==2020.12.5
cffi==1.14.5
chardet==3.0.4
chardet==4.0.0
click==7.1.2
colorama==0.4.3
contextlib2==0.6.0
colorama==0.4.4
contextlib2==0.6.0.post1
cryptography==3.4.7
distlib==0.3.0
distro==1.4.0
distlib==0.3.1
distro==1.5.0
Flask==1.1.2
html5lib==1.0.1
idna==2.8
Flask-Cors==3.0.10
html5lib==1.1
idna==2.10
importlib-metadata==4.0.1
ipaddr==2.2.0
itsdangerous==1.1.0
Jinja2==2.11.3
lockfile==0.12.2
MarkupSafe==1.1.1
minecraft-data==2.82.2
msgpack==0.6.2
msgpack==1.0.2
munch==2.5.0
mutf8==1.0.3
packaging==20.3
pep517==0.8.2
packaging==20.9
pep517==0.10.0
pkg-resources==0.0.0
progress==1.5
pycparser==2.20
git+https://github.com/ammaraskar/pyCraft.git@2813d02ae7fb8182c3e5227a73de2240b09878d9
PyNBT==3.1.0
pyparsing==2.4.6
pyparsing==2.4.7
pytoml==0.1.21
requests==2.22.0
requests==2.25.1
retrying==1.3.3
six==1.14.0
urllib3==1.25.8
watchdog==2.0.2
six==1.15.0
toml==0.10.2
typing-extensions==3.7.4.3
urllib3==1.26.4
watchdog==2.0.3
webencodings==0.5.1
Werkzeug==1.0.1
zipp==3.4.1

View File

@@ -11,7 +11,7 @@ fi
if ! command -v virtualenv &> /dev/null
then
echo "virtualenv could not be found, please install with:"
echo "sudo apt install python3-virtualenv"
echo "sudo apt install virtualenv"
exit
fi
@@ -49,7 +49,6 @@ fi
if [ ! -d "minecraft_data" ]
then
echo "Grabbing minecraft data..."
sleep 2
VERSION="1.16.4"
@@ -59,12 +58,14 @@ then
rm mcdata.zip
fi
# https://github.com/pypa/virtualenv/issues/1029
PS1=${PS1:-}
# create virtual environment
if [ ! -d "env" ]
then
echo "Installing Python requirements..."
sleep 2
virtualenv -p python3 env
source env/bin/activate

View File

@@ -11,7 +11,7 @@ fi
if ! command -v virtualenv &> /dev/null
then
echo "virtualenv could not be found, please install with:"
echo "sudo apt install python3-virtualenv"
echo "sudo apt install virtualenv"
exit
fi
@@ -54,7 +54,6 @@ fi
# download minecraft data
echo "Grabbing minecraft data..."
sleep 2
VERSION="1.16.4"
@@ -72,10 +71,12 @@ git pull --rebase
git stash pop || true
# https://github.com/pypa/virtualenv/issues/1029
PS1=${PS1:-}
# create virtual environment
echo "Installing Python requirements..."
sleep 2
rm -r env || true
rm -r __pycache__ || true

20
web_interface/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -0,0 +1,17 @@
{
"files": {
"main.js": "/static/js/main.27336089.chunk.js",
"main.js.map": "/static/js/main.27336089.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.97b64705.js",
"runtime-main.js.map": "/static/js/runtime-main.97b64705.js.map",
"static/js/2.2b306052.chunk.js": "/static/js/2.2b306052.chunk.js",
"static/js/2.2b306052.chunk.js.map": "/static/js/2.2b306052.chunk.js.map",
"index.html": "/index.html",
"static/js/2.2b306052.chunk.js.LICENSE.txt": "/static/js/2.2b306052.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.97b64705.js",
"static/js/2.2b306052.chunk.js",
"static/js/main.27336089.chunk.js"
]
}

View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><title>React App</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],p=0,s=[];p<i.length;p++)f=i[p],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var i=this.webpackJsonpweb_interface=this.webpackJsonpweb_interface||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var c=l;t()}([])</script><script src="/static/js/2.2b306052.chunk.js"></script><script src="/static/js/main.27336089.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,41 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/** @license React v0.20.2
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
(this.webpackJsonpweb_interface=this.webpackJsonpweb_interface||[]).push([[0],{41:function(e,t,n){"use strict";n.r(t);var c=n(2),r=n.n(c),s=n(14),a=n.n(s),i=n(4),j=n.n(i),l=n(15),d=n(16),o=n(5),p=n.n(o),b=n(0);p.a.defaults.baseURL=Object({NODE_ENV:"production",PUBLIC_URL:"",WDS_SOCKET_HOST:void 0,WDS_SOCKET_PATH:void 0,WDS_SOCKET_PORT:void 0,FAST_REFRESH:!0}).REACT_APP_SERVER;var u=function(){var e=Object(c.useState)(!1),t=Object(d.a)(e,2),n=t[0],r=t[1];return Object(c.useEffect)((function(){var e=setInterval(function(){var e=Object(l.a)(j.a.mark((function e(){var t;return j.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,p.a.get("/api/global");case 3:t=e.sent,r(t.data),e.next=10;break;case 7:e.prev=7,e.t0=e.catch(0),r(!1);case 10:case"end":return e.stop()}}),e,null,[[0,7]])})));return function(){return e.apply(this,arguments)}}(),500);return function(){return clearInterval(e)}}),[]),Object(b.jsx)("div",{className:"app",children:n&&Object(b.jsxs)(b.Fragment,{children:[Object(b.jsxs)("p",{children:["Name: ",n.name]}),Object(b.jsxs)("p",{children:["Pos: ",n.pos]}),Object(b.jsxs)("p",{children:["Yaw: ",n.yaw]}),Object(b.jsxs)("p",{children:["Pitch: ",n.pitch]}),Object(b.jsxs)("p",{children:["Dimention: ",n.dimension]}),Object(b.jsxs)("p",{children:["Players:",Object.values(n.players).map((function(e){return Object(b.jsx)("div",{children:n.player_names[e.player_uuid]})}))]}),Object(b.jsxs)("p",{children:["Holding: ",n.holding]}),Object(b.jsxs)("p",{children:["AFK: ","".concat(n.afk)]}),Object(b.jsxs)("p",{children:["Health: ",n.health]}),Object(b.jsxs)("p",{children:["Food: ",n.food]}),Object(b.jsxs)("p",{children:["Time: ",n.time]})]})})};a.a.render(Object(b.jsx)(r.a.StrictMode,{children:Object(b.jsx)(u,{})}),document.getElementById("root"))}},[[41,1,2]]]);
//# sourceMappingURL=main.27336089.chunk.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["App.js","index.js"],"names":["axios","defaults","baseURL","process","REACT_APP_SERVER","App","useState","global","setGlobal","useEffect","interval","setInterval","a","get","res","data","clearInterval","className","name","pos","yaw","pitch","dimension","Object","values","players","map","x","player_names","player_uuid","holding","afk","health","food","time","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"kNAGAA,IAAMC,SAASC,QAAUC,mIAAYC,iBAiDtBC,MA/Cf,WAAgB,IAAD,EACcC,oBAAS,GADvB,mBACPC,EADO,KACCC,EADD,KAiBd,OAdAC,qBAAU,WACT,IASMC,EAAWC,YATR,uCAAG,4BAAAC,EAAA,+EAEQZ,IAAMa,IAAI,eAFlB,OAEJC,EAFI,OAGVN,EAAUM,EAAIC,MAHJ,gDAKVP,GAAU,GALA,yDAAH,qDASyB,KAClC,OAAO,kBAAMQ,cAAcN,MACzB,IAGF,qBAAKO,UAAU,MAAf,SACEV,GACA,qCACC,uCAAUA,EAAOW,QACjB,sCAASX,EAAOY,OAChB,sCAASZ,EAAOa,OAChB,wCAAWb,EAAOc,SAElB,4CAAed,EAAOe,aAEtB,yCACEC,OAAOC,OAAOjB,EAAOkB,SAASC,KAAI,SAAAC,GAAC,OACnC,8BACEpB,EAAOqB,aAAaD,EAAEE,qBAK1B,0CAAatB,EAAOuB,WACpB,gDAAYvB,EAAOwB,QACnB,yCAAYxB,EAAOyB,UACnB,uCAAUzB,EAAO0B,QACjB,uCAAU1B,EAAO2B,cCzCtBC,IAASC,OACR,cAAC,IAAMC,WAAP,UACC,cAAC,EAAD,MAEDC,SAASC,eAAe,W","file":"static/js/main.27336089.chunk.js","sourcesContent":["import React, { useState, useEffect } from 'react';\nimport axios from 'axios';\n\naxios.defaults.baseURL = process.env.REACT_APP_SERVER;\n\nfunction App() {\n\tconst [global, setGlobal] = useState(false);\n\n\tuseEffect(() => {\n\t\tconst get = async() => {\n\t\t\ttry {\n\t\t\t\tconst res = await axios.get('/api/global');\n\t\t\t\tsetGlobal(res.data);\n\t\t\t} catch (error) {\n\t\t\t\tsetGlobal(false);\n\t\t\t}\n\t\t};\n\n\t\tconst interval = setInterval(get, 500);\n\t\treturn () => clearInterval(interval);\n\t}, []);\n\n\treturn (\n\t\t<div className=\"app\">\n\t\t\t{global &&\n\t\t\t\t<>\n\t\t\t\t\t<p>Name: {global.name}</p>\n\t\t\t\t\t<p>Pos: {global.pos}</p>\n\t\t\t\t\t<p>Yaw: {global.yaw}</p>\n\t\t\t\t\t<p>Pitch: {global.pitch}</p>\n\n\t\t\t\t\t<p>Dimention: {global.dimension}</p>\n\n\t\t\t\t\t<p>Players:\n\t\t\t\t\t\t{Object.values(global.players).map(x =>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{global.player_names[x.player_uuid]}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</p>\n\n\t\t\t\t\t<p>Holding: {global.holding}</p>\n\t\t\t\t\t<p>AFK: {`${global.afk}`}</p>\n\t\t\t\t\t<p>Health: {global.health}</p>\n\t\t\t\t\t<p>Food: {global.food}</p>\n\t\t\t\t\t<p>Time: {global.time}</p>\n\t\t\t\t</>\n\t\t\t}\n\t\t</div>\n\t);\n}\n\nexport default App;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nReactDOM.render(\n\t<React.StrictMode>\n\t\t<App />\n\t</React.StrictMode>,\n\tdocument.getElementById('root')\n);\n"],"sourceRoot":""}

View File

@@ -0,0 +1,2 @@
!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],p=0,s=[];p<i.length;p++)f=i[p],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="/";var i=this.webpackJsonpweb_interface=this.webpackJsonpweb_interface||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var c=l;t()}([]);
//# sourceMappingURL=runtime-main.97b64705.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
{
"name": "web_interface",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"axios": "^0.21.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

53
web_interface/src/App.js Normal file
View File

@@ -0,0 +1,53 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = process.env.REACT_APP_SERVER;
function App() {
const [global, setGlobal] = useState(false);
useEffect(() => {
const get = async() => {
try {
const res = await axios.get('/api/global');
setGlobal(res.data);
} catch (error) {
setGlobal(false);
}
};
const interval = setInterval(get, 500);
return () => clearInterval(interval);
}, []);
return (
<div className="app">
{global &&
<>
<p>Name: {global.name}</p>
<p>Pos: {global.pos}</p>
<p>Yaw: {global.yaw}</p>
<p>Pitch: {global.pitch}</p>
<p>Dimention: {global.dimension}</p>
<p>Players:
{Object.values(global.players).map(x =>
<div>
{global.player_names[x.player_uuid]}
</div>
)}
</p>
<p>Holding: {global.holding}</p>
<p>AFK: {`${global.afk}`}</p>
<p>Health: {global.health}</p>
<p>Food: {global.food}</p>
<p>Time: {global.time}</p>
</>
}
</div>
);
}
export default App;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

11418
web_interface/yarn.lock Normal file

File diff suppressed because it is too large Load Diff