mcpq / mcpq-python

Python library for communicating with and controlling Minecraft servers via MCPQ plugin
https://mcpq.github.io/mcpq-python/
Other
7 stars 2 forks source link

How to place and write text on signs using mcpq, similar to mcpi's mc.setSign()? #2

Open javier-cantu opened 2 months ago

javier-cantu commented 2 months ago

I have successfully managed to place blocks and get a list of available blocks. However, I'm struggling with placing signs and writing text on them. In mcpi, there was a method mc.setSign() that allowed us to do this easily.

Is there an equivalent way to place a sign and add text to it using mcpq? Any guidance or examples would be greatly appreciated. Thanks in advance!

icezyclon commented 2 months ago

Hi there :wave:

The function setSign is not natively supported at the moment, as this would involve setting block nbt data, which is on the todo list for the features of the next version of the protocol!

At the moment I would recommend to set the nbt data with a /-command explicitly, like this:

from mcpq import Minecraft, Vec3, NBT
mc = ...
pos = ...

def write_on_sign(
    world, pos: Vec3, messages: list[str], glowing: bool = False, color: str = "black"
):
    assert (
        len(messages) == 4
    ), "Must provide exactly 4 lines for sign (use empty string for empty line)"
    pos = pos.floor()
    nbt = NBT()
    front = nbt.get_or_create_nbt("front_text")
    front["has_glowing_text"] = 1 if glowing else 0
    front["color"] = color
    front_msg = front.get_or_create_list("messages")
    for msg in messages:
        front_msg.append(f'{{"text": "{msg}"}}')
    # you could also do the same for "back_text"
    world.runCommand(f"data merge block {pos.x} {pos.y} {pos.z} {nbt}")

mc.setBlock("acacia_sign", pos)
write_on_sign(mc, pos, ["Line 1", "Line 2", "", "Line 4"], glowing=True, color="green")

The function requires that there is already a sign at pos and will merge the nbt data onto it. It uses the NBT class as a helper, the class is currently undocumented as I am still thinking about a better of using nbt data and Minecraft is switching a lot of nbt data to components from version 1.20.5+, which I still have not looked into.

Please note, this code works and was tested for version 1.20.1! Signs have changed quite a bit over time, for example, prior to 1.20 signs did only support one side for writing and require a different format for writing it!

PS: You can read the nbt data of a block with the in-game command /data get block <x> <y> <z>, which allows you to then construct the nbt in a way you want, or you can use a tool like mcstacker (or minecraft tools for older signs prior to 1.20)

javier-cantu commented 2 months ago

Thank you very much, I will give it a try!

javier-cantu commented 1 week ago

I even put text on both sides

image

# This script places a sign with rotation 0
# Then adds text to both the front and back sides
# No loops are used here to make the process clearer.

from mcpq import Minecraft, Vec3, NBT

# Connect to the Minecraft world
mc = Minecraft()

# Get the default player
player = mc.getPlayer()

# Get the player's current position
pos = player.pos  # Retrieve the player's position
# Extract the player's coordinates as integers
x = int(pos.x)
y = int(pos.y)
z = int(pos.z)

# Place the sign with rotation 0 at the player's position
command = f"setblock {x} {y} {z} minecraft:oak_sign[rotation=0]"
mc.runCommand(command)  # Execute the command to place the sign

# Configure the text for the front side of the sign
nbt = NBT()  # Create a new NBT object to manage sign data
front = nbt.get_or_create_nbt("front_text")  # Access or create the front text section
front["has_glowing_text"] = 1  # Enable glowing text (1 = True, 0 = False)
front["color"] = "green"  # Set the text color to green
front_msg = front.get_or_create_list("messages")  # Get the list of messages for the front
# Add text to each line of the front side
front_msg.append('{"text": "Front Line 1"}')  # Line 1
front_msg.append('{"text": "Front Line 2"}')  # Line 2
front_msg.append('{"text": "Front Line 3"}')  # Line 3
front_msg.append('{"text": "Front Line 4"}')  # Line 4

# Configure the text for the back side of the sign
back = nbt.get_or_create_nbt("back_text")  # Access or create the back text section
back["has_glowing_text"] = 1  # Enable glowing text for the back
back["color"] = "blue"  # Set the text color to blue
back_msg = back.get_or_create_list("messages")  # Get the list of messages for the back
# Add text to each line of the back side
back_msg.append('{"text": "Back Line 1"}')  # Line 1
back_msg.append('{"text": "Back Line 2"}')  # Line 2
back_msg.append('{"text": "Back Line 3"}')  # Line 3
back_msg.append('{"text": "Back Line 4"}')  # Line 4

# Merge the NBT data with the sign block to apply the text
mc.runCommand(f"data merge block {x} {y} {z} {nbt}")