parent
ae2b0f4875
commit
a5642409d2
4 changed files with 533 additions and 509 deletions
@ -0,0 +1,524 @@ |
|||||||
|
import re |
||||||
|
import time |
||||||
|
import random |
||||||
|
from math import hypot |
||||||
|
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, text = message |
||||||
|
reply = None |
||||||
|
private = False |
||||||
|
for_me = False |
||||||
|
authed = False |
||||||
|
|
||||||
|
if source == 'SYSTEM': |
||||||
|
self.g.command_lock = False |
||||||
|
|
||||||
|
if text == 'You are now AFK.': |
||||||
|
self.g.afk = True |
||||||
|
elif text == 'You are no longer AFK.': |
||||||
|
self.g.afk = False |
||||||
|
|
||||||
|
match1 = re.match(r'<?(\w+)> (.*)', text) |
||||||
|
match2 = re.match(r'\[(\w+) -> me] (.*)', text) |
||||||
|
if match1: |
||||||
|
sender, text = match1.groups() |
||||||
|
elif match2: |
||||||
|
sender, text = match2.groups() |
||||||
|
private = True |
||||||
|
else: |
||||||
|
return |
||||||
|
|
||||||
|
if sender == 'tanner6': |
||||||
|
authed = True |
||||||
|
|
||||||
|
if text.startswith('zzz'): |
||||||
|
text = '!zzz' |
||||||
|
|
||||||
|
bot_num = self.g.name[-1] |
||||||
|
|
||||||
|
if text.startswith(bot_num): |
||||||
|
text = text[1:] |
||||||
|
for_me = True |
||||||
|
elif text.startswith('! '): |
||||||
|
text = text[2:] |
||||||
|
elif text.startswith('!'): |
||||||
|
text = text[1:] |
||||||
|
else: |
||||||
|
return |
||||||
|
|
||||||
|
if ' ' in text: |
||||||
|
command = text.split(' ', 1)[0] |
||||||
|
data = text.split(' ', 1)[1] |
||||||
|
else: |
||||||
|
command = text |
||||||
|
data = None |
||||||
|
|
||||||
|
try: |
||||||
|
|
||||||
|
## ### Public Commands |
||||||
|
## These can be ran by anyone, all bots will reply. |
||||||
|
|
||||||
|
## !help - prints this whole help message to console |
||||||
|
## !help [command] - replies in-game explaining command |
||||||
|
if command == 'help': |
||||||
|
if data: |
||||||
|
for line in print_help.HELP_LINES: |
||||||
|
if line[1:].startswith(data) or line[1:].startswith(data[1:]): |
||||||
|
reply = 'command ' + line |
||||||
|
break |
||||||
|
else: # for |
||||||
|
reply = 'command not found' |
||||||
|
else: |
||||||
|
print() |
||||||
|
print() |
||||||
|
for line in print_help.HELP_LINES: |
||||||
|
print(line) |
||||||
|
reply = 'check console' |
||||||
|
|
||||||
|
## !ping - replies with "pong" |
||||||
|
if command == 'ping': |
||||||
|
reply = 'pong' |
||||||
|
|
||||||
|
## !echo [data] - replies with "data" |
||||||
|
if command == 'echo' and data: |
||||||
|
reply = data |
||||||
|
|
||||||
|
## !pos - replies with position and dimension |
||||||
|
if command == 'pos': |
||||||
|
reply = str(utils.pint(self.g.pos))[1:-1] + ', ' + self.g.dimension |
||||||
|
|
||||||
|
## !afk - goes AFK with /afk |
||||||
|
if command == 'afk': |
||||||
|
if not self.g.afk: |
||||||
|
reply = '/afk' |
||||||
|
|
||||||
|
## !unafk - goes not AFK with /afk |
||||||
|
if command == 'unafk': |
||||||
|
if self.g.afk: |
||||||
|
reply = '/afk' |
||||||
|
|
||||||
|
## !error - raises an error |
||||||
|
if command == 'error': |
||||||
|
reply = 'ok' |
||||||
|
raise |
||||||
|
|
||||||
|
## !inv - prints current inventory |
||||||
|
if command == 'inv': |
||||||
|
inv_list = [] |
||||||
|
for i in self.g.inv.values(): |
||||||
|
if i.present: |
||||||
|
inv_list.append('{}:{} x {}'.format(items.ITEM_NAMES[i.item_id], str(i.item_id), i.item_count)) |
||||||
|
inv_list.sort() |
||||||
|
result = '\n'.join(inv_list) |
||||||
|
print(result or 'Empty') |
||||||
|
|
||||||
|
## !time - replies with Minecraft world time |
||||||
|
if command == 'time': |
||||||
|
reply = str(self.g.time) |
||||||
|
|
||||||
|
## !count [id] - counts the number of items with that id |
||||||
|
if command == 'count' and data: |
||||||
|
item = int(data) |
||||||
|
reply = str(self.g.game.count_items([item])) |
||||||
|
|
||||||
|
## !loaded - replies with the current loaded area |
||||||
|
if command == 'loaded': |
||||||
|
reply = str(self.g.chunks.get_loaded_area()) |
||||||
|
|
||||||
|
## !players - prints the current players |
||||||
|
## !players clear - clears the current player list |
||||||
|
if command == 'players': |
||||||
|
if data == 'clear': |
||||||
|
self.g.players = {} |
||||||
|
reply = 'ok' |
||||||
|
else: |
||||||
|
for k, v in self.g.players.items(): |
||||||
|
print(str(k) + ':', v, self.g.player_names[v.player_uuid]) |
||||||
|
|
||||||
|
## !objects - prints the current items on ground |
||||||
|
## !objects clear - clears the current object list |
||||||
|
if command == 'objects': |
||||||
|
if data == 'clear': |
||||||
|
self.g.objects = {} |
||||||
|
reply = 'ok' |
||||||
|
else: |
||||||
|
for k, v in self.g.objects.items(): |
||||||
|
if data and v.item_id != int(data): continue |
||||||
|
print(str(k) + ':', v, items.ITEM_NAMES[v.item_id]) |
||||||
|
|
||||||
|
## !mobs - prints the current mobs |
||||||
|
## !mobs clear - clears the current mob list |
||||||
|
if command == 'mobs': |
||||||
|
if data == 'clear': |
||||||
|
self.g.mobs = {} |
||||||
|
reply = 'ok' |
||||||
|
else: |
||||||
|
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type) |
||||||
|
for k, v in all_mobs: |
||||||
|
if data and v.type != int(data): continue |
||||||
|
print(str(k) + ':', v, mobs.MOB_NAMES[v.type]) |
||||||
|
reply = str(len(all_mobs)) + ' mobs' |
||||||
|
|
||||||
|
## !monsters - prints the current monsters |
||||||
|
if command == 'monsters': |
||||||
|
monsters = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type) |
||||||
|
count = 0 |
||||||
|
for k, v in monsters: |
||||||
|
if v.type not in mobs.EVIL_IDS: continue |
||||||
|
if data and v.type != int(data): continue |
||||||
|
count += 1 |
||||||
|
print(str(k) + ':', v, mobs.MOB_NAMES[v.type]) |
||||||
|
reply = str(count) + ' monsters' |
||||||
|
|
||||||
|
## !villagers - prints the current villagers |
||||||
|
if command == 'villagers': |
||||||
|
all_mobs = sorted(list(self.g.mobs.items()), key=lambda x: x[1].type) |
||||||
|
count = 0 |
||||||
|
for k, v in all_mobs: |
||||||
|
type_name = mobs.MOB_NAMES[v.type] |
||||||
|
if type_name != 'villager' : continue |
||||||
|
count += 1 |
||||||
|
print(str(k) + ':', v, type_name) |
||||||
|
reply = str(count) + ' villagers' |
||||||
|
|
||||||
|
## !threats - prints the dangerous monsters within 20 blocks |
||||||
|
## !threats [num] - prints the dangerous monsters within num blocks |
||||||
|
if command == 'threats': |
||||||
|
distance = int(data) if data else 20 |
||||||
|
p = utils.pint(self.g.pos) |
||||||
|
threats = self.g.world.find_threats(p, distance) |
||||||
|
|
||||||
|
for t in threats: |
||||||
|
print(str(t.entity_id) + ':', t, mobs.MOB_NAMES[t.type]) |
||||||
|
reply = str(len(threats)) + ' threats' |
||||||
|
|
||||||
|
if command == 'spiral' and data: |
||||||
|
for i in range(int(data)): |
||||||
|
print(utils.spiral(i)) |
||||||
|
|
||||||
|
if command == 'sand_slice': |
||||||
|
result = self.g.world.find_sand_slice(utils.pint(self.g.pos), 50) |
||||||
|
reply = str(result) |
||||||
|
|
||||||
|
## "zzz" or !zzz - bot does /afk to let others sleep |
||||||
|
if command == 'zzz': |
||||||
|
if not self.g.afk and self.g.dimension == 'overworld': |
||||||
|
reply = '/afk' |
||||||
|
self.g.afk_timeout = 5.0 |
||||||
|
|
||||||
|
## !tree - replies with the closest tree |
||||||
|
if command == 'tree': |
||||||
|
pos = utils.pint(self.g.pos) |
||||||
|
tree = next(self.g.world.find_trees(pos, 50)) |
||||||
|
reply = str(tree)[1:-1] |
||||||
|
|
||||||
|
## !block x y z - replies what block is at (x, y, z) |
||||||
|
if command == 'block': |
||||||
|
try: |
||||||
|
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ') |
||||||
|
x1, y1, z1 = [int(x) for x in data.split()] |
||||||
|
except (AttributeError, ValueError): |
||||||
|
reply = 'usage: !block x1 y1 z1' |
||||||
|
|
||||||
|
if not reply: |
||||||
|
coord = (x1, y1, z1) |
||||||
|
block = self.g.world.block_at(*coord) |
||||||
|
|
||||||
|
if not reply and block is None: |
||||||
|
reply = 'first coord out of range' |
||||||
|
|
||||||
|
if not reply: |
||||||
|
reply = blocks.BLOCKS[block] + ':' + str(block) |
||||||
|
|
||||||
|
|
||||||
|
################# Specific commands ########################## |
||||||
|
|
||||||
|
## ### Bot-specific Commands |
||||||
|
## These will only run for the bot they are addressed to. |
||||||
|
|
||||||
|
if for_me: |
||||||
|
|
||||||
|
## 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': |
||||||
|
self.g.job.state = self.g.job.gather_sand |
||||||
|
reply = 'ok' |
||||||
|
|
||||||
|
if reply: |
||||||
|
for i in self.g.inv.values(): |
||||||
|
print(i.item_id) |
||||||
|
if i.item_id in items.BED_IDS: |
||||||
|
break |
||||||
|
else: |
||||||
|
reply += ', I need a bed' |
||||||
|
|
||||||
|
## 1farm wood - farms wood from a certain area |
||||||
|
## 1farm sand - farms sand from a certain area |
||||||
|
## 1farm wart - farms netherwart from a certain area |
||||||
|
## 1farm crop - farms mature crops from a certain area |
||||||
|
if command == 'farm' and data: |
||||||
|
if data == 'wood': |
||||||
|
self.g.job.state = self.g.job.farm_wood |
||||||
|
reply = 'ok' |
||||||
|
elif data == 'sand': |
||||||
|
self.g.job.state = self.g.job.farm_sand |
||||||
|
reply = 'ok' |
||||||
|
elif data == 'wart': |
||||||
|
self.g.job.state = self.g.job.farm_wart |
||||||
|
reply = 'ok' |
||||||
|
elif data.startswith('crop'): |
||||||
|
self.g.job.state = self.g.job.farm_crop |
||||||
|
reply = 'ok' |
||||||
|
|
||||||
|
if reply and self.g.dimension == 'overworld': |
||||||
|
for i in self.g.inv.values(): |
||||||
|
if i.item_id in items.BED_IDS: |
||||||
|
break |
||||||
|
else: |
||||||
|
reply += ', I need a bed' |
||||||
|
|
||||||
|
## 1loiter - stands still but eats, sleeps, and flees |
||||||
|
if command == 'loiter': |
||||||
|
self.g.job.state = self.g.job.loiter |
||||||
|
reply = 'ok' |
||||||
|
|
||||||
|
## 1trade - sells items to villagers to get emeralds |
||||||
|
if command == 'trade': |
||||||
|
self.g.job.state = self.g.job.trade |
||||||
|
reply = 'ok' |
||||||
|
|
||||||
|
## 1stop - stops the current job and resets bot |
||||||
|
if command == 'stop': |
||||||
|
self.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': |
||||||
|
try: |
||||||
|
sender_uuid = self.g.player_names[sender] |
||||||
|
except KeyError: |
||||||
|
reply = 'can\'t find your uuid' |
||||||
|
|
||||||
|
if not reply: |
||||||
|
for p in self.g.players.values(): |
||||||
|
if p.player_uuid == sender_uuid: |
||||||
|
player = p |
||||||
|
break |
||||||
|
else: # for |
||||||
|
reply = 'can\'t find you' |
||||||
|
|
||||||
|
if not reply: |
||||||
|
pos = utils.pint(self.g.pos) |
||||||
|
goal = utils.pint((p.x, p.y, p.z)) |
||||||
|
start = time.time() |
||||||
|
navpath = self.g.world.path_to_place(pos, goal) |
||||||
|
|
||||||
|
if navpath: |
||||||
|
self.g.path = navpath |
||||||
|
if self.g.job: |
||||||
|
self.g.job.stop() |
||||||
|
print(len(navpath)) |
||||||
|
print(navpath) |
||||||
|
print(round(time.time() - start, 3), 'seconds') |
||||||
|
if self.g.job: |
||||||
|
self.g.job.stop() |
||||||
|
self.g.look_at = None |
||||||
|
reply = 'ok' |
||||||
|
else: |
||||||
|
reply = 'no path' |
||||||
|
|
||||||
|
## 1goto [x] [y] [z] - sends the bot to coordinate (x, y, z) |
||||||
|
if command == 'goto': |
||||||
|
try: |
||||||
|
data = data.replace('(', ' ').replace(')', ' ').replace(',', ' ') |
||||||
|
x2, y2, z2 = [int(x) for x in data.split()] |
||||||
|
except (AttributeError, ValueError): |
||||||
|
reply = 'usage: !goto x y z' |
||||||
|
|
||||||
|
if not reply: |
||||||
|
pos = utils.pint(self.g.pos) |
||||||
|
goal = utils.pint((x2, y2, z2)) |
||||||
|
start = time.time() |
||||||
|
navpath = self.g.world.path_to_place(pos, goal) |
||||||
|
|
||||||
|
if navpath: |
||||||
|
self.g.path = navpath |
||||||
|
if self.g.job: |
||||||
|
self.g.job.stop() |
||||||
|
print(len(navpath)) |
||||||
|
print(navpath) |
||||||
|
print(round(time.time() - start, 3), 'seconds') |
||||||
|
if self.g.job: |
||||||
|
self.g.job.stop() |
||||||
|
self.g.look_at = None |
||||||
|
reply = 'ok' |
||||||
|
else: |
||||||
|
reply = 'no path' |
||||||
|
|
||||||
|
if command == 'break': |
||||||
|
self.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) |
||||||
|
if private and not reply.startswith('/'): |
||||||
|
self.g.chat.send('/m ' + sender + ' ' + reply) |
||||||
|
else: |
||||||
|
self.g.chat.send(reply) |
||||||
|
|
Loading…
Reference in new issue