Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to GDMC-HTTP 1.0.0 #59

Merged
merged 1 commit into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GDPC 5.0 (Manicule)

GDPC (Generative Design Python Client) is a framework for use in conjunction with the [Minecraft HTTP Interface Mod](https://github.com/Niels-NTG/gdmc_http_interface) (version >= 0.7.6, < 0.8.0), built for the [GDMC competition](https://gendesignmc.engineering.nyu.edu).
GDPC (Generative Design Python Client) is a framework for use in conjunction with the [Minecraft HTTP Interface Mod](https://github.com/Niels-NTG/gdmc_http_interface) (version >= 1.0.0, < 2.0.0), built for the [GDMC competition](https://gendesignmc.engineering.nyu.edu).

You need to be playing in a Minecraft world with the mod installed to use the framework.

Expand Down
34 changes: 15 additions & 19 deletions gdpc/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,16 @@
from nbt import nbt

from .vector_tools import Vec3bLike
from .nbt_tools import nbtToPythonObject, pythonObjectToSnbt
from .nbt_tools import nbtToSnbt
from .block_state_tools import transformAxis, transformFacing, transformRotation


@dataclass
class Block:
"""A Minecraft block.

Block states can be stored in .states, and block entity NBT data can be stored in .data.

Block entity NBT data should be specified as a JSON-like structure of built-in Python objects
(dict, list, int, etc.). For example: `{"Key1": [1, 2, 3], "Key2": "foobar"}`
Block states can be stored in .states, and block entity data can be stored in .data as an SNBT
string.

If .id is a sequence, the instance represents a palette of blocks that share the same
block states and block entity NBT data.
Expand All @@ -44,7 +42,7 @@ class Block:

id: Union[Optional[str], Sequence[Optional[str]]] = "minecraft:stone"
states: Dict[str, str] = field(default_factory=dict)
data: Any = None
data: Optional[str] = None


def chooseId(self):
Expand Down Expand Up @@ -81,13 +79,10 @@ def stateString(self):
return "" if stateString == "" else f"[{stateString}]"


def dataString(self):
"""Returns this block's block entity NBT data as an SNBT string."""
return pythonObjectToSnbt(self.data) if self.data else ""


def __str__(self):
statesAndData = self.stateString() + self.dataString()
if self.id is None:
return ""
statesAndData = self.stateString() + (self.data if self.data else "")
if isinstance(self.id, str):
return self.id + statesAndData
return ",".join(id + statesAndData for id in self.id if id is not None)
Expand All @@ -100,7 +95,7 @@ def __repr__(self):
return (
f"Block({repr(self.id)}"
+ (f",states={repr(self.states)}" if self.states else "")
+ (f",data={repr(self.data)}" if self.data is not None else "")
+ (f",data={repr(self.data)}" if self.data else "")
+ ")"
)

Expand All @@ -116,11 +111,12 @@ def fromBlockStateTag(blockStateTag: nbt.TAG_Compound, blockEntityTag: Optional[
block.states[str(tag.name)] = str(tag.value)

if blockEntityTag is not None:
block.data = nbtToPythonObject(blockEntityTag)
del block.data['x']
del block.data['y']
del block.data['z']
del block.data['id']
del block.data['keepPacked']
blockEntityTag = deepcopy(blockEntityTag)
del blockEntityTag["x"]
del blockEntityTag["y"]
del blockEntityTag["z"]
del blockEntityTag["id"]
del blockEntityTag["keepPacked"]
block.data = nbtToSnbt(blockEntityTag)

return block
20 changes: 10 additions & 10 deletions gdpc/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ def runCommand(self, command: str, syncWithBuffer=False):
self._commandBuffer.append(command)
return
result = interface.runCommand(command, retries=self.retries, timeout=self.timeout, host=self.host)
if not result[0].isnumeric():
logger.error("Server returned error upon running command:\n %s", result[0])
if not result[0][0]:
logger.error("Server returned error upon running command:\n %s", result[0][1])


def getBuildArea(self) -> Box:
Expand Down Expand Up @@ -417,8 +417,8 @@ def _placeSingleBlockGlobalDirect(self, position: ivec3, block: Block):
"""Place a single block in the world directly.\n
Returns whether the placement succeeded."""
result = interface.placeBlocks([(position, block)], doBlockUpdates=self.doBlockUpdates, spawnDrops=self.spawnDrops, retries=self.retries, timeout=self.timeout, host=self.host)
if not result[0].isnumeric():
logger.error("Server returned error upon placing block:\n %s", result[0])
if not result[0][0]:
logger.error("Server returned error upon placing block:\n %s", result[0][1])
return False
return True

Expand All @@ -443,18 +443,18 @@ def flush(blockBuffer: Dict[ivec3, Block], commandBuffer: List[str]):
response = interface.placeBlocks(blockBuffer.items(), doBlockUpdates=self._bufferDoBlockUpdates, spawnDrops=self.spawnDrops, retries=self.retries, timeout=self.timeout, host=self.host)
blockBuffer.clear()

for line in response:
if not line.isnumeric():
logger.error("Server returned error upon placing buffered block:\n %s", line)
for entry in response:
if not entry[0]:
logger.error("Server returned error upon placing buffered block:\n %s", entry[1])

# Flush command buffer
if commandBuffer:
response = interface.runCommand("\n".join(commandBuffer), retries=self.retries, timeout=self.timeout, host=self.host)
commandBuffer.clear()

for line in response:
if not line.isnumeric():
logger.error("Server returned error upon running buffered command:\n %s", line)
for entry in response:
if not entry[0]:
logger.error("Server returned error upon running buffered command:\n %s", entry[1])

if self._multithreading:
# Clean up finished buffer flush futures
Expand Down
29 changes: 17 additions & 12 deletions gdpc/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""


from typing import Sequence, Tuple, Optional, List, Dict, Any
from typing import Sequence, Tuple, Optional, List, Dict, Any, Union
from functools import partial
import time
from urllib.parse import urlparse
Expand Down Expand Up @@ -80,7 +80,7 @@ def getBlocks(position: Vec3iLike, size: Optional[Vec3iLike] = None, dimension:
'includeData': True if includeData else None,
'dimension': dimension
}
response = _request("GET", url, params=parameters, headers={"accept": "application/json"}, retries=retries, timeout=timeout)
response = _request("GET", url, params=parameters, retries=retries, timeout=timeout)
blockDicts: List[Dict[str, Any]] = response.json()
return [(ivec3(b["x"], b["y"], b["z"]), Block(b["id"], b.get("state", {}), b.get("data"))) for b in blockDicts]

Expand All @@ -106,7 +106,7 @@ def getBiomes(position: Vec3iLike, size: Optional[Vec3iLike] = None, dimension:
'dz': dz,
'dimension': dimension
}
response = _request("GET", url, params=parameters, headers={"accept": "application/json"}, retries=retries, timeout=timeout)
response = _request("GET", url, params=parameters, retries=retries, timeout=timeout)
biomeDicts: List[Dict[str, Any]] = response.json()
return [(ivec3(b["x"], b["y"], b["z"]), str(b["id"])) for b in biomeDicts]

Expand All @@ -122,9 +122,9 @@ def placeBlocks(blocks: Sequence[Tuple[Vec3iLike, Block]], dimension: Optional[s
The <doBlockUpdates>, <spawnDrops> and <customFlags> parameters control block update
behavior. See the GDMC HTTP API documentation for more info.

Returns a list with one string for each block placement. If the block placement was
successful, the string is "1" if the block changed, or "0" otherwise. If the placement
failed, it is the error message.
Returns a list of (success, result)-tuples, one for each block. If a block placement was
successful, result will be 1 if the block changed, or 0 otherwise. If a block placement failed,
result will be the error message.
"""
url = f"{host}/blocks"

Expand All @@ -141,15 +141,18 @@ def placeBlocks(blocks: Sequence[Tuple[Vec3iLike, Block]], dimension: Optional[s
",".join(
'{' +
f'"x":{pos[0]},"y":{pos[1]},"z":{pos[2]},"id":"{block.id}"' +
(f',"state":{json.dumps(block.states)}' if block.states else '') +
(f',"data":{json.dumps(block.data)}' if block.data is not None else '') +
(f',"state":{json.dumps(block.states, separators=(",",":"))}' if block.states else '') +
(f',"data":{repr(block.data)}' if block.data is not None else '') +
'}'
for pos, block in blocks
) +
"]"
)

return _request("PUT", url, data=bytes(body, "utf-8"), params=parameters, headers={"Content-Type": "application/json"}, retries=retries, timeout=timeout).text.split("\n")
response = _request("PUT", url, data=bytes(body, "utf-8"), params=parameters, retries=retries, timeout=timeout)

result: List[Tuple[bool, Union[int, str]]] = [("message" not in entry, entry.get("message", int(entry["status"]))) for entry in response.json()]
return result


def runCommand(command: str, dimension: Optional[str] = None, retries=0, timeout=None, host=DEFAULT_HOST):
Expand All @@ -159,11 +162,13 @@ def runCommand(command: str, dimension: Optional[str] = None, retries=0, timeout

<dimension> can be one of {"overworld", "the_nether", "the_end"} (default "overworld").

Returns a list with one string for each command. If the command was successful, the string
is its return value. Otherwise, it is the error message.
Returns a list of (success, result)-tuples, one for each command. If a command was succesful,
result is its return value (if any). Otherwise, it is the error message.
"""
url = f"{host}/command"
return _request("POST", url, bytes(command, "utf-8"), params={'dimension': dimension}, retries=retries, timeout=timeout).text.split("\n")
response = _request("POST", url, bytes(command, "utf-8"), params={'dimension': dimension}, retries=retries, timeout=timeout)
result: List[Tuple[bool, Optional[str]]] = [(bool(entry["status"]), entry.get("message")) for entry in response.json()]
return result


def getBuildArea(retries=0, timeout=None, host=DEFAULT_HOST):
Expand Down
Loading