diff --git a/endstone/__init__.pyi b/endstone/__init__.pyi index d8b87f44e6..d57e3c4b5a 100644 --- a/endstone/__init__.pyi +++ b/endstone/__init__.pyi @@ -14,6 +14,7 @@ from . import ( boss, command, damage, + debug, effect, enchantments, event, @@ -36,6 +37,7 @@ from .ban import IpBanList, PlayerBanList from .block import BlockData from .boss import BarColor, BarFlag, BarStyle, BossBar from .command import CommandSender, ConsoleCommandSender +from .debug import DebugArrow, DebugBox, DebugCircle, DebugLine, DebugSphere, DebugText from .form import ActionForm, MessageForm, ModalForm from .inventory import Inventory, ItemFactory, PlayerInventory from .lang import Language, Translatable @@ -64,6 +66,7 @@ __all__ = [ "boss", "command", "damage", + "debug", "effect", "enchantments", "event", @@ -624,6 +627,23 @@ class Player(Mob): Sends a packet to the player. """ ... + def add_debug_shape( + self, location: Location, shape: DebugBox | DebugSphere | DebugCircle | DebugLine | DebugArrow | DebugText + ) -> int: + """ + Adds a debug shape visible only to this player. + """ + ... + def remove_debug_shape(self, id: int) -> None: + """ + Removes a specific debug shape from this player by its id. + """ + ... + def remove_debug_shapes(self) -> None: + """ + Removes all debug shapes visible to this player. + """ + ... class ColorFormat: """ diff --git a/endstone/debug/__init__.py b/endstone/debug/__init__.py new file mode 100644 index 0000000000..4806d6fb36 --- /dev/null +++ b/endstone/debug/__init__.py @@ -0,0 +1,8 @@ +import lazy_loader as lazy + +__getattr__, __dir__, __all__ = lazy.attach( + "endstone._python", + submod_attrs={ + "debug": ["DebugBox", "DebugSphere", "DebugCircle", "DebugLine", "DebugArrow", "DebugText"], + }, +) diff --git a/endstone/debug/__init__.pyi b/endstone/debug/__init__.pyi new file mode 100644 index 0000000000..6482c9306e --- /dev/null +++ b/endstone/debug/__init__.pyi @@ -0,0 +1,243 @@ +""" +Classes relating to debug shape drawing utilities. +""" + +from endstone.util import Vector + +__all__ = ["DebugArrow", "DebugBox", "DebugCircle", "DebugLine", "DebugSphere", "DebugText"] + +class DebugBox: + """ + Represents a debug box (cuboid) shape. + """ + def __init__(self) -> None: ... + @property + def color(self) -> tuple[int, ...]: + """ + The color of this shape. + """ + ... + @color.setter + def color(self, arg1: tuple[int, ...]) -> DebugBox: ... + @property + def scale(self) -> float: + """ + The uniform scale factor. + """ + ... + @scale.setter + def scale(self, arg1: float) -> DebugBox: ... + @property + def rotation(self) -> Vector: + """ + The rotation of this shape as Euler angles (pitch, yaw, roll). + """ + ... + @rotation.setter + def rotation(self, arg1: Vector) -> DebugBox: ... + @property + def bound(self) -> Vector: + """ + The bounding size of the box. Final size = bound * scale. + """ + ... + @bound.setter + def bound(self, arg1: Vector) -> DebugBox: ... + +class DebugSphere: + """ + Represents a debug sphere shape. Radius is controlled via scale. + """ + def __init__(self) -> None: ... + @property + def color(self) -> tuple[int, ...]: + """ + The color of this shape. + """ + ... + @color.setter + def color(self, arg1: tuple[int, ...]) -> DebugSphere: ... + @property + def scale(self) -> float: + """ + The uniform scale factor. + """ + ... + @scale.setter + def scale(self, arg1: float) -> DebugSphere: ... + @property + def rotation(self) -> Vector: + """ + The rotation of this shape as Euler angles (pitch, yaw, roll). + """ + ... + @rotation.setter + def rotation(self, arg1: Vector) -> DebugSphere: ... + +class DebugCircle: + """ + Represents a debug 2D circle shape. Radius is controlled via scale. + """ + def __init__(self) -> None: ... + @property + def color(self) -> tuple[int, ...]: + """ + The color of this shape. + """ + ... + @color.setter + def color(self, arg1: tuple[int, ...]) -> DebugCircle: ... + @property + def scale(self) -> float: + """ + The uniform scale factor. + """ + ... + @scale.setter + def scale(self, arg1: float) -> DebugCircle: ... + @property + def rotation(self) -> Vector: + """ + The rotation of this shape as Euler angles (pitch, yaw, roll). + """ + ... + @rotation.setter + def rotation(self, arg1: Vector) -> DebugCircle: ... + +class DebugLine: + """ + Represents a debug line segment shape. + """ + def __init__(self) -> None: ... + @property + def color(self) -> tuple[int, ...]: + """ + The color of this shape. + """ + ... + @color.setter + def color(self, arg1: tuple[int, ...]) -> DebugLine: ... + @property + def scale(self) -> float: + """ + The uniform scale factor. + """ + ... + @scale.setter + def scale(self, arg1: float) -> DebugLine: ... + @property + def rotation(self) -> Vector: + """ + The rotation of this shape as Euler angles (pitch, yaw, roll). + """ + ... + @rotation.setter + def rotation(self, arg1: Vector) -> DebugLine: ... + @property + def length(self) -> float: + """ + The length of the line segment. + """ + ... + @length.setter + def length(self, arg1: float) -> DebugLine: ... + +class DebugArrow: + """ + Represents a debug arrow shape. + """ + def __init__(self) -> None: ... + @property + def color(self) -> tuple[int, ...]: + """ + The color of this shape. + """ + ... + @color.setter + def color(self, arg1: tuple[int, ...]) -> DebugArrow: ... + @property + def scale(self) -> float: + """ + The uniform scale factor. + """ + ... + @scale.setter + def scale(self, arg1: float) -> DebugArrow: ... + @property + def rotation(self) -> Vector: + """ + The rotation of this shape as Euler angles (pitch, yaw, roll). + """ + ... + @rotation.setter + def rotation(self, arg1: Vector) -> DebugArrow: ... + @property + def length(self) -> float: + """ + The length of the arrow. + """ + ... + @length.setter + def length(self, arg1: float) -> DebugArrow: ... + @property + def head_length(self) -> float: + """ + The length of the arrow's head. + """ + ... + @head_length.setter + def head_length(self, arg1: float) -> DebugArrow: ... + @property + def head_radius(self) -> float: + """ + The radius of the arrow's head. + """ + ... + @head_radius.setter + def head_radius(self, arg1: float) -> DebugArrow: ... + @property + def head_segments(self) -> int: + """ + The number of segments for the arrow head's base circle. + """ + ... + @head_segments.setter + def head_segments(self, arg1: int) -> DebugArrow: ... + +class DebugText: + """ + Represents a debug billboard text label. + """ + def __init__(self) -> None: ... + @property + def color(self) -> tuple[int, ...]: + """ + The color of this shape. + """ + ... + @color.setter + def color(self, arg1: tuple[int, ...]) -> DebugText: ... + @property + def scale(self) -> float: + """ + The uniform scale factor. + """ + ... + @scale.setter + def scale(self, arg1: float) -> DebugText: ... + @property + def rotation(self) -> Vector: + """ + The rotation of this shape as Euler angles (pitch, yaw, roll). + """ + ... + @rotation.setter + def rotation(self, arg1: Vector) -> DebugText: ... + @property + def text(self) -> str: + """ + The text displayed by this label. + """ + ... + @text.setter + def text(self, arg1: str) -> DebugText: ... diff --git a/include/endstone/debug/shape.h b/include/endstone/debug/shape.h new file mode 100644 index 0000000000..4ccd06027d --- /dev/null +++ b/include/endstone/debug/shape.h @@ -0,0 +1,30 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "endstone/debug/shape/arrow.h" +#include "endstone/debug/shape/box.h" +#include "endstone/debug/shape/circle.h" +#include "endstone/debug/shape/line.h" +#include "endstone/debug/shape/sphere.h" +#include "endstone/debug/shape/text.h" + +namespace endstone { + +using DebugShapeVariant = std::variant; + +} // namespace endstone diff --git a/include/endstone/debug/shape/arrow.h b/include/endstone/debug/shape/arrow.h new file mode 100644 index 0000000000..4acf12cf92 --- /dev/null +++ b/include/endstone/debug/shape/arrow.h @@ -0,0 +1,93 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "endstone/debug/shape/shape.h" + +namespace endstone { + +/** + * @brief Represents a debug arrow shape. + * An arrow is a line segment with a cone head at the end point. + */ +class DebugArrow : public DebugShape { +public: + /** + * @brief Gets the length of the arrow. + */ + [[nodiscard]] float getLength() const { return length_; } + + /** + * @brief Sets the length of the arrow. + */ + DebugArrow &setLength(float length) + { + length_ = length; + return *this; + } + + /** + * @brief Gets the length of the arrow's head. + */ + [[nodiscard]] float getHeadLength() const { return head_length_; } + + /** + * @brief Sets the length of the arrow's head. + */ + DebugArrow &setHeadLength(float length) + { + head_length_ = length; + return *this; + } + + /** + * @brief Gets the radius of the arrow's head. + */ + [[nodiscard]] float getHeadRadius() const { return head_radius_; } + + /** + * @brief Sets the radius of the arrow's head. + */ + DebugArrow &setHeadRadius(float radius) + { + head_radius_ = radius; + return *this; + } + + /** + * @brief Gets the number of segments for the arrow head's base circle. + */ + [[nodiscard]] int getHeadSegments() const { return head_segments_; } + + /** + * @brief Sets the number of segments for the arrow head's base circle. + * @param segments value between 3 and 128, default is 4 + */ + DebugArrow &setHeadSegments(int segments) + { + head_segments_ = std::clamp(segments, 3, 128); + return *this; + } + +private: + float length_{1.0F}; + float head_length_{0.5F}; + float head_radius_{0.25F}; + int head_segments_{4}; +}; + +} // namespace endstone diff --git a/include/endstone/debug/shape/box.h b/include/endstone/debug/shape/box.h new file mode 100644 index 0000000000..26b88b781b --- /dev/null +++ b/include/endstone/debug/shape/box.h @@ -0,0 +1,46 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/debug/shape/shape.h" +#include "endstone/util/vector.h" + +namespace endstone { + +/** + * @brief Represents a debug box (cuboid) shape. + * Final rendered size = bound * scale. + */ +class DebugBox : public DebugShape { +public: + /** + * @brief Gets the bounding size of the box. + */ + [[nodiscard]] Vector getBound() const { return bound_; } + + /** + * @brief Sets the bounding size of the box. + */ + DebugBox &setBound(Vector bound) + { + bound_ = bound; + return *this; + } + +private: + Vector bound_{1.0F, 1.0F, 1.0F}; +}; + +} // namespace endstone diff --git a/include/endstone/debug/shape/circle.h b/include/endstone/debug/shape/circle.h new file mode 100644 index 0000000000..6492549094 --- /dev/null +++ b/include/endstone/debug/shape/circle.h @@ -0,0 +1,29 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/debug/shape/shape.h" + +namespace endstone { + +/** + * @brief Represents a debug 2D circle shape. + * Circle radius is controlled via scale. + */ +class DebugCircle : public DebugShape { +public: +}; + +} // namespace endstone diff --git a/include/endstone/debug/shape/line.h b/include/endstone/debug/shape/line.h new file mode 100644 index 0000000000..f15c77e36f --- /dev/null +++ b/include/endstone/debug/shape/line.h @@ -0,0 +1,44 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/debug/shape/shape.h" + +namespace endstone { + +/** + * @brief Represents a debug line segment shape. + */ +class DebugLine : public DebugShape { +public: + /** + * @brief Gets the length of the line segment. + */ + [[nodiscard]] float getLength() const { return length_; } + + /** + * @brief Sets the length of the line segment. + */ + DebugLine &setLength(float length) + { + length_ = length; + return *this; + } + +private: + float length_{1.0F}; +}; + +} // namespace endstone diff --git a/include/endstone/debug/shape/shape.h b/include/endstone/debug/shape/shape.h new file mode 100644 index 0000000000..19771ce013 --- /dev/null +++ b/include/endstone/debug/shape/shape.h @@ -0,0 +1,77 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/util/color.h" +#include "endstone/util/vector.h" + +namespace endstone { + +/** + * @brief Base class for debug shapes rendered in world space. + */ +template +class DebugShape { +public: + /** + * @brief Gets the color of this shape. + */ + [[nodiscard]] Color getColor() const { return color_; } + + /** + * @brief Sets the color of this shape. + */ + T &setColor(Color color) + { + color_ = color; + return *static_cast(this); + } + + /** + * @brief Gets the uniform scale factor. + */ + [[nodiscard]] float getScale() const { return scale_; } + + /** + * @brief Sets the uniform scale factor. + */ + T &setScale(float scale) + { + scale_ = scale; + return *static_cast(this); + } + + /** + * @brief Gets the rotation of this shape as Euler angles (pitch, yaw, roll). + */ + [[nodiscard]] Vector getRotation() const { return rotation_; } + + /** + * @brief Sets the rotation of this shape as Euler angles (pitch, yaw, roll). + */ + T &setRotation(Vector rotation) + { + rotation_ = rotation; + return *static_cast(this); + } + +protected: + Color color_{255, 255, 255}; + float scale_{1.0F}; + Vector rotation_{}; + +}; + +} // namespace endstone diff --git a/include/endstone/debug/shape/sphere.h b/include/endstone/debug/shape/sphere.h new file mode 100644 index 0000000000..2a08c4e251 --- /dev/null +++ b/include/endstone/debug/shape/sphere.h @@ -0,0 +1,29 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "endstone/debug/shape/shape.h" + +namespace endstone { + +/** + * @brief Represents a debug sphere shape. + * Sphere radius is controlled via scale. + */ +class DebugSphere : public DebugShape { +public: +}; + +} // namespace endstone diff --git a/include/endstone/debug/shape/text.h b/include/endstone/debug/shape/text.h new file mode 100644 index 0000000000..3ff8a7002b --- /dev/null +++ b/include/endstone/debug/shape/text.h @@ -0,0 +1,48 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include "endstone/debug/shape/shape.h" + +namespace endstone { + +/** + * @brief Represents a debug billboard text label. + * The label automatically faces the player's camera. + */ +class DebugText : public DebugShape { +public: + /** + * @brief Gets the text displayed by this label. + */ + [[nodiscard]] std::string getText() const { return text_; } + + /** + * @brief Sets the text displayed by this label. + */ + DebugText &setText(std::string text) + { + text_ = std::move(text); + return *this; + } + +private: + std::string text_; +}; + +} // namespace endstone diff --git a/include/endstone/endstone.hpp b/include/endstone/endstone.hpp index b4d6c0ff3f..8f58eaa2de 100644 --- a/include/endstone/endstone.hpp +++ b/include/endstone/endstone.hpp @@ -54,6 +54,14 @@ static_assert(_ITERATOR_DEBUG_LEVEL == 0, #include "command/console_command_sender.h" #include "command/plugin_command.h" #include "damage/damage_source.h" +#include "debug/shape.h" +#include "debug/shape/arrow.h" +#include "debug/shape/box.h" +#include "debug/shape/circle.h" +#include "debug/shape/line.h" +#include "debug/shape/shape.h" +#include "debug/shape/sphere.h" +#include "debug/shape/text.h" #include "effect/effect_type.h" #include "enchantments/enchantment.h" #include "event/actor/actor_damage_event.h" diff --git a/include/endstone/level/dimension.h b/include/endstone/level/dimension.h index cc4b43ec72..6d89bb4af2 100644 --- a/include/endstone/level/dimension.h +++ b/include/endstone/level/dimension.h @@ -139,6 +139,7 @@ class Dimension { * @return A List of all actors currently residing in this dimension */ [[nodiscard]] virtual std::vector getActors() const = 0; + }; inline std::unique_ptr Location::getBlock() const diff --git a/include/endstone/player.h b/include/endstone/player.h index 02d58eb4c2..ab68d9a02d 100644 --- a/include/endstone/player.h +++ b/include/endstone/player.h @@ -21,6 +21,7 @@ #include #include "endstone/actor/mob.h" +#include "endstone/debug/shape.h" #include "endstone/form/action_form.h" #include "endstone/form/message_form.h" #include "endstone/form/modal_form.h" @@ -487,6 +488,27 @@ class Player : public Mob { * @param map The map to send */ virtual void sendMap(MapView &map) = 0; + + /** + * @brief Adds a debug shape visible only to this player. + * + * @param location the location to place the shape + * @param shape the shape to add + * @return the unique id assigned to the shape + */ + virtual std::uint64_t addDebugShape(Location location, DebugShapeVariant shape) = 0; + + /** + * @brief Removes a specific debug shape from this player by its id. + * + * @param id the id returned by addDebugShape() + */ + virtual void removeDebugShape(std::uint64_t id) = 0; + + /** + * @brief Removes all debug shapes visible to this player. + */ + virtual void removeDebugShapes() = 0; }; } // namespace endstone diff --git a/src/bedrock/network/packet/debug_drawer_packet.h b/src/bedrock/network/packet/debug_drawer_packet.h new file mode 100644 index 0000000000..78ec6237ce --- /dev/null +++ b/src/bedrock/network/packet/debug_drawer_packet.h @@ -0,0 +1,94 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "bedrock/core/math/color.h" +#include "bedrock/core/math/vec3.h" +#include "bedrock/network/packet.h" +#include "bedrock/world/actor/actor_runtime_id.h" +#include "bedrock/world/level/dimension/dimension_type.h" + +namespace ScriptModuleDebugUtilities { +enum class ScriptDebugShapeType : std::uint8_t { + Line = 0, + Box = 1, + Sphere = 2, + Circle = 3, + Text = 4, + Arrow = 5, + NumShapeTypes = 6, +}; +class ScriptDebugShape; +} // namespace ScriptModuleDebugUtilities + +namespace cereal { +struct NullType {}; +} // namespace cereal + +struct ArrowDataPayload { + std::optional end_location; + std::optional arrow_head_length; + std::optional arrow_head_radius; + std::optional num_segments; +}; + +struct TextDataPayload { + std::string text; +}; + +struct BoxDataPayload { + Vec3 box_bound; +}; + +struct LineDataPayload { + Vec3 end_location; +}; + +struct SphereDataPayload { + std::byte num_segments; +}; + +struct ShapeDataPayload { + std::uint64_t network_id; + std::optional shape_type; + std::optional location; + std::optional rotation; + std::optional scale; + std::optional color; + std::optional time_left_total_sec; + std::optional dimension_id; + std::optional attached_to_id; + std::variant + extra_data_payload; +}; + +struct DebugDrawerPacketPayload { + std::vector shapes; +}; + +class DebugDrawerPacket : public Packet { +public: + static constexpr bool SHARE_WITH_HANDLER = false; + DebugDrawerPacketPayload payload; + SerializationMode serialization_mode; +}; diff --git a/src/endstone/core/level/dimension.h b/src/endstone/core/level/dimension.h index de7256b85f..8289cd1a39 100644 --- a/src/endstone/core/level/dimension.h +++ b/src/endstone/core/level/dimension.h @@ -36,7 +36,6 @@ class EndstoneDimension : public Dimension { [[nodiscard]] Item &dropItem(Location location, const ItemStack &item) override; [[nodiscard]] Actor *spawnActor(Location location, std::string type) override; [[nodiscard]] std::vector getActors() const override; - [[nodiscard]] ::Dimension &getHandle() const; private: diff --git a/src/endstone/core/player.cpp b/src/endstone/core/player.cpp index 717d7dac9e..847b4962cf 100644 --- a/src/endstone/core/player.cpp +++ b/src/endstone/core/player.cpp @@ -20,6 +20,7 @@ #include "bedrock/entity/components/user_entity_identifier_component.h" #include "bedrock/network/packet.h" #include "bedrock/network/packet/clientbound_map_item_data_packet.h" +#include "bedrock/network/packet/debug_drawer_packet.h" #include "bedrock/network/packet/emote_packet.h" #include "bedrock/network/packet/mob_equipment_packet.h" #include "bedrock/network/packet/modal_form_request_packet.h" @@ -43,6 +44,7 @@ #include "endstone/core/game_mode.h" #include "endstone/core/inventory/item_stack.h" #include "endstone/core/inventory/player_inventory.h" +#include "endstone/core/level/dimension.h" #include "endstone/core/map/map_view.h" #include "endstone/core/message.h" #include "endstone/core/network/data_packet.h" @@ -629,6 +631,120 @@ void EndstonePlayer::sendMap(MapView &map) getHandle().sendNetworkPacket(*packet); } +std::uint64_t EndstonePlayer::addDebugShape(Location location, DebugShapeVariant shape) +{ + using ShapeType = ScriptModuleDebugUtilities::ScriptDebugShapeType; + + auto id = debug_shape_ids_--; + auto &dimension = static_cast(location.getDimension()); + + ShapeDataPayload data; + data.network_id = id; + data.location = Vec3{location.getX(), location.getY(), location.getZ()}; + data.dimension_id = dimension.getHandle().getDimensionId(); + + std::visit( + [&data, &location](auto &&s) { + using T = std::decay_t; + auto c = s.getColor(); + data.color = + mce::Color(static_cast(c.getRed()) / 255.0F, static_cast(c.getGreen()) / 255.0F, + static_cast(c.getBlue()) / 255.0F, static_cast(c.getAlpha()) / 255.0F); + data.scale = s.getScale(); + auto r = s.getRotation(); + data.rotation = Vec3{r.getX(), r.getY(), r.getZ()}; + + if constexpr (std::is_same_v) { + data.shape_type = ShapeType::Box; + auto b = s.getBound(); + data.extra_data_payload = BoxDataPayload{Vec3{b.getX(), b.getY(), b.getZ()}}; + } + else if constexpr (std::is_same_v) { + data.shape_type = ShapeType::Sphere; + } + else if constexpr (std::is_same_v) { + data.shape_type = ShapeType::Circle; + } + else if constexpr (std::is_same_v) { + data.shape_type = ShapeType::Line; + // Compute direction from rotation (pitch=x, yaw=y), same as Location::getDirection + auto rot_x = r.getY() * std::numbers::pi_v / 180.0F; + auto rot_y = r.getX() * std::numbers::pi_v / 180.0F; + auto xz = std::cos(rot_y); + auto dx = -xz * std::sin(rot_x); + auto dy = -std::sin(rot_y); + auto dz = xz * std::cos(rot_x); + auto end = Vec3{location.getX() + dx * s.getLength(), + location.getY() + dy * s.getLength(), + location.getZ() + dz * s.getLength()}; + data.extra_data_payload = LineDataPayload{end}; + } + else if constexpr (std::is_same_v) { + data.shape_type = ShapeType::Arrow; + auto rot_x = r.getY() * std::numbers::pi_v / 180.0F; + auto rot_y = r.getX() * std::numbers::pi_v / 180.0F; + auto xz = std::cos(rot_y); + auto dx = -xz * std::sin(rot_x); + auto dy = -std::sin(rot_y); + auto dz = xz * std::cos(rot_x); + auto end = Vec3{location.getX() + dx * s.getLength(), + location.getY() + dy * s.getLength(), + location.getZ() + dz * s.getLength()}; + ArrowDataPayload arrow; + arrow.end_location = end; + arrow.arrow_head_length = s.getHeadLength(); + arrow.arrow_head_radius = s.getHeadRadius(); + arrow.num_segments = static_cast(s.getHeadSegments()); + data.extra_data_payload = arrow; + } + else if constexpr (std::is_same_v) { + data.shape_type = ShapeType::Text; + data.extra_data_payload = TextDataPayload{s.getText()}; + } + }, + shape); + + auto packet = MinecraftPackets::createPacket(MinecraftPacketIds::ServerScriptDebugDrawerPacket); + auto &pk = static_cast(*packet); + pk.payload.shapes.emplace_back(std::move(data)); + getHandle().sendNetworkPacket(*packet); + debug_shapes_.insert(id); + return id; +} + +void EndstonePlayer::removeDebugShape(std::uint64_t id) +{ + if (debug_shapes_.erase(id) == 0) { + return; + } + + ShapeDataPayload shape_data; + shape_data.network_id = id; + shape_data.time_left_total_sec = 0; + auto packet = MinecraftPackets::createPacket(MinecraftPacketIds::ServerScriptDebugDrawerPacket); + auto &pk = static_cast(*packet); + pk.payload.shapes.emplace_back(std::move(shape_data)); + getHandle().sendNetworkPacket(*packet); +} + +void EndstonePlayer::removeDebugShapes() +{ + if (debug_shapes_.empty()) { + return; + } + + auto packet = MinecraftPackets::createPacket(MinecraftPacketIds::ServerScriptDebugDrawerPacket); + auto &pk = static_cast(*packet); + for (auto id : debug_shapes_) { + ShapeDataPayload shape_data; + shape_data.network_id = id; + shape_data.time_left_total_sec = 0; + pk.payload.shapes.emplace_back(std::move(shape_data)); + } + getHandle().sendNetworkPacket(*packet); + debug_shapes_.clear(); +} + bool EndstonePlayer::handlePacket(Packet &packet) { switch (packet.getId()) { diff --git a/src/endstone/core/player.h b/src/endstone/core/player.h index d4305d4ac6..fcfa2a677b 100644 --- a/src/endstone/core/player.h +++ b/src/endstone/core/player.h @@ -14,7 +14,9 @@ #pragma once +#include #include +#include #include @@ -120,6 +122,9 @@ class EndstonePlayer : public EndstoneMobBase { void closeForm() override; void sendPacket(int packet_id, std::string_view payload) const override; void sendMap(MapView &map) override; + std::uint64_t addDebugShape(Location location, DebugShapeVariant shape) override; + void removeDebugShape(std::uint64_t id) override; + void removeDebugShapes() override; bool handlePacket(Packet &packet); void onFormClose(std::uint32_t form_id, PlayerFormCloseReason reason); @@ -143,6 +148,8 @@ class EndstonePlayer : public EndstoneMobBase { std::string game_version_; std::uint32_t form_ids_ = 0xffff; // Set to a large value to avoid collision with forms created by script api std::unordered_map forms_; + std::uint64_t debug_shape_ids_ = std::numeric_limits::max(); // Decrement to avoid collision with script api + std::unordered_set debug_shapes_; bool spawned_ = false; bool last_op_status_ = false; }; diff --git a/src/endstone/python/CMakeLists.txt b/src/endstone/python/CMakeLists.txt index 1a196ee4c9..d50e02a588 100644 --- a/src/endstone/python/CMakeLists.txt +++ b/src/endstone/python/CMakeLists.txt @@ -12,6 +12,7 @@ pybind11_add_module(_python MODULE boss.cpp command.cpp damage.cpp + debug.cpp effect.cpp enchantments.cpp endstone_python.cpp diff --git a/src/endstone/python/debug.cpp b/src/endstone/python/debug.cpp new file mode 100644 index 0000000000..32f25789bf --- /dev/null +++ b/src/endstone/python/debug.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "endstone_python.h" + +namespace py = pybind11; + +namespace endstone::python { + +void init_debug(py::module_ &m) +{ + py::class_(m, "DebugBox", "Represents a debug box (cuboid) shape.") + .def(py::init<>()) + .def_property("color", &DebugBox::getColor, &DebugBox::setColor, "The color of this shape.") + .def_property("scale", &DebugBox::getScale, &DebugBox::setScale, "The uniform scale factor.") + .def_property("rotation", &DebugBox::getRotation, &DebugBox::setRotation, + "The rotation of this shape as Euler angles (pitch, yaw, roll).") + .def_property("bound", &DebugBox::getBound, &DebugBox::setBound, + "The bounding size of the box. Final size = bound * scale."); + + py::class_(m, "DebugSphere", "Represents a debug sphere shape. Radius is controlled via scale.") + .def(py::init<>()) + .def_property("color", &DebugSphere::getColor, &DebugSphere::setColor, "The color of this shape.") + .def_property("scale", &DebugSphere::getScale, &DebugSphere::setScale, "The uniform scale factor.") + .def_property("rotation", &DebugSphere::getRotation, &DebugSphere::setRotation, + "The rotation of this shape as Euler angles (pitch, yaw, roll)."); + + py::class_(m, "DebugCircle", "Represents a debug 2D circle shape. Radius is controlled via scale.") + .def(py::init<>()) + .def_property("color", &DebugCircle::getColor, &DebugCircle::setColor, "The color of this shape.") + .def_property("scale", &DebugCircle::getScale, &DebugCircle::setScale, "The uniform scale factor.") + .def_property("rotation", &DebugCircle::getRotation, &DebugCircle::setRotation, + "The rotation of this shape as Euler angles (pitch, yaw, roll)."); + + py::class_(m, "DebugLine", "Represents a debug line segment shape.") + .def(py::init<>()) + .def_property("color", &DebugLine::getColor, &DebugLine::setColor, "The color of this shape.") + .def_property("scale", &DebugLine::getScale, &DebugLine::setScale, "The uniform scale factor.") + .def_property("rotation", &DebugLine::getRotation, &DebugLine::setRotation, + "The rotation of this shape as Euler angles (pitch, yaw, roll).") + .def_property("length", &DebugLine::getLength, &DebugLine::setLength, "The length of the line segment."); + + py::class_(m, "DebugArrow", "Represents a debug arrow shape.") + .def(py::init<>()) + .def_property("color", &DebugArrow::getColor, &DebugArrow::setColor, "The color of this shape.") + .def_property("scale", &DebugArrow::getScale, &DebugArrow::setScale, "The uniform scale factor.") + .def_property("rotation", &DebugArrow::getRotation, &DebugArrow::setRotation, + "The rotation of this shape as Euler angles (pitch, yaw, roll).") + .def_property("length", &DebugArrow::getLength, &DebugArrow::setLength, "The length of the arrow.") + .def_property("head_length", &DebugArrow::getHeadLength, &DebugArrow::setHeadLength, + "The length of the arrow's head.") + .def_property("head_radius", &DebugArrow::getHeadRadius, &DebugArrow::setHeadRadius, + "The radius of the arrow's head.") + .def_property("head_segments", &DebugArrow::getHeadSegments, &DebugArrow::setHeadSegments, + "The number of segments for the arrow head's base circle."); + + py::class_(m, "DebugText", "Represents a debug billboard text label.") + .def(py::init<>()) + .def_property("color", &DebugText::getColor, &DebugText::setColor, "The color of this shape.") + .def_property("scale", &DebugText::getScale, &DebugText::setScale, "The uniform scale factor.") + .def_property("rotation", &DebugText::getRotation, &DebugText::setRotation, + "The rotation of this shape as Euler angles (pitch, yaw, roll).") + .def_property("text", &DebugText::getText, &DebugText::setText, "The text displayed by this label."); +} + +} // namespace endstone::python diff --git a/src/endstone/python/endstone_python.cpp b/src/endstone/python/endstone_python.cpp index 51a298565b..1f40ceb78d 100644 --- a/src/endstone/python/endstone_python.cpp +++ b/src/endstone/python/endstone_python.cpp @@ -30,6 +30,7 @@ void init_boss(py::module_ &); void init_color_format(py::module_ &); void init_command(py::module &, py_class &command_sender); void init_damage(py::module_ &); +void init_debug(py::module_ &); void init_effect(py::module_ &); void init_enchantments(py::module_ &); void init_event(py::module_ &, py::class_ &event); @@ -69,6 +70,7 @@ PYBIND11_MODULE(_python, m) // NOLINT(*-use-anonymous-namespace) auto m_command = m.def_submodule("command", "Classes relating to handling specialized non-chat player input."); auto m_damage = m.def_submodule("damage", "Classes relating to damage types and sources applicable to mobs (living entities)."); + auto m_debug = m.def_submodule("debug", "Classes relating to debug shape drawing utilities."); auto m_effect = m.def_submodule("effect", "Classes relating to the effects that can be applied to entities."); auto m_enchantments = m.def_submodule("enchantments", "Classes relating to the specialized enhancements to ItemStacks."); @@ -133,8 +135,8 @@ PYBIND11_MODULE(_python, m) // NOLINT(*-use-anonymous-namespace) auto block = py::class_(m_block, "Block", "Represents a block."); auto command_sender = py_class(m_command, "CommandSender", "Represents a command sender."); auto actor = py_class(m_actor, "Actor", "Represents a base actor in the level."); - auto mob = py_class(m_actor, "Mob", - "Represents a mobile entity (i.e. living entity), such as a monster or player."); + auto mob = + py_class(m_actor, "Mob", "Represents a mobile entity (i.e. living entity), such as a monster or player."); py::class_( m, "OfflinePlayer", "Represents a reference to a player identity and the data belonging to a player that is stored on the disk and " @@ -152,6 +154,7 @@ PYBIND11_MODULE(_python, m) // NOLINT(*-use-anonymous-namespace) init_attribute(m_attribute); init_color_format(m); init_damage(m_damage); + init_debug(m_debug); init_game_mode(m); init_logger(m); init_lang(m_lang); @@ -469,7 +472,12 @@ void init_player(py::module_ &m, py_class &player) [](const Player &self, const int packet_id, const py::bytes &payload) { return self.sendPacket(packet_id, payload); }, - py::arg("packet_id"), py::arg("payload"), "Sends a packet to the player."); + py::arg("packet_id"), py::arg("payload"), "Sends a packet to the player.") + .def("add_debug_shape", &Player::addDebugShape, py::arg("location"), py::arg("shape"), + "Adds a debug shape visible only to this player.") + .def("remove_debug_shape", &Player::removeDebugShape, py::arg("id"), + "Removes a specific debug shape from this player by its id.") + .def("remove_debug_shapes", &Player::removeDebugShapes, "Removes all debug shapes visible to this player."); } } // namespace endstone::python