Compare commits
27 Commits
ad6c412802
...
master
Author | SHA1 | Date | |
---|---|---|---|
bbe2d9f99c | |||
2f77c7b990 | |||
3540e5580e | |||
261684ea90 | |||
09b09b3f96 | |||
4d2d358175 | |||
324ad41bf7 | |||
343268af24 | |||
e5fd062a4a | |||
5184d4a173 | |||
396c1e2e33 | |||
217bb0dae0 | |||
f7400a1225 | |||
3c31c41acd | |||
15d4dd7922 | |||
8f0d2eb417 | |||
6315f8c309 | |||
2fc08be581 | |||
c39a8c6d93 | |||
43e8bf97a0 | |||
b59c260e90 | |||
a41d9f2793 | |||
0d2be1378f | |||
0d23dbaf9f | |||
a87cc85eab | |||
080895421d | |||
e4ea9aeaa0 |
51
README.md
51
README.md
@@ -1,18 +1,16 @@
|
|||||||
# Mosfet Minecraft Bot
|
# Mosfet Minecraft Bot
|
||||||
|
|
||||||
A general-purpose Minecraft 1.16 bot written in Python.
|
A general-purpose Minecraft 1.16 bot written in Python that uses an actual
|
||||||
|
Minecraft account to play.
|
||||||
|
|
||||||
Mosfet is able to farm wood by cutting trees, gather sand, gather netherwart,
|
Mosfet is able to farm wood by cutting trees, farm crops, gather sand, farm
|
||||||
and trade with villagers to get emeralds. He can eat, sleep, and flee from
|
netherwart, and trade with villagers to get emeralds. He can eat, sleep, and
|
||||||
threats.
|
flee from threats.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Python >= 3.6
|
- Python >= 3.6
|
||||||
- pip
|
- pip, virtualenv, git, wget, unzip
|
||||||
- virtualenv
|
|
||||||
- git
|
|
||||||
- wget
|
|
||||||
- unzip
|
|
||||||
|
|
||||||
## Linux Setup
|
## Linux Setup
|
||||||
|
|
||||||
@@ -28,15 +26,38 @@ $ cd minecraft-bot/
|
|||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
If you want to use the built-in burner account (Minecraft name `mattstack`):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ USERNAME=you@domain.com PASSWORD=supersecret SERVER=example.com ./run_linux.sh
|
$ SERVER=minecraft.example.com ./run_linux.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or, use `PORT` to specify a custom port to connect to:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ SERVER=localhost PORT=12345 ./run_linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you have your own alt account for the bot:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
[How to Cache Items](docs/cache_items.md)
|
||||||
|
|
||||||
|
[How to Grab Supplies](docs/grab_supplies.md)
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
The command prefix character is the last character of the bot's name. For
|
The command prefix character is the last character of the bot's name. For
|
||||||
example, if the bot's name is `mosfet1`, then you would issue commands like
|
example, if the bot's name is `mattstack`, then you would issue commands like
|
||||||
`1farm wood` or `1pos`. This lets you run multiple bots on the same server.
|
`kfarm wood` or `kpos`. This lets you run multiple bots on the same server.
|
||||||
|
|
||||||
|
In the following examples, we'll assume the bot's name is `mosfet1`, so commands
|
||||||
|
would be ran like `1farm wood` or `1pos`.
|
||||||
|
|
||||||
The exception are the below public commands, they can optionally be prefixed with `!`
|
The exception are the below public commands, they can optionally be prefixed with `!`
|
||||||
and all bots will run the command.
|
and all bots will run the command.
|
||||||
@@ -67,7 +88,7 @@ These can be ran by anyone, all bots will reply.
|
|||||||
|
|
||||||
`!error` - raises an error
|
`!error` - raises an error
|
||||||
|
|
||||||
`!inv` - prints current inventory
|
`!inv` - replies and prints current inventory
|
||||||
|
|
||||||
`!time` - replies with Minecraft world time
|
`!time` - replies with Minecraft world time
|
||||||
|
|
||||||
@@ -103,7 +124,7 @@ These can be ran by anyone, all bots will reply.
|
|||||||
|
|
||||||
### Bot-specific Commands
|
### Bot-specific Commands
|
||||||
|
|
||||||
These will only run for the bot they are addressed to.
|
These will only run for the bot they are addressed to. Replace `1` with the last character of the bot's name.
|
||||||
|
|
||||||
`1respawn` - respawns the bot if it's dead
|
`1respawn` - respawns the bot if it's dead
|
||||||
|
|
||||||
@@ -168,4 +189,4 @@ put to use.
|
|||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
Thanks to Isaia and the devs behind pyCraft.
|
Thanks to Isaia, sose, and the devs behind pyCraft.
|
||||||
|
28
docs/cache_items.md
Normal file
28
docs/cache_items.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Mosfet Minecraft Bot
|
||||||
|
|
||||||
|
## How to Cache Items
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
30
docs/grab_supplies.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Mosfet Minecraft Bot
|
||||||
|
|
||||||
|
## How to Grab Supplies
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
BIN
docs/media/cache_items.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 525 KiB |
BIN
docs/media/grab_supplies.png
Normal file
BIN
docs/media/grab_supplies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 490 KiB |
20
main.py
20
main.py
@@ -71,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:
|
||||||
@@ -91,14 +91,22 @@ def main():
|
|||||||
observer.stop()
|
observer.stop()
|
||||||
observer.join()
|
observer.join()
|
||||||
|
|
||||||
|
def run_api():
|
||||||
if __name__ == '__main__':
|
|
||||||
host = '0.0.0.0'
|
host = '0.0.0.0'
|
||||||
port = 3300
|
port = 3300
|
||||||
|
|
||||||
threading.Thread(target=app.run, kwargs={'host': host, 'port': port}).start()
|
while True:
|
||||||
print('Web interface listening on port:', port)
|
print('Trying to run web interface on port:', port)
|
||||||
print('Try going to http://localhost:' + str(port))
|
print('If it works, go to http://localhost:' + str(port))
|
||||||
|
try:
|
||||||
|
app.run(host=host, port=port)
|
||||||
|
except OSError:
|
||||||
|
print()
|
||||||
|
print('Error: Port already taken.')
|
||||||
|
port += 1
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
threading.Thread(target=run_api).start()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
@@ -9,11 +9,6 @@ import importlib
|
|||||||
from math import floor, ceil
|
from math import floor, ceil
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
USERNAME = os.getenv('USERNAME')
|
|
||||||
PASSWORD = os.getenv('PASSWORD')
|
|
||||||
SERVER = os.getenv('SERVER')
|
|
||||||
PORT = int(os.environ.get('PORT', 25565))
|
|
||||||
|
|
||||||
from . import monkey_patch # must be before any possible pyCraft imports
|
from . import monkey_patch # must be before any possible pyCraft imports
|
||||||
|
|
||||||
from minecraft import authentication
|
from minecraft import authentication
|
||||||
@@ -70,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
|
||||||
|
|
||||||
@@ -245,27 +238,57 @@ def init(global_state):
|
|||||||
g.maximum_supply_slots = 33
|
g.maximum_supply_slots = 33
|
||||||
|
|
||||||
def bot(global_state):
|
def bot(global_state):
|
||||||
|
EMAIL = os.getenv('EMAIL')
|
||||||
|
PASSWORD = os.getenv('PASSWORD')
|
||||||
|
SERVER = os.getenv('SERVER')
|
||||||
|
PORT = int(os.environ.get('PORT', 25565))
|
||||||
|
|
||||||
g = global_state
|
g = global_state
|
||||||
|
|
||||||
if not g.mcdata:
|
if not g.mcdata:
|
||||||
g.mcdata = DataManager('./minecraft_data')
|
g.mcdata = DataManager('./minecraft_data')
|
||||||
|
|
||||||
if not SERVER:
|
if not SERVER:
|
||||||
print('You must specify a server to connect to.')
|
print()
|
||||||
sys.exit()
|
print('You must specify a server to connect to. For example:')
|
||||||
|
print('SERVER=minecraft.example.com ./run_linux.sh')
|
||||||
|
print('SERVER=localhost PORT=12345 ./run_linux.sh')
|
||||||
|
print()
|
||||||
|
print('If you want to use your own account:')
|
||||||
|
print('EMAIL=you@domain.com PASSWORD=supersecret SERVER=minecraft.example.com ./run_linux.sh')
|
||||||
|
os._exit(0)
|
||||||
elif not g.connection:
|
elif not g.connection:
|
||||||
if USERNAME and PASSWORD:
|
if EMAIL and PASSWORD:
|
||||||
auth_token = authentication.AuthenticationToken()
|
auth_token = authentication.AuthenticationToken()
|
||||||
try:
|
try:
|
||||||
auth_token.authenticate(USERNAME, PASSWORD)
|
auth_token.authenticate(EMAIL, PASSWORD)
|
||||||
except YggdrasilError as e:
|
except YggdrasilError as e:
|
||||||
print(e)
|
print(e)
|
||||||
sys.exit()
|
os._exit(0)
|
||||||
print("Logged in as %s..." % auth_token.username)
|
print("Logged in as %s..." % auth_token.username)
|
||||||
g.connection = Connection(SERVER, PORT, auth_token=auth_token)
|
g.connection = Connection(SERVER, PORT, auth_token=auth_token)
|
||||||
elif USERNAME:
|
elif EMAIL:
|
||||||
print('No password provided, attempting to connect in offline mode...')
|
print('No password provided, attempting to connect in offline mode...')
|
||||||
g.connection = Connection(SERVER, PORT, username=USERNAME)
|
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)
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import random
|
import random
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from munch import Munch
|
from munch import Munch
|
||||||
@@ -21,11 +22,12 @@ class Commands:
|
|||||||
self.g.chat.set_handler(self.handle_chat)
|
self.g.chat.set_handler(self.handle_chat)
|
||||||
|
|
||||||
def handle_chat(self, message):
|
def handle_chat(self, message):
|
||||||
source, text = message
|
source, sender, text = message
|
||||||
reply = None
|
reply = ''
|
||||||
private = False
|
private = False
|
||||||
for_me = False
|
for_me = False
|
||||||
authed = False
|
authed = sender == '0c123cfa-1697-4427-9413-4b645dee7ec0'
|
||||||
|
bot_num = self.g.name[-1]
|
||||||
|
|
||||||
if source == 'SYSTEM':
|
if source == 'SYSTEM':
|
||||||
self.g.command_lock = False
|
self.g.command_lock = False
|
||||||
@@ -35,40 +37,23 @@ class Commands:
|
|||||||
elif text == 'You are no longer AFK.':
|
elif text == 'You are no longer AFK.':
|
||||||
self.g.afk = False
|
self.g.afk = False
|
||||||
|
|
||||||
match1 = re.match(r'.*<(\w+)> (.*)', text)
|
text = text.replace('zzz', '!zzz')
|
||||||
match2 = re.match(r'\[(\w+) -> me] (.*)', text)
|
|
||||||
if match1:
|
match = re.match(r'(.*\W+)\s+(['+bot_num+'|!])(\w+) ?(.*)', text)
|
||||||
sender, text = match1.groups()
|
if match:
|
||||||
elif match2:
|
meta, prefix, command, data = match.groups()
|
||||||
sender, text = match2.groups()
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if '-> me' in meta:
|
||||||
private = True
|
private = True
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
if sender == 'tanner6':
|
if prefix == bot_num:
|
||||||
authed = True
|
|
||||||
|
|
||||||
if text.startswith('zzz'):
|
|
||||||
text = '!zzz'
|
|
||||||
|
|
||||||
bot_num = self.g.name[-1]
|
|
||||||
|
|
||||||
if text.startswith(bot_num):
|
|
||||||
text = text[1:]
|
|
||||||
for_me = True
|
for_me = True
|
||||||
elif text.startswith('! '):
|
|
||||||
text = text[2:]
|
|
||||||
elif text.startswith('!'):
|
|
||||||
text = text[1:]
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
if ' ' in text:
|
if data.startswith('[') and data.endswith(']'):
|
||||||
command = text.split(' ', 1)[0]
|
command = 'nosquarebrackets'
|
||||||
data = text.split(' ', 1)[1]
|
|
||||||
else:
|
|
||||||
command = text
|
|
||||||
data = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
@@ -100,6 +85,9 @@ class Commands:
|
|||||||
if command == 'echo' and data:
|
if command == 'echo' and data:
|
||||||
reply = data
|
reply = data
|
||||||
|
|
||||||
|
if command == 'nosquarebrackets':
|
||||||
|
reply = 'don\'t literally put the [ ]'
|
||||||
|
|
||||||
## !pos - replies with position and dimension
|
## !pos - replies with position and dimension
|
||||||
if command == 'pos':
|
if command == 'pos':
|
||||||
reply = str(utils.pint(self.g.pos))[1:-1] + ', ' + self.g.dimension
|
reply = str(utils.pint(self.g.pos))[1:-1] + ', ' + self.g.dimension
|
||||||
@@ -119,19 +107,44 @@ class Commands:
|
|||||||
reply = 'ok'
|
reply = 'ok'
|
||||||
raise
|
raise
|
||||||
|
|
||||||
## !inv - prints current inventory
|
## !inv - replies and prints current inventory
|
||||||
if command == 'inv':
|
if command == 'inv' or command == 'inventory':
|
||||||
inv_list = []
|
inv_list = []
|
||||||
|
uniq_item_counts = {}
|
||||||
for i in self.g.inv.values():
|
for i in self.g.inv.values():
|
||||||
if i.present:
|
if i.present:
|
||||||
inv_list.append('{}:{} x {}'.format(items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count))
|
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()
|
inv_list.sort()
|
||||||
result = '\n'.join(inv_list)
|
console_result = '\n'.join(['{}:{} x {}'.format(*x) for x in inv_list])
|
||||||
print(result or 'Empty')
|
|
||||||
|
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
|
## !time - replies with Minecraft world time
|
||||||
if command == 'time':
|
if command == 'time':
|
||||||
reply = str(self.g.time)
|
seconds = self.g.time * 3.6
|
||||||
|
start = datetime(2000, 1, 1, hour=6)
|
||||||
|
mctime = start + timedelta(seconds=seconds)
|
||||||
|
reply = str(self.g.time) + ' - ' + mctime.strftime('%I:%M %p')
|
||||||
|
|
||||||
## !count [id] - counts the number of items with that id
|
## !count [id] - counts the number of items with that id
|
||||||
if command == 'count' and data:
|
if command == 'count' and data:
|
||||||
@@ -140,7 +153,9 @@ class Commands:
|
|||||||
|
|
||||||
## !loaded - replies with the current loaded area
|
## !loaded - replies with the current loaded area
|
||||||
if command == 'loaded':
|
if command == 'loaded':
|
||||||
reply = str(self.g.chunks.get_loaded_area())
|
l = self.g.chunks.get_loaded_area()
|
||||||
|
reply = '{}, {}, {} to {}, {}, {}'.format(*l[0], *l[1])
|
||||||
|
reply += ' - ' + str(len(self.g.chunks.chunks)//16) + ' chunks'
|
||||||
|
|
||||||
## !players - prints the current players
|
## !players - prints the current players
|
||||||
## !players clear - clears the current player list
|
## !players clear - clears the current player list
|
||||||
@@ -229,23 +244,67 @@ class Commands:
|
|||||||
tree = next(self.g.world.find_trees(pos, 50))
|
tree = next(self.g.world.find_trees(pos, 50))
|
||||||
reply = str(tree)[1:-1]
|
reply = str(tree)[1:-1]
|
||||||
|
|
||||||
## !block x y z - replies what block is at (x, y, z)
|
## !info [query] - replies with info on a coordinate, block, item, or player
|
||||||
if command == 'block':
|
if command == 'info':
|
||||||
try:
|
if not reply:
|
||||||
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
|
try:
|
||||||
x1, y1, z1 = [int(x) for x in data.split()]
|
check = data.replace('(', ' ').replace(')', ' ').replace(',', ' ')
|
||||||
except (AttributeError, ValueError):
|
x1, y1, z1 = [int(x) for x in check.split()]
|
||||||
reply = 'usage: !block x1 y1 z1'
|
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:
|
if not reply:
|
||||||
coord = (x1, y1, z1)
|
try:
|
||||||
block = self.g.world.block_at(*coord)
|
check = int(data)
|
||||||
|
|
||||||
if not reply and block is None:
|
if check in blocks.BLOCKS:
|
||||||
reply = 'first coord out of range'
|
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 not reply:
|
if check in blocks.BLOCKS and check in items.ITEM_NAMES:
|
||||||
reply = blocks.BLOCKS[block] + ':' + str(block)
|
reply += ' / '
|
||||||
|
|
||||||
|
if check in items.ITEM_NAMES:
|
||||||
|
item = check
|
||||||
|
reply += 'Item: ' + items.ITEM_NAMES[item] + ':' + str(item)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
check = data.lower()
|
||||||
|
if not reply and check in self.g.player_names:
|
||||||
|
uuid = self.g.player_names[check]
|
||||||
|
|
||||||
|
for p in self.g.players.values():
|
||||||
|
if p.player_uuid == uuid:
|
||||||
|
player = p
|
||||||
|
break
|
||||||
|
else: # for
|
||||||
|
reply = 'player out of range'
|
||||||
|
|
||||||
|
if not reply:
|
||||||
|
reply += 'Player: '
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for k, v in player.items():
|
||||||
|
try:
|
||||||
|
results.append('{}:{}'.format(k, int(v)))
|
||||||
|
except ValueError:
|
||||||
|
results.append('{}:{}'.format(k, str(v)))
|
||||||
|
|
||||||
|
reply += ', '.join(results)
|
||||||
|
|
||||||
|
|
||||||
################# Specific commands ##########################
|
################# Specific commands ##########################
|
||||||
@@ -391,14 +450,9 @@ class Commands:
|
|||||||
|
|
||||||
## 1here - bot comes to your location
|
## 1here - bot comes to your location
|
||||||
if command == 'here':
|
if command == 'here':
|
||||||
try:
|
|
||||||
sender_uuid = self.g.player_names[sender]
|
|
||||||
except KeyError:
|
|
||||||
reply = 'can\'t find your uuid'
|
|
||||||
|
|
||||||
if not reply:
|
if not reply:
|
||||||
for p in self.g.players.values():
|
for p in self.g.players.values():
|
||||||
if p.player_uuid == sender_uuid:
|
if p.player_uuid == sender:
|
||||||
player = p
|
player = p
|
||||||
break
|
break
|
||||||
else: # for
|
else: # for
|
||||||
@@ -525,7 +579,7 @@ class Commands:
|
|||||||
reply = 'reply too long, check console'
|
reply = 'reply too long, check console'
|
||||||
|
|
||||||
if private and not reply.startswith('/'):
|
if private and not reply.startswith('/'):
|
||||||
self.g.chat.send('/m ' + sender + ' ' + reply)
|
self.g.chat.send('/r ' + reply)
|
||||||
else:
|
else:
|
||||||
self.g.chat.send(reply)
|
self.g.chat.send(reply)
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ from mosfet.protocol.packets import (
|
|||||||
ClientWindowConfirmationPacket, EntityMetadataPacket,
|
ClientWindowConfirmationPacket, EntityMetadataPacket,
|
||||||
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
|
SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket,
|
||||||
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
|
EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket,
|
||||||
SelectTradePacket, DisconnectPacket,
|
SelectTradePacket, DisconnectPacket, UnloadChunkPacket,
|
||||||
)
|
)
|
||||||
|
|
||||||
from mosfet.protocol.types import Slot
|
from mosfet.protocol.types import Slot
|
||||||
@@ -49,6 +49,7 @@ class Game:
|
|||||||
register(self.handle_spawn_living, SpawnLivingEntityPacket)
|
register(self.handle_spawn_living, SpawnLivingEntityPacket)
|
||||||
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
|
register(self.handle_entity_position, clientbound.play.EntityPositionDeltaPacket)
|
||||||
register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
|
register(self.handle_entity_position_rotation, EntityPositionRotationPacket)
|
||||||
|
register(self.handle_entity_look, clientbound.play.EntityLookPacket)
|
||||||
register(self.handle_destroy_entities, DestroyEntitiesPacket)
|
register(self.handle_destroy_entities, DestroyEntitiesPacket)
|
||||||
register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket)
|
register(self.handle_spawn_player, clientbound.play.SpawnPlayerPacket)
|
||||||
register(self.handle_respawn, clientbound.play.RespawnPacket)
|
register(self.handle_respawn, clientbound.play.RespawnPacket)
|
||||||
@@ -58,6 +59,7 @@ class Game:
|
|||||||
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
|
#register(self.handle_entity_velocity, clientbound.play.EntityVelocityPacket)
|
||||||
register(self.handle_trade_list, TradeListPacket)
|
register(self.handle_trade_list, TradeListPacket)
|
||||||
register(self.handle_disconnect, DisconnectPacket)
|
register(self.handle_disconnect, DisconnectPacket)
|
||||||
|
register(self.handle_unload_chunk, UnloadChunkPacket)
|
||||||
|
|
||||||
#register(self.handle_packet, Packet, early=True)
|
#register(self.handle_packet, Packet, early=True)
|
||||||
|
|
||||||
@@ -393,6 +395,13 @@ class Game:
|
|||||||
obj.item_id = entry.value.item_id
|
obj.item_id = entry.value.item_id
|
||||||
obj.item_count = entry.value.item_count
|
obj.item_count = entry.value.item_count
|
||||||
|
|
||||||
|
mob = self.g.mobs.get(packet.entity_id, None)
|
||||||
|
if mob:
|
||||||
|
for entry in packet.metadata:
|
||||||
|
if mob.type == mobs.VILLAGER:
|
||||||
|
if entry.index == 17:
|
||||||
|
mob.profession = entry.value[1]
|
||||||
|
|
||||||
player = self.g.players.get(packet.entity_id, None)
|
player = self.g.players.get(packet.entity_id, None)
|
||||||
if player:
|
if player:
|
||||||
return
|
return
|
||||||
@@ -434,8 +443,14 @@ class Game:
|
|||||||
player.x += packet.delta_x / 4096.0
|
player.x += packet.delta_x / 4096.0
|
||||||
player.y += packet.delta_y / 4096.0
|
player.y += packet.delta_y / 4096.0
|
||||||
player.z += packet.delta_z / 4096.0
|
player.z += packet.delta_z / 4096.0
|
||||||
|
player.yaw = packet.yaw
|
||||||
|
player.pitch = packet.pitch
|
||||||
|
|
||||||
#if player.player_uuid == '0c123cfa-1697-4427-9413-4b645dee7ec0': print(packet)
|
def handle_entity_look(self, packet):
|
||||||
|
player = self.g.players.get(packet.entity_id, None)
|
||||||
|
if player:
|
||||||
|
player.yaw = packet.yaw
|
||||||
|
player.pitch = packet.pitch
|
||||||
|
|
||||||
def handle_entity_teleport(self, packet):
|
def handle_entity_teleport(self, packet):
|
||||||
mob = self.g.mobs.get(packet.entity_id, None)
|
mob = self.g.mobs.get(packet.entity_id, None)
|
||||||
@@ -479,12 +494,14 @@ class Game:
|
|||||||
def handle_respawn(self, packet):
|
def handle_respawn(self, packet):
|
||||||
print(packet)
|
print(packet)
|
||||||
self.g.dimension = packet.world_name.replace('minecraft:', '')
|
self.g.dimension = packet.world_name.replace('minecraft:', '')
|
||||||
|
self.g.chunks.unload_all_chunks()
|
||||||
|
|
||||||
def handle_player_list(self, packet):
|
def handle_player_list(self, packet):
|
||||||
for action in packet.actions:
|
for action in packet.actions:
|
||||||
if isinstance(action, packet.AddPlayerAction):
|
if isinstance(action, packet.AddPlayerAction):
|
||||||
self.g.player_names[action.uuid] = action.name
|
self.g.player_names[action.uuid] = action.name
|
||||||
self.g.player_names[action.name] = action.uuid # porque no los dos?
|
self.g.player_names[action.name] = action.uuid
|
||||||
|
self.g.player_names[action.name.lower()] = action.uuid # porque no los dos?
|
||||||
|
|
||||||
def handle_update_health(self, packet):
|
def handle_update_health(self, packet):
|
||||||
print(packet)
|
print(packet)
|
||||||
@@ -525,6 +542,9 @@ class Game:
|
|||||||
import os
|
import os
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
|
def handle_unload_chunk(self, packet):
|
||||||
|
self.g.chunks.unload_chunk(packet.chunk_x, packet.chunk_z)
|
||||||
|
|
||||||
def tick(self):
|
def tick(self):
|
||||||
if self.g.breaking:
|
if self.g.breaking:
|
||||||
self.animate()
|
self.animate()
|
||||||
|
@@ -15,6 +15,11 @@ for name, data in JSON_BLOCKS.items():
|
|||||||
for state in data['states']:
|
for state in data['states']:
|
||||||
BLOCKS[state['id']] = name.replace('minecraft:', '')
|
BLOCKS[state['id']] = name.replace('minecraft:', '')
|
||||||
|
|
||||||
|
PROPS = {}
|
||||||
|
for name, data in JSON_BLOCKS.items():
|
||||||
|
for state in data['states']:
|
||||||
|
PROPS[state['id']] = state.get('properties', {})
|
||||||
|
|
||||||
BREAK_DISTANCE = 6
|
BREAK_DISTANCE = 6
|
||||||
|
|
||||||
AIR = 0
|
AIR = 0
|
||||||
|
@@ -103,6 +103,7 @@ WHEAT_ID = get_id('wheat')
|
|||||||
WHEAT_SEEDS_ID = get_id('wheat_seeds')
|
WHEAT_SEEDS_ID = get_id('wheat_seeds')
|
||||||
BEETROOT_SEEDS_ID = get_id('beetroot_seeds')
|
BEETROOT_SEEDS_ID = get_id('beetroot_seeds')
|
||||||
PUMPKIN_ID = get_id('pumpkin')
|
PUMPKIN_ID = get_id('pumpkin')
|
||||||
|
BEETROOT_ID = get_id('beetroot')
|
||||||
|
|
||||||
EMERALD_ID = get_id('emerald')
|
EMERALD_ID = get_id('emerald')
|
||||||
BERRIES_ID = get_id('sweet_berries')
|
BERRIES_ID = get_id('sweet_berries')
|
||||||
|
@@ -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',
|
||||||
|
@@ -235,6 +235,8 @@ class JobStates:
|
|||||||
tuple([items.IRON_INGOT_ID]): (64, 3),
|
tuple([items.IRON_INGOT_ID]): (64, 3),
|
||||||
tuple([items.WHEAT_ID]): (64, 3),
|
tuple([items.WHEAT_ID]): (64, 3),
|
||||||
tuple([items.POTATO_ID]): (64, 3),
|
tuple([items.POTATO_ID]): (64, 3),
|
||||||
|
tuple([items.CARROT_ID]): (64, 3),
|
||||||
|
tuple([items.BEETROOT_ID]): (64, 3),
|
||||||
}
|
}
|
||||||
|
|
||||||
items.set_needed(set([
|
items.set_needed(set([
|
||||||
@@ -243,6 +245,8 @@ class JobStates:
|
|||||||
items.IRON_INGOT_ID,
|
items.IRON_INGOT_ID,
|
||||||
items.WHEAT_ID,
|
items.WHEAT_ID,
|
||||||
items.POTATO_ID,
|
items.POTATO_ID,
|
||||||
|
items.CARROT_ID,
|
||||||
|
items.BEETROOT_ID,
|
||||||
]))
|
]))
|
||||||
return machines
|
return machines
|
||||||
|
|
||||||
|
@@ -72,15 +72,7 @@ class GatherCropStates:
|
|||||||
|
|
||||||
def break_crop(self):
|
def break_crop(self):
|
||||||
self.g.game.break_block(self.crop)
|
self.g.game.break_block(self.crop)
|
||||||
self.wait_time = 0.5
|
self.state = self.select_seed
|
||||||
self.state = self.wait
|
|
||||||
|
|
||||||
def wait(self):
|
|
||||||
# wait for the item
|
|
||||||
if self.wait_time > 0:
|
|
||||||
self.wait_time -= utils.TICK
|
|
||||||
else:
|
|
||||||
self.state = self.select_seed
|
|
||||||
|
|
||||||
def select_seed(self):
|
def select_seed(self):
|
||||||
p = utils.pint(self.g.pos)
|
p = utils.pint(self.g.pos)
|
||||||
@@ -91,18 +83,17 @@ class GatherCropStates:
|
|||||||
blocks.MATURE_CARROT_ID: items.CARROT_ID,
|
blocks.MATURE_CARROT_ID: items.CARROT_ID,
|
||||||
blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID,
|
blocks.MATURE_BEETROOT_ID: items.BEETROOT_SEEDS_ID,
|
||||||
}
|
}
|
||||||
|
self.target_seed = crop_seeds[self.type_id]
|
||||||
|
|
||||||
if self.g.game.select_item([crop_seeds[self.type_id]]):
|
if self.g.game.select_item([self.target_seed]):
|
||||||
self.state = self.wait_select
|
self.state = self.wait_select
|
||||||
self.wait_time = 0.5
|
|
||||||
else:
|
else:
|
||||||
print('Aborting planting, no crop')
|
print('Havent picked up seed yet')
|
||||||
self.state = self.cleanup
|
return
|
||||||
|
|
||||||
def wait_select(self):
|
def wait_select(self):
|
||||||
# wait a bit to select
|
if self.target_seed != self.g.holding:
|
||||||
if self.wait_time > 0:
|
return
|
||||||
self.wait_time -= utils.TICK
|
|
||||||
else:
|
else:
|
||||||
self.state = self.place_crop
|
self.state = self.place_crop
|
||||||
|
|
||||||
@@ -111,12 +102,11 @@ class GatherCropStates:
|
|||||||
self.g.game.place_block(p, BlockFace.TOP)
|
self.g.game.place_block(p, BlockFace.TOP)
|
||||||
print('Placed crop')
|
print('Placed crop')
|
||||||
self.state = self.wait_place
|
self.state = self.wait_place
|
||||||
self.wait_time = 0.5
|
|
||||||
|
|
||||||
def wait_place(self):
|
def wait_place(self):
|
||||||
# wait a bit for chunk data to update
|
w = self.g.world
|
||||||
if self.wait_time > 0:
|
if w.block_at(*self.crop) == blocks.AIR:
|
||||||
self.wait_time -= utils.TICK
|
return
|
||||||
else:
|
else:
|
||||||
self.state = self.cleanup
|
self.state = self.cleanup
|
||||||
|
|
||||||
@@ -134,8 +124,8 @@ class GatherCropStates:
|
|||||||
|
|
||||||
self.crop = None
|
self.crop = None
|
||||||
self.type_id = None
|
self.type_id = None
|
||||||
|
self.target_seed = None
|
||||||
self.bad_crops = []
|
self.bad_crops = []
|
||||||
self.wait_time = 0
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.state()
|
self.state()
|
||||||
|
@@ -102,7 +102,10 @@ class GrabSuppliesStates:
|
|||||||
print('No path, blacklisting barrel')
|
print('No path, blacklisting barrel')
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.bad_barrels.append(self.barrel)
|
self.bad_barrels.append(self.barrel)
|
||||||
self.state = self.choose_barrel
|
if len(self.bad_barrels) > 3:
|
||||||
|
self.state = self.cleanup
|
||||||
|
else:
|
||||||
|
self.state = self.choose_barrel
|
||||||
|
|
||||||
def going_to_barrel(self):
|
def going_to_barrel(self):
|
||||||
if utils.pint(self.g.pos) == self.opening:
|
if utils.pint(self.g.pos) == self.opening:
|
||||||
|
@@ -31,8 +31,29 @@ class SellToVillagerStates:
|
|||||||
|
|
||||||
for v in w.find_villagers(p, 100):
|
for v in w.find_villagers(p, 100):
|
||||||
print('Found villager:', v)
|
print('Found villager:', v)
|
||||||
if v not in self.bad_villagers and v not in self.spent_villagers:
|
|
||||||
break
|
if v in self.bad_villagers:
|
||||||
|
print('In bad villager list')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if v in self.spent_villagers:
|
||||||
|
print('In spent villager list')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'profession' not in v:
|
||||||
|
print('Villager has unknown profession')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if v.profession not in mobs.TRADES:
|
||||||
|
print('Villager doesnt sell stuff we collect')
|
||||||
|
continue
|
||||||
|
|
||||||
|
trades = mobs.TRADES[v.profession]
|
||||||
|
if not self.g.game.count_items(trades):
|
||||||
|
print('We dont have anything to sell him')
|
||||||
|
continue
|
||||||
|
|
||||||
|
break
|
||||||
else: # for
|
else: # for
|
||||||
print('No good villagers left, aborting.')
|
print('No good villagers left, aborting.')
|
||||||
self.spent_villagers = []
|
self.spent_villagers = []
|
||||||
@@ -59,7 +80,7 @@ class SellToVillagerStates:
|
|||||||
if self.villager not in self.good_villagers:
|
if self.villager not in self.good_villagers:
|
||||||
self.bad_villagers.append(self.villager)
|
self.bad_villagers.append(self.villager)
|
||||||
print('Added to bad villager list')
|
print('Added to bad villager list')
|
||||||
self.state = self.cleanup
|
self.state = self.find_villager
|
||||||
return
|
return
|
||||||
|
|
||||||
navpath = w.path_to_place(p, self.openings[0])
|
navpath = w.path_to_place(p, self.openings[0])
|
||||||
@@ -67,6 +88,7 @@ class SellToVillagerStates:
|
|||||||
if navpath:
|
if navpath:
|
||||||
self.g.path = navpath
|
self.g.path = navpath
|
||||||
self.state = self.going_to_villager
|
self.state = self.going_to_villager
|
||||||
|
self.g.look_at = None
|
||||||
else:
|
else:
|
||||||
self.openings.pop(0)
|
self.openings.pop(0)
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
@@ -144,8 +166,7 @@ class SellToVillagerStates:
|
|||||||
print('Villager has been spent, aborting')
|
print('Villager has been spent, aborting')
|
||||||
self.g.game.close_window()
|
self.g.game.close_window()
|
||||||
self.spent_villagers.append(self.villager)
|
self.spent_villagers.append(self.villager)
|
||||||
self.state = self.wait
|
self.state = self.find_villager
|
||||||
self.wait_time = 10
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def click_trade(self):
|
def click_trade(self):
|
||||||
|
@@ -139,7 +139,15 @@ class SleepWithBedStates:
|
|||||||
print('Placing bed')
|
print('Placing bed')
|
||||||
self.g.game.place_block(self.area, BlockFace.TOP)
|
self.g.game.place_block(self.area, BlockFace.TOP)
|
||||||
self.my_bed = True
|
self.my_bed = True
|
||||||
self.state = self.use_bed
|
self.wait_time = 0.5
|
||||||
|
self.state = self.wait_use
|
||||||
|
|
||||||
|
def wait_use(self):
|
||||||
|
# wait to use the bed
|
||||||
|
if self.wait_time > 0:
|
||||||
|
self.wait_time -= utils.TICK
|
||||||
|
else:
|
||||||
|
self.state = self.use_bed
|
||||||
|
|
||||||
def use_bed(self):
|
def use_bed(self):
|
||||||
w = self.g.world
|
w = self.g.world
|
||||||
|
@@ -20,6 +20,7 @@ def get_packets(old_get_packets):
|
|||||||
mc_packets.add(packets.EntityTeleport)
|
mc_packets.add(packets.EntityTeleport)
|
||||||
mc_packets.add(packets.TradeListPacket)
|
mc_packets.add(packets.TradeListPacket)
|
||||||
mc_packets.add(packets.DisconnectPacket)
|
mc_packets.add(packets.DisconnectPacket)
|
||||||
|
mc_packets.add(packets.UnloadChunkPacket)
|
||||||
|
|
||||||
|
|
||||||
return mc_packets
|
return mc_packets
|
||||||
|
@@ -69,11 +69,10 @@ class ChunksManager:
|
|||||||
|
|
||||||
for item_id, locations in chunk.sub_index.items():
|
for item_id, locations in chunk.sub_index.items():
|
||||||
if item_id not in self.index:
|
if item_id not in self.index:
|
||||||
self.index[item_id] = []
|
self.index[item_id] = set()
|
||||||
|
|
||||||
for l in locations:
|
for l in locations:
|
||||||
coords = (dx + l%16, dy + l//256, dz + l%256//16)
|
coords = (dx + l%16, dy + l//256, dz + l%256//16)
|
||||||
self.index[item_id].append(coords)
|
self.index[item_id].add(coords)
|
||||||
|
|
||||||
#self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME
|
#self.biomes[(chunk_packet.x, None, chunk_packet.z)] = chunk_packet.biomes # FIXME
|
||||||
if self.loading:
|
if self.loading:
|
||||||
@@ -119,31 +118,24 @@ class ChunksManager:
|
|||||||
|
|
||||||
if block in blocks.INDEXED_IDS:
|
if block in blocks.INDEXED_IDS:
|
||||||
if block not in self.index:
|
if block not in self.index:
|
||||||
self.index[block] = []
|
self.index[block] = set()
|
||||||
self.index[block].append((x, y, z))
|
self.index[block].add((x, y, z))
|
||||||
|
|
||||||
def check_loaded(self, position, steps=1):
|
def check_loaded(self, chunk_distance):
|
||||||
x, y, z = utils.pint(position)
|
num = (chunk_distance * 2 + 1) ** 2
|
||||||
player_chunk = (x//16, 1, z//16)
|
num_subchunks = num * 16
|
||||||
for i in range(steps): # TODO: base off render_distance?
|
return len(self.chunks) >= num_subchunks
|
||||||
offset = utils.spiral(i)
|
|
||||||
check = utils.padd(player_chunk, offset)
|
|
||||||
|
|
||||||
if check not in self.chunks:
|
def unload_chunk(self, x, z):
|
||||||
return False
|
for y in range(16):
|
||||||
|
try:
|
||||||
return True
|
del self.chunks[(x, y, z)]
|
||||||
|
except KeyError:
|
||||||
def unload_chunks(self, position):
|
pass
|
||||||
x, y, z = utils.pint(position)
|
|
||||||
player_chunk = (x//16, 0, z//16)
|
|
||||||
loaded_chunks = list(self.chunks.keys())
|
|
||||||
|
|
||||||
for chunk in loaded_chunks:
|
|
||||||
check = (chunk[0], 0, chunk[2])
|
|
||||||
if utils.phyp_king(player_chunk, check) > 20:
|
|
||||||
del self.chunks[chunk]
|
|
||||||
|
|
||||||
|
def unload_all_chunks(self):
|
||||||
|
self.chunks = {}
|
||||||
|
self.index = {}
|
||||||
|
|
||||||
class ChunkNotLoadedException(Exception):
|
class ChunkNotLoadedException(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -161,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:
|
||||||
@@ -186,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
|
||||||
|
@@ -31,10 +31,10 @@ class ChunkDataPacket(Packet):
|
|||||||
self.biomes.append(VarInt.read(file_object))
|
self.biomes.append(VarInt.read(file_object))
|
||||||
size = VarInt.read(file_object)
|
size = VarInt.read(file_object)
|
||||||
self.data = file_object.read(size)
|
self.data = file_object.read(size)
|
||||||
size_entities = VarInt.read(file_object)
|
#size_entities = VarInt.read(file_object)
|
||||||
self.entities = []
|
#self.entities = []
|
||||||
for i in range(size_entities):
|
#for i in range(size_entities):
|
||||||
self.entities.append(Nbt.read(file_object))
|
# self.entities.append(Nbt.read(file_object))
|
||||||
|
|
||||||
self.decode_chunk_data()
|
self.decode_chunk_data()
|
||||||
|
|
||||||
@@ -49,9 +49,9 @@ class ChunkDataPacket(Packet):
|
|||||||
Integer.send(self.biomes[i], packet_buffer)
|
Integer.send(self.biomes[i], packet_buffer)
|
||||||
VarInt.send(len(self.data), packet_buffer)
|
VarInt.send(len(self.data), packet_buffer)
|
||||||
packet_buffer.send(self.data)
|
packet_buffer.send(self.data)
|
||||||
VarInt.send(len(self.entities), packet_buffer)
|
#VarInt.send(len(self.entities), packet_buffer)
|
||||||
for e in self.entities:
|
#for e in self.entities:
|
||||||
Nbt.send(e, packet_buffer)
|
# Nbt.send(e, packet_buffer)
|
||||||
|
|
||||||
def decode_chunk_data(self):
|
def decode_chunk_data(self):
|
||||||
packet_data = PacketBuffer()
|
packet_data = PacketBuffer()
|
||||||
@@ -64,9 +64,9 @@ class ChunkDataPacket(Packet):
|
|||||||
if self.bit_mask_y & (1 << i):
|
if self.bit_mask_y & (1 << i):
|
||||||
self.chunks[i].read(packet_data)
|
self.chunks[i].read(packet_data)
|
||||||
|
|
||||||
for e in self.entities:
|
#for e in self.entities:
|
||||||
y = e['y']
|
# y = e['y']
|
||||||
self.chunks[floor(y/16)].entities.append(e)
|
# self.chunks[floor(y/16)].entities.append(e)
|
||||||
|
|
||||||
class Chunk:
|
class Chunk:
|
||||||
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
|
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
|
||||||
@@ -304,7 +304,7 @@ class EntityMetadataPacket(Packet):
|
|||||||
self.metadata = []
|
self.metadata = []
|
||||||
for _ in range(99):
|
for _ in range(99):
|
||||||
entry = Entry.read(file_object, self.context)
|
entry = Entry.read(file_object, self.context)
|
||||||
if not entry: break
|
if not entry: break # hit an unimplemented type, stops parsing
|
||||||
self.metadata.append(entry)
|
self.metadata.append(entry)
|
||||||
|
|
||||||
|
|
||||||
@@ -458,3 +458,15 @@ class DisconnectPacket(Packet):
|
|||||||
definition = [
|
definition = [
|
||||||
{'reason': String},
|
{'reason': String},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class UnloadChunkPacket(Packet):
|
||||||
|
# Tells the client to unload a chunk column
|
||||||
|
# https://wiki.vg/Protocol#Unload_Chunk
|
||||||
|
|
||||||
|
id = 0x1C
|
||||||
|
packet_name = 'unload chunk'
|
||||||
|
|
||||||
|
definition = [
|
||||||
|
{'chunk_x': Integer},
|
||||||
|
{'chunk_z': Integer},
|
||||||
|
]
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -280,13 +280,13 @@ class World:
|
|||||||
|
|
||||||
def check_bed_occupied(self, bed):
|
def check_bed_occupied(self, bed):
|
||||||
# returns true if the bed is occupied by a player
|
# returns true if the bed is occupied by a player
|
||||||
print('Checking bed occupancy:', bed)
|
bid = self.g.chunks.get_block_at(*bed)
|
||||||
for player in self.g.players.values():
|
if blocks.PROPS[bid]['occupied'] == 'true':
|
||||||
ppos = utils.pint((player.x, player.y, player.z))
|
print('Checking bed occupancy:', bed, '-> occupied')
|
||||||
if utils.phyp(bed, ppos) <= 1 and player.y - int(player.y) == 0.6875:
|
return True
|
||||||
print('Bed is occupied by:', player, self.g.player_names[player.player_uuid])
|
else:
|
||||||
return True
|
print('Checking bed occupancy:', bed, '-> free')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def find_cache_openings(self, area):
|
def find_cache_openings(self, area):
|
||||||
return self.find_bed_openings(area)
|
return self.find_bed_openings(area)
|
||||||
@@ -312,6 +312,7 @@ class World:
|
|||||||
if utils.phyp(center, pos) > distance:
|
if utils.phyp(center, pos) > distance:
|
||||||
continue
|
continue
|
||||||
result.append(mob)
|
result.append(mob)
|
||||||
|
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def find_threats(self, center, distance):
|
def find_threats(self, center, distance):
|
||||||
@@ -324,6 +325,7 @@ class World:
|
|||||||
if not self.check_air_column(pos, distance):
|
if not self.check_air_column(pos, distance):
|
||||||
continue
|
continue
|
||||||
result.append(mob)
|
result.append(mob)
|
||||||
|
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def find_villagers(self, center, distance):
|
def find_villagers(self, center, distance):
|
||||||
@@ -336,6 +338,7 @@ class World:
|
|||||||
if utils.phyp(center, pos) > distance:
|
if utils.phyp(center, pos) > distance:
|
||||||
continue
|
continue
|
||||||
result.append(mob)
|
result.append(mob)
|
||||||
|
result.sort(key=lambda mob: utils.phyp(center, (mob.x, mob.y, mob.z)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def find_villager_openings(self, villager):
|
def find_villager_openings(self, villager):
|
||||||
|
Reference in New Issue
Block a user