Compare commits

..

74 Commits

Author SHA1 Message Date
bbe2d9f99c Fix farm crop seed pick up bug 2021-06-19 22:17:52 +00:00
2f77c7b990 Sort mobs by distance 2021-06-19 21:30:55 +00:00
3540e5580e Remove delays from gather_crop job 2021-06-19 21:30:41 +00:00
261684ea90 Handle entity rotation packets 2021-05-07 04:13:36 +00:00
09b09b3f96 Improve !info command 2021-05-07 04:02:51 +00:00
4d2d358175 Use block state id to check bed occupancy 2021-05-07 04:02:26 +00:00
324ad41bf7 Print Minecraft time in 12h format 2021-05-07 00:30:29 +00:00
343268af24 Use a set for the block index to limit size 2021-05-07 00:29:41 +00:00
e5fd062a4a Handle loading / unloading chunks better 2021-05-07 00:14:25 +00:00
5184d4a173 Reply in-game with inventory 2021-05-06 09:29:26 +00:00
396c1e2e33 Update README 2021-05-06 07:13:41 +00:00
217bb0dae0 Remove extra picture 2021-05-06 07:12:34 +00:00
f7400a1225 Make bot look where he's going while trading 2021-05-03 02:59:23 +00:00
3c31c41acd Try different ports for web interface 2021-05-02 01:47:40 +00:00
15d4dd7922 Add check for missing email 2021-05-02 01:40:15 +00:00
8f0d2eb417 Add check for square brackets 2021-05-02 01:38:26 +00:00
6315f8c309 Add docs about grabbing supplies 2021-05-02 01:26:06 +00:00
2fc08be581 Add link to trapped chest minecraft wiki 2021-05-02 00:35:19 +00:00
c39a8c6d93 Add info about caching items 2021-05-02 00:32:03 +00:00
43e8bf97a0 Add picture of trapped chest 2021-05-02 00:15:13 +00:00
b59c260e90 Begin cache items page 2021-05-01 23:44:33 +00:00
a41d9f2793 Switch USERNAME to EMAIL to avoid collisions 2021-05-01 23:34:43 +00:00
0d2be1378f Improve README 2021-05-01 23:27:34 +00:00
0d23dbaf9f Add burner account credentials to use by default 2021-05-01 23:22:43 +00:00
a87cc85eab Improve trading villager selection based on profession 2021-05-01 21:31:21 +00:00
080895421d Improve parsing chat commands 2021-04-29 02:18:35 +00:00
e4ea9aeaa0 Update README 2021-04-29 02:18:35 +00:00
ad6c412802 Add support for Python 3.6 (thanks sose) 2021-04-28 02:12:15 -06:00
aec057c89c Use our own hypot() for older Python support 2021-04-28 07:12:30 +00:00
sose
af9cc4b546 allow connection to offline mode servers without a password 2021-04-26 20:31:00 -07:00
sose
666435ddae added requirements, updated install command 2021-04-26 20:29:31 -07:00
1c0824a167 Fix README typo 2021-04-26 03:26:07 +00:00
47c2fca00a Print web interface port 2021-04-26 03:25:42 +00:00
76cdf90b44 Commit web interface build and serve statically 2021-04-26 03:07:06 +00:00
33145cb3f2 Display some basic stats on web 2021-04-26 02:37:43 +00:00
963b3d9736 Begin web interface hello world 2021-04-25 23:43:55 +00:00
a49caaedf5 Sleep 0.1 s after failing to pathfind to prevent timeouts 2021-04-25 23:39:57 +00:00
7caa51f011 Add function for faking blocks while pathing 2021-04-25 21:25:15 +00:00
e588c8fa1a Remove script delays 2021-04-25 21:24:58 +00:00
650398255b Detect being forced to wake up 2021-04-25 00:59:43 +00:00
d69f9cf09e Don't sleep in occupied beds 2021-04-25 00:33:55 +00:00
8b85de2b2a Limit reply length so bot doesn't get kicked 2021-04-24 23:50:52 +00:00
ce9613c00c Index blocks on change 2021-04-24 23:37:11 +00:00
09b9002c58 Fix bug where needed and wanted items werent updating 2021-04-24 22:59:22 +00:00
4026b410f4 Set sand origin when command is given 2021-04-24 22:34:25 +00:00
a5642409d2 Move command processing to separate file 2021-04-23 07:01:00 +00:00
ae2b0f4875 Move blocks, items, mcdata, mobs to info/ folder 2021-04-23 01:41:01 +00:00
23891066c0 Improve searching for crops 2021-04-23 01:25:44 +00:00
9874e23aa6 Move World class into its own file 2021-04-23 00:18:53 +00:00
221d497204 Reset the bot on death 2021-04-23 00:05:56 +00:00
433e35e79b Add update script 2021-04-22 02:57:00 +00:00
3c90a09d82 Simplify README 2021-04-22 01:46:19 +00:00
390c3405c3 Add dependency checks 2021-04-22 01:40:01 +00:00
1f621333de Add run script 2021-04-22 01:29:52 +00:00
38f3b0ed67 Fix imports 2021-04-22 00:46:54 +00:00
d099ae6965 Improve download_mcdata script 2021-04-21 22:13:20 +00:00
3c373ccfe3 Move bot files into mosfet/ 2021-04-21 22:09:28 +00:00
4d2b09e578 Improve README 2021-04-21 21:51:26 +00:00
f88809af64 Remove redundant module reloads 2021-04-19 07:51:48 +00:00
87bb638270 Add !help command 2021-04-19 04:18:40 +00:00
bdfec0f9a4 Format commands in code 2021-04-19 04:02:51 +00:00
679030ff9a Document bot commands 2021-04-19 03:59:00 +00:00
3aa766926b Update requirements 2021-04-19 01:51:20 +00:00
8c9a28beac Split jobs.py into files 2021-04-19 01:34:54 +00:00
5ff0394dc3 Simplify handling block and item ids 2021-04-18 23:02:16 +00:00
fac4309e43 Adjust item counts 2021-04-18 22:41:03 +00:00
fc929db658 Manage needed items and item counts better 2021-04-17 23:48:38 +00:00
7d0cef0e2e Fix order of grabbing supplies and checking barrels 2021-04-17 22:04:41 +00:00
daf152389a Only try to sleep in one existing bed 2021-04-17 20:59:06 +00:00
ef68956e63 Try using already placed beds first 2021-03-26 22:25:55 +00:00
0bdae1f775 Move to our own vector module instead of Panda3D 2021-03-26 22:25:55 +00:00
2fa3044acb Program the bot to crawl, fix wood farming bugs 2021-03-26 22:25:55 +00:00
e642e426b9 Add exit function 2021-03-26 22:25:55 +00:00
f35c7a4e51 Detect disconnection and exit the script 2021-03-26 22:25:55 +00:00
65 changed files with 16791 additions and 4003 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/

234
README.md
View File

@@ -1,110 +1,192 @@
# Minecraft Bot # Mosfet Minecraft Bot
## Setup 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, farm crops, gather sand, farm
netherwart, and trade with villagers to get emeralds. He can eat, sleep, and
flee from threats.
## Requirements
- Python >= 3.6
- pip, virtualenv, git, wget, unzip
## Linux Setup
Assuming Debian / Ubuntu based distro: Assuming Debian / Ubuntu based distro:
``` ```
$ sudo apt update $ sudo apt update
$ sudo apt install build-essential python3 python3-dev python3-pip python3-virtualenv git $ 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/
$ bash download_mcdata.sh
$ virtualenv -p python3 env
$ source env/bin/activate
(env) $ pip install -r requirements.txt
``` ```
Always make sure the virtual environment is running `(env)`.
## Running ## Running
If you want to use the built-in burner account (Minecraft name `mattstack`):
``` ```
(env) $ USERNAME=you@domain.com PASSWORD=supersecret SERVER=example.com python main.py $ 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
Talking to bot (All commands can be found in "game.py" under "public commands", starting around line 494) The command prefix character is the last character of the bot's name. For
example, if the bot's name is `mattstack`, then you would issue commands like
`kfarm wood` or `kpos`. This lets you run multiple bots on the same server.
Use prefix (in this case, "1") to call to attention before giving command. (EX. 1ping, 1here, etc.) In the following examples, we'll assume the bot's name is `mosfet1`, so commands
would be ran like `1farm wood` or `1pos`.
# ping The exception are the below public commands, they can optionally be prefixed with `!`
Will respond "pong" if active and listening and all bots will run the command.
# pos Commands can be issued in public chat or private message like `/msg mosfet1
Will respond with current position 1farm wood`. The bot will reply the same way the command was issued.
# afk Use `echo` to make Mosfet run Minecraft commands. For example, `1echo /sethome
Will afk (if not already) home` will cause him to issue the `/sethome` command.
# unafk ### Public Commands
Will unafk (if afk)
# error These can be ran by anyone, all bots will reply.
---
# inv `!help` - prints this whole help message to console
Will list inventory (in terminal)
`!help [command]` - replies in-game explaining command
`!ping` - replies with "pong"
`!echo [data]` - replies with "data"
`!pos` - replies with position and dimension
`!afk` - goes AFK with /afk
`!unafk` - goes not AFK with /afk
`!error` - raises an error
`!inv` - replies and prints current inventory
`!time` - replies with Minecraft world time
`!count [id]` - counts the number of items with that id
`!loaded` - replies with the current loaded area
`!players` - prints the current players
`!players clear` - clears the current player list
`!objects` - prints the current items on ground
`!objects clear` - clears the current object list
`!mobs` - prints the current mobs
`!mobs clear` - clears the current mob list
`!monsters` - prints the current monsters
`!villagers` - prints the current villagers
`!threats` - prints the dangerous monsters within 20 blocks
`!threats [num]` - prints the dangerous monsters within num blocks
`"zzz" or !zzz` - bot does /afk to let others sleep
`!tree` - replies with the closest tree
`!block x y z` - replies what block is at (x, y, z)
### Bot-specific Commands
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
`1gather wood` - gathers wood from the world
`1gather sand` - gathers sand from the world
`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
`1loiter` - stands still but eats, sleeps, and flees
`1trade` - sells items to villagers to get emeralds
`1stop` - stops the current job and resets bot
`1drop` - drops the current stack its holding
`1select [id]` - moves item with id into main hand
`1dump [id]` - drops all items matching id
`1drain` - drops all items in inventory
`1fill [x] [y] [z] [x] [y] [z]` - fills the cuboid with the block at the first coordinate
`1here` - bot comes to your location
`1goto [x] [y] [z]` - sends the bot to coordinate (x, y, z)
`1close` - closes the current Minecraft window
`1click [slot] [button] [mode]` - clicks the current window
`1use` - use the item it's currently holding
`1interact [entity id]` - interacts with that entity
### Authorized Commands
These dangerous commands can only be ran by the bot owner.
`1print [expression]` - replies with Python eval(expression)
`1exit` - exits the program
# time ## License
---
# count This program is free and open-source software licensed under the MIT License.
--- Please see the `LICENSE` file for details.
# loaded That means you have the right to study, change, and distribute the software and
--- source code to anyone and for any purpose. You deserve these rights. Please take
advantage of them because I like pull requests and would love to see this code
# players put to use.
---
# objects
---
# mobs
---
# monsters
---
# villagers
---
# threats
---
# spiral
---
# sand slice
---
# zzz
Will afk if in overworld, and if not already afk
# tree
---
# block
---
#
## temporary workarounds
# Add "?" to line 457, which is "match1 = re.match(r'<(\w+)> (.*)', text)"
It should now look like this "match1 = re.match(r'<?(\w+)> (.*)', text)"
This allows the bot to react to commands made by someone else than the creator of code (Tanner6)
## Acknowledgements
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

View File

@@ -1,10 +0,0 @@
#!/bin/sh
VERSION="1.16.4"
wget -O/tmp/mcdata.zip https://apimon.de/mcdata/$VERSION/$VERSION.zip
rm -rf minecraft_data
mkdir minecraft_data
unzip /tmp/mcdata.zip -d minecraft_data
rm /tmp/mcdata.zip

1255
game.py

File diff suppressed because it is too large Load Diff

2190
jobs.py

File diff suppressed because it is too large Load Diff

39
main.py
View File

@@ -4,14 +4,21 @@ 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
from watchdog.events import PatternMatchingEventHandler from watchdog.events import PatternMatchingEventHandler
import bot from mosfet import bot
global_state = Munch() global_state = Munch()
g = global_state g = global_state
@@ -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,47 +3,57 @@ 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'] from . import monkey_patch # must be before any possible pyCraft imports
PASSWORD = os.environ['PASSWORD']
SERVER = os.environ['SERVER']
PORT = int(os.environ.get('PORT', 25565))
import monkey_patch # must be before any possible pyCraft imports
from minecraft import authentication from minecraft import authentication
from minecraft.exceptions import YggdrasilError from minecraft.exceptions import YggdrasilError
from minecraft.networking.connection import Connection from minecraft.networking.connection import Connection
from minecraft.networking.packets import Packet, clientbound, serverbound from minecraft.networking.packets import Packet, clientbound, serverbound
from protocol.managers import DataManager, ChunksManager, ChatManager, ChunkNotLoadedException from mosfet.protocol.managers import DataManager, ChunksManager, ChatManager, ChunkNotLoadedException
from munch import Munch from munch import Munch
from panda3d.core import LPoint3f, LVector3f
import game from mosfet import commands
importlib.reload(game) from mosfet import game
import blocks from mosfet import job
importlib.reload(blocks) from mosfet import path
import utils from mosfet import print_help
importlib.reload(utils) from mosfet import utils
import path from mosfet import vector
importlib.reload(path) from mosfet import world
import jobs from mosfet.info import blocks
importlib.reload(jobs) from mosfet.info import items
import mcdata from mosfet.info import mcdata
importlib.reload(mcdata) from mosfet.info import mobs
for module in [
blocks,
commands,
game,
items,
job,
mcdata,
mobs,
path,
print_help,
utils,
vector,
world,
]:
importlib.reload(module)
last_tick = time.time() last_tick = time.time()
PITCH_ANGLE_DIR = LVector3f(x=0, y=1, z=0) PITCH_ANGLE_DIR = vector.Vector3D((0, 1, 0))
YAW_ANGLE_DIR = LVector3f(x=0, y=0, z=-1) YAW_ANGLE_DIR = vector.Vector3D((0, 0, -1))
YAW_ANGLE_REF = LVector3f(x=0, y=1, z=0) YAW_ANGLE_REF = vector.Vector3D((0, 1, 0))
YAW_LOOK_AHEAD = 4 YAW_LOOK_AHEAD = 4
@@ -55,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
@@ -105,7 +113,7 @@ def tick(global_state):
########## player physics ########## ########## player physics ##########
if g.path and len(g.path): if g.path and len(g.path):
target = LPoint3f(g.path[0]) target = vector.Point3D(g.path[0])
target.x += 0.5 target.x += 0.5
target.z += 0.5 target.z += 0.5
@@ -142,9 +150,11 @@ def tick(global_state):
g.y_v += g.y_a * utils.TICK g.y_v += g.y_a * utils.TICK
block_below = g.chunks.get_block_at(floor(p.x), ceil(p.y-1), floor(p.z)) block_below = g.chunks.get_block_at(floor(p.x), ceil(p.y-1), floor(p.z))
block_above = g.chunks.get_block_at(floor(p.x), ceil(p.y+1), floor(p.z))
in_void = p.y < 0 in_void = p.y < 0
in_air = block_below in blocks.NON_SOLID_IDS or in_void in_air = block_below in blocks.NON_SOLID_IDS or in_void
in_water = block_below in blocks.WATER_IDS in_water = block_below in blocks.WATER_IDS
g.crawling = block_above not in blocks.NON_SOLID_IDS
if in_air: if in_air:
g.y_a = -36.0 g.y_a = -36.0
@@ -156,11 +166,11 @@ def tick(global_state):
g.y_a = 0 g.y_a = 0
if g.look_at: if g.look_at:
look_at = LPoint3f(g.look_at) look_at = vector.Point3D(g.look_at)
elif g.path and len(g.path) > YAW_LOOK_AHEAD: elif g.path and len(g.path) > YAW_LOOK_AHEAD:
look_at = LPoint3f(g.path[YAW_LOOK_AHEAD]) look_at = vector.Point3D(g.path[YAW_LOOK_AHEAD])
elif g.path and len(g.path): elif g.path and len(g.path):
look_at = LPoint3f(g.path[-1]) look_at = vector.Point3D(g.path[-1])
else: else:
look_at = None look_at = None
@@ -175,9 +185,10 @@ def tick(global_state):
target_pitch_d = target_pitch - g.pitch target_pitch_d = target_pitch - g.pitch
g.pitch += utils.cap(target_pitch_d, 10) g.pitch += utils.cap(target_pitch_d, 10)
# remove vertical component for yaw calculation # remove vertical component for yaw calculation
look_at_d.y = 0 look_at_d.y = 0
if look_at_d.length() > 0.6:
target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF) target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF)
target_yaw_d = target_yaw - g.yaw target_yaw_d = target_yaw - g.yaw
target_yaw_d = (target_yaw_d + 180) % 360 - 180 target_yaw_d = (target_yaw_d + 180) % 360 - 180
@@ -189,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):
@@ -204,6 +215,7 @@ def init(global_state):
g.y_a = 0 g.y_a = 0
g.yaw = 360 g.yaw = 360
g.pitch = 0 g.pitch = 0
g.crawling = False
g.breaking = None g.breaking = None
g.break_time = 0 g.break_time = 0
@@ -215,28 +227,68 @@ def init(global_state):
g.trades = [] g.trades = []
g.job = jobs.JobStates(g) g.job = job.JobStates(g)
g.chopped_tree = False g.chopped_tree = False
g.afk_timeout = 0 g.afk_timeout = 0
g.filling = False g.filling = False
g.minimum_cache_slots = 27
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)
@@ -247,7 +299,8 @@ def bot(global_state):
g.chat = ChatManager(g) g.chat = ChatManager(g)
g.game = game.Game(g) g.game = game.Game(g)
g.world = game.MCWorld(g) g.world = world.World(g)
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)

570
mosfet/game.py Normal file
View File

@@ -0,0 +1,570 @@
import re
import time
import random
from itertools import count
from munch import Munch
from minecraft.networking.packets import Packet, clientbound, serverbound
from minecraft.networking.types import BlockFace
from mosfet.protocol.packets import (
SetSlotPacket, PlayerDiggingPacket,
BlockBreakAnimationPacket, AcknowledgePlayerDiggingPacket,
HeldItemChangePacket, PickItemPacket, OpenWindowPacket,
ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket,
ClientWindowConfirmationPacket, EntityMetadataPacket,
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
SelectTradePacket, DisconnectPacket, UnloadChunkPacket,
)
from mosfet.protocol.types import Slot
from mosfet import utils
from mosfet import path
from mosfet import bot
from mosfet import vector
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class Game:
def __init__(self, global_state):
self.g = global_state
register = self.g.connection.register_packet_listener
register(self.handle_login_success, clientbound.login.LoginSuccessPacket)
register(self.handle_block_change, clientbound.play.BlockChangePacket)
register(self.handle_join_game, clientbound.play.JoinGamePacket)
register(self.handle_position_and_look, clientbound.play.PlayerPositionAndLookPacket)
register(self.handle_time_update, clientbound.play.TimeUpdatePacket)
register(self.handle_set_slot, SetSlotPacket)
register(self.handle_break_animation, BlockBreakAnimationPacket)
register(self.handle_break_ack, AcknowledgePlayerDiggingPacket)
register(self.handle_window, OpenWindowPacket)
register(self.handle_window_confirmation, ClientWindowConfirmationPacket)
register(self.handle_spawn_object, clientbound.play.SpawnObjectPacket)
register(self.handle_entity_metadata, EntityMetadataPacket)
register(self.handle_spawn_living, SpawnLivingEntityPacket)
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
register(self.handle_entity_look, clientbound.play.EntityLookPacket)
register(self.handle_destroy_entities, DestroyEntitiesPacket)
register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket)
register(self.handle_respawn, clientbound.play.RespawnPacket)
register(self.handle_player_list, clientbound.play.PlayerListItemPacket)
register(self.handle_entity_teleport, EntityTeleport)
register(self.handle_update_health, clientbound.play.UpdateHealthPacket)
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
register(self.handle_trade_list, TradeListPacket)
register(self.handle_disconnect, DisconnectPacket)
register(self.handle_unload_chunk, UnloadChunkPacket)
#register(self.handle_packet, Packet, early=True)
def handle_login_success(self, packet):
print(packet)
self.g.name = packet.Username
def handle_join_game(self, packet):
print('Received join game packet')
print('Connected.')
self.g.info = packet
self.g.eid = packet.entity_id
self.g.dimension = packet.world_name.replace('minecraft:', '')
def handle_block_change(self, packet):
if packet.block_state_id == blocks.SOUL_TORCH:
try:
self.g.goal = vector.Point3D((packet.location[0], packet.location[1], packet.location[2]))
print('new waypoint:', self.g.goal)
start = time.time()
solution = path.Pathfinder(self.g).astar(utils.pint(self.g.pos), utils.pint(self.g.goal))
if solution:
solution = list(solution)
self.g.path = solution
if self.g.job:
self.g.job.stop()
print(len(solution))
print(solution)
print(round(time.time() - start, 3), 'seconds')
else:
print('No path found')
#say(connection, 'No path found')
#g.y_v = 10.0
#g.y_a = -36.0
except BaseException as e:
import traceback
print(traceback.format_exc())
#print(packet)
def handle_position_and_look(self, packet):
print(packet)
p = vector.Point3D((packet.x, packet.y, packet.z))
self.g.pos = p
confirm_packet = serverbound.play.TeleportConfirmPacket()
confirm_packet.teleport_id = packet.teleport_id
self.g.connection.write_packet(confirm_packet)
self.g.correction_count += 1
if self.g.get('path', None) and self.g.correction_count > 5:
self.g.correction_count = 0
dest = self.g.path[-1]
w = self.g.world
p = utils.pint(self.g.pos)
new_path = w.path_to_place(p, dest)
if new_path:
self.g.path = new_path
def handle_time_update(self, packet):
self.g.time = packet.time_of_day % 24000
def handle_set_slot(self, packet):
g = self.g
print(packet)
if packet.window_id == 0:
g.inv[packet.slot] = packet.slot_data
elif g.window:
g.window.contents[packet.slot] = packet.slot_data
if g.item_lock and packet.window_id >= 0 and not packet.slot_data.present:
print('Unlocking item lock')
g.item_lock = False
def break_block(self, location):
p = utils.pint(self.g.pos)
#if utils.phyp(p, location) > blocks.BREAK_DISTANCE + 1:
# return False
bid = self.g.chunks.get_block_at(*location)
if bid == 0:
return False
packet = PlayerDiggingPacket()
packet.status = 0
packet.location = location
packet.face = 1
self.g.connection.write_packet(packet)
self.g.breaking = location
self.g.break_time = time.time() + utils.break_time(bid, self.g.holding)
return True
def break_finish(self):
packet = PlayerDiggingPacket()
packet.status = 2
packet.location = self.g.breaking
packet.face = 1
self.g.connection.write_packet(packet)
#self.g.chunks.set_block_at(*self.g.breaking, 0)
if self.g.chunks.get_block_at(*self.g.breaking) == 0:
self.g.breaking = None
def handle_break_animation(self, packet):
return
print(packet)
def handle_break_ack(self, packet):
#print(packet)
return
def respawn(self):
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
self.g.connection.write_packet(packet)
def animate(self):
packet = serverbound.play.AnimationPacket()
packet.hand = packet.HAND_MAIN
self.g.connection.write_packet(packet)
def place_block(self, location, face):
packet = serverbound.play.PlayerBlockPlacementPacket()
packet.hand = 0
packet.location = location
packet.face = face
packet.x = 0.5
packet.y = 0.5
packet.z = 0.5
packet.inside_block = False
self.g.connection.write_packet(packet)
def pick(self, slot):
packet = PickItemPacket()
packet.slot_to_use = slot
self.g.connection.write_packet(packet)
def hold(self, slot):
packet = HeldItemChangePacket()
packet.slot = slot
self.g.connection.write_packet(packet)
def choose_slot(self, slot):
if slot >= 36:
slot -= 36
self.hold(slot)
else:
self.pick(slot)
def count_items(self, items):
# count how many items are in inv
count = 0
for slot, item in self.g.inv.items():
if item.item_id in items:
count += item.item_count
return count
def count_inventory_slots(self):
# count how many inventory slots are filled
# excludes armour, crafting slots, off-hand
count = 0
for slot, item in self.g.inv.items():
if item.present and slot >= 9 and slot <= 45:
count += 1
return count
def count_window_slots(self):
# count how many window slots are filled
# excludes player inventory
w = self.g.window
w_info = mcdata.WINDOWS[w.data.window_type]
w_container_slots = w_info.container
count = 0
for slot, item in w.contents.items():
if item.present and slot in w_container_slots:
count += 1
return count
def get_window_slot(self, item_id):
# get the first slot that matches item of a window
window_items = list(self.g.window.contents.items())
for slot, item in window_items:
if not item.present: continue
if item.item_id == item_id:
return slot, item
else: #for
return False, False
def select_item(self, items):
# select the first match from items of inv
# uses smallest stack of that match
# and optionally the most damaged item
inv_items = list(self.g.inv.items())
inv_items.sort(key=lambda x: (x[1].nbt or {}).get('Damage', 0), reverse=True)
inv_items.sort(key=lambda x: x[1].item_count or 0)
for slot, item in inv_items:
if item.item_id in items:
self.g.game.choose_slot(slot)
self.g.holding = item.item_id
return True
else: #for
return False
def select_random_item(self, items):
# select a random match from items of inv
# this is random per item type
# example: 5 stacks wood, 1 stack glass
# -> still 50/50 chance between them
matches = set()
for slot, item in self.g.inv.items():
if item.item_id in items:
matches.add(item.item_id)
if matches:
return self.select_item([random.choice(list(matches))])
else:
return False
def select_next_item(self):
# select the next item slot that has an item
for slot, item in self.g.inv.items():
if slot < 9: continue # skip armour slots
if item.present:
print('slot:', slot, 'item:', item)
self.g.game.choose_slot(slot)
self.g.holding = item.item_id
return True
else: # for
return False
def drop_stack(self):
packet = PlayerDiggingPacket()
packet.status = 3
packet.location = utils.pint(self.g.pos)
packet.face = 1
self.g.connection.write_packet(packet)
def open_container(self, location):
bid = self.g.chunks.get_block_at(*location)
# TODO: check if block is a chest??
self.place_block(location, BlockFace.TOP)
def handle_window(self, packet):
print(packet)
self.g.window = Munch(data=packet, contents=dict(), count=0)
def click_window(self, slot, button, mode, item):
w = self.g.window
packet = ClickWindowPacket()
packet.window_id = w.data.window_id
packet.slot = slot
packet.button = button
packet.action_number = w.count
packet.mode = mode
packet.clicked_item = item
self.g.connection.write_packet(packet)
print('<--', packet)
w.count += 1
def close_window(self):
if self.g.window:
packet = CloseWindowPacket()
packet.window_id = self.g.window.data.window_id
self.g.connection.write_packet(packet)
self.g.window = None
def handle_window_confirmation(self, packet):
print(packet)
packet2 = ServerWindowConfirmationPacket()
packet2.window_id = packet.window_id
packet2.action_number = packet.action_number
packet2.accepted = packet.accepted
self.g.connection.write_packet(packet2)
def handle_spawn_player(self, packet):
print(packet)
self.g.players[packet.entity_id] = Munch(
entity_id=packet.entity_id,
player_uuid=packet.player_UUID,
x=packet.x,
y=packet.y,
z=packet.z,
yaw=packet.yaw,
pitch=packet.pitch,
)
def handle_spawn_object(self, packet):
#return
if packet.type_id != 37: return
#print(packet)
self.g.objects[packet.entity_id] = Munch(
entity_id=packet.entity_id,
x=packet.x,
y=packet.y,
z=packet.z,
velocity_x=packet.velocity_x,
velocity_y=packet.velocity_y,
velocity_z=packet.velocity_z,
)
def check_gapple(self, packet):
current_gapple_chest = self.g.job.find_gapple_states.current_chest
if current_gapple_chest:
for entry in packet.metadata:
if entry.type != 6:
continue
if entry.value.item_id in items.GAPPLE_ID:
self.g.chat.send('gapple found: ' + str(current_gapple_chest)[1:-1])
print('gapple found:', str(current_gapple_chest)[1:-1])
def handle_entity_metadata(self, packet):
if not packet.metadata:
return
if self.g.job and self.g.job.state == self.g.job.find_gapple_states:
self.check_gapple(packet)
obj = self.g.objects.get(packet.entity_id, None)
if obj:
for entry in packet.metadata:
if entry.type != 6:
continue
obj.item_id = entry.value.item_id
obj.item_count = entry.value.item_count
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
for entry in packet.metadata:
if mob.type == mobs.VILLAGER:
if entry.index == 17:
mob.profession = entry.value[1]
player = self.g.players.get(packet.entity_id, None)
if player:
return
def handle_spawn_living(self, packet):
self.g.mobs[packet.entity_id] = Munch(
entity_id=packet.entity_id,
entity_uuid=packet.entity_uuid,
type=packet.type,
x=packet.x,
y=packet.y,
z=packet.z,
)
def handle_entity_position(self, packet):
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
mob.x += packet.delta_x / 4096.0
mob.y += packet.delta_y / 4096.0
mob.z += packet.delta_z / 4096.0
player = self.g.players.get(packet.entity_id, None)
if player:
player.x += packet.delta_x / 4096.0
player.y += packet.delta_y / 4096.0
player.z += packet.delta_z / 4096.0
#if player.player_uuid == '0c123cfa-1697-4427-9413-4b645dee7ec0': print(packet)
def handle_entity_position_rotation(self, packet):
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
mob.x += packet.delta_x / 4096.0
mob.y += packet.delta_y / 4096.0
mob.z += packet.delta_z / 4096.0
player = self.g.players.get(packet.entity_id, None)
if player:
player.x += packet.delta_x / 4096.0
player.y += packet.delta_y / 4096.0
player.z += packet.delta_z / 4096.0
player.yaw = packet.yaw
player.pitch = packet.pitch
def handle_entity_look(self, packet):
player = self.g.players.get(packet.entity_id, None)
if player:
player.yaw = packet.yaw
player.pitch = packet.pitch
def handle_entity_teleport(self, packet):
mob = self.g.mobs.get(packet.entity_id, None)
if mob:
mob.x = packet.x
mob.y = packet.y
mob.z = packet.z
player = self.g.players.get(packet.entity_id, None)
if player:
player.x = packet.x
player.y = packet.y
player.z = packet.z
#if player.player_uuid == '0c123cfa-1697-4427-9413-4b645dee7ec0': print(packet)
def handle_entity_velocity(self, packet):
obj = self.g.objects.get(packet.entity_id, None)
if obj:
print(packet)
#obj.velocity_x = packet.velocity_x
#obj.velocity_y = packet.velocity_y
#obj.velocity_z = packet.velocity_z
def handle_destroy_entities(self, packet):
for eid in packet.entity_ids:
if eid in self.g.objects:
del self.g.objects[eid]
if eid in self.g.mobs:
del self.g.mobs[eid]
if eid in self.g.players:
del self.g.players[eid]
def leave_bed(self):
packet = EntityActionPacket()
packet.entity_id = self.g.eid
packet.action_id = 2
packet.jump_boost = 0
self.g.connection.write_packet(packet)
def handle_respawn(self, packet):
print(packet)
self.g.dimension = packet.world_name.replace('minecraft:', '')
self.g.chunks.unload_all_chunks()
def handle_player_list(self, packet):
for action in packet.actions:
if isinstance(action, packet.AddPlayerAction):
self.g.player_names[action.uuid] = action.name
self.g.player_names[action.name] = action.uuid
self.g.player_names[action.name.lower()] = action.uuid # porque no los dos?
def handle_update_health(self, packet):
print(packet)
self.g.health = packet.health
self.g.food = packet.food
if packet.health == 0:
print('Died, stopping')
print('Use 1respawn to respawn the bot')
self.close_window()
bot.init(self.g)
def use_item(self, hand):
packet = serverbound.play.UseItemPacket()
packet.hand = hand
self.g.connection.write_packet(packet)
def interact(self, eid):
packet = InteractEntityPacket()
packet.entity_id = eid
packet.type = 0
packet.hand = 0
packet.sneaking = False
self.g.connection.write_packet(packet)
def handle_trade_list(self, packet):
print(packet)
self.g.trades = packet.trades
def select_trade(self, num):
packet = SelectTradePacket()
packet.selected_slot = num
self.g.connection.write_packet(packet)
def handle_disconnect(self, packet):
print(packet)
print('Client disconnected!')
import os
os._exit(1)
def handle_unload_chunk(self, packet):
self.g.chunks.unload_chunk(packet.chunk_x, packet.chunk_z)
def tick(self):
if self.g.breaking:
self.animate()
if time.time() >= self.g.break_time: #- 2*utils.TICK:
self.break_finish()
if self.g.dumping and not self.g.item_lock:
if self.select_item([self.g.dumping]):
self.drop_stack()
self.g.item_lock = True
else:
self.g.dumping = None
if self.g.draining and not self.g.item_lock:
if self.select_next_item():
self.drop_stack()
self.g.item_lock = True
else:
self.g.draining = False
if not self.g.path:
self.g.correction_count = 0

View File

@@ -1,8 +1,7 @@
import json import json
import importlib import importlib
import mcdata from mosfet.info import mcdata
importlib.reload(mcdata)
MCD_BLOCKS = {} MCD_BLOCKS = {}
for d in mcdata.mcd.blocks.values(): for d in mcdata.mcd.blocks.values():
@@ -16,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
@@ -28,10 +32,6 @@ EMERALD_BLOCK = 5407
TEST_BLOCK = (616, 78, 496) TEST_BLOCK = (616, 78, 496)
WATER = [
'water',
]
AVOID = [ AVOID = [
'lava', 'lava',
'water', 'water',
@@ -237,88 +237,62 @@ SAPLINGS = [
'dark_oak_sapling', 'dark_oak_sapling',
] ]
BEDS = [
'white_bed',
'orange_bed',
'magenta_bed',
'light_blue_bed',
'yellow_bed',
'lime_bed',
'pink_bed',
'gray_bed',
'light_gray_bed',
'cyan_bed',
'purple_bed',
'blue_bed',
'brown_bed',
'green_bed',
'red_bed',
'black_bed',
]
INDEXED = [ INDEXED = [
'chest', 'chest',
'trapped_chest', 'trapped_chest',
'emerald_block', 'emerald_block',
'barrel', 'barrel',
] ] + BEDS
def get_set(ids):
result = set()
for block_name in ids:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
result.add(state['id'])
return result
NON_SOLID_IDS = set([SINGLE_SNOW]) NON_SOLID_IDS = get_set(NON_SOLID)
for block_name in NON_SOLID: NON_SOLID_IDS.add(SINGLE_SNOW)
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
NON_SOLID_IDS.add(state['id'])
AVOID_IDS = set() AVOID_IDS = get_set(AVOID)
for block_name in AVOID: WATER_IDS = get_set(['water'])
for state in JSON_BLOCKS['minecraft:' + block_name]['states']: LOG_IDS = get_set(LOGS)
AVOID_IDS.add(state['id']) LEAF_IDS = get_set(LEAVES)
CHEST_IDS = get_set(['chest'])
WATER_IDS = set() BARREL_IDS = get_set(['barrel'])
for block_name in WATER: TRAPPED_CHEST_IDS = get_set(['trapped_chest'])
for state in JSON_BLOCKS['minecraft:' + block_name]['states']: NETHERWART_IDS = get_set(['nether_wart'])
WATER_IDS.add(state['id']) WHEAT_IDS = get_set(['wheat'])
POTATO_IDS = get_set(['potatoes'])
LOG_IDS = set() CARROT_IDS = get_set(['carrots'])
for block_name in LOGS: BEETROOT_IDS = get_set(['beetroots'])
for state in JSON_BLOCKS['minecraft:' + block_name]['states']: SAPLING_IDS = get_set(SAPLINGS)
LOG_IDS.add(state['id']) BED_IDS = get_set(BEDS)
LEAF_IDS = set()
for block_name in LEAVES:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
LEAF_IDS.add(state['id'])
CHEST_IDS = set()
for block_name in ['chest']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
CHEST_IDS.add(state['id'])
BARREL_IDS = set()
for block_name in ['barrel']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
BARREL_IDS.add(state['id'])
TRAPPED_CHEST_IDS = set()
for block_name in ['trapped_chest']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
TRAPPED_CHEST_IDS.add(state['id'])
NETHERWART_IDS = set()
for block_name in ['nether_wart']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
NETHERWART_IDS.add(state['id'])
WHEAT_IDS = set()
for block_name in ['wheat']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
WHEAT_IDS.add(state['id'])
POTATO_IDS = set()
for block_name in ['potatoes']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
POTATO_IDS.add(state['id'])
CARROT_IDS = set()
for block_name in ['carrots']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
CARROT_IDS.add(state['id'])
BEETROOT_IDS = set()
for block_name in ['beetroots']:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
BEETROOT_IDS.add(state['id'])
INDEXED_IDS = set() INDEXED_IDS = set()
for block_name in INDEXED: for block_name in INDEXED:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']: for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
INDEXED_IDS.add(state['id']) INDEXED_IDS.add(state['id'])
SAPLING_IDS = set()
for block_name in SAPLINGS:
for state in JSON_BLOCKS['minecraft:' + block_name]['states']:
SAPLING_IDS.add(state['id'])
MATURE_WHEAT_ID = max(WHEAT_IDS) MATURE_WHEAT_ID = max(WHEAT_IDS)
MATURE_POTATO_ID = max(POTATO_IDS) MATURE_POTATO_ID = max(POTATO_IDS)

View File

@@ -67,37 +67,31 @@ LOGS = [
'dark_oak_log', 'dark_oak_log',
] ]
BED_IDS = set()
for item_name in BEDS:
BED_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id'])
SHOVEL_IDS = set() def get_set(ids):
for item_name in SHOVELS: result = set()
SHOVEL_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id']) for item_name in ids:
result.add(ITEMS['minecraft:'+item_name]['protocol_id'])
return result
AXE_IDS = set() BED_IDS = get_set(BEDS)
for item_name in AXES: SHOVEL_IDS = get_set(SHOVELS)
AXE_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id']) AXE_IDS = get_set(AXES)
FOOD_IDS = get_set(FOOD)
FOOD_IDS = set() SAPLING_IDS = get_set(SAPLINGS)
for item_name in FOOD: LOG_IDS = get_set(LOGS)
FOOD_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id'])
SAPLING_IDS = set()
for item_name in SAPLINGS:
SAPLING_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id'])
LOG_IDS = set()
for item_name in LOGS:
LOG_IDS.add(ITEMS['minecraft:'+item_name]['protocol_id'])
ITEM_NAMES = {} ITEM_NAMES = {}
for item_name, item in ITEMS.items(): for item_name, item in ITEMS.items():
ITEM_NAMES[ITEMS[item_name]['protocol_id']] = item_name.replace('minecraft:', '') ITEM_NAMES[ITEMS[item_name]['protocol_id']] = item_name.replace('minecraft:', '')
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')
@@ -105,13 +99,30 @@ NETHERWART_ID = get_id('nether_wart')
CARROT_ID = get_id('carrot') CARROT_ID = get_id('carrot')
POTATO_ID = get_id('potato') POTATO_ID = get_id('potato')
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')
IRON_INGOT_ID = get_id('iron_ingot') IRON_INGOT_ID = get_id('iron_ingot')
NEEDED_ITEMS = BED_IDS | SHOVEL_IDS | AXE_IDS | FOOD_IDS | set([CHEST_ID, PUMPKIN_ID, BERRIES_ID, IRON_INGOT_ID])
WANTED_ITEMS = SAPLING_IDS | set([NETHERWART_ID, CARROT_ID, POTATO_ID, WHEAT_SEEDS_ID, BEETROOT_SEEDS_ID]) INIT_NEEDED_ITEMS = BED_IDS | FOOD_IDS
INIT_NEEDED_ITEMS.add(CHEST_ID)
NEEDED_ITEMS = INIT_NEEDED_ITEMS
INIT_WANTED_ITEMS = set()
WANTED_ITEMS = INIT_WANTED_ITEMS
def set_needed(items):
global NEEDED_ITEMS
NEEDED_ITEMS = INIT_NEEDED_ITEMS | items
def set_wanted(items):
global WANTED_ITEMS
WANTED_ITEMS = INIT_WANTED_ITEMS | items

View File

@@ -1,8 +1,33 @@
from mosfet.info import items
import json 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',

264
mosfet/job.py Normal file
View File

@@ -0,0 +1,264 @@
import re
import time
import importlib
import random
from itertools import count
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
from mosfet.jobs import (
cache_items,
check_threats,
clear_leaves,
eat_food,
fill_blocks,
find_gapple,
gather_crop,
gather_sand,
gather_wart,
gather_wood,
grab_sand,
grab_sapling,
grab_supplies,
plant_tree,
sell_to_villager,
sleep_with_bed,
)
for module in [
cache_items,
check_threats,
clear_leaves,
eat_food,
fill_blocks,
find_gapple,
gather_crop,
gather_sand,
gather_wart,
gather_wood,
grab_sand,
grab_sapling,
grab_supplies,
plant_tree,
sell_to_villager,
sleep_with_bed,
]:
importlib.reload(module)
class JobStates:
def idle(self):
return []
def init_machines(self):
self.gather_wood_states = gather_wood.GatherWoodStates(self.g)
self.gather_sand_states = gather_sand.GatherSandStates(self.g)
self.sleep_with_bed_states = sleep_with_bed.SleepWithBedStates(self.g)
self.cache_items_states = cache_items.CacheItemsStates(self.g)
self.grab_supplies_states = grab_supplies.GrabSuppliesStates(self.g)
self.find_gapple_states = find_gapple.FindGappleStates(self.g)
self.plant_tree_states = plant_tree.PlantTreeStates(self.g)
self.clear_leaves_states = clear_leaves.ClearLeavesStates(self.g)
self.grab_sapling_states = grab_sapling.GrabSaplingStates(self.g)
self.grab_sand_states = grab_sand.GrabSandStates(self.g)
self.fill_blocks_states = fill_blocks.FillBlocksStates(self.g)
self.check_threats_states = check_threats.CheckThreatsStates(self.g)
self.gather_wart_states = gather_wart.GatherWartStates(self.g)
self.gather_crop_states = gather_crop.GatherCropStates(self.g)
self.eat_food_states = eat_food.EatFoodStates(self.g)
self.sell_to_villager_states = sell_to_villager.SellToVillagerStates(self.g)
def run_machines(self, machines):
for m in machines:
if m.state == m.idle:
continue
if m.state != m.done:
m.run()
return
# if we went through them all
for m in machines:
m.state = m.init
def gather_sand(self):
machines = [
self.gather_sand_states,
self.grab_sand_states,
self.cache_items_states,
self.sleep_with_bed_states,
self.eat_food_states,
]
return machines
def farm_sand(self):
machines = [
self.grab_supplies_states,
self.check_threats_states,
self.gather_sand_states,
self.grab_sand_states,
self.cache_items_states,
self.sleep_with_bed_states,
self.eat_food_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
self.grab_supplies_states.supplies = {
tuple(items.SHOVEL_IDS): (1, 9),
}
items.set_needed(items.SHOVEL_IDS)
return machines
def cache_items(self):
machines = [
self.cache_items_states,
]
return machines
def find_gapple(self):
machines = [
self.find_gapple_states,
]
return machines
def gather_wood(self):
machines = [
self.gather_wood_states,
self.sleep_with_bed_states,
self.eat_food_states,
self.cache_items_states,
]
return machines
def farm_wood(self):
machines = [
self.grab_supplies_states,
self.gather_wood_states,
self.clear_leaves_states,
self.plant_tree_states,
self.grab_sapling_states,
self.sleep_with_bed_states,
self.eat_food_states,
self.cache_items_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
self.grab_supplies_states.supplies = {
tuple(items.AXE_IDS): (1, 9),
}
items.set_needed(items.AXE_IDS)
items.set_wanted(items.SAPLING_IDS)
return machines
def farm_wart(self):
machines = [
self.gather_wart_states,
self.sleep_with_bed_states,
self.eat_food_states,
self.cache_items_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
items.set_wanted(set([items.NETHERWART_ID]))
return machines
def farm_crop(self):
machines = [
self.gather_crop_states,
self.sleep_with_bed_states,
self.eat_food_states,
self.cache_items_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
items.set_wanted(set([
items.CARROT_ID,
items.POTATO_ID,
items.WHEAT_SEEDS_ID,
items.BEETROOT_SEEDS_ID,
]))
return machines
def fill_blocks(self):
machines = [
self.grab_supplies_states,
self.fill_blocks_states,
self.sleep_with_bed_states,
self.eat_food_states,
]
self.sleep_with_bed_states.silent = True
f = self.g.filling
if f:
name = blocks.BLOCKS[f.block]
item = items.ITEMS['minecraft:'+name]['protocol_id']
self.grab_supplies_states.supplies = {
tuple([item]): (1, 0),
}
return machines
def loiter(self):
machines = [
self.check_threats_states,
self.sleep_with_bed_states,
self.eat_food_states,
]
self.sleep_with_bed_states.silent = True
return machines
def trade(self):
machines = [
self.grab_supplies_states,
self.sell_to_villager_states,
self.sleep_with_bed_states,
self.eat_food_states,
self.cache_items_states,
]
self.sleep_with_bed_states.silent = True
self.cache_items_states.silent = True
self.grab_supplies_states.supplies = {
tuple([items.PUMPKIN_ID]): (64, 3),
tuple([items.BERRIES_ID]): (64, 3),
tuple([items.IRON_INGOT_ID]): (64, 3),
tuple([items.WHEAT_ID]): (64, 3),
tuple([items.POTATO_ID]): (64, 3),
tuple([items.CARROT_ID]): (64, 3),
tuple([items.BEETROOT_ID]): (64, 3),
}
items.set_needed(set([
items.PUMPKIN_ID,
items.BERRIES_ID,
items.IRON_INGOT_ID,
items.WHEAT_ID,
items.POTATO_ID,
items.CARROT_ID,
items.BEETROOT_ID,
]))
return machines
def stop(self):
self.init_machines()
self.state = self.idle
def __init__(self, global_state):
self.g = global_state
self.init_machines()
self.state = self.idle
def tick(self):
self.run_machines(self.state())

0
mosfet/jobs/__init__.py Normal file
View File

228
mosfet/jobs/cache_items.py Normal file
View File

@@ -0,0 +1,228 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class CacheItemsStates:
def idle(self):
return None
def init(self):
self.skip_slots = []
self.skip_items = []
num_stacks = self.g.game.count_inventory_slots()
print('Inventory amount:', num_stacks)
if num_stacks >= self.g.minimum_cache_slots:
self.state = self.find_trapped_chests
else:
print('Aborting caching, not full')
self.state = self.cleanup
def find_trapped_chests(self):
print('Finding trapped chests...')
w = self.g.world
p = utils.pint(self.g.pos)
self.trapped_chests = w.find_blocks_indexed(p, blocks.TRAPPED_CHEST_IDS)
print('Found:', self.trapped_chests)
self.state = self.choose_trapped_chest
def choose_trapped_chest(self):
print('Choosing a trapped chest...')
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
if not len(self.trapped_chests):
print('No trapped chests')
self.state = self.select_chest
return
chest = self.trapped_chests[0]
navpath = w.path_to_place_faked(p, chest)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.opening = self.g.path[-1]
self.area = chest
self.state = self.going_to_trapped_chest
return
else:
self.trapped_chests.pop(0)
time.sleep(0.1)
def going_to_trapped_chest(self):
if utils.pint(self.g.pos) == self.opening:
self.g.look_at = self.area
self.state = self.open_chest
def select_chest(self):
if self.g.game.select_item([items.CHEST_ID]):
self.state = self.find_cache_spot
else:
print('No chest, aborting')
self.state = self.cleanup
def find_cache_spot(self):
print('Finding a chest spot...')
w = self.g.world
p = utils.pint(self.g.pos)
for area in w.find_cache_areas(p, 100):
print('Found area:', area)
if area not in self.bad_areas:
break
else: # for
print('Unable to find area')
self.state = self.cleanup
return
self.area = area
openings = w.find_cache_openings(self.area)
for o in openings:
navpath = w.path_to_place(p, o)
self.opening = o
if navpath: break
else: # for
print('Unable to get to cache area', self.area)
self.bad_areas.append(self.area)
self.state = self.cleanup
return
self.g.path = navpath
self.state = self.going_to_area
def going_to_area(self):
if utils.pint(self.g.pos) == self.opening:
self.g.look_at = self.area
self.state = self.place_chest
def place_chest(self):
self.g.game.place_block(self.area, BlockFace.TOP)
self.state = self.open_chest
def open_chest(self):
print('Opening chest')
self.g.game.open_container(self.area)
self.wait_time = 1
self.state = self.wait
def wait(self):
# wait for server to send us chest contents
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.move_items
def move_items(self):
if self.g.item_lock: return
w = self.g.window
if not w:
print('Didnt get a window, aborting')
self.state = self.cleanup
return
if w.data.window_type != mcdata.SINGLE_CHEST:
print('Got wrong window, aborting')
self.state = self.cleanup
return
w_info = mcdata.WINDOWS[w.data.window_type]
w_inventory_slots = w_info.inventory
w_container_slots = w_info.container
used_slots = self.g.game.count_window_slots()
print('used:', used_slots, 'total:', len(w_container_slots))
if used_slots >= len(w_container_slots):
print('Container is too full, aborting')
self.g.game.close_window()
self.g.look_at = None
self.state = self.cleanup
return
slot_list = []
for slot_num in w_inventory_slots:
if slot_num not in w.contents:
continue
slot = w.contents[slot_num]
if not slot.present:
continue
if slot.item_id in items.NEEDED_ITEMS:
continue
if slot_num in self.skip_slots:
continue
slot_list.append((slot_num, slot))
slot_list.sort(key=lambda x: x[1].item_count, reverse=True)
for slot_num, slot in slot_list:
if slot.item_id in items.WANTED_ITEMS and slot.item_id not in self.skip_items:
print('skipping wanted item', slot)
self.skip_slots.append(slot_num)
self.skip_items.append(slot.item_id)
continue
print('moving', slot)
self.g.item_lock = True
self.g.game.click_window(slot_num, 0, 1, slot)
return
print('nothing left to move')
self.state = self.close_chest
def close_chest(self):
print('closing chest')
self.g.game.close_window()
if not self.silent:
self.g.chat.send('cache at ' + str(self.area)[1:-1])
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.silent = False
self.skip_slots = []
self.skip_items = []
self.area = None
self.opening = None
self.trapped_chests = []
self.bad_areas = []
self.wait_time = 0
def run(self):
self.state()

View File

@@ -0,0 +1,109 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class CheckThreatsStates:
def idle(self):
return None
def init(self):
self.state = self.find_threats
print('Checking for threats')
def find_threats(self):
w = self.g.world
p = utils.pint(self.g.pos)
threats = w.find_threats(p, 40)
if threats:
print('Found', len(threats), 'threats, fleeing:')
print(threats)
self.state = self.find_safety
else:
print('Aborting, no threats')
self.state = self.cleanup
def find_safety(self):
w = self.g.world
p = utils.pint(self.g.pos)
safety = w.find_blocks_indexed(p, [blocks.EMERALD_BLOCK])
if not safety:
print('No emerald blocks found, aborting')
self.state = self.cleanup
return
safety.sort(key=lambda s: utils.phyp(p, s))
print('Found emerald blocks:', safety)
for s in safety:
s = utils.padd(s, path.BLOCK_ABOVE)
navpath = w.path_to_place(p, s)
if navpath:
self.g.path = navpath
self.state = self.going_to_safety
self.safety = s
print('Going to safety', self.safety)
return
else:
print('Cant get to safety', self.safety)
time.sleep(0.1)
print('Cant get to safety, aborting')
self.state = self.cleanup
def going_to_safety(self):
if utils.pint(self.g.pos) == self.safety:
print('At safety spot, waiting to be moved')
self.state = self.wait_for_move
def wait_for_move(self):
# wait for the server to move the bot when it's safe
# ie. a piston + daylight sensor
if utils.pint(self.g.pos) != self.safety:
print('Moved, resuming job')
self.state = self.wait
self.wait_time = 3
def wait(self):
# wait to land, etc
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.safety = None
self.wait_time = 0
def run(self):
self.state()

View File

@@ -0,0 +1,91 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class ClearLeavesStates:
def idle(self):
return None
def init(self):
if not self.g.chopped_tree:
print('Didnt chop tree, clearing leaves')
self.state = self.cleanup
return
sapling_type = self.g.chopped_tree + '_sapling'
sapling_item = items.get_id(sapling_type)
num_saplings = self.g.game.count_items([sapling_item])
print('Have', num_saplings, sapling_type, 'in inventory')
if num_saplings > 8:
print('Have enough saplings, aborting clearing leaves')
self.state = self.cleanup
return
self.state = self.select_log
print('Clearing leaves...')
def select_log(self):
# select a log to avoid using tools
self.g.game.select_item(items.LOG_IDS)
self.state = self.find_leaves
def find_leaves(self):
w = self.g.world
p = utils.pint(self.g.pos)
pos = utils.padd(p, path.BLOCK_ABOVE)
for l in w.find_leaves(pos, blocks.BREAK_DISTANCE):
self.leaves.append(l)
self.state = self.break_leaves
def break_leaves(self):
if not self.g.breaking:
if self.leaves:
leaf = self.leaves.pop(0)
self.g.look_at = leaf
self.g.game.break_block(leaf)
print('Breaking leaf', leaf)
else:
self.wait_time = 1
self.state = self.wait
def wait(self):
# wait for the items to drop
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.leaves = []
self.wait_time = 0
def run(self):
self.state()

72
mosfet/jobs/eat_food.py Normal file
View File

@@ -0,0 +1,72 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class EatFoodStates:
def idle(self):
return None
def init(self):
if self.g.food < 12:
print('Hungry, eating')
self.state = self.select_food
return
if self.g.health < 20 and self.g.food < 18:
print('Low health, eating')
self.state = self.select_food
return
print('Don\'t need to eat, aborting')
self.state = self.cleanup
def select_food(self):
if self.g.game.select_item(items.FOOD_IDS):
self.state = self.eat_food
else:
print('No food, aborting')
self.state = self.cleanup
def eat_food(self):
self.g.game.use_item(0)
print('Eating food')
self.wait_time = 3
self.state = self.wait
def wait(self):
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.wait_time = 0
def run(self):
self.state()

215
mosfet/jobs/fill_blocks.py Normal file
View File

@@ -0,0 +1,215 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class FillBlocksStates:
def idle(self):
return None
def init(self):
f = self.g.filling
if not f:
self.state = self.cleanup
print('Aborting, nothing to fill')
return
if self.last_block:
self.state = self.select_item
else:
self.state = self.find_last_block
def find_last_block(self):
w = self.g.world
f = self.g.filling
print('Finding last block')
b1, b2 = utils.pboundingbox(f.coord1, f.coord2)
box = utils.psub(b2, b1)
xz_distance = utils.hypot(box[0]+1, box[2]+1)
y_start = f.coord1[1]
y_end = f.coord2[1]
for y in range(y_start, y_end+1):
for offset in utils.search_2d(xz_distance):
check = utils.padd(f.coord1, offset)
check = (check[0], y, check[2])
# ensure block is within fill area
if check[0] < b1[0] or check[0] > b2[0]:
continue
if check[2] < b1[2] or check[2] > b2[2]:
continue
if w.block_at(*check) == blocks.AIR:
self.state = self.select_item
return
self.last_block = check
else: # for
self.state = self.cleanup
print('Aborting, no air left')
return
def select_item(self):
f = self.g.filling
name = blocks.BLOCKS[f.block]
item = items.ITEMS['minecraft:'+name]['protocol_id']
if self.g.game.select_item([item]):
#self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.find_next_block
else:
print('No blocks, aborting')
self.state = self.cleanup
def find_next_block(self):
w = self.g.world
f = self.g.filling
print('Finding next block, last:', self.last_block)
b1, b2 = utils.pboundingbox(f.coord1, f.coord2)
box = utils.psub(b2, b1)
xz_distance = utils.hypot(box[0]+1, box[2]+1)
y_start = f.coord1[1]
y_end = f.coord2[1]
for y in range(y_start, y_end+1):
if y not in self.iterators:
self.iterators[y] = utils.search_2d(xz_distance)
for offset in self.iterators[y]:
check = utils.padd(f.coord1, offset)
check = (check[0], y, check[2])
# ensure block is within fill area
if check[0] < b1[0] or check[0] > b2[0]:
continue
if check[2] < b1[2] or check[2] > b2[2]:
continue
if w.block_at(*check) == blocks.AIR:
print('Found next block:', check)
self.next_block = check
self.state = self.check_block_distance
return
# if there's nothing left to fill
self.g.filling = None
self.state = self.cleanup
def check_block_distance(self):
w = self.g.world
p = utils.pint(self.g.pos)
head = utils.padd(p, path.BLOCK_ABOVE)
if utils.phyp(head, self.next_block) < 4:
self.state = self.fill_block
else:
self.state = self.nav_to_block
def nav_to_block(self):
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
tmp = c.get_block_at(*self.next_block)
c.set_block_at(*self.next_block, blocks.STONE)
pos = utils.padd(self.next_block, path.BLOCK_ABOVE)
navpath = w.path_to_place(p, pos)
c.set_block_at(*self.next_block, tmp)
if navpath:
self.g.path = navpath[:-1]
self.state = self.going_to_block
else:
print('Cant get to that block')
self.state = self.cleanup
time.sleep(0.1)
def going_to_block(self):
if not len(self.g.path):
self.state = self.fill_block
def fill_block(self):
print('Filling block', self.next_block)
self.g.game.place_block(self.next_block, BlockFace.TOP)
self.g.look_at = self.next_block
self.wait_time = 0.25
self.state = self.wait_for_block
def wait_for_block(self):
w = self.g.world
if w.block_at(*self.next_block) != blocks.AIR:
self.last_block = self.next_block
self.state = self.check_obstruction
elif self.wait_time > 0:
self.wait_time -= utils.TICK
else:
print('Block didnt appear')
self.state = self.check_obstruction
def check_obstruction(self):
p = utils.pint(self.g.pos)
f = self.g.filling
print('last', self.last_block)
print('p', p)
if self.last_block[1] >= p[1] and f.block not in blocks.NON_SOLID_IDS:
print('Obstructed, going to last block')
self.state = self.nav_to_last_block
else:
self.state = self.cleanup
def nav_to_last_block(self):
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
pos = utils.padd(self.last_block, path.BLOCK_ABOVE)
navpath = w.path_to_place(p, pos)
if navpath:
self.g.path = navpath
self.state = self.going_to_last_block
else:
print('Cant get to that block')
self.state = self.cleanup
def going_to_last_block(self):
if not len(self.g.path):
self.state = self.cleanup
def cleanup(self):
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.iterators = {}
self.wait_time = 0
self.last_block = None
self.next_block = None
def run(self):
self.state()

117
mosfet/jobs/find_gapple.py Normal file
View File

@@ -0,0 +1,117 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class FindGappleStates:
def idle(self):
return None
def init(self):
self.state = self.go_spectator
def go_spectator(self):
print('Going spectator...')
self.g.chat.send('/gamemode spectator')
self.state = self.tp_to_coord
def tp_to_coord(self):
step = utils.spiral(self.count)
step_scaled = utils.pmul(step, 192)
self.coord = utils.padd(self.origin, step_scaled)
self.coord = (self.coord[0], 50, self.coord[2])
print('count:', self.count, 'teleporting to:', self.coord)
self.g.chat.send('/tp {} {} {}'.format(*self.coord))
self.g.command_lock = True
self.state = self.wait_for_load
def wait_for_load(self):
if self.g.command_lock:
return
if self.g.chunks.check_loaded(self.g.pos, 169):
print('chunks have been loaded')
self.state = self.pick_chest
def pick_chest(self):
chest_list = []
for chest_id in blocks.CHEST_IDS:
chest_list.extend(self.g.chunks.index.get(chest_id, []))
for chest in chest_list:
if chest in self.checked_chests:
# slow but simple
continue
if utils.phyp_king(self.coord, chest) > 96:
# skip because we can't detect item drops
continue
self.current_chest = chest
self.checked_chests.append(self.current_chest)
self.state = self.break_chest
break
else: # for
print('exhausted chest list')
self.state = self.cleanup
def break_chest(self):
print('Breaking chest', self.current_chest)
self.g.command_lock = True
self.g.item_lock = True
self.g.chat.send('/setblock {} {} {} air destroy'.format(*self.current_chest))
self.wait_time = 0.5
self.state = self.wait_for_items
def wait_for_items(self):
# wait for command to execute
if self.g.command_lock:
return
# wait for items to drop
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
print('done waiting for items')
self.state = self.pick_chest
def cleanup(self):
self.count += 1
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.origin = utils.pint(self.g.pos)
self.count = 0
self.coord = None
self.current_chest = None
self.checked_chests = []
self.wait_time = 0
def run(self):
self.state()

131
mosfet/jobs/gather_crop.py Normal file
View File

@@ -0,0 +1,131 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherCropStates:
def idle(self):
return None
def init(self):
self.state = self.find_new_crop
def find_new_crop(self):
print('Finding new crop...')
w = self.g.world
p = utils.pint(self.g.pos)
mature_crops = [
blocks.MATURE_WHEAT_ID,
blocks.MATURE_POTATO_ID,
blocks.MATURE_CARROT_ID,
blocks.MATURE_BEETROOT_ID,
]
for crop in w.find_blocks_3d(p, mature_crops, 50, 20, True):
if crop not in self.bad_crops:
break
else: # for
print('No good crops left, aborting.')
self.state = self.cleanup
return
print('Found crop:', crop)
self.crop = crop
self.type_id = w.block_at(*crop)
self.state = self.nav_to_crop
def nav_to_crop(self):
w = self.g.world
p = utils.pint(self.g.pos)
navpath = w.path_to_place(p, self.crop)
if navpath:
print('Going to crop', self.crop)
self.g.path = navpath
self.g.look_at = utils.padd(self.crop, path.BLOCK_BELOW)
self.state = self.going_to_crop
else:
print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_crops.append(self.crop)
self.state = self.find_new_crop
def going_to_crop(self):
if utils.pint(self.g.pos) == self.crop:
print('At the crop')
self.state = self.break_crop
def break_crop(self):
self.g.game.break_block(self.crop)
self.state = self.select_seed
def select_seed(self):
p = utils.pint(self.g.pos)
crop_seeds = {
blocks.MATURE_WHEAT_ID: items.WHEAT_SEEDS_ID,
blocks.MATURE_POTATO_ID: items.POTATO_ID,
blocks.MATURE_CARROT_ID: items.CARROT_ID,
blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID,
}
self.target_seed = crop_seeds[self.type_id]
if self.g.game.select_item([self.target_seed]):
self.state = self.wait_select
else:
print('Havent picked up seed yet')
return
def wait_select(self):
if self.target_seed != self.g.holding:
return
else:
self.state = self.place_crop
def place_crop(self):
p = utils.pint(self.g.pos)
self.g.game.place_block(p, BlockFace.TOP)
print('Placed crop')
self.state = self.wait_place
def wait_place(self):
w = self.g.world
if w.block_at(*self.crop) == blocks.AIR:
return
else:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.crop = None
self.type_id = None
self.target_seed = None
self.bad_crops = []
def run(self):
self.state()

131
mosfet/jobs/gather_sand.py Normal file
View File

@@ -0,0 +1,131 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherSandStates:
def bair(self, p):
return self.g.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
def bsand(self, p):
return self.g.chunks.get_block_at(*p) == blocks.SAND
def idle(self):
return None
def init(self):
if not self.g.sand_origin or not self.g.chunks.check_loaded(self.g.sand_origin):
self.g.sand_origin = utils.pint(self.g.pos)
self.state = self.select_shovel
def select_shovel(self):
self.g.game.select_item(items.SHOVEL_IDS)
self.state = self.find_new_slice
def find_new_slice(self):
print('Finding new slice...')
w = self.g.world
origin = self.g.sand_origin
print('using origin', self.g.sand_origin)
start = time.time()
self.prev_layer, s = w.find_sand_slice(self.g.sand_origin, 200, 10, self.bad_slices, self.prev_layer)
print('Found slice:', s, 'in', time.time() - start, 'seconds')
if s:
self.slice = s
self.bad_slices.append(s)
self.state = self.find_new_sand
else:
print('No slices remaining.')
self.state = self.cleanup
def find_new_sand(self):
print('Finding new sand...')
w = self.g.world
p = utils.pint(self.g.pos)
head = utils.padd(p, path.BLOCK_ABOVE)
for sand in w.find_sand(self.slice, 2, p):
if sand not in self.bad_sand:
print('Found sand:', sand)
break
else: # for
print('No good sands left, aborting.')
self.state = self.cleanup
return
self.sand = sand
if utils.phyp(head, self.sand) < blocks.BREAK_DISTANCE:
self.state = self.dig_sand
else:
self.state = self.nav_to_sand
def nav_to_sand(self):
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
navpath = w.path_to_place_faked(p, self.sand)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.state = self.going_to_sand
else:
print('Cant get to that sand')
time.sleep(0.1)
self.bad_sand.append(self.sand)
self.state = self.find_new_sand
def going_to_sand(self):
if not len(self.g.path):
self.g.look_at = self.sand
self.state = self.dig_sand
def dig_sand(self):
if not self.g.breaking:
if self.bsand(self.sand):
self.g.look_at = self.sand
self.g.game.break_block(self.sand)
print('digging sand')
else:
self.state = self.find_new_sand
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.slice = None
self.bad_slices = []
self.prev_layer = 0
self.sand = None
self.bad_sand = []
self.wait_time = 0
def run(self):
self.state()

126
mosfet/jobs/gather_wart.py Normal file
View File

@@ -0,0 +1,126 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherWartStates:
def idle(self):
return None
def init(self):
self.state = self.find_new_wart
def find_new_wart(self):
print('Finding new wart...')
w = self.g.world
p = utils.pint(self.g.pos)
mature_wart = max(blocks.NETHERWART_IDS)
for wart in w.find_blocks_3d(p, [mature_wart], 50, 20, True):
if wart not in self.bad_warts:
break
else: # for
print('No good warts left, aborting.')
self.state = self.cleanup
return
print('Found wart:', wart)
self.wart = wart
self.state = self.nav_to_wart
def nav_to_wart(self):
w = self.g.world
p = utils.pint(self.g.pos)
navpath = w.path_to_place(p, self.wart)
if navpath:
print('Going to wart', self.wart)
self.g.path = navpath
self.g.look_at = utils.padd(self.wart, path.BLOCK_BELOW)
self.state = self.going_to_wart
else:
print('Cant get to it, blacklisting')
time.sleep(0.1)
self.bad_warts.append(wart)
self.state = self.find_new_wart
def going_to_wart(self):
if utils.pint(self.g.pos) == self.wart:
print('At the wart')
self.state = self.break_wart
def break_wart(self):
self.g.game.break_block(self.wart)
self.wait_time = 0.5
self.state = self.wait
def wait(self):
# wait for the item
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.select_wart
def select_wart(self):
p = utils.pint(self.g.pos)
if self.g.game.select_item([items.NETHERWART_ID]):
self.state = self.wait_select
self.wait_time = 0.5
else:
print('Aborting planting, no wart')
self.state = self.cleanup
def wait_select(self):
# wait a bit to select
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.place_wart
def place_wart(self):
p = utils.pint(self.g.pos)
self.g.game.place_block(p, BlockFace.TOP)
print('Placed wart')
self.state = self.wait_place
self.wait_time = 0.5
def wait_place(self):
# wait a bit for chunk data to update
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.wart = None
self.bad_warts = []
self.wait_time = 0
def run(self):
self.state()

191
mosfet/jobs/gather_wood.py Normal file
View File

@@ -0,0 +1,191 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GatherWoodStates:
def bair(self, p):
return self.g.chunks.get_block_at(*p) in blocks.NON_SOLID_IDS
def blog(self, p):
return self.g.chunks.get_block_at(*p) in blocks.LOG_IDS
def idle(self):
return None
def init(self):
self.g.chopped_tree = False
self.state = self.select_axe
def select_axe(self):
self.g.game.select_item(items.AXE_IDS)
self.state = self.find_new_tree
def find_new_tree(self):
print('Finding new tree...')
w = self.g.world
p = utils.pint(self.g.pos)
for tree in w.find_trees(p, 100):
print('Found tree:', tree)
if tree not in self.bad_trees:
break
else: # for
print('No good trees left, aborting.')
self.state = self.cleanup
return
self.tree = tree
self.type = blocks.BLOCKS[w.block_at(*tree)].replace('_log', '')
print('Type:', self.type)
self.state = self.find_openings
def find_openings(self):
w = self.g.world
self.openings = w.find_tree_openings(self.tree)
self.state = self.choose_opening
def choose_opening(self):
w = self.g.world
p = utils.pint(self.g.pos)
print('openings:', self.openings)
if not len(self.openings):
print('Unable to get to tree', self.tree)
if self.tree not in self.good_trees:
self.bad_trees.append(self.tree)
print('Added to bad trees list')
self.state = self.cleanup
return
navpath = w.path_to_place(p, self.openings[0])
if navpath:
self.g.path = navpath
self.state = self.going_to_tree
else:
self.openings.pop(0)
time.sleep(0.1)
def going_to_tree(self):
if utils.pint(self.g.pos) == self.openings[0]:
self.g.look_at = self.tree
self.state = self.clear_leaves
def clear_leaves(self):
if not self.g.breaking:
p = utils.pint(self.g.pos)
diff = utils.psub(self.tree, p)
for x in utils.diffrange(diff[0]):
for z in utils.diffrange(diff[2]):
for y in range(2):
check = utils.padd(p, (x, y, z))
if check == self.tree:
break
if not self.bair(check):
print('Breaking leaf')
self.g.game.break_block(check)
return
self.state = self.clear_trunk_base
def clear_trunk_base(self):
if not self.g.breaking:
base = self.tree
above = utils.padd(self.tree, path.BLOCK_ABOVE)
if self.blog(base):
self.g.game.break_block(base)
print('breaking base')
elif self.blog(above):
self.g.game.break_block(above)
print('breaking above')
else:
w = self.g.world
p = utils.pint(self.g.pos)
navpath = w.path_to_place(p, self.tree)
if navpath:
self.g.path = navpath
self.state = self.going_to_trunk_base
else:
self.openings.pop(0)
self.state = self.choose_opening
time.sleep(0.1)
def going_to_trunk_base(self):
if utils.pint(self.g.pos) == self.tree:
self.g.look_at = utils.padd(self.tree, path.BLOCK_ABOVE2)
self.state = self.clear_trunk
def clear_trunk(self):
if not self.g.breaking:
check = self.tree
count = 0
while self.bair(check) and count < 6:
check = utils.padd(check, path.BLOCK_ABOVE)
count += 1
if self.blog(check):
print('breaking log', check)
self.g.game.break_block(check)
else:
print('Finished clearing tree')
self.wait_time = 0.5
self.state = self.wait
def wait(self):
# wait for the last log to fall
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.g.chopped_tree = self.type
self.good_trees.append(self.tree)
self.state = self.check_pos
def check_pos(self):
# make sure we are at base of trunk
# doesn't always happen, for some reason
if utils.pint(self.g.pos) == self.tree:
self.state = self.cleanup
else:
self.state = self.find_openings
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.tree = None
self.type = None
self.openings = []
self.bad_trees = []
self.good_trees = []
self.wait_time = 0
def run(self):
self.state()

89
mosfet/jobs/grab_sand.py Normal file
View File

@@ -0,0 +1,89 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GrabSandStates:
def idle(self):
return None
def init(self):
self.state = self.find_sand
print('Trying to grab sand')
def find_sand(self):
w = self.g.world
p = utils.pint(self.g.pos)
sand = w.find_objects([items.SAND_ID])
if not sand:
print('No sand objects found, aborting')
self.state = self.cleanup
return
sand.sort(key=lambda s: utils.phyp(p, (s.x, s.y, s.z)))
for s in sand:
s_pos = utils.pint((s.x, s.y, s.z))
check = utils.padd(s_pos, path.BLOCK_BELOW)
if utils.phyp(p, s_pos) > 6:
continue
# skip if the sand is floating
if self.g.chunks.get_block_at(*check) in {0}:
continue
if s.entity_id in self.eid_blacklist:
continue
self.eid_blacklist.append(s.entity_id)
navpath = w.path_to_place(p, s_pos)
if navpath:
self.g.path = navpath
self.state = self.going_to_sand
self.sand = s_pos
print('Going to sand', self.sand)
return
else:
print('Cant get to sand', self.sand)
time.sleep(0.1)
print('Cant get to any more sand, aborting')
self.state = self.cleanup
def going_to_sand(self):
if utils.pint(self.g.pos) == self.sand:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.sand = None
self.eid_blacklist = []
def run(self):
self.state()

View File

@@ -0,0 +1,85 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GrabSaplingStates:
def idle(self):
return None
def init(self):
self.state = self.find_saplings
print('Trying to grab a sapling')
def find_saplings(self):
w = self.g.world
p = utils.pint(self.g.pos)
saplings = w.find_objects(items.SAPLING_IDS)
if not saplings:
print('No sapling objects found, aborting')
self.state = self.cleanup
return
saplings.sort(key=lambda s: utils.phyp(p, (s.x, s.y, s.z)))
for s in saplings:
s_pos = utils.pint((s.x, s.y, s.z))
check = utils.padd(s_pos, path.BLOCK_BELOW)
if s.entity_id in self.eid_blacklist:
continue
# skip if the sapling is floating
if self.g.chunks.get_block_at(*check) in blocks.LEAF_IDS | {0}:
continue
navpath = w.path_to_place(p, s_pos)
if navpath:
self.g.path = navpath
self.state = self.going_to_sapling
self.sapling = s_pos
self.eid_blacklist.append(s.entity_id)
print('Going to sapling', self.sapling)
return
print('Cant get to any more saplings, aborting')
self.state = self.cleanup
def going_to_sapling(self):
if utils.pint(self.g.pos) == self.sapling:
self.state = self.find_saplings
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.sapling = None
self.eid_blacklist = []
def run(self):
self.state()

View File

@@ -0,0 +1,236 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class GrabSuppliesStates:
def idle(self):
return None
def init(self):
print('Started grab supplies states')
self.checked_barrels = []
used_slots = self.g.game.count_inventory_slots()
print('used:', used_slots, 'total:', self.g.maximum_supply_slots)
if used_slots >= self.g.maximum_supply_slots:
print('Inventory is too full, aborting')
self.state = self.cleanup
return
if self.supplies:
self.state = self.check_supplies
else:
print('Aborting getting supplies, none specified')
self.state = self.cleanup
def check_supplies(self):
# check if we need to grab anything
for items, limits in self.supplies.items():
minimum, maximum = limits
print('Checking items:', items)
num_items = self.g.game.count_items(items)
print('Have:', num_items)
if num_items >= minimum:
print('Have enough, skipping')
continue
print('Need at least one item')
self.state = self.find_barrels
return
print('Aborting, dont need any supplies')
self.state = self.cleanup
def find_barrels(self):
print('Finding barrels...')
w = self.g.world
p = utils.pint(self.g.pos)
self.barrels = w.find_blocks_indexed(p, blocks.BARREL_IDS, 80)
print('Found:', self.barrels)
self.state = self.choose_barrel
def choose_barrel(self):
print('Choosing a barrel...')
for barrel in self.barrels:
if barrel in self.checked_barrels:
continue
if barrel in self.bad_barrels:
continue
print('Chose:', barrel)
self.barrel = barrel
self.state = self.path_to_barrel
return
print('No barrels')
self.state = self.cleanup
def path_to_barrel(self):
print('Finding path to barrel')
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
navpath = w.path_to_place_faked(p, self.barrel)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.opening = self.g.path[-1]
self.checked_barrels.append(self.barrel)
self.area = self.barrel
self.state = self.going_to_barrel
self.checked_supplies = []
return
else:
print('No path, blacklisting barrel')
time.sleep(0.1)
self.bad_barrels.append(self.barrel)
if len(self.bad_barrels) > 3:
self.state = self.cleanup
else:
self.state = self.choose_barrel
def going_to_barrel(self):
if utils.pint(self.g.pos) == self.opening:
self.g.look_at = self.area
self.state = self.open_barrel
def open_barrel(self):
print('Opening barrel')
self.g.game.open_container(self.area)
self.wait_time = 1
self.state = self.wait
def wait(self):
# wait for server to send us inventory contents
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.choose_items
def choose_items(self):
print('Selecting next item')
for items, limits in self.supplies.items():
minimum_items, maximum_stacks = limits
print('Checking items:', items)
num_items = self.g.game.count_items(items)
print('Have:', num_items)
if num_items >= minimum_items:
print('Have enough, skipping')
continue
if items in self.checked_supplies:
print('Already checked, skipping')
continue
print('Need some')
self.checked_supplies.append(items)
self.target_items = items
self.maximum_stacks = maximum_stacks
self.count = 0
self.state = self.grab_items
return
print('Aborting, dont need any more supplies')
self.state = self.close_barrel
def grab_items(self):
if self.g.item_lock: return
w = self.g.window
if not w:
print('Didnt get a window, aborting')
self.state = self.cleanup
return
if w.data.window_type != mcdata.SINGLE_CHEST:
print('Got wrong window, aborting')
self.state = self.cleanup
return
used_slots = self.g.game.count_inventory_slots()
print('used:', used_slots, 'total:', self.g.maximum_supply_slots)
if used_slots >= self.g.maximum_supply_slots:
print('Inventory is too full, aborting')
self.g.game.close_window()
self.g.look_at = None
self.state = self.cleanup
return
w_info = mcdata.WINDOWS[w.data.window_type]
w_container_slots = w_info.container
for slot_num in w_container_slots:
if slot_num not in w.contents:
continue
slot = w.contents[slot_num]
if not slot.present:
continue
if slot.item_id not in self.target_items:
continue
if self.maximum_stacks and self.count >= self.maximum_stacks:
break
self.count += 1
print('Moving', slot)
self.g.item_lock = True
self.g.game.click_window(slot_num, 0, 1, slot)
return
print('None left to move')
self.wait_time = 0.25
self.state = self.wait
def close_barrel(self):
print('Closing barrel')
self.g.game.close_window()
self.g.look_at = None
self.state = self.choose_barrel
def cleanup(self):
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.supplies = {}
self.barrels = []
self.checked_barrels = []
self.bad_barrels = []
self.barrel = None
self.checked_supplies = []
self.target_items = None
self.maximum_stacks = 0
self.count = 0
self.area = None
self.opening = None
self.wait_time = 0
def run(self):
self.state()

110
mosfet/jobs/plant_tree.py Normal file
View File

@@ -0,0 +1,110 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class PlantTreeStates:
def idle(self):
return None
def init(self):
if self.g.chopped_tree:
self.state = self.check_feet
else:
print('Aborting planting, did not plant')
self.state = self.cleanup
def check_feet(self):
p = utils.pint(self.g.pos)
# check for air at feet
if self.g.chunks.get_block_at(*p) in [0]:
self.state = self.select_sapling
else:
print('Aborting planting, feet not air')
self.state = self.cleanup
def select_sapling(self):
p = utils.pint(self.g.pos)
sapling_type = self.g.chopped_tree + '_sapling'
sapling_item = items.get_id(sapling_type)
if self.g.game.select_item([sapling_item]):
self.g.look_at = utils.padd(p, path.BLOCK_BELOW)
self.state = self.wait_select
self.wait_time = 1
else:
print('Aborting planting, no', sapling_type)
self.state = self.cleanup
def wait_select(self):
# wait a bit to look down
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.place_sapling
def place_sapling(self):
p = utils.pint(self.g.pos)
self.g.game.place_block(p, BlockFace.TOP)
print('Placed sapling')
self.state = self.wait_place
self.wait_time = 1
def wait_place(self):
# wait a bit for chunk data to update
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.find_open_spot
def find_open_spot(self):
w = self.g.world
p = utils.pint(self.g.pos)
for opening in w.find_tree_openings(p)[::-1]:
print('trying sapling opening', opening)
navpath = w.path_to_place(p, opening)
if navpath:
self.g.path = navpath
self.area = opening
self.state = self.going_to_area
return
else: # for
print('cant escape sapling')
self.state = self.cleanup
def going_to_area(self):
if utils.pint(self.g.pos) == self.area:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.wait_time = 0
self.area = None
def run(self):
self.state()

View File

@@ -0,0 +1,234 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class SellToVillagerStates:
def idle(self):
return None
def init(self):
self.trade = None
self.state = self.find_villager
def find_villager(self):
print('Finding new villager...')
w = self.g.world
p = utils.pint(self.g.pos)
for v in w.find_villagers(p, 100):
print('Found villager:', v)
if v in self.bad_villagers:
print('In bad villager list')
continue
if v in self.spent_villagers:
print('In spent villager list')
continue
if 'profession' not in v:
print('Villager has unknown profession')
continue
if v.profession not in mobs.TRADES:
print('Villager doesnt sell stuff we collect')
continue
trades = mobs.TRADES[v.profession]
if not self.g.game.count_items(trades):
print('We dont have anything to sell him')
continue
break
else: # for
print('No good villagers left, aborting.')
self.spent_villagers = []
self.state = self.cleanup
return
self.villager = v
self.villager_pos = utils.pint((v.x, v.y, v.z))
self.state = self.find_openings
def find_openings(self):
w = self.g.world
self.openings = w.find_villager_openings(self.villager_pos)
self.state = self.choose_opening
def choose_opening(self):
w = self.g.world
p = utils.pint(self.g.pos)
print('openings:', self.openings)
if not len(self.openings):
print('Unable to get to villager:', self.villager)
if self.villager not in self.good_villagers:
self.bad_villagers.append(self.villager)
print('Added to bad villager list')
self.state = self.find_villager
return
navpath = w.path_to_place(p, self.openings[0])
if navpath:
self.g.path = navpath
self.state = self.going_to_villager
self.g.look_at = None
else:
self.openings.pop(0)
time.sleep(0.1)
def going_to_villager(self):
if utils.pint(self.g.pos) == self.openings[0]:
print('Arrived at villager')
self.g.look_at = self.villager_pos
self.wait_time = 0.5
self.state = self.wait_to_interact
def wait_to_interact(self):
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.interact_villager
def interact_villager(self):
print('Interacting with villager')
self.g.game.interact(self.villager.entity_id)
self.g.game.animate()
self.wait_time = 0.5
self.state = self.wait_for_window
def wait_for_window(self):
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.choose_trade
def choose_trade(self):
g = self.g.game
w = self.g.window
if not w or not self.g.trades:
print('Didnt get a trade window, aborting')
self.state = self.cleanup
return
if w.data.window_type != mcdata.VILLAGER_TRADE:
print('Got wrong window, aborting')
self.state = self.cleanup
return
for trade_num, trade in enumerate(self.g.trades):
in_id = trade.input_item_1.item_id
in_num = trade.input_item_1.item_count
out_id = trade.output_item.item_id
price = in_num \
+ floor(in_num * trade.price_multiplier * trade.demand) \
+ trade.special_price
if price < 1: price = 1
if g.count_items([in_id]) < price:
continue
print('Checking trade #', trade_num)
if trade.trade_disabled:
print('Trade disabled, skipping')
continue
if out_id != items.EMERALD_ID:
print('Not for emeralds, skipping')
continue
if price > in_num:
print('Trade in demand, skipping')
continue
print('Found trade:', trade)
print('Adjusted price:', price)
self.trade = trade
self.trade_num = trade_num
self.state = self.click_trade
break
else:
print('Villager has been spent, aborting')
self.g.game.close_window()
self.spent_villagers.append(self.villager)
self.state = self.find_villager
return
def click_trade(self):
print('Clicking trade')
self.g.item_lock = True
self.g.game.select_trade(self.trade_num)
self.state = self.execute_trade
def execute_trade(self):
if self.g.item_lock:
return
w = self.g.window
w_info = mcdata.WINDOWS[w.data.window_type]
slot_num = w_info.output
if slot_num in w.contents:
print('Executing trade')
slot = w.contents[slot_num]
self.g.game.click_window(slot_num, 0, 1, slot)
else:
print('Bad trade, aborting')
self.state = self.end_trade
def end_trade(self):
print('Trading complete')
self.g.game.close_window()
self.wait_time = 1
self.state = self.wait_for_trade
def wait_for_trade(self):
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.interact_villager
def wait(self):
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.villager = None
self.villager_pos = None
self.bad_villagers = []
self.good_villagers = []
self.spent_villagers = []
self.openings = []
self.trade = None
self.wait_time = 0
def run(self):
self.state()

View File

@@ -0,0 +1,229 @@
import re
import time
import importlib
import random
from itertools import count
from math import floor
from minecraft.networking.types import BlockFace
from mosfet.protocol.managers import ChunkNotLoadedException
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import items
from mosfet.info import mcdata
from mosfet.info import mobs
class SleepWithBedStates:
def idle(self):
return None
def init(self):
if self.g.time < 12000:
print('Aborting sleep, not night')
self.state = self.cleanup
return
if self.g.dimension != 'overworld':
print('Aborting sleep, not in overworld')
self.state = self.cleanup
return
self.state = self.find_beds
def find_beds(self):
print('Finding beds...')
w = self.g.world
p = utils.pint(self.g.pos)
result = w.find_blocks_indexed(p, blocks.BED_IDS, 80)
self.beds = []
for bed in result:
if bed in self.bad_beds:
continue
if w.check_bed_occupied(bed):
continue
self.beds.append(bed)
print('Found:', self.beds)
self.state = self.choose_bed
def choose_bed(self):
print('Choosing a bed...')
w = self.g.world
p = utils.pint(self.g.pos)
c = self.g.chunks
if not len(self.beds):
print('No beds')
self.state = self.select_bed
return
bed = self.beds[0]
print('Chose:', bed)
navpath = w.path_to_place_faked(p, bed)
print('navpath:', navpath)
if navpath:
self.g.path = navpath[:-1]
self.opening = self.g.path[-1]
self.g.look_at = self.opening
self.area = bed
self.state = self.going_to_bed
return
else:
self.beds.pop(0)
self.bad_beds.append(bed)
print('Cant get to bed, blacklisting')
time.sleep(0.1)
self.state = self.select_bed
def going_to_bed(self):
if utils.pint(self.g.pos) == self.opening:
print('At existing bed')
self.my_bed = False
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.use_bed
def select_bed(self):
if self.g.game.select_item(items.BED_IDS):
self.state = self.find_bed_spot
else:
print('No bed, aborting.')
self.state = self.cleanup
def find_bed_spot(self):
print('Finding a bed spot...')
w = self.g.world
p = utils.pint(self.g.pos)
for area in w.find_bed_areas(p, 100):
print('Found area:', area)
if area not in self.bad_areas:
break
else: # for
print('Unable to find area')
self.state = self.cleanup
return
self.area = area
openings = w.find_bed_openings(self.area)
for o in openings:
navpath = w.path_to_place(p, o)
self.opening = o
if navpath: break
else: # for
print('Unable to get to bed area', self.area)
self.bad_areas.append(self.area)
self.state = self.cleanup
return
self.g.path = navpath
self.g.look_at = self.opening
self.state = self.going_to_area
def going_to_area(self):
if utils.pint(self.g.pos) == self.opening:
print('At bed area')
self.g.look_at = utils.padd(self.area, path.BLOCK_BELOW)
self.state = self.place_bed
def place_bed(self):
print('Placing bed')
self.g.game.place_block(self.area, BlockFace.TOP)
self.my_bed = True
self.wait_time = 0.5
self.state = self.wait_use
def wait_use(self):
# wait to use the bed
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.use_bed
def use_bed(self):
w = self.g.world
if self.g.time < 12550:
return
if w.check_bed_occupied(self.area):
print('Bed occupied, aborting.')
self.state = self.cleanup
return
print('Using bed')
self.g.game.place_block(self.area, BlockFace.TOP)
if not self.silent:
self.g.chat.send('zzz')
self.state = self.sleep_bed
def sleep_bed(self):
w = self.g.world
p = utils.pint(self.g.pos)
threats = w.find_threats(p, 10)
if threats:
print('Waking up due to threats:')
print(threats)
self.g.game.leave_bed()
self.state = self.cleanup
elif self.g.time < 100:
print('Woke up time')
self.state = self.break_bed
elif self.g.correction_count:
print('Woke up by movement')
self.state = self.break_bed
def break_bed(self):
if self.my_bed:
self.g.game.break_block(self.area)
self.state = self.collect_bed
else:
self.state = self.cleanup
def collect_bed(self):
if not self.g.breaking:
self.g.path = [utils.padd(self.area, utils.spiral(n)) for n in range(9)]
self.wait_time = 2
self.state = self.wait
def wait(self):
# wait to pick up bed
if self.wait_time > 0:
self.wait_time -= utils.TICK
else:
self.state = self.cleanup
def cleanup(self):
self.g.look_at = None
self.state = self.done
def done(self):
# never gets ran, placeholder
return None
def __init__(self, global_state):
self.g = global_state
self.state = self.idle
self.silent = False
self.my_bed = False
self.beds = []
self.bad_beds = []
self.area = None
self.opening = None
self.bad_areas = []
self.wait_time = 0
def run(self):
self.state()

View File

@@ -1,5 +1,5 @@
import minecraft.networking.packets import minecraft.networking.packets
from protocol import packets from .protocol import packets
def get_packets(old_get_packets): def get_packets(old_get_packets):
def wrapper(func, context): def wrapper(func, context):
@@ -19,6 +19,8 @@ def get_packets(old_get_packets):
mc_packets.add(packets.DestroyEntitiesPacket) mc_packets.add(packets.DestroyEntitiesPacket)
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.UnloadChunkPacket)
return mc_packets return mc_packets

View File

@@ -1,14 +1,11 @@
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
import blocks from mosfet.info import blocks
importlib.reload(blocks) from mosfet import utils
import utils
importlib.reload(utils)
class AStarTimeout(Exception): class AStarTimeout(Exception):
pass pass
@@ -121,8 +118,9 @@ HALF_PARKOUR = {
BLOCK_CACHE_SIZE = 2**14 BLOCK_CACHE_SIZE = 2**14
class Pathfinder(AStar): class Pathfinder(AStar):
def __init__(self, chunks): def __init__(self, g):
self.chunks = chunks self.g = g
self.chunks = g.chunks
self.start_time = time.time() self.start_time = time.time()
@functools.lru_cache(maxsize=BLOCK_CACHE_SIZE) @functools.lru_cache(maxsize=BLOCK_CACHE_SIZE)
@@ -133,6 +131,23 @@ class Pathfinder(AStar):
def bavoid(self, p): def bavoid(self, p):
return self.chunks.get_block_at(*p) in blocks.AVOID_IDS or p[1] < 0 return self.chunks.get_block_at(*p) in blocks.AVOID_IDS or p[1] < 0
def check_traverse_crawling(self, node, offset):
dest = utils.padd(node, offset)
if not self.bair(dest):
return False
if self.bair(utils.padd(dest, BLOCK_BELOW)):
return False
if self.bavoid(dest):
return False
if self.bavoid(utils.padd(dest, BLOCK_BELOW)):
return False
return True
def check_traverse(self, node, offset): def check_traverse(self, node, offset):
dest = utils.padd(node, offset) dest = utils.padd(node, offset)
@@ -254,27 +269,32 @@ class Pathfinder(AStar):
def neighbors(self, node): def neighbors(self, node):
results = [] results = []
for offset in TRAVERSE: if self.g.crawling:
if self.check_traverse(node, offset): for offset in TRAVERSE:
results.append(utils.padd(node, offset)) if self.check_traverse_crawling(node, offset):
for offset in DIAGONAL: results.append(utils.padd(node, offset))
if self.check_diagonal(node, offset): else:
results.append(utils.padd(node, offset)) for offset in TRAVERSE:
for offset in ASCEND: if self.check_traverse(node, offset):
if self.check_ascend(node, offset): results.append(utils.padd(node, offset))
results.append(utils.padd(node, offset)) for offset in DIAGONAL:
for offset in DESCEND: if self.check_diagonal(node, offset):
if self.check_descend(node, offset): results.append(utils.padd(node, offset))
results.append(utils.padd(node, offset)) for offset in ASCEND:
for offset in DESCEND2: if self.check_ascend(node, offset):
if self.check_descend2(node, offset): results.append(utils.padd(node, offset))
results.append(utils.padd(node, offset)) for offset in DESCEND:
for offset in DESCEND3: if self.check_descend(node, offset):
if self.check_descend3(node, offset): results.append(utils.padd(node, offset))
results.append(utils.padd(node, offset)) for offset in DESCEND2:
for offset in PARKOUR: if self.check_descend2(node, offset):
if self.check_parkour(node, offset): results.append(utils.padd(node, offset))
results.append(utils.padd(node, offset)) for offset in DESCEND3:
if self.check_descend3(node, offset):
results.append(utils.padd(node, offset))
for offset in PARKOUR:
if self.check_parkour(node, offset):
results.append(utils.padd(node, offset))
if not results: if not results:
if time.time() - self.start_time > 2.0: if time.time() - self.start_time > 2.0:
@@ -285,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

16
mosfet/print_help.py Normal file
View File

@@ -0,0 +1,16 @@
HELP_LINES = []
with open('mosfet/commands.py', 'r') as f:
for line in f.readlines():
if line.strip().startswith('## '):
HELP_LINES.append(line.strip()[3:])
if __name__ == '__main__':
for line in HELP_LINES:
if ' - ' in line:
command, doc = line.split(' - ')
print('`{}` - {}\n'.format(command, doc))
else:
print(line)
print()

View File

View File

@@ -4,9 +4,10 @@ import json
import time import time
from minecraft.networking.packets import clientbound, serverbound from minecraft.networking.packets import clientbound, serverbound
from protocol import packets from mosfet.protocol import packets
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

@@ -8,9 +8,9 @@ from minecraft.networking.types import (
Float, Direction, PositionAndLook Float, Direction, PositionAndLook
) )
from protocol.types import Nbt, Slot, Entry, Trade from .types import Nbt, Slot, Entry, Trade
import blocks from mosfet.info import blocks
class ChunkDataPacket(Packet): class ChunkDataPacket(Packet):
@@ -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)
@@ -447,3 +447,26 @@ class SelectTradePacket(Packet):
definition = [ definition = [
{'selected_slot': VarInt}, {'selected_slot': VarInt},
] ]
class DisconnectPacket(Packet):
# Sent by the server before it disconnects a client
# https://wiki.vg/Protocol#Disconnect_.28play.29
id = 0x19
packet_name = 'disconnect'
definition = [
{'reason': String},
]
class UnloadChunkPacket(Packet):
# Tells the client to unload a chunk column
# https://wiki.vg/Protocol#Unload_Chunk
id = 0x1C
packet_name = 'unload chunk'
definition = [
{'chunk_x': Integer},
{'chunk_z': Integer},
]

View File

@@ -141,7 +141,7 @@ class Slot(Type):
class Entry(Type): 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,14 +1,16 @@
import importlib import importlib
import collections import collections
from math import floor, ceil, sqrt, hypot from math import floor, ceil, sqrt
import blocks from mosfet.info import blocks
importlib.reload(blocks) from mosfet.info import mcdata
import mcdata
importlib.reload(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])
@@ -135,49 +137,32 @@ def search_2d(distance=0):
visited.add(cur) visited.add(cur)
yield cur yield cur
def search_3d(distance=0, y_limit=0): def get_neighbors_3d(x,y,z):
def get_neighbors(x,y,z): return [
return [ #(x+1, y+1, z+0),
(x+1, y+1, z+0), #(x+1, y-1, z+0),
(x+1, y-1, z+0), #(x+1, y+1, z+1),
(x+1, y+1, z+1), #(x+1, y+0, z+1),
(x+1, y+0, z+1), #(x+1, y-1, z+1),
(x+1, y-1, z+1), #(x+1, y+1, z-1),
(x+1, y+1, z-1), #(x+1, y+0, z-1),
(x+1, y+0, z-1), #(x+1, y-1, z-1),
(x+1, y-1, z-1), (x+1, y+0, z+0),
(x+1, y+0, z+0), (x+0, y+1, z+0),
(x+0, y+1, z+0), (x+0, y-1, z+0),
(x+0, y-1, z+0), #(x+0, y+1, z+1),
(x+0, y+1, z+1), (x+0, y+0, z+1),
(x+0, y+0, z+1), #(x+0, y-1, z+1),
(x+0, y-1, z+1), #(x+0, y+1, z-1),
(x+0, y+1, z-1), (x+0, y+0, z-1),
(x+0, y+0, z-1), #(x+0, y-1, z-1),
(x+0, y-1, z-1), #(x-1, y+1, z+0),
(x-1, y+1, z+0), #(x-1, y-1, z+0),
(x-1, y-1, z+0), #(x-1, y+1, z+1),
(x-1, y+1, z+1), #(x-1, y+0, z+1),
(x-1, y+0, z+1), #(x-1, y-1, z+1),
(x-1, y-1, z+1), #(x-1, y+1, z-1),
(x-1, y+1, z-1), #(x-1, y+0, z-1),
(x-1, y+0, z-1), #(x-1, y-1, z-1),
(x-1, y-1, z-1), (x-1, y+0, z+0),
(x-1, y+0, z+0), ]
]
to_visit = collections.deque([(0, 0, 0)])
visited = set()
while to_visit:
cur = to_visit.pop()
if cur in visited:
continue
if y_limit and abs(cur[1]) > y_limit:
continue
if distance and hypot(*cur) > distance:
continue
for neighbor in get_neighbors(*cur):
to_visit.appendleft(neighbor)
visited.add(cur)
yield cur

124
mosfet/vector.py Normal file
View File

@@ -0,0 +1,124 @@
import math
from mosfet import utils
class Vector3D:
def __init__(self, vector):
self.x = vector[0]
self.y = vector[1]
self.z = vector[2]
@property
def xz(self):
return Vector3D((self.x, 0, self.z))
def tuple(self):
return (self.x, self.y, self.z)
def __getitem__(self, key):
return self.tuple()[key]
def length(self):
return utils.hypot(self.x, self.y, self.z)
def normalized(self):
x = self.x / self.length()
y = self.y / self.length()
z = self.z / self.length()
return Vector3D((x, y, z))
def dot(self, other):
return self.x * other.x + self.y * other.y + self.z * other.z
def cross(self, other):
a1, a2, a3 = self.tuple()
b1, b2, b3 = other.tuple()
return Vector3D((a2*b3-a3*b2, a3*b1-a1*b3, a1*b2-a2*b1))
def angleDeg(self, other):
ratio = self.dot(other) / (self.length() * other.length())
rads = math.acos(ratio)
return math.degrees(rads)
def signedAngleDeg(self, other, ref):
angle1 = self.angleDeg(other)
cross = self.cross(ref)
angle2 = other.angleDeg(cross)
if angle2 < 90:
return -angle1
else:
return angle1
def __repr__(self):
return 'Vector3D(x={}, y={}, z={})'.format(self.x, self.y, self.z)
def __str__(self):
return '[{}, {}, {}]'.format(self.x, self.y, self.z)
class Point3D:
def __init__(self, point):
self.x = point[0]
self.y = point[1]
self.z = point[2]
def __sub__(self, other):
#x = other.x - self.x
#y = other.y - self.y
#z = other.z - self.z
x = self.x - other.x
y = self.y - other.y
z = self.z - other.z
return Vector3D((x, y, z))
def tuple(self):
return (self.x, self.y, self.z)
def __getitem__(self, key):
return self.tuple()[key]
def __repr__(self):
return 'Point3D(x={}, y={}, z={})'.format(self.x, self.y, self.z)
def __str__(self):
return '({}, {}, {})'.format(self.x, self.y, self.z)
if __name__ == '__main__':
# test to make sure our Vector module is the same as Panda3D
from panda3d.core import LPoint3f, LVector3f
import random
pPITCH_ANGLE_DIR = LVector3f(x=0, y=1, z=0)
pYAW_ANGLE_DIR = LVector3f(x=0, y=0, z=-1)
pYAW_ANGLE_REF = LVector3f(x=0, y=1, z=0)
PITCH_ANGLE_DIR = Vector3D((0, 1, 0))
YAW_ANGLE_DIR = Vector3D((0, 0, -1))
YAW_ANGLE_REF = Vector3D((0, 1, 0))
for _ in range(1000):
r = lambda: random.uniform(-10, 10)
a, b, c = r(), r(), r()
plook_at_d = LVector3f(x=a, y=b, z=c)
look_at_d = Vector3D((a, b, c))
ptarget_pitch = plook_at_d.normalized().angleDeg(pPITCH_ANGLE_DIR)
target_pitch = look_at_d.normalized().angleDeg(PITCH_ANGLE_DIR)
if round(ptarget_pitch) != round(target_pitch):
print('mismatch:', ptarget_pitch, target_pitch)
break
else: # for
print('no mismatches')
for _ in range(1000):
r = lambda: random.uniform(-10, 10)
a, b, c = r(), r(), r()
plook_at_d = LVector3f(x=a, y=b, z=c)
look_at_d = Vector3D((a, b, c))
ptarget_yaw = plook_at_d.normalized().signedAngleDeg(other=pYAW_ANGLE_DIR, ref=pYAW_ANGLE_REF)
target_yaw = look_at_d.normalized().signedAngleDeg(other=YAW_ANGLE_DIR, ref=YAW_ANGLE_REF)
if round(ptarget_yaw) != round(target_yaw):
print('mismatch:', ptarget_yaw, target_yaw)
break
else: # for
print('no mismatches')

365
mosfet/world.py Normal file
View File

@@ -0,0 +1,365 @@
import collections
import re
import time
import random
from itertools import count
from copy import copy
from mosfet import utils
from mosfet import path
from mosfet.info import blocks
from mosfet.info import mobs
class World:
def __init__(self, global_state):
self.g = global_state
def block_at(self, x, y, z):
return self.g.chunks.get_block_at(x, y, z)
def check_air_column(self, pos, distance):
for i in range(distance):
check = utils.padd(pos, (0, i, 0))
if self.block_at(*check) not in blocks.NON_SOLID_IDS:
return False
return True
def find_blocks_3d(self, center, block_ids, distance=0, y_limit=0, thru_air=False):
to_visit = collections.deque([(0, 0, 0)])
visited = set()
while to_visit:
cur = to_visit.pop()
if cur in visited:
continue
if y_limit and abs(cur[1]) > y_limit:
continue
if distance and utils.hypot(*cur) > distance:
continue
check = utils.padd(center, cur)
if not thru_air or self.block_at(*check) in blocks.NON_SOLID_IDS:
for neighbor in utils.get_neighbors_3d(*cur):
to_visit.appendleft(neighbor)
visited.add(cur)
if self.block_at(*check) in block_ids:
yield check
def find_blocks_indexed(self, center, block_ids, distance=0):
print('finding', block_ids)
index = []
for bid in block_ids:
index.extend(self.g.chunks.index.get(bid, []))
print('index', index)
result = []
for block in index:
if self.block_at(*block) not in block_ids:
continue
if distance and utils.phyp(center, block) > distance:
continue
if block not in result:
result.append(block)
result.sort(key=lambda x: utils.phyp(center, x))
return result
def find_blocks(self, center, distance, block_ids, limit=0):
# search in a spiral from center to all blocks with ID
result = []
for n in count():
offset = utils.spiral(n)
check = utils.padd(center, offset)
if self.block_at(*check) in block_ids:
if utils.hypot(*offset) < distance:
result.append(check)
if limit and len(result) == limit:
return result
if offset[0] > distance:
return result
def find_trees(self, center, distance):
found_trees = []
for log in self.find_blocks_3d(center, blocks.LOG_IDS, distance, 15):
# crawl to the bottom log
while self.block_at(*utils.padd(log, path.BLOCK_BELOW)) in blocks.LOG_IDS:
log = utils.padd(log, path.BLOCK_BELOW)
base = log
if base in found_trees:
continue
# make sure we are on the ground
if self.block_at(*utils.padd(base, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS:
continue
# crawl to the top log to count and check leaves
log_count = 1
good_leaves = False
while self.block_at(*utils.padd(log, path.BLOCK_ABOVE)) in blocks.LOG_IDS:
log = utils.padd(log, path.BLOCK_ABOVE)
log_count += 1
for offset in path.CHECK_DIRECTIONS:
if self.block_at(*utils.padd(log, offset)) in blocks.LEAF_IDS:
good_leaves = True
# make sure it's a good tree
if not good_leaves or log_count < 3:
continue
found_trees.append(base)
yield base
def find_tree_openings(self, tree):
# returns coords in a cardinal direction where we can stand by tree
maze_solver = path.Pathfinder(self.g)
result = []
# TODO: make sure only non-solid and leaves between
# make sure traversable too and non-avoid
for distance in range(5):
for direction in path.CHECK_DIRECTIONS:
offset = utils.pmul(direction, distance+1)
if maze_solver.check_traverse(tree, offset):
result.append(utils.padd(tree, offset))
return result
def path_to_place(self, start, place):
maze_solver = path.Pathfinder(self.g)
try:
s = maze_solver.astar(start, place)
return list(s) if s else None
except path.AStarTimeout:
return None
def path_to_place_faked(self, start, place):
# same as above, but adds a fake block below and air before pathfinding
# so that the pathfinder can actually make it to the block
c = self.g.chunks
above = utils.padd(place, path.BLOCK_ABOVE)
below = utils.padd(place, path.BLOCK_BELOW)
tmp = c.get_block_at(*place)
tmp2 = c.get_block_at(*above)
tmp3 = c.get_block_at(*below)
c.set_block_at(*place, blocks.AIR)
c.set_block_at(*above, blocks.AIR)
c.set_block_at(*below, blocks.STONE)
navpath = self.path_to_place(start, place)
c.set_block_at(*place, tmp)
c.set_block_at(*above, tmp2)
c.set_block_at(*below, tmp3)
return navpath
def find_bed_areas(self, center, distance):
bed_clearance = 9 # 5x5 area
clear_distance = 2
for a in self.find_blocks_3d(center, [0], distance, 50):
# check for air around the area
if len(self.find_blocks(a, clear_distance, [0], bed_clearance)) < bed_clearance:
continue
# check for ground around the area
if len(self.find_blocks(utils.padd(a, path.BLOCK_BELOW), clear_distance, blocks.NON_SOLID_IDS, bed_clearance)):
continue
# check for air above the area
if len(self.find_blocks(utils.padd(a, path.BLOCK_ABOVE), clear_distance, [0], bed_clearance)) < bed_clearance:
continue
# ensure there's no monsters within 20 blocks
# can't sleep if they are within 10, good to have a buffer
if self.find_monsters(a, 20):
continue
yield a
def find_cache_areas(self, center, distance):
return self.find_bed_areas(center, distance)
def sand_adjacent_safe(self, sand):
for direction in path.CHECK_DIRECTIONS:
if self.block_at(*utils.padd(sand, direction)) in blocks.AVOID_IDS:
return False
return True
def find_sand(self, center, distance, player):
sand = []
sand.extend(self.find_blocks(center, distance, [blocks.SAND], 25))
safe_sand = []
for s in sand:
# make sure it has solid below
if self.block_at(*utils.padd(s, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS:
continue
# make sure it has solid two below - prevent hanging sand
if self.block_at(*utils.padd(s, path.BLOCK_BELOW2)) in blocks.NON_SOLID_IDS:
continue
# and walkable air above
if self.block_at(*utils.padd(s, path.BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS:
continue
if not self.sand_adjacent_safe(s):
continue
safe_sand.append(s)
safe_sand.sort(key=lambda x: utils.phyp(player, x))
return safe_sand
def check_sand_slice(self, center):
# checks if a 5x5x1 slice has sand in it
for i in range(9):
s = utils.padd(center, utils.spiral(i))
if self.block_at(*s) != blocks.SAND:
continue
# make sure it has solid below
if self.block_at(*utils.padd(s, path.BLOCK_BELOW)) in blocks.NON_SOLID_IDS:
continue
# make sure it has solid two below - prevent hanging sand
if self.block_at(*utils.padd(s, path.BLOCK_BELOW2)) in blocks.NON_SOLID_IDS:
continue
# and walkable air above
if self.block_at(*utils.padd(s, path.BLOCK_ABOVE)) not in blocks.NON_SOLID_IDS:
continue
if not self.sand_adjacent_safe(s):
continue
return True
return False
def find_sand_slice(self, center, distance, y_limit=0, bad_slices=[], prev_layer=0):
# returns the centre coord of the next 5x5x1 slice that still has
# diggable sand in it. lower slices are only valid if there's an
# adjacent slice farther at the same level. this should ensure an
# upside down pyramid gets excavated so the edges are still climbable
for v in count(prev_layer):
peak = utils.padd(center, (0, 10-v, 0))
slices = []
layer = 0
for step in count():
offset = utils.spiral(step)
layer = max(layer, *offset)
offset = utils.pmul(offset, 3)
check = utils.padd(peak, offset)
check = utils.padd(check, (0, layer, 0))
if y_limit and check[1] - center[1] > y_limit:
break
if utils.phyp_king(center, check) > distance:
break
if self.check_sand_slice(check) and check not in bad_slices:
slices.append(check)
if len(slices):
return v, slices[-1]
elif v > 40:
return None, None
def find_bed_openings(self, area):
# returns coords in a cardinal direction where we can stand by bed
result = []
for direction in path.CHECK_DIRECTIONS:
result.append(utils.padd(area, direction))
return result
def check_bed_occupied(self, bed):
# returns true if the bed is occupied by a player
bid = self.g.chunks.get_block_at(*bed)
if blocks.PROPS[bid]['occupied'] == 'true':
print('Checking bed occupancy:', bed, '-> occupied')
return True
else:
print('Checking bed occupancy:', bed, '-> free')
return False
def find_cache_openings(self, area):
return self.find_bed_openings(area)
def find_objects(self, object_ids):
result = []
for eid, obj in copy(self.g.objects).items():
if obj.get('item_id', None) in object_ids:
result.append(obj)
return result
def find_leaves(self, center, distance):
for a in self.find_blocks_3d(center, blocks.LEAF_IDS, distance, 10):
yield a
def find_monsters(self, center, distance):
# finds monsters within distance
result = []
for eid, mob in copy(self.g.mobs).items():
if mob.type not in mobs.EVIL_IDS:
continue
pos = utils.pint((mob.x, mob.y, mob.z))
if utils.phyp(center, pos) > distance:
continue
result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result
def find_threats(self, center, distance):
# finds monsters on the surface within distance
monsters = self.find_monsters(center, distance)
result = []
for mob in monsters:
pos = utils.pint((mob.x, mob.y, mob.z))
# check distance number of blocks above, close enough?
if not self.check_air_column(pos, distance):
continue
result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result
def find_villagers(self, center, distance):
# finds villagers within distance
result = []
for eid, mob in copy(self.g.mobs).items():
type_name = mobs.MOB_NAMES[mob.type]
if type_name != 'villager' : continue
pos = utils.pint((mob.x, mob.y, mob.z))
if utils.phyp(center, pos) > distance:
continue
result.append(mob)
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
return result
def find_villager_openings(self, villager):
# returns coords in a cardinal direction where we can stand by a villager
maze_solver = path.Pathfinder(self.g)
result = []
for distance in range(3):
for direction in path.CHECK_DIRECTIONS:
offset = utils.pmul(direction, distance+1)
if not maze_solver.check_traverse(villager, offset):
continue
# check for line of sight
for check in range(distance+1):
offset2 = utils.pmul(direction, check+1)
offset2 = utils.padd(offset2, path.BLOCK_ABOVE)
check = utils.padd(villager, offset2)
if self.block_at(*check) not in blocks.NON_SOLID_IDS:
break
else: # for
result.append(utils.padd(villager, offset))
return result

View File

@@ -1,40 +1,45 @@
appdirs==1.4.3 appdirs==1.4.4
astar==0.92 astar==0.93
CacheControl==0.12.6 CacheControl==0.12.6
certifi==2020.6.20 certifi==2020.12.5
cffi==1.14.2 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.1 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
html5lib==1.1
idna==2.10 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.2 Jinja2==2.11.3
lockfile==0.12.2 lockfile==0.12.2
MarkupSafe==1.1.1 MarkupSafe==1.1.1
minecraft-data==2.70.1 minecraft-data==2.82.2
msgpack==0.6.2 msgpack==1.0.2
munch==2.5.0 munch==2.5.0
packaging==20.3 mutf8==1.0.3
panda3d==1.10.6.post2 packaging==20.9
pathtools==0.1.2 pep517==0.10.0
pep517==0.8.2 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.0.0 PyNBT==3.1.0
pyparsing==2.4.6 pyparsing==2.4.7
pytoml==0.1.21 pytoml==0.1.21
requests==2.24.0 requests==2.25.1
retrying==1.3.3 retrying==1.3.3
six==1.15.0 six==1.15.0
urllib3==1.25.10 toml==0.10.2
watchdog==0.10.3 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

79
run_linux.sh Executable file
View File

@@ -0,0 +1,79 @@
#!/bin/bash
set -eu
if [ ! -d "mosfet" ]
then
echo "You must run this from the mosfet-minecraft-bot directory."
exit
fi
if ! command -v virtualenv &> /dev/null
then
echo "virtualenv could not be found, please install with:"
echo "sudo apt install virtualenv"
exit
fi
if ! command -v wget &> /dev/null
then
echo "wget could not be found, please install with:"
echo "sudo apt install wget"
exit
fi
if ! command -v unzip &> /dev/null
then
echo "unzip could not be found, please install with:"
echo "sudo apt install unzip"
exit
fi
if ! command -v python3 &> /dev/null
then
echo "python3 could not be found, please install with:"
echo "sudo apt install python3"
exit
fi
if ! command -v pip3 &> /dev/null
then
echo "pip3 could not be found, please install with:"
echo "sudo apt install pip3"
exit
fi
# download minecraft data
if [ ! -d "minecraft_data" ]
then
echo "Grabbing minecraft data..."
VERSION="1.16.4"
wget -Omcdata.zip "https://apimon.de/mcdata/$VERSION/$VERSION.zip"
mkdir minecraft_data
unzip mcdata.zip -d minecraft_data
rm mcdata.zip
fi
# https://github.com/pypa/virtualenv/issues/1029
PS1=${PS1:-}
# create virtual environment
if [ ! -d "env" ]
then
echo "Installing Python requirements..."
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt
else
source env/bin/activate
fi
echo "Running bot..."
python main.py

View File

@@ -1,157 +0,0 @@
#!/usr/bin/env python
import getpass
import sys
import re
from optparse import OptionParser
from custom.managers import DataManager, ChunksManager
from custom.networking.packets.clientbound.play import chunk_data, block_change_packet
import minecraft.networking.packets
def get_packets(old_get_packets):
def wrapper(func, context):
print('Monkey-patched.')
packets = func(context)
packets.add(chunk_data.ChunkDataPacket)
packets.add(block_change_packet.BlockChangePacket)
packets.add(block_change_packet.MultiBlockChangePacket)
return packets
return lambda x: wrapper(old_get_packets, x)
minecraft.networking.packets.clientbound.play.get_packets = get_packets(minecraft.networking.packets.clientbound.play.get_packets)
from minecraft import authentication
from minecraft.exceptions import YggdrasilError
from minecraft.networking.connection import Connection
from minecraft.networking.packets import Packet, clientbound, serverbound
def get_options():
parser = OptionParser()
parser.add_option("-u", "--username", dest="username", default=None,
help="username to log in with")
parser.add_option("-p", "--password", dest="password", default=None,
help="password to log in with")
parser.add_option("-s", "--server", dest="server", default=None,
help="server host or host:port "
"(enclose IPv6 addresses in square brackets)")
parser.add_option("-o", "--offline", dest="offline", action="store_true",
help="connect to a server in offline mode "
"(no password required)")
parser.add_option("-d", "--dump-packets", dest="dump_packets",
action="store_true",
help="print sent and received packets to standard error")
parser.add_option("-v", "--dump-unknown-packets", dest="dump_unknown",
action="store_true",
help="include unknown packets in --dump-packets output")
(options, args) = parser.parse_args()
if not options.username:
options.username = input("Enter your username: ")
if not options.password and not options.offline:
options.password = getpass.getpass("Enter your password (leave "
"blank for offline mode): ")
options.offline = options.offline or (options.password == "")
if not options.server:
options.server = input("Enter server host or host:port "
"(enclose IPv6 addresses in square brackets): ")
# Try to split out port and address
match = re.match(r"((?P<host>[^\[\]:]+)|\[(?P<addr>[^\[\]]+)\])"
r"(:(?P<port>\d+))?$", options.server)
if match is None:
raise ValueError("Invalid server address: '%s'." % options.server)
options.address = match.group("host") or match.group("addr")
options.port = int(match.group("port") or 25565)
return options
def main():
options = get_options()
mcdata = DataManager('./mcdata')
if options.offline:
print("Connecting in offline mode...")
connection = Connection(
options.address, options.port, username=options.username)
else:
auth_token = authentication.AuthenticationToken()
try:
auth_token.authenticate(options.username, options.password)
except YggdrasilError as e:
print(e)
sys.exit()
print("Logged in as %s..." % auth_token.username)
connection = Connection(
options.address, options.port, auth_token=auth_token)
if options.dump_packets:
def print_incoming(packet):
if type(packet) is Packet:
# This is a direct instance of the base Packet type, meaning
# that it is a packet of unknown type, so we do not print it
# unless explicitly requested by the user.
if options.dump_unknown:
print('--> [unknown packet] %s' % packet, file=sys.stderr)
else:
print('--> %s' % packet, file=sys.stderr)
def print_outgoing(packet):
print('<-- %s' % packet, file=sys.stderr)
connection.register_packet_listener(
print_incoming, Packet, early=True)
connection.register_packet_listener(
print_outgoing, Packet, outgoing=True)
def handle_join_game(join_game_packet):
print('Connected.')
connection.register_packet_listener(
handle_join_game, clientbound.play.JoinGamePacket)
def print_chat(chat_packet):
print("Message (%s): %s" % (
chat_packet.field_string('position'), chat_packet.json_data))
chunks = ChunksManager(mcdata)
chunks.register(connection)
connection.register_packet_listener(
print_chat, clientbound.play.ChatMessagePacket)
connection.connect()
while True:
try:
text = input()
if text == "/respawn":
print("respawning...")
packet = serverbound.play.ClientStatusPacket()
packet.action_id = serverbound.play.ClientStatusPacket.RESPAWN
connection.write_packet(packet)
elif text == '!test':
print(chunks.get_block_at(91, 65, 57))
else:
packet = serverbound.play.ChatPacket()
packet.message = text
connection.write_packet(packet)
except KeyboardInterrupt:
print("Bye!")
sys.exit()
if __name__ == "__main__":
main()

88
update_linux.sh Executable file
View File

@@ -0,0 +1,88 @@
#!/bin/bash
set -eu
if [ ! -d "mosfet" ]
then
echo "You must run this from the mosfet-minecraft-bot directory."
exit
fi
if ! command -v virtualenv &> /dev/null
then
echo "virtualenv could not be found, please install with:"
echo "sudo apt install virtualenv"
exit
fi
if ! command -v wget &> /dev/null
then
echo "wget could not be found, please install with:"
echo "sudo apt install wget"
exit
fi
if ! command -v unzip &> /dev/null
then
echo "unzip could not be found, please install with:"
echo "sudo apt install unzip"
exit
fi
if ! command -v python3 &> /dev/null
then
echo "python3 could not be found, please install with:"
echo "sudo apt install python3"
exit
fi
if ! command -v pip3 &> /dev/null
then
echo "pip3 could not be found, please install with:"
echo "sudo apt install pip3"
exit
fi
if ! command -v git &> /dev/null
then
echo "git could not be found, please install with:"
echo "sudo apt install pip3"
exit
fi
# download minecraft data
echo "Grabbing minecraft data..."
VERSION="1.16.4"
wget -Omcdata.zip "https://apimon.de/mcdata/$VERSION/$VERSION.zip"
rm -r minecraft_data || true
mkdir minecraft_data
unzip mcdata.zip -d minecraft_data
rm mcdata.zip
# update code
git stash
git pull --rebase
git stash pop || true
# https://github.com/pypa/virtualenv/issues/1029
PS1=${PS1:-}
# create virtual environment
echo "Installing Python requirements..."
rm -r env || true
rm -r __pycache__ || true
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt
echo "Done."

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