pyncraft 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyncraft/__init__.py +0 -0
- pyncraft/block.py +103 -0
- pyncraft/connection.py +75 -0
- pyncraft/coord.py +289 -0
- pyncraft/entity.py +102 -0
- pyncraft/event.py +65 -0
- pyncraft/logger.py +33 -0
- pyncraft/minecraft.py +408 -0
- pyncraft/settings.py +10 -0
- pyncraft/util.py +21 -0
- pyncraft/vec3.py +116 -0
- pyncraft-0.2.2.dist-info/METADATA +38 -0
- pyncraft-0.2.2.dist-info/RECORD +16 -0
- pyncraft-0.2.2.dist-info/WHEEL +5 -0
- pyncraft-0.2.2.dist-info/licenses/LICENSE +201 -0
- pyncraft-0.2.2.dist-info/top_level.txt +1 -0
pyncraft/__init__.py
ADDED
|
File without changes
|
pyncraft/block.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'''
|
|
2
|
+
This isn't tested (and isn't that useful since the 1.13/FruitJuice update),
|
|
3
|
+
but has been modified with the intent of maintaining backward compatibility
|
|
4
|
+
with mcpi-developed programs, and may be extended as a list of allowed blocks.
|
|
5
|
+
NOTE: See data/blocks_1.19.csv for many more available blocks
|
|
6
|
+
'''
|
|
7
|
+
class Block:
|
|
8
|
+
"""Minecraft PI block description. Can be sent to Minecraft.setBlock/s"""
|
|
9
|
+
def __init__(self, id, data=0):
|
|
10
|
+
self.id = id
|
|
11
|
+
self.data = data
|
|
12
|
+
|
|
13
|
+
def __cmp__(self, rhs):
|
|
14
|
+
return hash(self) - hash(rhs)
|
|
15
|
+
|
|
16
|
+
def __eq__(self, rhs):
|
|
17
|
+
return self.id == rhs.id and self.data == rhs.data
|
|
18
|
+
|
|
19
|
+
def __hash__(self):
|
|
20
|
+
return (self.id << 8) + self.data
|
|
21
|
+
|
|
22
|
+
def withData(self, data):
|
|
23
|
+
return Block(self.id, data)
|
|
24
|
+
|
|
25
|
+
def __iter__(self):
|
|
26
|
+
"""Allows a Block to be sent whenever id [and data] is needed"""
|
|
27
|
+
return iter((self.id, self.data))
|
|
28
|
+
|
|
29
|
+
def __repr__(self):
|
|
30
|
+
return "Block(%d, %d)"%(self.id, self.data)
|
|
31
|
+
|
|
32
|
+
AIR = Block("AIR")
|
|
33
|
+
STONE = Block("STONE")
|
|
34
|
+
GRASS = Block("GRASS_BLOCK")
|
|
35
|
+
DIRT = Block("DIRT")
|
|
36
|
+
COBBLESTONE = Block("COBBLESTONE")
|
|
37
|
+
WOOD_PLANKS = Block("OAK_PLANKS")
|
|
38
|
+
SAPLING = Block("OAK_SAPLING")
|
|
39
|
+
BEDROCK = Block("BEDROCK")
|
|
40
|
+
WATER_FLOWING = Block("WATER")
|
|
41
|
+
WATER = WATER_FLOWING
|
|
42
|
+
WATER_STATIONARY = Block("WATER")
|
|
43
|
+
LAVA_FLOWING = Block("LAVA")
|
|
44
|
+
LAVA = LAVA_FLOWING
|
|
45
|
+
LAVA_STATIONARY = Block("LAVA")
|
|
46
|
+
SAND = Block("SAND")
|
|
47
|
+
GRAVEL = Block("GRAVEL")
|
|
48
|
+
GOLD_ORE = Block("GOLD_ORE")
|
|
49
|
+
IRON_ORE = Block("IRON_ORE")
|
|
50
|
+
COAL_ORE = Block("COAL_ORE")
|
|
51
|
+
WOOD = Block("OAK_LOG")
|
|
52
|
+
LEAVES = Block("OAK_LEAVES")
|
|
53
|
+
GLASS = Block("GLASS")
|
|
54
|
+
LAPIS_LAZULI_ORE = Block("LAPIS_ORE")
|
|
55
|
+
LAPIS_LAZULI_BLOCK = Block("LAPIS_BLOCK")
|
|
56
|
+
SANDSTONE = Block("SANDSTONE")
|
|
57
|
+
BED = Block("WHITE_BED") # NOTE: this doesn't really work (2-block piece needs special handling)
|
|
58
|
+
COBWEB = Block("COBWEB")
|
|
59
|
+
GRASS_TALL = Block("TALL_GRASS")
|
|
60
|
+
WOOL = Block("WHITE_WOOL")
|
|
61
|
+
FLOWER_YELLOW = Block("DANDELION")
|
|
62
|
+
FLOWER_CYAN = Block(38)
|
|
63
|
+
MUSHROOM_BROWN = Block("BROWN_MUSHROOM")
|
|
64
|
+
MUSHROOM_RED = Block("RED_MUSHROOM")
|
|
65
|
+
GOLD_BLOCK = Block("GOLD_BLOCK")
|
|
66
|
+
IRON_BLOCK = Block("IRON_BLOCK")
|
|
67
|
+
STONE_SLAB_DOUBLE = Block(43)
|
|
68
|
+
STONE_SLAB = Block("STONE_SLAB")
|
|
69
|
+
BRICK_BLOCK = Block("BRICKS")
|
|
70
|
+
TNT = Block("TNT")
|
|
71
|
+
BOOKSHELF = Block("BOOKSHELF")
|
|
72
|
+
MOSS_STONE = Block("MOSS_STONE")
|
|
73
|
+
OBSIDIAN = Block("OBSIDIAN")
|
|
74
|
+
TORCH = Block("TORCH")
|
|
75
|
+
FIRE = Block("FIRE")
|
|
76
|
+
STAIRS_WOOD = Block("OAK_STAIRS")
|
|
77
|
+
CHEST = Block("CHEST")
|
|
78
|
+
DIAMOND_ORE = Block("DIAMOND_ORE")
|
|
79
|
+
DIAMOND_BLOCK = Block("DIAMOND_BLOCK")
|
|
80
|
+
CRAFTING_TABLE = Block("CRAFTING_TABLE")
|
|
81
|
+
FARMLAND = Block("FARMLAND")
|
|
82
|
+
FURNACE_INACTIVE = Block("FURNACE_INACTIVE")
|
|
83
|
+
FURNACE_ACTIVE = Block("FURNACE_ACTIVE")
|
|
84
|
+
DOOR_WOOD = Block("OAK_DOOR") # NOTE: this doesn't really work (2-block piece needs special handling)
|
|
85
|
+
LADDER = Block("LADDER")
|
|
86
|
+
STAIRS_COBBLESTONE = Block("COBBLESTONE_STAIRS")
|
|
87
|
+
DOOR_IRON = Block("IRON_DOOR")
|
|
88
|
+
REDSTONE_ORE = Block("REDSTONE_ORE")
|
|
89
|
+
SNOW = Block("SNOW")
|
|
90
|
+
ICE = Block("ICE")
|
|
91
|
+
SNOW_BLOCK = Block("SNOW_BLOCK")
|
|
92
|
+
CACTUS = Block("CACTUS")
|
|
93
|
+
CLAY = Block("CLAY")
|
|
94
|
+
SUGAR_CANE = Block("SUGAR_CANE")
|
|
95
|
+
FENCE = Block("OAK_FENCE")
|
|
96
|
+
GLOWSTONE_BLOCK = Block("GLOWSTONE")
|
|
97
|
+
BEDROCK_INVISIBLE = Block("BEDROCK")
|
|
98
|
+
STONE_BRICK = Block("STONE_BRICKS")
|
|
99
|
+
GLASS_PANE = Block("GLASS_PANE")
|
|
100
|
+
MELON = Block("MELON")
|
|
101
|
+
FENCE_GATE = Block("OAK_FENCE_GATE")
|
|
102
|
+
GLOWING_OBSIDIAN = Block("GLOWING_OBSIDIAN")
|
|
103
|
+
NETHER_REACTOR_CORE = Block(247)
|
pyncraft/connection.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import select
|
|
3
|
+
import sys
|
|
4
|
+
from .util import flatten_parameters_to_bytestring
|
|
5
|
+
|
|
6
|
+
""" @author: Aron Nieminen, Mojang AB"""
|
|
7
|
+
|
|
8
|
+
class RequestError(Exception):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
class ConnectionClosed(Exception):
|
|
12
|
+
"""The server closed the TCP connection.
|
|
13
|
+
|
|
14
|
+
Deliberately not named ConnectionError: that has been a Python builtin
|
|
15
|
+
since 3.3, and shadowing it here would mean any except ConnectionError
|
|
16
|
+
in this module silently stops catching real socket errors.
|
|
17
|
+
"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class Connection:
|
|
21
|
+
"""Connection to a Minecraft Pi game"""
|
|
22
|
+
RequestFailed = "Fail"
|
|
23
|
+
|
|
24
|
+
def __init__(self, address, port):
|
|
25
|
+
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
26
|
+
self.socket.connect((address, port))
|
|
27
|
+
self.lastSent = ""
|
|
28
|
+
|
|
29
|
+
def drain(self):
|
|
30
|
+
"""Drains the socket of incoming data"""
|
|
31
|
+
while True:
|
|
32
|
+
readable, _, _ = select.select([self.socket], [], [], 0.0)
|
|
33
|
+
if not readable:
|
|
34
|
+
break
|
|
35
|
+
data = self.socket.recv(1500)
|
|
36
|
+
if len(data) == 0:
|
|
37
|
+
raise ConnectionClosed("%s failed! Cause: connection closed" % self.lastSent.strip())
|
|
38
|
+
e = "Drained Data: <%s>\n"%data.strip().decode('cp437')
|
|
39
|
+
e += "Last Message: <%s>\n"%self.lastSent.strip().decode('cp437')
|
|
40
|
+
sys.stderr.write(e)
|
|
41
|
+
|
|
42
|
+
def send(self, f, *data):
|
|
43
|
+
"""
|
|
44
|
+
Sends data. Note that a trailing newline '\n' is added here
|
|
45
|
+
|
|
46
|
+
The protocol uses CP437 encoding - https://en.wikipedia.org/wiki/Code_page_437
|
|
47
|
+
which is mildly distressing as it can't encode all of Unicode.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
s = b"".join([f, b"(", flatten_parameters_to_bytestring(data), b")", b"\n"])
|
|
51
|
+
self._send(s)
|
|
52
|
+
|
|
53
|
+
def _send(self, s):
|
|
54
|
+
"""
|
|
55
|
+
The actual socket interaction from self.send, extracted for easier mocking
|
|
56
|
+
and testing
|
|
57
|
+
"""
|
|
58
|
+
self.drain()
|
|
59
|
+
self.lastSent = s
|
|
60
|
+
self.socket.sendall(s)
|
|
61
|
+
|
|
62
|
+
def receive(self):
|
|
63
|
+
"""Receives data. Note that the trailing newline '\n' is trimmed"""
|
|
64
|
+
s = self.socket.makefile("r").readline().rstrip("\n")
|
|
65
|
+
checkFail = s.split(",")
|
|
66
|
+
if checkFail[0] == Connection.RequestFailed:
|
|
67
|
+
# clear anything still queued, or the next call reads this failure's tail
|
|
68
|
+
self.drain()
|
|
69
|
+
raise RequestError("%s failed! Cause: %s" % (self.lastSent.strip(),checkFail[-1]))
|
|
70
|
+
return s
|
|
71
|
+
|
|
72
|
+
def sendReceive(self, *data):
|
|
73
|
+
"""Sends and receive data"""
|
|
74
|
+
self.send(*data)
|
|
75
|
+
return self.receive()
|
pyncraft/coord.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from typing import Callable, Iterable, Union, Optional
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Coord(np.ndarray):
|
|
8
|
+
"""
|
|
9
|
+
Implement 3D coordinates as a 3-element (x, y, z) numpy array subclass.
|
|
10
|
+
|
|
11
|
+
This avoids having to reinvent the wheel for basic vector operations.
|
|
12
|
+
You can pass in a Vec3, list, tuple, pd.Series or individual x, y, z values.
|
|
13
|
+
They must be numeric (int or float).
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
x : int, float, Iterable
|
|
18
|
+
The x coordinate or a 3-element iterable containing x, y, z coordinates.
|
|
19
|
+
Positive x is east, negative x is west.
|
|
20
|
+
y : int, float, None
|
|
21
|
+
The y coordinate (vertical position). Positive y is up, negative y is
|
|
22
|
+
down. Unused if `x` is an iterable.
|
|
23
|
+
z : int, float, None
|
|
24
|
+
The z coordinate. Positive z is south, negative z is north. Unused if
|
|
25
|
+
`x` is an iterable.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__int_types__ = (int, np.int32, np.int64)
|
|
29
|
+
__float_types__ = (float, np.float32, np.float64)
|
|
30
|
+
__numeric__ = __int_types__ + __float_types__
|
|
31
|
+
|
|
32
|
+
def __new__(cls,
|
|
33
|
+
x: Union[int, float, Iterable],
|
|
34
|
+
y: Union[int, float, None] = None,
|
|
35
|
+
z: Union[int, float, None] = None
|
|
36
|
+
):
|
|
37
|
+
if isinstance(x, Iterable):
|
|
38
|
+
x = [v for i, v in enumerate(x) if i < 4]
|
|
39
|
+
if len(x) != 3:
|
|
40
|
+
raise ValueError(f"Expected 3 coordinates, got {len(x)}: {x}")
|
|
41
|
+
x, y, z = tuple(x)
|
|
42
|
+
else:
|
|
43
|
+
if not isinstance(x, cls.__numeric__):
|
|
44
|
+
raise TypeError(f"x must be int or float, got {type(x)}: {x}")
|
|
45
|
+
if not isinstance(y, cls.__numeric__):
|
|
46
|
+
raise TypeError(f"y must be int or float, got {type(y)}: {y}")
|
|
47
|
+
if not isinstance(z, cls.__numeric__):
|
|
48
|
+
raise TypeError(f"z must be int or float, got {type(z)}: {z}")
|
|
49
|
+
|
|
50
|
+
# float64 always: an int array would silently truncate a later
|
|
51
|
+
# fractional assignment, and minecraft positions are fractional.
|
|
52
|
+
# Use block() when you want whole-number block coordinates.
|
|
53
|
+
obj = np.asarray([x, y, z], dtype=float).view(cls)
|
|
54
|
+
return obj
|
|
55
|
+
|
|
56
|
+
# accessor methods convert numpy int/float to Python int/float
|
|
57
|
+
@property
|
|
58
|
+
def x(self):
|
|
59
|
+
return int(self[0]) if isinstance(self[0], self.__int_types__) else float(self[0])
|
|
60
|
+
|
|
61
|
+
@x.setter
|
|
62
|
+
def x(self, value):
|
|
63
|
+
self[0] = value
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def y(self):
|
|
67
|
+
return int(self[1]) if isinstance(self[1], self.__int_types__) else float(self[1])
|
|
68
|
+
|
|
69
|
+
@y.setter
|
|
70
|
+
def y(self, value):
|
|
71
|
+
self[1] = value
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def z(self):
|
|
75
|
+
return int(self[2]) if isinstance(self[2], self.__int_types__) else float(self[2])
|
|
76
|
+
|
|
77
|
+
@z.setter
|
|
78
|
+
def z(self, value):
|
|
79
|
+
self[2] = value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def adjust(self, axis: str, delta: Union[int, float]) -> 'Coord':
|
|
83
|
+
"""
|
|
84
|
+
Copy Coord and adjust one axis (x, y or z) by delta
|
|
85
|
+
"""
|
|
86
|
+
axis = {'x': 0, 'y': 1, 'z': 2}.get(str(axis).lower())
|
|
87
|
+
if axis is None:
|
|
88
|
+
# without this, coord[None] += delta broadcasts across every axis
|
|
89
|
+
raise ValueError("axis must be one of 'x', 'y' or 'z'")
|
|
90
|
+
coord = self.copy()
|
|
91
|
+
coord[axis] += delta
|
|
92
|
+
return coord
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def block(self, as_int: bool = True) -> 'Coord':
|
|
96
|
+
"""
|
|
97
|
+
Get the whole-number block coordinates as Coord object By Flooring.
|
|
98
|
+
|
|
99
|
+
If as_int is True, returns the block coordinate as an integer.
|
|
100
|
+
Otherwise, float.
|
|
101
|
+
"""
|
|
102
|
+
block = np.floor(self)
|
|
103
|
+
if as_int:
|
|
104
|
+
block = block.astype(int)
|
|
105
|
+
return block
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def midblock(self) -> 'Coord':
|
|
109
|
+
return self.block(as_int=False) + Coord(0.5, 0.0, 0.5)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def coplane(self, c2: 'Coord') -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Check whether the second coordinate shares a plane with this one.
|
|
115
|
+
|
|
116
|
+
Use Vec3D(c1, c2).coplane() if you want to know which plane.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
return bool(Vec3D(self, c2).coplane())
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def direction(self, c2: 'Coord') -> 'Vec3D':
|
|
123
|
+
"""
|
|
124
|
+
Get the unit vector from c1 to c2 (normalized to length 1).
|
|
125
|
+
"""
|
|
126
|
+
return Vec3D(self, c2).direction()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def cardinal_direction(self, c2: 'Coord', *args, **kwargs) -> Union[str, 'Coord']:
|
|
130
|
+
"""
|
|
131
|
+
Get the Cardinal Direction of a Second Coordinate.
|
|
132
|
+
|
|
133
|
+
See help for Vec3D.cardinal_direction() for details.
|
|
134
|
+
"""
|
|
135
|
+
return Vec3D(self, c2).cardinal_direction(*args, **kwargs)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def distance(self, c2: 'Coord') -> float:
|
|
139
|
+
"""
|
|
140
|
+
Calculate the distance between two coordinates.
|
|
141
|
+
"""
|
|
142
|
+
return float(np.sqrt(((self - c2) ** 2).sum()))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Vec3D(Coord):
|
|
147
|
+
"""
|
|
148
|
+
A 3D vector class that takes one or two Coord objects as input.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self, c1: Coord, c2: Coord = None):
|
|
152
|
+
|
|
153
|
+
v = c1 if c2 is None else c2 - c1
|
|
154
|
+
|
|
155
|
+
self.x = v.x
|
|
156
|
+
self.y = v.y
|
|
157
|
+
self.z = v.z
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def rotateLeft(self): self.x, self.z = self.z, -self.x
|
|
161
|
+
|
|
162
|
+
def rotateRight(self): self.x, self.z = -self.z, self.x
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def is_unit(self):
|
|
166
|
+
"""Test if the vector is a unit vector (normalized to length 1)."""
|
|
167
|
+
return np.isclose(np.linalg.norm(self), 1.0)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def direction(self) -> Coord:
|
|
171
|
+
"""
|
|
172
|
+
Get the unit vector from c1 to c2 (normalized to length 1).
|
|
173
|
+
"""
|
|
174
|
+
return (self) / np.linalg.norm(self)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def coplane(self) -> str:
|
|
178
|
+
"""
|
|
179
|
+
Which plane, if any, this vector lies in.
|
|
180
|
+
|
|
181
|
+
Returns the axes the vector does not move along: 'y' means it stays at
|
|
182
|
+
one height, 'xz' means it only moves up and down, '' means it moves in
|
|
183
|
+
all three. Falsy when the vector is in no plane.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
plane = ''
|
|
187
|
+
if np.isclose(self.x, 0.0):
|
|
188
|
+
plane += 'x'
|
|
189
|
+
if np.isclose(self.y, 0.0):
|
|
190
|
+
plane += 'y'
|
|
191
|
+
if np.isclose(self.z, 0.0):
|
|
192
|
+
plane += 'z'
|
|
193
|
+
|
|
194
|
+
return plane
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def cardinal_direction(
|
|
198
|
+
self,
|
|
199
|
+
returns: str = 'string',
|
|
200
|
+
compass_points: int = 4
|
|
201
|
+
) -> Union[str, int, Coord]:
|
|
202
|
+
"""
|
|
203
|
+
Get the Cardinal Direction Of The Vector In The XY Plane.
|
|
204
|
+
|
|
205
|
+
Uses the x and z components only, and returns the closest direction
|
|
206
|
+
based on the number fo compass points. Direction may be returned
|
|
207
|
+
as a string ("north"), minecraft rotation value (0-15), degrees (0-360),
|
|
208
|
+
radians (0-2pi), or a Vec3D/Coord unit vector with y==0.
|
|
209
|
+
|
|
210
|
+
Note that when there is a tie (like x=1 and z=-1 with 4 compass points),
|
|
211
|
+
I'm not doing anything specific to resolve (north or east in this case).
|
|
212
|
+
Could return 'indeteminate', None/np.nan or raise an exception. Also
|
|
213
|
+
leaving it up to the user whether to convert Coords to integer.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
returns : str
|
|
218
|
+
The type of return value. Can be 'string', 'rotation', 'coord', or
|
|
219
|
+
'degrees'. Default is 'string'.
|
|
220
|
+
compass_points : int
|
|
221
|
+
The number of compass points to use for the direction.
|
|
222
|
+
Default is 4 (north, east, south, west). If set to 8, it will also
|
|
223
|
+
return northeast, southeast, southwest, and northwest. If set
|
|
224
|
+
to 16, it will return all 16 compass points ('northnortheast',
|
|
225
|
+
'northeast', 'eastnortheast', 'east', etc).
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
Union[str, int, Coord]:
|
|
230
|
+
If returns is string: 'north', 'south', 'east', 'west',.
|
|
231
|
+
"""
|
|
232
|
+
ok = {'string', 'rotation', 'coord', 'degrees', 'radians'}
|
|
233
|
+
invalid = {returns} - ok
|
|
234
|
+
if invalid:
|
|
235
|
+
raise ValueError(
|
|
236
|
+
f'Invalid returns argument "{returns}"; must be one of:\n '
|
|
237
|
+
+ ', '.join(list(ok)))
|
|
238
|
+
|
|
239
|
+
if self.x == 0 and self.z == 0:
|
|
240
|
+
raise ValueError('Vector point straight up or down; indeterminate.')
|
|
241
|
+
|
|
242
|
+
# drop the y component and normalize the xz vector (z -> y)
|
|
243
|
+
xz = np.delete(self, 1)
|
|
244
|
+
xz = xz / np.linalg.norm(xz)
|
|
245
|
+
|
|
246
|
+
# atan2 returns angle from x-axis (east), so swap arguments and invert z
|
|
247
|
+
# calculate fraction of a turn [0, 1) from North = 0 degrees
|
|
248
|
+
frac360 = (np.arctan2(xz.x, -xz.y) / (np.pi * 2)) % 1.0
|
|
249
|
+
|
|
250
|
+
bearings = ['north', 'northnortheast', 'northeast', 'eastnortheast',
|
|
251
|
+
'east', 'eastsoutheast', 'southeast', 'southsoutheast',
|
|
252
|
+
'south', 'southsouthwest', 'southwest', 'westsouthwest',
|
|
253
|
+
'west', 'westnorthwest', 'northwest', 'northnorthwest',
|
|
254
|
+
'north']
|
|
255
|
+
|
|
256
|
+
if compass_points > 0:
|
|
257
|
+
# convert to a rotation value, rounded to the nearest compass point
|
|
258
|
+
rotation = int(
|
|
259
|
+
np.round(frac360 * compass_points) * 16 / compass_points)
|
|
260
|
+
|
|
261
|
+
if returns == 'rotation':
|
|
262
|
+
return rotation
|
|
263
|
+
elif returns == 'degrees':
|
|
264
|
+
return rotation * 360 / 16
|
|
265
|
+
elif returns == 'string':
|
|
266
|
+
return bearings[rotation]
|
|
267
|
+
else:
|
|
268
|
+
radians = rotation * 2 * np.pi / 16
|
|
269
|
+
if returns == 'radians':
|
|
270
|
+
return radians
|
|
271
|
+
else:
|
|
272
|
+
# rounded unit vector in the xz plane
|
|
273
|
+
coord = Coord(np.sin(radians), 0, -np.cos(radians))
|
|
274
|
+
if compass_points == 4:
|
|
275
|
+
coord = coord.astype(int)
|
|
276
|
+
return coord
|
|
277
|
+
elif returns in ('string', 'rotation'):
|
|
278
|
+
raise ValueError(
|
|
279
|
+
f'Must set compass_points > 0 for returns "string" or "rotation"')
|
|
280
|
+
else:
|
|
281
|
+
# return results without rounding
|
|
282
|
+
radians = frac360 * 2 * np.pi
|
|
283
|
+
if returns == 'degrees':
|
|
284
|
+
return frac360 * 360
|
|
285
|
+
else:
|
|
286
|
+
if returns == 'radians':
|
|
287
|
+
return radians
|
|
288
|
+
else:
|
|
289
|
+
return Coord(np.sin(radians), 0,-np.cos(radians))
|
pyncraft/entity.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
class Entity:
|
|
2
|
+
'''Minecraft PI entity description. Can be sent to Minecraft.spawnEntity'''
|
|
3
|
+
|
|
4
|
+
def __init__(self, id, name = None):
|
|
5
|
+
self.id = id
|
|
6
|
+
self.name = name
|
|
7
|
+
|
|
8
|
+
def __cmp__(self, rhs):
|
|
9
|
+
return hash(self) - hash(rhs)
|
|
10
|
+
|
|
11
|
+
def __eq__(self, rhs):
|
|
12
|
+
return self.id == rhs.id
|
|
13
|
+
|
|
14
|
+
def __hash__(self):
|
|
15
|
+
return self.id
|
|
16
|
+
|
|
17
|
+
def __iter__(self):
|
|
18
|
+
'''Allows an Entity to be sent whenever id is needed'''
|
|
19
|
+
return iter((self.id,))
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return 'Entity(%d)'%(self.id)
|
|
23
|
+
|
|
24
|
+
EXPERIENCE_ORB = Entity(2, "EXPERIENCE_ORB")
|
|
25
|
+
AREA_EFFECT_CLOUD = Entity(3, "AREA_EFFECT_CLOUD")
|
|
26
|
+
ELDER_GUARDIAN = Entity(4, "ELDER_GUARDIAN")
|
|
27
|
+
WITHER_SKELETON = Entity(5, "WITHER_SKELETON")
|
|
28
|
+
STRAY = Entity(6, "STRAY")
|
|
29
|
+
EGG = Entity(7, "EGG")
|
|
30
|
+
LEASH_HITCH = Entity(8, "LEASH_HITCH")
|
|
31
|
+
PAINTING = Entity(9, "PAINTING")
|
|
32
|
+
ARROW = Entity(10, "ARROW")
|
|
33
|
+
SNOWBALL = Entity(11, "SNOWBALL")
|
|
34
|
+
FIREBALL = Entity(12, "FIREBALL")
|
|
35
|
+
SMALL_FIREBALL = Entity(13, "SMALL_FIREBALL")
|
|
36
|
+
ENDER_PEARL = Entity(14, "ENDER_PEARL")
|
|
37
|
+
ENDER_SIGNAL = Entity(15, "ENDER_SIGNAL")
|
|
38
|
+
THROWN_EXP_BOTTLE = Entity(17, "THROWN_EXP_BOTTLE")
|
|
39
|
+
ITEM_FRAME = Entity(18, "ITEM_FRAME")
|
|
40
|
+
WITHER_SKULL = Entity(19, "WITHER_SKULL")
|
|
41
|
+
PRIMED_TNT = Entity(20, "PRIMED_TNT")
|
|
42
|
+
HUSK = Entity(23, "HUSK")
|
|
43
|
+
SPECTRAL_ARROW = Entity(24, "SPECTRAL_ARROW")
|
|
44
|
+
SHULKER_BULLET = Entity(25, "SHULKER_BULLET")
|
|
45
|
+
DRAGON_FIREBALL = Entity(26, "DRAGON_FIREBALL")
|
|
46
|
+
ZOMBIE_VILLAGER = Entity(27, "ZOMBIE_VILLAGER")
|
|
47
|
+
SKELETON_HORSE = Entity(28, "SKELETON_HORSE")
|
|
48
|
+
ZOMBIE_HORSE = Entity(29, "ZOMBIE_HORSE")
|
|
49
|
+
ARMOR_STAND = Entity(30, "ARMOR_STAND")
|
|
50
|
+
DONKEY = Entity(31, "DONKEY")
|
|
51
|
+
MULE = Entity(32, "MULE")
|
|
52
|
+
EVOKER_FANGS = Entity(33, "EVOKER_FANGS")
|
|
53
|
+
EVOKER = Entity(34, "EVOKER")
|
|
54
|
+
VEX = Entity(35, "VEX")
|
|
55
|
+
VINDICATOR = Entity(36, "VINDICATOR")
|
|
56
|
+
ILLUSIONER = Entity(37, "ILLUSIONER")
|
|
57
|
+
MINECART_COMMAND = Entity(40, "MINECART_COMMAND")
|
|
58
|
+
BOAT = Entity(41, "BOAT")
|
|
59
|
+
MINECART = Entity(42, "MINECART")
|
|
60
|
+
MINECART_CHEST = Entity(43, "MINECART_CHEST")
|
|
61
|
+
MINECART_FURNACE = Entity(44, "MINECART_FURNACE")
|
|
62
|
+
MINECART_TNT = Entity(45, "MINECART_TNT")
|
|
63
|
+
MINECART_HOPPER = Entity(46, "MINECART_HOPPER")
|
|
64
|
+
MINECART_MOB_SPAWNER = Entity(47, "MINECART_MOB_SPAWNER")
|
|
65
|
+
CREEPER = Entity(50, "CREEPER")
|
|
66
|
+
SKELETON = Entity(51, "SKELETON")
|
|
67
|
+
SPIDER = Entity(52, "SPIDER")
|
|
68
|
+
GIANT = Entity(53, "GIANT")
|
|
69
|
+
ZOMBIE = Entity(54, "ZOMBIE")
|
|
70
|
+
SLIME = Entity(55, "SLIME")
|
|
71
|
+
GHAST = Entity(56, "GHAST")
|
|
72
|
+
PIG_ZOMBIE = Entity(57, "PIG_ZOMBIE")
|
|
73
|
+
ENDERMAN = Entity(58, "ENDERMAN")
|
|
74
|
+
CAVE_SPIDER = Entity(59, "CAVE_SPIDER")
|
|
75
|
+
SILVERFISH = Entity(60, "SILVERFISH")
|
|
76
|
+
BLAZE = Entity(61, "BLAZE")
|
|
77
|
+
MAGMA_CUBE = Entity(62, "MAGMA_CUBE")
|
|
78
|
+
ENDER_DRAGON = Entity(63, "ENDER_DRAGON")
|
|
79
|
+
WITHER = Entity(64, "WITHER")
|
|
80
|
+
BAT = Entity(65, "BAT")
|
|
81
|
+
WITCH = Entity(66, "WITCH")
|
|
82
|
+
ENDERMITE = Entity(67, "ENDERMITE")
|
|
83
|
+
GUARDIAN = Entity(68, "GUARDIAN")
|
|
84
|
+
SHULKER = Entity(69, "SHULKER")
|
|
85
|
+
PIG = Entity(90, "PIG")
|
|
86
|
+
SHEEP = Entity(91, "SHEEP")
|
|
87
|
+
COW = Entity(92, "COW")
|
|
88
|
+
CHICKEN = Entity(93, "CHICKEN")
|
|
89
|
+
SQUID = Entity(94, "SQUID")
|
|
90
|
+
WOLF = Entity(95, "WOLF")
|
|
91
|
+
MUSHROOM_COW = Entity(96, "MUSHROOM_COW")
|
|
92
|
+
SNOWMAN = Entity(97, "SNOWMAN")
|
|
93
|
+
OCELOT = Entity(98, "OCELOT")
|
|
94
|
+
IRON_GOLEM = Entity(99, "IRON_GOLEM")
|
|
95
|
+
HORSE = Entity(100, "HORSE")
|
|
96
|
+
RABBIT = Entity(101, "RABBIT")
|
|
97
|
+
POLAR_BEAR = Entity(102, "POLAR_BEAR")
|
|
98
|
+
LLAMA = Entity(103, "LLAMA")
|
|
99
|
+
LLAMA_SPIT = Entity(104, "LLAMA_SPIT")
|
|
100
|
+
PARROT = Entity(105, "PARROT")
|
|
101
|
+
VILLAGER = Entity(120, "VILLAGER")
|
|
102
|
+
ENDER_CRYSTAL = Entity(200, "ENDER_CRYSTAL")
|
pyncraft/event.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from .vec3 import Vec3
|
|
2
|
+
|
|
3
|
+
class BlockEvent:
|
|
4
|
+
"""An Event related to blocks (e.g. placed, removed, hit)"""
|
|
5
|
+
HIT = 0
|
|
6
|
+
|
|
7
|
+
def __init__(self, type, x, y, z, face, entityId):
|
|
8
|
+
self.type = type
|
|
9
|
+
self.pos = Vec3(x, y, z)
|
|
10
|
+
self.face = face
|
|
11
|
+
self.entityId = entityId
|
|
12
|
+
|
|
13
|
+
def __repr__(self):
|
|
14
|
+
sType = {
|
|
15
|
+
BlockEvent.HIT: "BlockEvent.HIT"
|
|
16
|
+
}.get(self.type, "???")
|
|
17
|
+
|
|
18
|
+
return "BlockEvent(%s, %d, %d, %d, %d, %d)"%(
|
|
19
|
+
sType,self.pos.x,self.pos.y,self.pos.z,self.face,self.entityId);
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def Hit(x, y, z, face, entityId):
|
|
23
|
+
return BlockEvent(BlockEvent.HIT, x, y, z, face, entityId)
|
|
24
|
+
|
|
25
|
+
class ArrowHitEvent:
|
|
26
|
+
"""An Event related to blocks (e.g. placed, removed, hit)"""
|
|
27
|
+
HIT = 0
|
|
28
|
+
|
|
29
|
+
def __init__(self, type, x, y, z, entityId):
|
|
30
|
+
self.type = type
|
|
31
|
+
self.pos = Vec3(x, y, z)
|
|
32
|
+
self.entityId = entityId
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
sType = {
|
|
36
|
+
ArrowHitEvent.HIT: "ArrowHitEvent.HIT"
|
|
37
|
+
}.get(self.type, "???")
|
|
38
|
+
|
|
39
|
+
return "BlockEvent(%s, %d, %d, %d, %d)"%(
|
|
40
|
+
sType,self.pos.x,self.pos.y,self.pos.z,self.entityId);
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def Hit(x, y, z, entityId):
|
|
44
|
+
return ArrowHitEvent(ArrowHitEvent.HIT, x, y, z, entityId)
|
|
45
|
+
|
|
46
|
+
class ChatEvent:
|
|
47
|
+
"""An Event related to chat (e.g. posts)"""
|
|
48
|
+
POST = 0
|
|
49
|
+
|
|
50
|
+
def __init__(self, type, entityId, message):
|
|
51
|
+
self.type = type
|
|
52
|
+
self.entityId = entityId
|
|
53
|
+
self.message = message
|
|
54
|
+
|
|
55
|
+
def __repr__(self):
|
|
56
|
+
sType = {
|
|
57
|
+
ChatEvent.POST: "ChatEvent.POST"
|
|
58
|
+
}.get(self.type, "???")
|
|
59
|
+
|
|
60
|
+
return "ChatEvent(%s, %d, %s)"%(
|
|
61
|
+
sType,self.entityId,self.message);
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def Post(entityId, message):
|
|
65
|
+
return ChatEvent(ChatEvent.POST, entityId, message)
|
pyncraft/logger.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pyncraft.settings as settings
|
|
2
|
+
|
|
3
|
+
class BColors:
|
|
4
|
+
|
|
5
|
+
TWHITE = '\033[32m'
|
|
6
|
+
TGREY = '\033[90m'
|
|
7
|
+
TRED = '\033[31m'
|
|
8
|
+
HEADER = '\033[95m'
|
|
9
|
+
OKBLUE = '\033[94m'
|
|
10
|
+
OKGREEN = '\033[92m'
|
|
11
|
+
LOG ='\033[96m'
|
|
12
|
+
WARNING = '\033[45m'
|
|
13
|
+
FAIL = '\033[91m'
|
|
14
|
+
ENDC = '\033[0m'
|
|
15
|
+
BOLD = '\033[1m'
|
|
16
|
+
UNDERLINE = '\033[4m'
|
|
17
|
+
def disable(self):
|
|
18
|
+
self.HEADER = ''
|
|
19
|
+
self.OKBLUE = ''
|
|
20
|
+
self.OKGREEN = ''
|
|
21
|
+
self.WARNING = ''
|
|
22
|
+
self.FAIL = ''
|
|
23
|
+
self.ENDC = ''
|
|
24
|
+
|
|
25
|
+
def debug(*msg):
|
|
26
|
+
if(settings.SHOW_DEBUG):
|
|
27
|
+
print(BColors.TGREY,msg,BColors.ENDC)
|
|
28
|
+
|
|
29
|
+
def log(*msg):
|
|
30
|
+
if(settings.SHOW_Log):
|
|
31
|
+
print(BColors.LOG,msg,BColors.ENDC)
|
|
32
|
+
def warn(*msg):
|
|
33
|
+
print(BColors.WARNING,msg,BColors.ENDC)
|