• Yet Another Minecraft Bot Question
    3 replies, posted
Hello Gents, This forum seems to have quite a number of people who know what they're doing when it comes to writing minecraft stuff, so I figured I'd ask. I'm trying to write a function which'll move my bot to a position and set a block. Unfortunately the server always kicks me for "Cheat Detected: Distance" no matter how large of a delay I have between the move and the destroy/place action. Is there something wrong that I'm doing? My code is below: [code] ## License: MIT, attribution welcomed. import struct from twisted.internet.protocol import Protocol, ClientFactory from twisted.internet import reactor, task from sys import stdout import socket NAME = "~" VERIFICATION_STR = "~" class Player: def __init__(self,name,pid): self.name = name self.x = 0 self.y = 0 self.z = 0 self.heading = 0 self.pitch = 0 self.pid = pid def pos(self,x,y,z): self.x = x self.y = y self.z = z def delta_pos(self,x,y,z): self.x += x self.y += y self.z += z def orient(self,heading,pitch): self.heading = heading self.pitch = pitch class Action: def __init__(self,bot): self.bot = bot def do(self): pass class MoveAction(Action): def __init__(self,bot,x,y,z): Action.__init__(self,bot) self.x, self.y, self.z = x,y,z def do(self): self.bot.move(self.x,self.y,self.z) class DestroyBlockAction(Action): def __init__(self,bot,x,y,z): Action.__init__(self,bot) self.x, self.y, self.z = x,y,z def do(self): self.bot.destroy(self.x,self.y,self.z) class SetBlockAction(Action): def __init__(self,bot,x,y,z,type): Action.__init__(self,bot) self.x, self.y, self.z, self.type = x,y,z,type def do(self): self.bot.set(self.x,self.y,self.z,self.type) class MinecraftBot: def __init__(self): self.level_data = "" self.players = {} self.info = None self.action_queue = [] def onServerJoin(self, version, srv_name, motd, user_type): print "Joined server! Ver: %s, Name: %s, Motd: %s, Your type: %s"%(version,srv_name,motd,user_type) def onPing(self): ## atm does nothing? pass def onLevelStart(self): ## no actual data sent along with this print "Receiving level data" def onLevelData(self,length,data,percent_complete): ## self.level_data += data print "Downloading level, %s complete."%percent_complete def onLevelEnd(self,x,y,z): ## size of the level (x,y,z) print "Level downloaded. Size: (%s,%s,%s)"%(x,y,z) f_name = "out.gz" print "Writing %s bytes to %s"%(len(self.level_data),f_name) with open("out.gz", "w") as f: f.write(self.level_data) self.level_data = None self.start_bot() def onSetBlock(self,x,y,z,type): pass def onSpawnPlayer(self,pid,name,x,y,z,heading,pitch): p = Player(name,pid) p.pos(x,y,z) p.orient(heading,pitch) self.players[pid] = p if name == NAME: ## Name is the name of our bot.. ugly! D: self.info = p print "Spawned player %s [PID:%s]"%(name,pid) def onPlayerUpdate(self,pid,x,y,z,heading,pitch): p = self.players[pid] p.pos(x,y,z) p.orient(heading,pitch) ##print "%s -> (%s,%s,%s)"%(p.name,x,y,z) def onPlayerUpdate2(self,pid,x,y,z,heading,pitch): pass ## As I'm not sure what to do with the data ##print "PlayerUpdate2(): Is this even ever used?" def onPositionUpdate(self,pid,delta_x,delta_y,delta_z): p = self.players[pid] p.delta_pos(delta_x,delta_y,delta_z) ##print "%s + (%s,%s,%s)"%(p.name,delta_x,delta_y,delta_z) def onOrientationUpdate(self,pid,heading,pitch): p = self.players[pid] p.orient(heading,pitch) ##print "%s new orientation: (%s,%s)"%(p.name,heading,pitch) def onDespawnPlayer(self,pid): if pid in self.players: name = self.players[pid].name print "Removed player %s"%name del self.players[pid] else: print "Removed unknown player with pid %s"%pid def onMessage(self,pid,message): ## For some reason the bot's PID changes print "<%s>"%(message) ## all the time and I'm not sure how to get it ## at the moment. def onKick(self,message): print "Got kicked! Reason: %s"%message def onChangeUserType(self,new_type): pass def start_bot(self): ##self.protocol.sendMessage("hello world") self.erase_blocks() update = task.LoopingCall(self.update_server) update.start(.1) action = task.LoopingCall(self.do_action) action.start(5) def erase_blocks(self): ma = MoveAction(self,0,64,120) sa = SetBlockAction(self,0,64,120,0x01) self.action_queue.append(ma) self.action_queue.append(sa) ## for y in range(64,60,-1): ## for z in range(120,140,1): ## ma = MoveAction(self,0,y,z) ## da = DestroyBlockAction(self,0,y,z) ## self.action_queue.append(ma) ## self.action_queue.append(da) def update_server(self): ## start_bot() is called right when the level is done loading ## but for a brief second, the client's pid/position is not yet sent. if not self.info: ## for a while we won't get information about our avatar. return p = self.info self.protocol.sendPosition(p.x,p.y,p.z,p.heading,p.pitch) def do_action(self): if self.action_queue and self.info: i = self.action_queue.pop(0) i.do() def move(self,x,y,z): self.info.x = x self.info.y = y self.info.z = z def destroy(self,x,y,z): self.protocol.setBlock(x,y,z,0x00,0x01) ## ^ ^- Stone Block Type ## |------- 0x00 is destroy signal def set(self,x,y,z,type): self.protocol.setBlock(x,y,z,0x01,type) ## ^- 0x01 is set block class MinecraftBotProtocol(Protocol): def __init__(self,bot): self.bot = bot self.buffer = '' self.level_buffer = '' self.packet_type = '\xFF' ## 0xFF is not a valid packet type. Signifying no packet here! self.packet_length = {'\x00': 131, ## server id '\x01': 1, ## ping '\x02': 1, ## level initialize '\x03': 1028, ## level data '\x04': 7, ## level loaded '\x06': 8, ## set block '\x07': 74, ## spawn player '\x08': 10, ## player update '\x09': 7, ## player update? '\x0a': 5, ## position update? '\x0b': 4, ## orientation update '\x0c': 2, ## despawn player '\x0d': 66, ## text message '\x0e': 65, ## kick message '\x0f': 2 ## usermode changed } self.packet_name = {'\x00': 'server id', '\x01': 'ping', '\x02': 'level initialize', '\x03': 'level data', '\x04': 'level loaded', '\x06': 'set block', '\x07': 'spawn player', '\x08': 'player update', '\x09': 'player update?', '\x0a': 'position update?', '\x0b': 'orientation update?', '\x0c': 'despawn player', '\x0d': 'text message', '\x0e': 'kick message', '\x0f': 'usermode changed' } def sendMessage(self,msg): type = 0x0d pad = 0 while not len(msg) == 0: chunk = msg[0:64].ljust(64) msg = msg[len(chunk):] packet = struct.pack('!BB64s',type,pad,chunk) self.transport.write(packet) def setBlock(self,x,y,z,mode,block_type): p = self.bot.info print "Position: (%s,%s,%s)"%(p.x,p.y,p.z) print "Placing block at (%s,%s,%s) type: %s mode: %s"%(x,y,z,block_type,mode) ### end debug type = 0x05 x = int(x*32) y = int(y*32) z = int(z*32) packet = struct.pack('!BhhhBB',type,x,y,z,mode,block_type) self.transport.write(packet) print "Sending data: (%s,%s,%s) // %s)"%(x,y,z,packet) def sendPosition(self,x,y,z,heading=0,pitch=0): type = 0x08 ## position update packet type pid = 255 ## the player id is always 255 when sending updates x = int(x*32) y = int(y*32) z = int(z*32) heading = heading * 256/360 pitch = pitch * 245/360 packet = struct.pack('!BBhhhBB',type,pid,x,y,z,heading,pitch) print "Sending data: (%s,%s,%s) // %s"%(x,y,z,packet) self.transport.write(packet) def dataReceived(self, data): self.buffer += data ##print('Received %s bytes'%(len(data))) packet_type = self.buffer[0] while (len(self.buffer)) and (len(self.buffer) >= self.packet_length[packet_type]): self.process_data() if self.buffer: packet_type = self.buffer[0] def process_data(self): packet_type = self.buffer[0] length = self.packet_length[packet_type] if len(self.buffer) >= length: ##print ('Packet type: %s, Length: %s'%(self.packet_name[packet_type],length)) data = self.buffer[:length] self.dispatch_events(data) self.buffer = self.buffer[length:] ## trim this much data else: print ('Incomplete Packet [1]') def dispatch_events(self,data): packet_type = data[0] data = data[1:] if packet_type == '\x00': d = struct.unpack('B64s64sB',data) version = d[0] srv_name = d[1].strip() motd = d[2].strip() usertype = d[3] self.bot.onServerJoin(version,srv_name,motd,usertype) elif packet_type == '\x01': self.bot.onPing() elif packet_type == '\x02': self.bot.onLevelStart() elif packet_type == '\x03': d = struct.unpack('!h1024sB',data) length = d[0] level_data = d[1][:length] completeness = d[2] self.bot.onLevelData(length,level_data,completeness) elif packet_type == '\x04': d = struct.unpack('!hhh',data) self.bot.onLevelEnd(*d) elif packet_type == '\x06': d = struct.unpack('!hhhB',data) x = d[0]/32.0 y = d[1]/32.0 z = d[2]/32.0 type = d[3] self.bot.onSetBlock(x,y,z,type) elif packet_type == '\x07': d = struct.unpack('!B64s3h2B',data) pid = d[0] name = d[1].strip() x = d[2]/32.0 y = d[3]/32.0 z = d[4]/32.0 heading = d[5]*360/256 pitch = d[6]*360/256 self.bot.onSpawnPlayer(pid,name,x,y,z,heading,pitch) elif packet_type == '\x08': d = struct.unpack('!BhhhBB',data) pid = d[0] x = d[1]/32.0 y = d[2]/32.0 z = d[3]/32.0 heading = d[4]*360/256 pitch = d[5]*360/256 self.bot.onPlayerUpdate(pid,x,y,z,heading,pitch) elif packet_type == '\x09': d = struct.unpack('BbbbBB',data) pid = d[0] x = d[1]/32.0 y = d[2]/32.0 z = d[3]/32.0 heading = d[4]*360/256 pitch = d[5]*360/256 self.bot.onPlayerUpdate2(pid,x,y,z,heading,pitch) elif packet_type == '\x0a': d = struct.unpack('Bbbb',data) pid = d[0] d_x = d[1]/32.0 d_y = d[2]/32.0 d_z = d[3]/32.0 self.bot.onPositionUpdate(pid,d_x,d_y,d_z) elif packet_type == '\x0b': d = struct.unpack('BBB',data) pid = d[0] heading = d[1]*360/256 pitch = d[2]*360/256 self.bot.onOrientationUpdate(pid,heading,pitch) elif packet_type == '\x0c': d = struct.unpack('B',data) self.bot.onDespawnPlayer(*d) elif packet_type == '\x0d': d = struct.unpack('B64s',data) pid = d[0] msg = d[1].strip() self.bot.onMessage(pid,msg) elif packet_type == '\x0e': d = struct.unpack('64s',data) kick_msg = d[0].strip() self.bot.onKick(kick_msg) elif packet_type == '\x0f': d = struct.unpack('B',data) self.bot.onChangeUserType(*d) else: print "Protocol error" reactor.stop() def connectionMade(self): print("Sending connection string") player_id = '' player_id += struct.pack('b', 0x00) ## packet type player_id += struct.pack('b', 0x07) ## protocol version player_id += NAME.ljust(64,' ') ## name player_id += VERIFICATION_STR.ljust(64, ' ') ## verification string player_id += struct.pack('b', 0x00) ## unused self.transport.write(player_id) class MinecraftBotClientFactory(ClientFactory): def __init__(self): self.bot = MinecraftBot() def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' protocol = MinecraftBotProtocol(self.bot) self.bot.protocol = protocol return protocol def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason reactor.stop() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason reactor.stop() print "Running bot" reactor.connectTCP('72.68.131.232',25565,MinecraftBotClientFactory()) reactor.run() [/code] How the code works: The reactor calls dataReceived() everytime data is received. I use this to break down the packets and then note when the server is done loading and all of the players have been sent to the client. * At this time I call MinecraftBot.erase_blocks() which is supposed to queue up a bunch of actions (here I just have two -- move & then place a block). * I also start the main bot action queue loop -- the bot goes through the queue and performs an action in the queue every 5 sections. * There's another function, this function deals with updating the server with the player's current position 10 times a second. I'm not sure if this is required, but I saw from the protocol explanation here ( [url]http://www.minecraftwiki.net/wiki/Development_Resources[/url] ) that the client updates the server even when not moving. Anyway, the bot-client can teleport to the new position, but gets kicked as soon as I try to place a block. Edit: When I receive coordinates from the server containing the player/block coordinates, I divide them by 32 to get the tile coordinates and then store the data as a float. When I send data to the server regarding the tile to be deleted, I multiply the given tile-coordinate by 32 to get the coordinates in the format that minecraft likes. Edit2: Well, I whipped out wireshark and started packet sniffing. I noticed that I sent out a packet: [08] [ff][01 5e][04 33][00 0b][c1][12][05][00 07][00 20][00 00][01][24] So I broke it down. [code] [08] [ff] [01 5e] [04 33] [00 0b] [c1] [12] ID Player X Y Z Heading Pitch [05] [00 07] [00 20] [00 00] [01] [24] ID x y z Create White Block For the position: struct.unpack('!3h','\x01\x5e\x04\x33\x00\x0b') Gives me (350, 1075, 11) In tile cordinates that's (10,33,0) For the tile placement: >>> f = struct.unpack('!3h','\x00\x07\x00\x20\x00\x00') >>> f (7, 32, 0) ^ apparently that's ... in tile coordinates D: [/code] Apparently player coordinates are sent as coordinate as int(float * 32) whereas block coordinates are just .. left as is. Fuuu. Leaving this here for posterity.
When I wrote my Clique Craft sponge spam bot I experienced the same problem. I solved it by sending the position packet twice, and the set block packet once after that. Remember that if you exceed 255 in x y or z it will write 0x01 to the next byte, not the previous. The original minecraft client writes 0x01 to the previous byte. I haven't read the code you wrote because I'm in a hurry, but I will read it when I get back home from school.
I finished it :D [url]http://www.youtube.com/watch?v=ZIeKj1A8ZRA[/url] [code] import struct from twisted.internet.protocol import Protocol, ClientFactory from twisted.internet import reactor, task from sys import stdout import socket, time NAME = "xxxx" VERIFICATION_STR = "xxxxxxx" ONE = [ [0,1,0], [0,1,0], [0,1,0], [0,1,0], [0,1,0] ] TWO = [ [1,1,1], [0,0,1], [1,1,1], [1,0,0], [1,1,1] ] THREE = [ [1,1,1], [0,0,1], [0,1,1], [0,0,1], [1,1,1] ] FOUR = [ [1,0,1], [1,0,1], [1,1,1], [0,0,1], [0,0,1] ] FIVE = [ [1,1,1], [1,0,0], [1,1,1], [0,0,1], [1,1,1] ] SIX = [ [1,1,1], [1,0,0], [1,1,1], [1,0,1], [1,1,1] ] SEVEN = [ [1,1,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1] ] EIGHT = [ [1,1,1], [1,0,1], [1,1,1], [1,0,1], [1,1,1] ] NINE = [ [1,1,1], [1,0,1], [1,1,1], [0,0,1], [1,1,1] ] ZERO = [ [1,1,1], [1,0,1], [1,0,1], [1,0,1], [1,1,1] ] ## With any luck, the bot will write something like this ## 1 2 3 +z ## ignore these lines ## 12345679012345678901234567890123 ## they're the coordinate system. ## @@@ @@@ @@@ @ @ @@@ @@@ -1 ## @ @ @ @ @ @ @ @ @ @ -2 ## @ @ @@@ @@@ @@@ @@@ @@@ -3 ## @ @ @ @ @ @ @ @ @ -4 ## @@@ @@@ @@@ @ @@@ @@@ -5 -y ## [ p1 ] [ p2 ] [ p3 ] def insert2DArray(arr,arr_to_insert,row,col): a1 = arr a2 = arr_to_insert init_col = col for r in range(0,len(a2)): for c in range(0,len(a2[0])): a1[row][col] = a2[r][c] col+=1 row +=1 col = init_col return a1 class Player: def __init__(self,name,pid): self.name = name self.x = 0 self.y = 0 self.z = 0 self.heading = 0 self.pitch = 0 self.pid = pid def pos(self,x,y,z): self.x = x self.y = y self.z = z def delta_pos(self,x,y,z): self.x += x self.y += y self.z += z def orient(self,heading,pitch): self.heading = heading self.pitch = pitch class Action: def __init__(self,bot): self.bot = bot def do(self): pass class SleepAction(Action): ## Okay, a much better thing would be to have def __init__(self,bot): ## some sort of a cron thing, I know. Action.__init__(self,bot) def do(self): pass class MoveAction(Action): def __init__(self,bot,x,y,z): Action.__init__(self,bot) self.x, self.y, self.z = x,y,z def do(self): self.bot.move(self.x,self.y,self.z) class DestroyBlockAction(Action): def __init__(self,bot,x,y,z): Action.__init__(self,bot) self.x, self.y, self.z = x,y,z def do(self): self.bot.destroy(self.x,self.y,self.z) class SetBlockAction(Action): def __init__(self,bot,x,y,z,type): Action.__init__(self,bot) self.x, self.y, self.z, self.type = x,y,z,type def do(self): self.bot.set(self.x,self.y,self.z,self.type) class TriggerAction(Action): def __init__(self,bot,func,args=None): Action.__init__(self,bot) self.f = func self.args = args def do(self): if self.args: self.f(self.args) else: self.f() class TimeBot: def __init__(self,bot,x,y,z): self.bot = bot ## this is the upper left coordinates of the clock that will be drawn self.init_x = x self.init_y = y self.init_z = z ## these will hold the positions self.pos1 = (x,y-2,z+4) self.pos2 = (x,y-2,z+15) self.pos3 = (x,y-2,z+25) self.hour_color = 0x22 ## black self.min_color = 0x15 ## red self.sec_color = 0x17 ## yellow self.colon_color = 0x31 ## obsidian ## used for storing if a block is written at that coordinate self.arr1 = [[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]] self.arr2 = [[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]] self.arr3 = [[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]] self.hour = None self.min = None self.sec = None self.last_spot = 01 ## 0 == hr, 1 == min, 2== sec, -1 no position yet def onStart(self): ## This queues up a bunch of events to prepare the area that we're going to start clearing self.erase_blocks() ta = TriggerAction(self.bot,self.start_clock) self.bot.action_queue.append(ta) def fill_array(self,str): ## This function fills in a 7x5 tile array that represents two digits arr = [[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]] time_to_arr = {'0':ZERO,'1':ONE,'2':TWO, '3':THREE,'4':FOUR, '5':FIVE,'6':SIX,'7':SEVEN,'8':EIGHT,'9':NINE} first = time_to_arr[ str[0] ] second = time_to_arr[ str[1] ] t = insert2DArray(arr,first,0,0) out_arr = insert2DArray(arr,second,0,4) return out_arr def update_tiles(self,arr_old,arr_new,x,y,z,color): ## z is col, y is row rows = len(arr_old) cols = len(arr_old[0]) for row in range(0,rows): for col in range(0,cols): old = arr_old[row][col] new = arr_new[row][col] if (new==1) and (old==0): sa = SetBlockAction(self.bot,x,y-row,z+col,color) self.bot.action_queue.append(sa) elif (new==0) and (old==1): da = DestroyBlockAction(self.bot,x,y-row,z+col) self.bot.action_queue.append(da) def sleep(self): for x in range(0,3): sa = SleepAction(self.bot) self.bot.action_queue.append(sa) def update_time(self): hr,min,sec = time.strftime("%I %M %S",time.localtime()).split() if not hr == self.hour: ma = MoveAction(self.bot,*self.pos1) self.bot.action_queue.append(ma) if not self.last_spot == 0: self.sleep() hr_arr = self.fill_array(hr) self.update_tiles(self.arr1,hr_arr,self.init_x,self.init_y,self.init_z,self.hour_color) self.hour = hr self.arr1 = hr_arr self.last_spot = 0 if not min == self.min: ma = MoveAction(self.bot,*self.pos2) self.bot.action_queue.append(ma) if not self.last_spot == 1: self.sleep() min_arr = self.fill_array(min) self.update_tiles(self.arr2,min_arr,self.init_x,self.init_y,self.init_z+12,self.min_color) self.min = min self.arr2 = min_arr self.last_spot = 1 if not sec == self.sec: ma = MoveAction(self.bot,*self.pos3) self.bot.action_queue.append(ma) if not self.last_spot == 2: self.sleep() sec_arr = self.fill_array(sec) self.update_tiles(self.arr3,sec_arr,self.init_x,self.init_y,self.init_z+22,self.sec_color) self.sec = sec self.arr3 = sec_arr self.last_spot = 2 def draw_colons(self): x,y,z = self.init_x,self.init_y,self.init_z ## z+15 is a good spot to draw from ma = MoveAction(self.bot,x,y-2,z+15) self.bot.action_queue.append(ma) ## located at z+10, z+20 sa = SleepAction(self.bot) self.bot.action_queue.append(sa) points = [ (x,y-1,z+10), (x,y-1,z+20), (x,y-3,z+10), (x,y-3,z+20)] for pt in points: x,y,z = pt sa = SetBlockAction(self.bot,x,y,z,self.colon_color) self.bot.action_queue.append(sa) def start_clock(self): self.draw_colons() self.clock_updater = task.LoopingCall(self.update_time) self.clock_updater.start(1.0) def erase_blocks(self): for pos in [self.pos1,self.pos2,self.pos3]: ma = MoveAction(self.bot,*pos) self.bot.action_queue.append(ma) for x in range(0,2): self.bot.action_queue.append(SleepAction(self.bot)) c_x, c_y, c_z = pos for y in range(c_y+2,c_y-3,-1): for z in range(c_z-4,c_z+6,1): da = DestroyBlockAction(self.bot,c_x,y,z) self.bot.action_queue.append(da) class MinecraftBot: def __init__(self): self.level_data = "" self.players = {} self.info = None self.action_queue = [] self.time_bot = TimeBot(self,254,63,130) ## This is on the center-top of a map wall def onServerJoin(self, version, srv_name, motd, user_type): print "Joined server! Ver: %s, Name: %s, Motd: %s, Your type: %s"%(version,srv_name,motd,user_type) def onPing(self): ## atm does nothing? pass def onLevelStart(self): ## no actual data sent along with this print "Receiving level data" def onLevelData(self,length,data,percent_complete): ## self.level_data += data print "Downloading level, %s complete."%percent_complete def onLevelEnd(self,x,y,z): ## size of the level (x,y,z) print "Level downloaded. Size: (%s,%s,%s)"%(x,y,z) f_name = "out.gz" print "Writing %s bytes to %s"%(len(self.level_data),f_name) with open("out.gz", "w") as f: f.write(self.level_data) self.level_data = None def onSetBlock(self,x,y,z,type): pass def onSpawnPlayer(self,pid,name,x,y,z,heading,pitch): p = Player(name,pid) p.pos(x,y,z) p.orient(heading,pitch) self.players[pid] = p if name == NAME: ## Name is the name of our bot.. ugly! D: self.info = p self.start_bot() ## Coincidentally, this is when we start the bot! print "Spawned player %s [PID:%s]"%(name,pid) def onPlayerUpdate(self,pid,x,y,z,heading,pitch): p = self.players[pid] p.pos(x,y,z) p.orient(heading,pitch) ##print "%s -> (%s,%s,%s)"%(p.name,x,y,z) def onPlayerUpdate2(self,pid,x,y,z,heading,pitch): pass ## As I'm not sure what to do with the data ##print "PlayerUpdate2(): Is this even ever used?" def onPositionUpdate(self,pid,delta_x,delta_y,delta_z): p = self.players[pid] p.delta_pos(delta_x,delta_y,delta_z) ##print "%s + (%s,%s,%s)"%(p.name,delta_x,delta_y,delta_z) def onOrientationUpdate(self,pid,heading,pitch): p = self.players[pid] p.orient(heading,pitch) ##print "%s new orientation: (%s,%s)"%(p.name,heading,pitch) def onDespawnPlayer(self,pid): if pid in self.players: name = self.players[pid].name print "Removed player %s"%name del self.players[pid] else: print "Removed unknown player with pid %s"%pid def onMessage(self,pid,message): ## For some reason the bot's PID changes print "<%s>"%(message) ## all the time and I'm not sure how to get it ## at the moment. def onKick(self,message): print "Got kicked! Reason: %s"%message def onChangeUserType(self,new_type): pass def start_bot(self): ## Here we'll start our bot thingamugummy self.time_bot.onStart() ## This handles the periodic updates to the server update = task.LoopingCall(self.update_server) update.start(.1) ## This sends out one action every 1/10th of a second action = task.LoopingCall(self.do_action) action.start(.1) def update_server(self): ## start_bot() is called right when the level is done loading ## but for a brief second, the client's pid/position is not yet sent. if not self.info: ## for a while we won't get information about our avatar. return p = self.info self.protocol.sendPosition(p.x,p.y,p.z,p.heading,p.pitch) def do_action(self): if self.action_queue and self.info: i = self.action_queue.pop(0) i.do() def move(self,x,y,z): self.info.x = x self.info.y = y self.info.z = z def destroy(self,x,y,z): self.protocol.setBlock(x,y,z,0x00,0x01) ## ^ ^- Stone Block Type ## |------- 0x00 is destroy signal def set(self,x,y,z,type): self.protocol.setBlock(x,y,z,0x01,type) ## ^- 0x01 is set block class MinecraftBotProtocol(Protocol): def __init__(self,bot): self.bot = bot self.buffer = '' self.level_buffer = '' self.packet_length = {'\x00': 131, ## server id '\x01': 1, ## ping '\x02': 1, ## level initialize '\x03': 1028, ## level data '\x04': 7, ## level loaded '\x06': 8, ## set block '\x07': 74, ## spawn player '\x08': 10, ## player update '\x09': 7, ## player update? '\x0a': 5, ## position update? '\x0b': 4, ## orientation update '\x0c': 2, ## despawn player '\x0d': 66, ## text message '\x0e': 65, ## kick message '\x0f': 2 ## usermode changed } self.packet_name = {'\x00': 'server id', '\x01': 'ping', '\x02': 'level initialize', '\x03': 'level data', '\x04': 'level loaded', '\x06': 'set block', '\x07': 'spawn player', '\x08': 'player update', '\x09': 'player update?', '\x0a': 'position update?', '\x0b': 'orientation update?', '\x0c': 'despawn player', '\x0d': 'text message', '\x0e': 'kick message', '\x0f': 'usermode changed' } def sendMessage(self,msg): type = 0x0d pad = 0 ## there's an unused pad byte for this packet while not len(msg) == 0: chunk = msg[0:64].ljust(64) msg = msg[len(chunk):] packet = struct.pack('!BB64s',type,pad,chunk) self.transport.write(packet) def setBlock(self,x,y,z,mode,block_type): type = 0x05 x = int(x) y = int(y) z = int(z) packet = struct.pack('!BhhhBB',type,x,y,z,mode,block_type) self.transport.write(packet) def sendPosition(self,x,y,z,heading=0,pitch=0): type = 0x08 ## position update packet type pid = 255 ## the player id is always 255 when sending updates x = int(x*32) y = int(y*32) z = int(z*32) heading = heading * 256/360 pitch = pitch * 256/360 packet = struct.pack('!BBhhhBB',type,pid,x,y,z,heading,pitch) self.transport.write(packet) def dataReceived(self, data): self.buffer += data packet_type = self.buffer[0] while (len(self.buffer)) and (len(self.buffer) >= self.packet_length[packet_type]): self.process_data() if self.buffer: packet_type = self.buffer[0] def process_data(self): packet_type = self.buffer[0] length = self.packet_length[packet_type] if len(self.buffer) >= length: ##print ('Packet type: %s, Length: %s'%(self.packet_name[packet_type],length)) data = self.buffer[:length] self.dispatch_events(data) self.buffer = self.buffer[length:] ## trim this much data else: print ('Incomplete Packet [1]') def dispatch_events(self,data): packet_type = data[0] data = data[1:] if packet_type == '\x00': d = struct.unpack('B64s64sB',data) version = d[0] srv_name = d[1].strip() motd = d[2].strip() usertype = d[3] self.bot.onServerJoin(version,srv_name,motd,usertype) elif packet_type == '\x01': self.bot.onPing() elif packet_type == '\x02': self.bot.onLevelStart() elif packet_type == '\x03': d = struct.unpack('!h1024sB',data) length = d[0] level_data = d[1][:length] completeness = d[2] self.bot.onLevelData(length,level_data,completeness) elif packet_type == '\x04': d = struct.unpack('!hhh',data) self.bot.onLevelEnd(*d) elif packet_type == '\x06': d = struct.unpack('!hhhB',data) x = d[0] y = d[1] z = d[2] type = d[3] self.bot.onSetBlock(x,y,z,type) elif packet_type == '\x07': d = struct.unpack('!B64s3h2B',data) pid = d[0] name = d[1].strip() x = d[2]/32.0 y = d[3]/32.0 z = d[4]/32.0 heading = d[5]*360/256 pitch = d[6]*360/256 self.bot.onSpawnPlayer(pid,name,x,y,z,heading,pitch) elif packet_type == '\x08': d = struct.unpack('!BhhhBB',data) pid = d[0] x = d[1]/32.0 y = d[2]/32.0 z = d[3]/32.0 heading = d[4]*360/256 pitch = d[5]*360/256 self.bot.onPlayerUpdate(pid,x,y,z,heading,pitch) elif packet_type == '\x09': d = struct.unpack('BbbbBB',data) pid = d[0] x = d[1]/32.0 y = d[2]/32.0 z = d[3]/32.0 heading = d[4]*360/256 pitch = d[5]*360/256 self.bot.onPlayerUpdate2(pid,x,y,z,heading,pitch) elif packet_type == '\x0a': d = struct.unpack('Bbbb',data) pid = d[0] d_x = d[1]/32.0 d_y = d[2]/32.0 d_z = d[3]/32.0 self.bot.onPositionUpdate(pid,d_x,d_y,d_z) elif packet_type == '\x0b': d = struct.unpack('BBB',data) pid = d[0] heading = d[1]*360/256 pitch = d[2]*360/256 self.bot.onOrientationUpdate(pid,heading,pitch) elif packet_type == '\x0c': d = struct.unpack('B',data) self.bot.onDespawnPlayer(*d) elif packet_type == '\x0d': d = struct.unpack('B64s',data) pid = d[0] msg = d[1].strip() self.bot.onMessage(pid,msg) elif packet_type == '\x0e': d = struct.unpack('64s',data) kick_msg = d[0].strip() self.bot.onKick(kick_msg) elif packet_type == '\x0f': d = struct.unpack('B',data) self.bot.onChangeUserType(*d) else: print "Protocol error" reactor.stop() def connectionMade(self): print("Sending connection string") player_id = '' player_id += struct.pack('b', 0x00) ## packet type player_id += struct.pack('b', 0x07) ## protocol version player_id += NAME.ljust(64,' ') ## name player_id += VERIFICATION_STR.ljust(64, ' ') ## verification string player_id += struct.pack('b', 0x00) ## unused self.transport.write(player_id) class MinecraftBotClientFactory(ClientFactory): def __init__(self): self.bot = MinecraftBot() def startedConnecting(self, connector): print 'Started to connect.' def buildProtocol(self, addr): print 'Connected.' protocol = MinecraftBotProtocol(self.bot) self.bot.protocol = protocol return protocol def clientConnectionLost(self, connector, reason): print 'Lost connection. Reason:', reason reactor.stop() def clientConnectionFailed(self, connector, reason): print 'Connection failed. Reason:', reason reactor.stop() print "Running bot" reactor.connectTCP('72.68.131.232',25565,MinecraftBotClientFactory()) reactor.run() [/code]
Haha, very amusing. Good work.
Sorry, you need to Log In to post a reply to this thread.