Compare commits

...

46 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
57 changed files with 12759 additions and 742 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,10 +1,16 @@
# Mosfet Minecraft Bot # 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, Mosfet is able to farm wood by cutting trees, farm crops, gather sand, farm
and trade with villagers to get emeralds. He can eat, sleep, and flee from netherwart, and trade with villagers to get emeralds. He can eat, sleep, and
threats. flee from threats.
## Requirements
- Python >= 3.6
- pip, virtualenv, git, wget, unzip
## Linux Setup ## Linux Setup
@@ -12,7 +18,7 @@ Assuming Debian / Ubuntu based distro:
``` ```
$ sudo apt update $ 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 $ git clone https://git.tannercollin.com/tanner/minecraft-bot.git
$ cd minecraft-bot/ $ cd minecraft-bot/
@@ -20,15 +26,38 @@ $ cd minecraft-bot/
## Running ## 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 ## Commands
The command prefix character is the last character of the bot's name. For 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 example, if the bot's name is `mattstack`, then you would issue commands like
`1farm wood` or `1pos`. This lets you run multiple bots on the same server. `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 `!` The exception are the below public commands, they can optionally be prefixed with `!`
and all bots will run the command. 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 `!error` - raises an error
`!inv` - prints current inventory `!inv` - replies and prints current inventory
`!time` - replies with Minecraft world time `!time` - replies with Minecraft world time
@@ -95,7 +124,7 @@ These can be ran by anyone, all bots will reply.
### Bot-specific Commands ### 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 `1respawn` - respawns the bot if it's dead
@@ -160,4 +189,4 @@ put to use.
## Acknowledgements ## 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 traceback
import json import json
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
from flask import Flask 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 munch import Munch
from watchdog.observers import Observer from watchdog.observers import Observer
@@ -20,6 +27,7 @@ g.name = None
g.mcdata = False g.mcdata = False
g.pos = False g.pos = False
g.dimension = None g.dimension = None
g.item_lock = False
g.inv = {} g.inv = {}
g.objects = {} g.objects = {}
g.mobs = {} g.mobs = {}
@@ -32,9 +40,10 @@ g.holding = 0
g.afk = False g.afk = False
g.health = 20 g.health = 20
g.food = 20 g.food = 20
g.sand_origin = None
@app.route('/') @app.route('/api/global')
def hello_world(): def api_global():
data = json.dumps(g, default=lambda o: str(o), indent=4) data = json.dumps(g, default=lambda o: str(o), indent=4)
response = app.response_class( response = app.response_class(
@@ -44,6 +53,10 @@ def hello_world():
) )
return response return response
@app.route('/')
def root():
return app.send_static_file('index.html')
reload_timeout = time.time() reload_timeout = time.time()
def main(): def main():
@@ -58,7 +71,7 @@ def main():
event_handler.on_any_event = reload_bot event_handler.on_any_event = reload_bot
observer = Observer() observer = Observer()
observer.schedule(event_handler, '.', recursive=True) observer.schedule(event_handler, 'mosfet', recursive=True)
observer.start() observer.start()
try: try:
@@ -78,8 +91,22 @@ def main():
observer.stop() observer.stop()
observer.join() 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__': if __name__ == '__main__':
threading.Thread(target=app.run).start() threading.Thread(target=run_api).start()
time.sleep(1) time.sleep(1)
main() main()

View File

@@ -3,16 +3,12 @@ if __name__ == '__main__':
exit(1) exit(1)
import os import os
import sys
import time import time
import importlib import importlib
from math import floor, ceil from math import floor, ceil
from copy import copy 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 . import monkey_patch # must be before any possible pyCraft imports
from minecraft import authentication from minecraft import authentication
@@ -24,6 +20,7 @@ from mosfet.protocol.managers import DataManager, ChunksManager, ChatManager, Ch
from munch import Munch from munch import Munch
from mosfet import commands
from mosfet import game from mosfet import game
from mosfet import job from mosfet import job
from mosfet import path from mosfet import path
@@ -38,6 +35,7 @@ from mosfet.info import mobs
for module in [ for module in [
blocks, blocks,
commands,
game, game,
items, items,
job, job,
@@ -67,21 +65,19 @@ def tick(global_state):
target = None target = None
# make sure current chunks are loaded for physics # make sure current chunks are loaded for physics
if not g.chunks.check_loaded(p, 288): if not g.chunks.check_loaded(g.info.render_distance):
if not g.chunks.loading: if not g.chunks.loading:
print('Loading chunks', end='', flush=True) print('Loading chunks', end='', flush=True)
g.chunks.loading = True g.chunks.loading = time.time()
packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True) packet = serverbound.play.PositionAndLookPacket(x=p.x, feet_y=p.y, z=p.z, pitch=0, yaw=0, on_ground=True)
g.connection.write_packet(packet, force=True) g.connection.write_packet(packet, force=True)
return return
else: else:
if g.chunks.loading: if g.chunks.loading:
print() print()
print('Chunks loaded.') print('Chunks loaded in', round(time.time() - g.chunks.loading, 2), 's')
g.chunks.loading = False g.chunks.loading = False
g.chunks.unload_chunks(p)
########## object physics ########## ########## object physics ##########
# note: it's possible the chunk data is out of date when this runs # note: it's possible the chunk data is out of date when this runs
@@ -204,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)) 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.connection.write_packet(packet)
g.game.tick()
g.job.tick() g.job.tick()
g.game.tick() # order important for correction_count
def init(global_state): def init(global_state):
@@ -242,20 +238,57 @@ def init(global_state):
g.maximum_supply_slots = 33 g.maximum_supply_slots = 33
def bot(global_state): 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 g = global_state
if not g.mcdata: if not g.mcdata:
g.mcdata = DataManager('./minecraft_data') g.mcdata = DataManager('./minecraft_data')
if not g.connection: if not SERVER:
auth_token = authentication.AuthenticationToken() print()
try: print('You must specify a server to connect to. For example:')
auth_token.authenticate(USERNAME, PASSWORD) print('SERVER=minecraft.example.com ./run_linux.sh')
except YggdrasilError as e: print('SERVER=localhost PORT=12345 ./run_linux.sh')
print(e) print()
sys.exit() print('If you want to use your own account:')
print("Logged in as %s..." % auth_token.username) print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh')
g.connection = Connection(SERVER, PORT, auth_token=auth_token) os._exit(0)
elif not g.connection:
if EMAIL and PASSWORD:
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)
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) g.chunks = ChunksManager(g.mcdata)
@@ -267,6 +300,7 @@ def bot(global_state):
g.game = game.Game(g) g.game = game.Game(g)
g.world = world.World(g) g.world = world.World(g)
g.commands = commands.Commands(g)
try: try:
while not g.pos: 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,8 +1,6 @@
import re import re
import time import time
import importlib
import random import random
from math import hypot
from itertools import count from itertools import count
from munch import Munch from munch import Munch
@@ -17,12 +15,11 @@ from mosfet.protocol.packets import (
ClientWindowConfirmationPacket, EntityMetadataPacket, ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket, SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket, EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
SelectTradePacket, DisconnectPacket, SelectTradePacket, DisconnectPacket, UnloadChunkPacket,
) )
from mosfet.protocol.types import Slot from mosfet.protocol.types import Slot
from mosfet import print_help
from mosfet import utils from mosfet import utils
from mosfet import path from mosfet import path
from mosfet import bot from mosfet import bot
@@ -52,6 +49,7 @@ class Game:
register(self.handle_spawn_living, SpawnLivingEntityPacket) register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket) register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
register(self.handle_entity_position_rotation, EntityPositionRotationPacket) register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
register(self.handle_entity_look, clientbound.play.EntityLookPacket)
register(self.handle_destroy_entities, DestroyEntitiesPacket) register(self.handle_destroy_entities, DestroyEntitiesPacket)
register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket) register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket)
register(self.handle_respawn, clientbound.play.RespawnPacket) register(self.handle_respawn, clientbound.play.RespawnPacket)
@@ -61,11 +59,10 @@ class Game:
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket) #register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
register(self.handle_trade_list, TradeListPacket) register(self.handle_trade_list, TradeListPacket)
register(self.handle_disconnect, DisconnectPacket) register(self.handle_disconnect, DisconnectPacket)
register(self.handle_unload_chunk, UnloadChunkPacket)
#register(self.handle_packet, Packet, early=True) #register(self.handle_packet, Packet, early=True)
self.g.chat.set_handler(self.handle_chat)
def handle_login_success(self, packet): def handle_login_success(self, packet):
print(packet) print(packet)
self.g.name = packet.Username self.g.name = packet.Username
@@ -126,510 +123,6 @@ class Game:
if new_path: if new_path:
self.g.path = 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): def handle_time_update(self, packet):
self.g.time = packet.time_of_day % 24000 self.g.time = packet.time_of_day % 24000
@@ -683,6 +176,11 @@ class Game:
#print(packet) #print(packet)
return return
def respawn(self):
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
self.g.connection.write_packet(packet)
def animate(self): def animate(self):
packet = serverbound.play.AnimationPacket() packet = serverbound.play.AnimationPacket()
packet.hand = packet.HAND_MAIN packet.hand = packet.HAND_MAIN
@@ -897,6 +395,13 @@ class Game:
obj.item_id = entry.value.item_id obj.item_id = entry.value.item_id
obj.item_count = entry.value.item_count 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) player = self.g.players.get(packet.entity_id, None)
if player: if player:
return return
@@ -938,8 +443,14 @@ class Game:
player.x += packet.delta_x / 4096.0 player.x += packet.delta_x / 4096.0
player.y += packet.delta_y / 4096.0 player.y += packet.delta_y / 4096.0
player.z += packet.delta_z / 4096.0 player.z += packet.delta_z / 4096.0
player.yaw = packet.yaw
player.pitch = packet.pitch
#if player.player_uuid == '0c123cfa-1697-4427-9413-4b645dee7ec0': print(packet) def handle_entity_look(self, packet):
player = self.g.players.get(packet.entity_id, None)
if player:
player.yaw = packet.yaw
player.pitch = packet.pitch
def handle_entity_teleport(self, packet): def handle_entity_teleport(self, packet):
mob = self.g.mobs.get(packet.entity_id, None) mob = self.g.mobs.get(packet.entity_id, None)
@@ -983,12 +494,14 @@ class Game:
def handle_respawn(self, packet): def handle_respawn(self, packet):
print(packet) print(packet)
self.g.dimension = packet.world_name.replace('minecraft:', '') self.g.dimension = packet.world_name.replace('minecraft:', '')
self.g.chunks.unload_all_chunks()
def handle_player_list(self, packet): def handle_player_list(self, packet):
for action in packet.actions: for action in packet.actions:
if isinstance(action, packet.AddPlayerAction): if isinstance(action, packet.AddPlayerAction):
self.g.player_names[action.uuid] = action.name self.g.player_names[action.uuid] = action.name
self.g.player_names[action.name] = action.uuid # porque no los dos? self.g.player_names[action.name] = action.uuid
self.g.player_names[action.name.lower()] = action.uuid # porque no los dos?
def handle_update_health(self, packet): def handle_update_health(self, packet):
print(packet) print(packet)
@@ -1029,6 +542,9 @@ class Game:
import os import os
os._exit(1) os._exit(1)
def handle_unload_chunk(self, packet):
self.g.chunks.unload_chunk(packet.chunk_x, packet.chunk_z)
def tick(self): def tick(self):
if self.g.breaking: if self.g.breaking:
self.animate() self.animate()

View File

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

View File

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

View File

@@ -1,8 +1,33 @@
from mosfet.info import items
import json import json
with open('minecraft_data/registries.json') as f: with open('minecraft_data/registries.json') as f:
MOBS = json.load(f)['minecraft:entity_type']['entries'] 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 = [ EVIL = [
'blaze', 'blaze',
'cave_spider', 'cave_spider',

View File

@@ -3,7 +3,6 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -236,6 +235,8 @@ class JobStates:
tuple([items.IRON_INGOT_ID]): (64, 3), tuple([items.IRON_INGOT_ID]): (64, 3),
tuple([items.WHEAT_ID]): (64, 3), tuple([items.WHEAT_ID]): (64, 3),
tuple([items.POTATO_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([ items.set_needed(set([
@@ -244,6 +245,8 @@ class JobStates:
items.IRON_INGOT_ID, items.IRON_INGOT_ID,
items.WHEAT_ID, items.WHEAT_ID,
items.POTATO_ID, items.POTATO_ID,
items.CARROT_ID,
items.BEETROOT_ID,
])) ]))
return machines return machines

View File

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

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -64,6 +64,7 @@ class CheckThreatsStates:
return return
else: else:
print('Cant get to safety', self.safety) print('Cant get to safety', self.safety)
time.sleep(0.1)
print('Cant get to safety, aborting') print('Cant get to safety, aborting')
self.state = self.cleanup self.state = self.cleanup

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -40,7 +40,7 @@ class FillBlocksStates:
b1, b2 = utils.pboundingbox(f.coord1, f.coord2) b1, b2 = utils.pboundingbox(f.coord1, f.coord2)
box = utils.psub(b2, b1) 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_start = f.coord1[1]
y_end = f.coord2[1] y_end = f.coord2[1]
@@ -84,7 +84,7 @@ class FillBlocksStates:
b1, b2 = utils.pboundingbox(f.coord1, f.coord2) b1, b2 = utils.pboundingbox(f.coord1, f.coord2)
box = utils.psub(b2, b1) 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_start = f.coord1[1]
y_end = f.coord2[1] y_end = f.coord2[1]
@@ -139,8 +139,7 @@ class FillBlocksStates:
else: else:
print('Cant get to that block') print('Cant get to that block')
self.state = self.cleanup self.state = self.cleanup
#self.bad_sand.append(self.sand) time.sleep(0.1)
#self.state = self.find_new_sand
def going_to_block(self): def going_to_block(self):
if not len(self.g.path): if not len(self.g.path):

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -61,15 +61,8 @@ class GatherCropStates:
self.state = self.going_to_crop self.state = self.going_to_crop
else: else:
print('Cant get to it, blacklisting') print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_crops.append(self.crop) 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 self.state = self.find_new_crop
def going_to_crop(self): def going_to_crop(self):
@@ -79,15 +72,7 @@ class GatherCropStates:
def break_crop(self): def break_crop(self):
self.g.game.break_block(self.crop) self.g.game.break_block(self.crop)
self.wait_time = 0.5 self.state = self.select_seed
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): def select_seed(self):
p = utils.pint(self.g.pos) p = utils.pint(self.g.pos)
@@ -98,18 +83,17 @@ class GatherCropStates:
blocks.MATURE_CARROT_ID: items.CARROT_ID, blocks.MATURE_CARROT_ID: items.CARROT_ID,
blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID, blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID,
} }
self.target_seed = crop_seeds[self.type_id]
if self.g.game.select_item([crop_seeds[self.type_id]]): if self.g.game.select_item([self.target_seed]):
self.state = self.wait_select self.state = self.wait_select
self.wait_time = 0.5
else: else:
print('Aborting planting, no crop') print('Havent picked up seed yet')
self.state = self.cleanup return
def wait_select(self): def wait_select(self):
# wait a bit to select if self.target_seed != self.g.holding:
if self.wait_time > 0: return
self.wait_time -= utils.TICK
else: else:
self.state = self.place_crop self.state = self.place_crop
@@ -118,12 +102,11 @@ class GatherCropStates:
self.g.game.place_block(p, BlockFace.TOP) self.g.game.place_block(p, BlockFace.TOP)
print('Placed crop') print('Placed crop')
self.state = self.wait_place self.state = self.wait_place
self.wait_time = 0.5
def wait_place(self): def wait_place(self):
# wait a bit for chunk data to update w = self.g.world
if self.wait_time > 0: if w.block_at(*self.crop) == blocks.AIR:
self.wait_time -= utils.TICK return
else: else:
self.state = self.cleanup self.state = self.cleanup
@@ -141,8 +124,8 @@ class GatherCropStates:
self.crop = None self.crop = None
self.type_id = None self.type_id = None
self.target_seed = None
self.bad_crops = [] self.bad_crops = []
self.wait_time = 0
def run(self): def run(self):
self.state() self.state()

View File

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

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -54,15 +54,8 @@ class GatherWartStates:
self.state = self.going_to_wart self.state = self.going_to_wart
else: else:
print('Cant get to it, blacklisting') print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_warts.append(wart) self.bad_warts.append(wart)
self.wait_time = 0.5
self.state = self.wait_to_restart
def wait_to_restart(self):
# prevent timeouts
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.find_new_wart self.state = self.find_new_wart
def going_to_wart(self): def going_to_wart(self):

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -80,6 +80,7 @@ class GatherWoodStates:
self.state = self.going_to_tree self.state = self.going_to_tree
else: else:
self.openings.pop(0) self.openings.pop(0)
time.sleep(0.1)
def going_to_tree(self): def going_to_tree(self):
if utils.pint(self.g.pos) == self.openings[0]: if utils.pint(self.g.pos) == self.openings[0]:
@@ -126,6 +127,7 @@ class GatherWoodStates:
else: else:
self.openings.pop(0) self.openings.pop(0)
self.state = self.choose_opening self.state = self.choose_opening
time.sleep(0.1)
def going_to_trunk_base(self): def going_to_trunk_base(self):
if utils.pint(self.g.pos) == self.tree: if utils.pint(self.g.pos) == self.tree:

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -61,6 +61,7 @@ class GrabSandStates:
return return
else: else:
print('Cant get to sand', self.sand) print('Cant get to sand', self.sand)
time.sleep(0.1)
print('Cant get to any more sand, aborting') print('Cant get to any more sand, aborting')
self.state = self.cleanup self.state = self.cleanup

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -61,7 +61,7 @@ class GrabSuppliesStates:
w = self.g.world w = self.g.world
p = utils.pint(self.g.pos) 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) print('Found:', self.barrels)
self.state = self.choose_barrel self.state = self.choose_barrel
@@ -73,6 +73,7 @@ class GrabSuppliesStates:
if barrel in self.bad_barrels: if barrel in self.bad_barrels:
continue continue
print('Chose:', barrel)
self.barrel = barrel self.barrel = barrel
self.state = self.path_to_barrel self.state = self.path_to_barrel
return return
@@ -86,27 +87,25 @@ class GrabSuppliesStates:
p = utils.pint(self.g.pos) p = utils.pint(self.g.pos)
c = self.g.chunks c = self.g.chunks
barrel = self.barrel navpath = w.path_to_place_faked(p, 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)
print('navpath:', navpath) print('navpath:', navpath)
if navpath: if navpath:
self.g.path = navpath[:-1] self.g.path = navpath[:-1]
self.opening = self.g.path[-1] self.opening = self.g.path[-1]
self.checked_barrels.append(barrel) self.checked_barrels.append(self.barrel)
self.area = barrel self.area = self.barrel
self.state = self.going_to_barrel self.state = self.going_to_barrel
self.checked_supplies = [] self.checked_supplies = []
return return
else: else:
print('No path, blacklisting barrel') print('No path, blacklisting barrel')
self.bad_barrels.append(barrel) time.sleep(0.1)
self.state = self.choose_barrel 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): def going_to_barrel(self):
if utils.pint(self.g.pos) == self.opening: if utils.pint(self.g.pos) == self.opening:

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace

View File

@@ -3,7 +3,7 @@ import time
import importlib import importlib
import random import random
from itertools import count from itertools import count
from math import hypot, floor from math import floor
from minecraft.networking.types import BlockFace from minecraft.networking.types import BlockFace
@@ -31,8 +31,29 @@ class SellToVillagerStates:
for v in w.find_villagers(p, 100): for v in w.find_villagers(p, 100):
print('Found villager:', v) print('Found villager:', v)
if v not in self.bad_villagers and v not in self.spent_villagers:
break 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 else: # for
print('No good villagers left, aborting.') print('No good villagers left, aborting.')
self.spent_villagers = [] self.spent_villagers = []
@@ -59,7 +80,7 @@ class SellToVillagerStates:
if self.villager not in self.good_villagers: if self.villager not in self.good_villagers:
self.bad_villagers.append(self.villager) self.bad_villagers.append(self.villager)
print('Added to bad villager list') print('Added to bad villager list')
self.state = self.cleanup self.state = self.find_villager
return return
navpath = w.path_to_place(p, self.openings[0]) navpath = w.path_to_place(p, self.openings[0])
@@ -67,8 +88,10 @@ class SellToVillagerStates:
if navpath: if navpath:
self.g.path = navpath self.g.path = navpath
self.state = self.going_to_villager self.state = self.going_to_villager
self.g.look_at = None
else: else:
self.openings.pop(0) self.openings.pop(0)
time.sleep(0.1)
def going_to_villager(self): def going_to_villager(self):
if utils.pint(self.g.pos) == self.openings[0]: if utils.pint(self.g.pos) == self.openings[0]:
@@ -143,8 +166,7 @@ class SellToVillagerStates:
print('Villager has been spent, aborting') print('Villager has been spent, aborting')
self.g.game.close_window() self.g.game.close_window()
self.spent_villagers.append(self.villager) self.spent_villagers.append(self.villager)
self.state = self.wait self.state = self.find_villager
self.wait_time = 10
return return
def click_trade(self): def click_trade(self):

View File

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

View File

@@ -1,7 +1,6 @@
import importlib import importlib
import functools import functools
import time import time
from math import hypot, sqrt
from astar import AStar from astar import AStar
@@ -306,7 +305,7 @@ class Pathfinder(AStar):
def distance_between(self, n1, n2): def distance_between(self, n1, n2):
(x1, y1, z1) = n1 (x1, y1, z1) = n1
(x2, y2, z2) = n2 (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): def heuristic_cost_estimate(self, n1, n2):
(x1, y1, z1) = n1 (x1, y1, z1) = n1

View File

@@ -1,6 +1,6 @@
HELP_LINES = [] HELP_LINES = []
with open('mosfet/game.py', 'r') as f: with open('mosfet/commands.py', 'r') as f:
for line in f.readlines(): for line in f.readlines():
if line.strip().startswith('## '): if line.strip().startswith('## '):
HELP_LINES.append(line.strip()[3:]) 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.protocol import packets
from mosfet import utils from mosfet import utils
from mosfet.info import blocks
class DataManager: class DataManager:
def __init__(self, directory): def __init__(self, directory):
@@ -42,7 +43,7 @@ class ChunksManager:
def __init__(self, data_manager): def __init__(self, data_manager):
self.data = data_manager self.data = data_manager
self.chunks = {} self.chunks = {}
self.biomes = {} #self.biomes = {}
self.index = {} self.index = {}
self.loading = False self.loading = False
@@ -68,13 +69,12 @@ class ChunksManager:
for item_id, locations in chunk.sub_index.items(): for item_id, locations in chunk.sub_index.items():
if item_id not in self.index: if item_id not in self.index:
self.index[item_id] = [] self.index[item_id] = set()
for l in locations: for l in locations:
coords = (dx + l%16, dy + l//256, dz + l%256//16) coords = (dx + l%16, dy + l//256, dz + l%256//16)
self.index[item_id].append(coords) self.index[item_id].add(coords)
self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME #self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME
if self.loading: if self.loading:
print('.', end='', flush=True) print('.', end='', flush=True)
@@ -116,28 +116,26 @@ class ChunksManager:
if not c: return None if not c: return None
c.set_block_at(x%16, y%16, z%16, block) c.set_block_at(x%16, y%16, z%16, block)
def check_loaded(self, position, steps): if block in blocks.INDEXED_IDS:
x, y, z = utils.pint(position) if block not in self.index:
player_chunk = (x//16, 1, z//16) self.index[block] = set()
for i in range(steps): # TODO: base off render_distance? self.index[block].add((x, y, z))
offset = utils.spiral(i)
check = utils.padd(player_chunk, offset)
if check not in self.chunks: def check_loaded(self, chunk_distance):
return False num = (chunk_distance * 2 + 1) ** 2
num_subchunks = num * 16
return len(self.chunks) >= num_subchunks
return True def unload_chunk(self, x, z):
for y in range(16):
def unload_chunks(self, position): try:
x, y, z = utils.pint(position) del self.chunks[(x, y, z)]
player_chunk = (x//16, 0, z//16) except KeyError:
loaded_chunks = list(self.chunks.keys()) pass
for chunk in loaded_chunks:
check = (chunk[0], 0, chunk[2])
if utils.phyp_king(player_chunk, check) > 20:
del self.chunks[chunk]
def unload_all_chunks(self):
self.chunks = {}
self.index = {}
class ChunkNotLoadedException(Exception): class ChunkNotLoadedException(Exception):
def __str__(self): def __str__(self):
@@ -155,24 +153,25 @@ class ChatManager:
def translate_chat(self, data): def translate_chat(self, data):
if isinstance(data, str): if isinstance(data, str):
return data 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: elif 'extra' in data:
return ''.join([self.translate_chat(x) for x in data['extra']]) result += ''.join([self.translate_chat(x) for x in data['extra']])
elif 'text' in data:
return data['text'] return result
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 '?'
def print_chat(self, chat_packet): 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: try:
source = chat_packet.field_string('position') source = chat_packet.field_string('position')
sender = chat_packet.sender
text = self.translate_chat(json.loads(chat_packet.json_data)) text = self.translate_chat(json.loads(chat_packet.json_data))
print('[%s] %s'%(source, text)) print('[%s] %s'%(source, text))
except Exception as ex: except Exception as ex:
@@ -180,7 +179,7 @@ class ChatManager:
return return
if self.handler: if self.handler:
self.handler((source, text)) self.handler((source, sender, text))
def set_handler(self, func): def set_handler(self, func):
self.handler = func self.handler = func

View File

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

View File

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

View File

@@ -1,12 +1,16 @@
import importlib import importlib
import collections import collections
from math import floor, ceil, sqrt, hypot from math import floor, ceil, sqrt
from mosfet.info import blocks from mosfet.info import blocks
from mosfet.info import mcdata from mosfet.info import mcdata
TICK = 0.05 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): def padd(p1, p2):
return (p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2]) return (p1[0] + p2[0], p1[1] + p2[1], p1[2] + p2[2])

View File

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

View File

@@ -2,7 +2,6 @@ import collections
import re import re
import time import time
import random import random
from math import hypot
from itertools import count from itertools import count
from copy import copy from copy import copy
@@ -35,7 +34,7 @@ class World:
continue continue
if y_limit and abs(cur[1]) > y_limit: if y_limit and abs(cur[1]) > y_limit:
continue continue
if distance and hypot(*cur) > distance: if distance and utils.hypot(*cur) > distance:
continue continue
check = utils.padd(center, cur) check = utils.padd(center, cur)
@@ -76,7 +75,7 @@ class World:
offset = utils.spiral(n) offset = utils.spiral(n)
check = utils.padd(center, offset) check = utils.padd(center, offset)
if self.block_at(*check) in block_ids: if self.block_at(*check) in block_ids:
if hypot(*offset) < distance: if utils.hypot(*offset) < distance:
result.append(check) result.append(check)
if limit and len(result) == limit: if limit and len(result) == limit:
return result return result
@@ -141,6 +140,26 @@ class World:
except path.AStarTimeout: except path.AStarTimeout:
return None 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): def find_bed_areas(self, center, distance):
bed_clearance = 9 # 5x5 area bed_clearance = 9 # 5x5 area
clear_distance = 2 clear_distance = 2
@@ -259,6 +278,16 @@ class World:
result.append(utils.padd(area, direction)) result.append(utils.padd(area, direction))
return result 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): def find_cache_openings(self, area):
return self.find_bed_openings(area) return self.find_bed_openings(area)
@@ -283,6 +312,7 @@ class World:
if utils.phyp(center, pos) > distance: if utils.phyp(center, pos) > distance:
continue continue
result.append(mob) result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result return result
def find_threats(self, center, distance): def find_threats(self, center, distance):
@@ -295,6 +325,7 @@ class World:
if not self.check_air_column(pos, distance): if not self.check_air_column(pos, distance):
continue continue
result.append(mob) result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result return result
def find_villagers(self, center, distance): def find_villagers(self, center, distance):
@@ -307,6 +338,7 @@ class World:
if utils.phyp(center, pos) > distance: if utils.phyp(center, pos) > distance:
continue continue
result.append(mob) result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result return result
def find_villager_openings(self, villager): def find_villager_openings(self, villager):

View File

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

View File

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

View File

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