diff --git a/game.py b/game.py index 965f0ee..40b4d7a 100644 --- a/game.py +++ b/game.py @@ -20,7 +20,8 @@ from protocol.packets import ( ClickWindowPacket, CloseWindowPacket, ServerWindowConfirmationPacket, ClientWindowConfirmationPacket, EntityMetadataPacket, SpawnLivingEntityPacket, EntityPositionRotationPacket, DestroyEntitiesPacket, - EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket + EntityActionPacket, EntityTeleport, InteractEntityPacket, TradeListPacket, + SelectTradePacket, ) from protocol.types import Slot @@ -789,6 +790,7 @@ class Game: if command == 'close': if self.g.window: self.close_window() + reply = 'ok' else: reply = 'nothing open' @@ -930,6 +932,16 @@ class Game: count += item.item_count return count + def get_window_slot(self, item_id): + # get the first slot that matches item of a window + window_items = list(self.g.window.contents.items()) + for slot, item in window_items: + if not item.present: continue + if item.item_id == item_id: + return slot, item + else: #for + return False, False + def select_item(self, items): # select the first match from items of inv # uses smallest stack of that match @@ -1186,6 +1198,11 @@ class Game: print(packet) self.g.trades = packet.trades + def select_trade(self, num): + packet = SelectTradePacket() + packet.selected_slot = num + self.g.connection.write_packet(packet) + def tick(self): if self.g.breaking: self.animate() diff --git a/items.py b/items.py index e37c86e..c015c09 100644 --- a/items.py +++ b/items.py @@ -108,5 +108,7 @@ POTATO_ID = get_id('potato') WHEAT_SEEDS_ID = get_id('wheat_seeds') BEETROOT_SEEDS_ID = get_id('beetroot_seeds') +EMERALD_ID = get_id('emerald') + NEEDED_ITEMS = BED_IDS | SHOVEL_IDS | AXE_IDS | FOOD_IDS | set([CHEST_ID]) WANTED_ITEMS = SAPLING_IDS | set([NETHERWART_ID, CARROT_ID, POTATO_ID, WHEAT_SEEDS_ID, BEETROOT_SEEDS_ID]) diff --git a/jobs.py b/jobs.py index fc7443d..f3ab4c8 100644 --- a/jobs.py +++ b/jobs.py @@ -1777,6 +1777,7 @@ class SellToVillagerStates: return None def init(self): + self.trade = None self.state = self.find_villager def find_villager(self): @@ -1784,17 +1785,18 @@ class SellToVillagerStates: w = self.g.world p = utils.pint(self.g.pos) - for villager in w.find_villagers(p, 100): - print('Found villager:', villager) - if villager not in self.bad_villagers: + for v in w.find_villagers(p, 100): + print('Found villager:', v) + if v not in self.bad_villagers and v not in self.spent_villagers: break else: # for print('No good villagers left, aborting.') + self.spent_villagers = [] self.state = self.cleanup return - self.villager = villager - self.villager_pos = utils.pint((villager.x, villager.y, villager.z)) + self.villager = v + self.villager_pos = utils.pint((v.x, v.y, v.z)) self.state = self.find_openings def find_openings(self): @@ -1841,14 +1843,83 @@ class SellToVillagerStates: print('Interacting with villager') self.g.game.interact(self.villager.entity_id) self.g.game.animate() - self.state = self.select_trade + self.state = self.choose_trade + + def choose_trade(self): + g = self.g.game - def select_trade(self): if not self.g.window or not self.g.trades: return - print('Got trades window') - self.state = self.freeze + for trade_num, trade in enumerate(self.g.trades): + in_id = trade.input_item_1.item_id + in_num = trade.input_item_1.item_count + out_id = trade.output_item.item_id + + + if g.count_items([in_id]) < in_num: + continue + if trade.trade_disabled: + continue + if out_id != items.EMERALD_ID: + continue + + print('Found trade: #', trade_num, trade) + self.trade = trade + self.trade_num = trade_num + self.state = self.click_trade + break + else: + print('Villager has been spent, aborting') + self.spent_villagers.append(self.villager) + self.state = self.cleanup + return + + def click_trade(self): + self.g.item_lock = True + self.g.game.select_trade(self.trade_num) + self.state = self.execute_trade + + def execute_trade(self): + if self.g.item_lock: + return + + w = self.g.window + w_info = mcdata.WINDOWS[w.data.window_type] + slot_num = w_info.output + slot = w.contents[slot_num] + + self.g.item_lock = True + self.g.game.click_window(slot_num, 0, 1, slot) + self.g.game.close_window() + self.state = self.end_trade + + def end_trade(self): + if self.g.item_lock: + return + + print('Trade completed.') + self.state = self.interact_villager + + + + #def grab_stack(self): + # g = self.g.game + # in_id = self.trade.input_item_1.item_id + # slot_num, slot = g.get_window_slot(in_id) + + # if not slot: + # print('Item not found, aborting') + # self.state = self.cleanup + # return + + # print('Grabbing:', slot) + # self.g.item_lock = True + # # double click stack to grab max + # self.g.game.click_window(slot_num, 0, 6, slot) + + # self.state = self.freeze + def freeze(self): return @@ -1874,7 +1945,9 @@ class SellToVillagerStates: self.villager_pos = None self.bad_villagers = [] self.good_villagers = [] + self.spent_villagers = [] self.openings = [] + self.trade = None self.wait_time = 0 def run(self): diff --git a/mcdata.py b/mcdata.py index 812e81c..eb31d2a 100644 --- a/mcdata.py +++ b/mcdata.py @@ -10,6 +10,7 @@ with open('minecraft_data/registries.json') as f: SINGLE_CHEST = 2 DOUBLE_CHEST = 5 +VILLAGER_TRADE = 18 WINDOWS = { SINGLE_CHEST: Munch( @@ -22,4 +23,10 @@ WINDOWS = { inventory=list(range(54, 90)), slot_diff=45, ), + VILLAGER_TRADE: Munch( + input1=0, + input2=1, + output=2, + inventory=list(range(3, 38)), + ), } diff --git a/protocol/packets.py b/protocol/packets.py index edbd472..577fd0a 100644 --- a/protocol/packets.py +++ b/protocol/packets.py @@ -436,3 +436,14 @@ class TradeListPacket(Packet): self.experience = VarInt.read(file_object) self.is_regular_villager = Boolean.read(file_object) self.can_restock = Boolean.read(file_object) + +class SelectTradePacket(Packet): + # Sent when a player selects a specific trade offered by a villager + # https://wiki.vg/Protocol#Select_Trade + + id = 0x23 + packet_name = 'select trade entity' + + definition = [ + {'selected_slot': VarInt}, + ]