ammaraskar / pyCraft

Minecraft-client networking library in Python
Other
817 stars 184 forks source link

how do manage inventory #177

Open game-been opened 4 years ago

game-been commented 4 years ago

I am sorry for asking so much questions but there is almost no documentation so i need to ask the community. My first question is how do i get the player inventory and move items. My second question is how do i move the current hot bar slot. My last question is how do i listen for if a server plugin forces the player to open an inventory.

game-been commented 4 years ago

or just how do i open inventory and read nbt

jj136975 commented 4 years ago

I'm stuck at the slot data reading...

Codehc commented 3 years ago

I am sorry for asking so much questions but there is almost no documentation so i need to ask the community. My first question is how do i get the player inventory and move items. My second question is how do i move the current hot bar slot. My last question is how do i listen for if a server plugin forces the player to open an inventory.

I have not done all of these myself so ill tell you how much I know. First of all I suggest checkout out these docs. That way you can see what packets do what. This is how I learn what I need to do when I am adding a packet. Keep in mind this is for 1.16 so you might have to change some packet ID's for older versions (a changelog can be found here). On packet you seem to be interested in is changing the hot bar slot. Ill show you how I did it.

(Keep in mind this is pretty general so you can apply the strategies to making any packet)

So first you want to define the packet class. Here I named it HeldItemChangePacket and it is an extension of the Packet class. This part will look like this:

class HeldItemChangePacket(Packet):
     # Rest of packet will go here

Next you want to return the packet ID. Im going to keep it simple and I will return the latest packet ID. You do that in a get_id() function with the argument context. This is for returning the ID of the packet so that when the client sends it the server will know what you are talking about, kind of like how we use packet names the computer uses packet ID's. The context argument has the version that you are connecting to the server in it so you can use that to see what packet ID you have to use (packet ID's change over versions). Im keeping it simple so I wont use that argument in my code but you can see how it is used in other packet classes. (Also ignore the staticmethod part, add that in but it isnt critical to know what it does for now)

class HeldItemChangePacket(Packet):
    @staticmethod
    def get_id(context):
        # Here I am just returning the latest packet ID for this class (assuming you use 1.16)
        return 0x23

Next you have to add the definition of the packet. This is so that the packet knows what arguments to send to the server. Your code will look like this when added:

class HeldItemChangePacket(Packet):
    @staticmethod
    def get_id(context):
        return 0x23

    packet_name = "held item change"
    get_definition = staticmethod(lambda context: [
        {'slot': Short}
    ])

Here we are adding the packet name which is just for human readability purposes as far as I know (I may be wrong correct me if someone who knows for a fact sees this). After the packet name we add the definition which just returns some data that looks like a Java Script object (or JSON if you know that better).

Here you have the whole packet created and all you have to do is call it. I wont explain in depth how to call because it is really simple but it goes something like this:

packet = serverbound.play.HeldItemChangePacket() # Creates an instance of the packet class
packet.slot = 2 # Changes it to slot 3, any number from 0-8
connection.write_packet(packet) # Writes the packet to the server

Everything I wrote here can be changed to make other packets I just wrote it with an example packet here.

NotTahaAli commented 3 years ago

I'm stuck at the slot data reading...

If you are still wondering, I have been able to successfully read Slot Data.

class NBTTypes(Type):
    def get_type(number):
        if number == 1:
            return Byte
        if number == 2:
            return Short
        if number == 3:
            return Integer
        if number == 4:
            return Long
        if number == 5:
            return Float
        if number == 6:
            return Double
        if number == 8:
            return NBTString
        if number == 9:
            return NBTList
        if number == 10:
            return NBTArray

class NBTArray(Type):
    @staticmethod
    def read(file_object):
        readbit = Byte.read(file_object)
        val = {}
        while readbit != 0:
            nbttagname = NBTString.read(file_object)
            aval = NBTTypes.get_type(readbit).read(file_object)
            val[nbttagname] = aval
            readbit = Byte.read(file_object)
        return val

class NBTList(Type):
    @staticmethod
    def read(file_object):
        listtype = Byte.read(file_object)
        file_object.read(3) #I don't know what these values are.
        listcount = Byte.read(file_object)
        print(listcount)
        val = []
        for i in range(listcount):
            print(listtype)
            val.append(NBTTypes.get_type(listtype).read(file_object))
        return val

class NBTString(Type):
    @staticmethod
    def read(file_object):
        return file_object.read(Short.read(file_object)).decode('utf-8')

class Slot(Type):
    @staticmethod
    def read(file_object):
        if Boolean.read(file_object):
            item_id = VarInt.read(file_object)
            item_count = Byte.read(file_object)
            if file_object.read(1) == b'\x00':
                item_nbt = False
            else:
                file_object.read(2)
                item_nbt = NBTArray.read(file_object)
            return {"id":item_id,"count":item_count,"nbt":item_nbt}
        else: return False

class SlotArray(Type):
    @staticmethod
    def read(file_object):
        #print(file_object.read())
        x = 0
        newlist = []
        while x < 100:
            try: newlist.append(Slot.read(file_object))
            except Exception as e: e 
            x = x+1
        return newlist

Add these classes to the Types Module in Networking, also make sure to allow external access to SlotArray by putting it in all.

After this you can use the Slot Array in clientbound packages, once you import it.

I use the Window Items Packet.

class WindowItemsPacket(Packet):
    @staticmethod
    def get_id(context):
        return 0x13

    packet_name = 'window items'
    definition = [
        {'window_id': UnsignedByte},
        {'count': Short},
        {'slot_data': SlotArray}
    ]

Note, this has been made to work with 1.16.5. some changes may be needed for earlier versions