Source code for mciwb.player
"""
Represent a player in the world and provide functions for monitoring their
state
"""
import math
import re
from time import sleep
from typing import List, Match, Pattern, Tuple
from mcwb.types import Direction, Vec3
from mcwb.volume import Volume
from mciwb.logging import log
from mciwb.threads import get_client
regex_coord = re.compile(r"\[(-?\d+.?\d*)d, *(-?\d+.?\d*)d, *(-?\d+.?\d*)d\]")
float_reg = r"(-?\ *[0-9]+\.?[0-9]*(?:[Ee]\ *-?\ *[0-9]+)?)f"
regex_angle = re.compile(float_reg)
regex_rot = re.compile(r"\[(-?\d+.?\d*)f, *(-?\d+.?\d*)f\]")
class PlayerNotInWorld(Exception):
pass
[docs]
class Player:
"""
Represent a player in the world and provide functions for monitoring their
position and direction they are facing.
"""
def __init__(self, name: str) -> None:
self.name = name
client = get_client()
# make sure the player is an operator in creative mode (but not in test mode)
if self.name != "georgeTest":
client.op(name)
client.gamemode("creative", name)
def _get_entity_data(self, path: str, regex: Pattern[str]) -> Match[str]:
"""
Get entity data with retries - the remote function sometimes fails to find
an entity that does exist
"""
client = get_client()
for _ in range(5):
# entity=self.name would not work for the dummy stand used for testing
data = client.data.get(entity=f"@e[name={self.name},limit=1]", path=path)
match = regex.search(data)
if match:
return match
else:
log.debug(f"{self.name} not found: {data}")
sleep(0.1)
raise PlayerNotInWorld(f"player {self.name} left")
@property
def inventory(self) -> List[str]:
"""
Get the player's inventory
"""
# TODO long term this should return a list of Item objects
# TODO and Item should be extended to allow properties
return get_client().data.get(entity=self.name, path="Inventory")
@property
def pos_f(self) -> Vec3:
"""
Return the player's precise position
"""
match = self._get_entity_data("Pos", regex_coord)
return Vec3(float(match.group(1)), float(match.group(2)), float(match.group(3)))
@property
def pos(self) -> Vec3:
"""
Return the player's block position
"""
return self.pos_f.with_ints()
@property
def facing(self) -> Vec3:
"""
Return the player's facing direction
:return: a Vec3 representing the direction the player is facing
NORTH = Vec3(0, 0, -1)
SOUTH = Vec3(0, 0, 1)
EAST = Vec3(1, 0, 0)
WEST = Vec3(-1, 0, 0)
"""
match = self._get_entity_data("Rotation", regex_angle)
angle = float(match.group(1))
index = int(((math.floor(angle) + 45) % 360) / 90)
return Direction.cardinals[index]
@property
def rotation(self) -> Tuple[float, float]:
"""
Get the player's rotation in degrees
"""
match = self._get_entity_data("Rotation", regex_rot)
return float(match.group(1)), float(match.group(2))
[docs]
def player_in(self, volume: Volume) -> bool:
"""
Check if the player is inside the Volume
:param volume: the volume of blocks to check
"""
return volume.inside(self.pos)
[docs]
@classmethod
def players_in(cls, volume: Volume) -> List["Player"]:
"""
return a list of player names whose position is inside the Volume
"""
client = get_client()
players = []
names = [p.name for p in client.players.players]
for name in names:
try:
pos = Player(name).pos
if volume.inside(pos, 2):
players.append(name)
except ValueError:
pass # players are sometimes missing temporarily
return players