levistone 0.6.1__1-cp313-cp313-win_amd64.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.

Potentially problematic release.


This version of levistone might be problematic. Click here for more details.

@@ -0,0 +1,101 @@
1
+ import platform
2
+ from pathlib import Path
3
+ from typing import Any, Dict
4
+
5
+ import psutil
6
+ from endstone_bstats import AdvancedPie, DrilldownPie, MetricsBase, MetricsConfig, SimplePie, SingleLineChart
7
+
8
+ from endstone import Server
9
+
10
+
11
+ class Metrics(MetricsBase):
12
+ def __init__(self, server: Server):
13
+ self._server = server
14
+
15
+ # Get the config file
16
+ bstats_folder = Path("plugins") / "bstats"
17
+ config_file = bstats_folder / "config.toml"
18
+ self._config = MetricsConfig(config_file, True)
19
+
20
+ super().__init__(
21
+ platform="server-implementation",
22
+ server_uuid=self._config.server_uuid,
23
+ service_id=23296,
24
+ log_errors=self._config.log_errors_enabled,
25
+ log_sent_data=self._config.log_sent_data_enabled,
26
+ log_response_status_text=self._config.log_response_status_text_enabled,
27
+ )
28
+
29
+ self.add_custom_chart(SingleLineChart("players", lambda: len(self._server.online_players)))
30
+ self.add_custom_chart(SimplePie("endstone_version", lambda: self._server.version))
31
+ self.add_custom_chart(SimplePie("minecraft_version", lambda: self._server.minecraft_version))
32
+ self.add_custom_chart(DrilldownPie("online_mode", self._get_online_mode))
33
+ self.add_custom_chart(DrilldownPie("python_version", self._get_python_version))
34
+ self.add_custom_chart(AdvancedPie("player_platform", self._get_player_platforms))
35
+ self.add_custom_chart(AdvancedPie("player_game_version", self._get_player_game_versions))
36
+
37
+ @property
38
+ def enabled(self) -> bool:
39
+ return self._config.enabled
40
+
41
+ @property
42
+ def service_enabled(self) -> bool:
43
+ return True
44
+
45
+ def append_platform_data(self, platform_data: Dict[str, Any]) -> None:
46
+ os_name = platform.system()
47
+ if os_name == "Windows":
48
+ platform_data["osName"] = f"Windows {platform.release()}"
49
+ platform_data["osVersion"] = platform.version()
50
+ elif os_name == "Linux":
51
+ platform_data["osName"] = "Linux"
52
+ platform_data["osVersion"] = platform.release()
53
+
54
+ os_arch = platform.machine().lower()
55
+ if os_arch == "x86_64":
56
+ os_arch = "amd64"
57
+ platform_data["osArch"] = os_arch
58
+ platform_data["coreCount"] = psutil.cpu_count(logical=False)
59
+
60
+ def log_info(self, message: str) -> None:
61
+ self._server.logger.info(message)
62
+
63
+ def log_error(self, message: str, exception: Exception) -> None:
64
+ self._server.logger.warning(f"{message}: {exception}")
65
+
66
+ def _get_online_mode(self) -> dict[str, dict[str, int]]:
67
+ value = "true" if self._server.online_mode else "false"
68
+ return {
69
+ value: {
70
+ value: 1,
71
+ },
72
+ }
73
+
74
+ def _get_python_version(self) -> dict[str, dict[str, int]]:
75
+ python_impl = platform.python_implementation()
76
+ major, minor, patch = platform.python_version_tuple()
77
+ return {
78
+ f"{python_impl} {major}.{minor}": {
79
+ f"{major}.{minor}.{patch}": 1,
80
+ },
81
+ }
82
+
83
+ def _get_player_platforms(self) -> dict[str, int]:
84
+ result: dict[str, int] = {}
85
+ for player in self._server.online_players:
86
+ if player.device_os not in result:
87
+ result[player.device_os] = 1
88
+ else:
89
+ result[player.device_os] += 1
90
+
91
+ return result
92
+
93
+ def _get_player_game_versions(self) -> dict[str, int]:
94
+ result: dict[str, int] = {}
95
+ for player in self._server.online_players:
96
+ if player.game_version not in result:
97
+ result[player.game_version] = 1
98
+ else:
99
+ result[player.game_version] += 1
100
+
101
+ return result
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import glob
4
+ import importlib
5
+ import os
6
+ import os.path
7
+ import shutil
8
+ import site
9
+ import subprocess
10
+ import sys
11
+ import warnings
12
+
13
+ import pkginfo
14
+ from importlib_metadata import EntryPoint, distribution, entry_points, metadata
15
+
16
+ from endstone import Server
17
+ from endstone._internal.metrics import Metrics
18
+ from endstone.command import Command
19
+ from endstone.permissions import Permission, PermissionDefault
20
+ from endstone.plugin import Plugin, PluginDescription, PluginLoader, PluginLoadOrder
21
+
22
+ __all__ = ["PythonPluginLoader"]
23
+
24
+ warnings.simplefilter(action="always", category=FutureWarning)
25
+
26
+
27
+ def find_python():
28
+ paths = []
29
+ if sys.platform == "win32":
30
+ paths.append(os.path.join(sys.base_prefix, "python.exe"))
31
+ else:
32
+ paths.append(
33
+ os.path.join(sys.base_prefix, "bin", "python" + f"{sys.version_info.major}.{sys.version_info.minor}")
34
+ )
35
+ paths.append(os.path.join(sys.base_prefix, "bin", "python" + f"{sys.version_info.major}"))
36
+ paths.append(os.path.join(sys.base_prefix, "bin", "python"))
37
+
38
+ for path in paths:
39
+ if os.path.isfile(path):
40
+ return path
41
+
42
+ raise RuntimeError(f"Unable to find Python executable. Attempted paths: {paths}")
43
+
44
+
45
+ class PythonPluginLoader(PluginLoader):
46
+ SUPPORTED_API = ["0.5", "0.6"]
47
+
48
+ def __init__(self, server: Server):
49
+ PluginLoader.__init__(self, server)
50
+
51
+ self._plugins = []
52
+
53
+ # fix sys.executable variable
54
+ sys.executable = find_python()
55
+
56
+ # invalidate previously loaded modules (in case of /reload)
57
+ importlib.invalidate_caches()
58
+ for module in list(sys.modules.keys()):
59
+ if module.startswith("endstone_"):
60
+ del sys.modules[module]
61
+
62
+ # prepare the temp site-dir
63
+ self._prefix = os.path.join("plugins", ".local")
64
+ shutil.rmtree(self._prefix, ignore_errors=True)
65
+ for site_dir in site.getsitepackages(prefixes=[self._prefix]):
66
+ site.addsitedir(site_dir)
67
+
68
+ # initialize the metrics
69
+ self._metrics = Metrics(self.server)
70
+
71
+ def __del__(self):
72
+ self._metrics.shutdown()
73
+
74
+ @staticmethod
75
+ def _build_commands(commands: dict) -> list[Command]:
76
+ results = []
77
+ for name, command in commands.items():
78
+ command = Command(name, **command)
79
+ results.append(command)
80
+ return results
81
+
82
+ @staticmethod
83
+ def _build_permissions(permissions: dict) -> list[Permission]:
84
+ results = []
85
+ for name, permission in permissions.items():
86
+ if "default" in permission:
87
+ value = permission["default"]
88
+ if isinstance(value, bool):
89
+ permission["default"] = PermissionDefault.TRUE if value else PermissionDefault.FALSE
90
+ elif isinstance(value, str):
91
+ permission["default"] = PermissionDefault.__members__[value.strip().replace(" ", "_").upper()]
92
+ elif not isinstance(value, PermissionDefault):
93
+ raise TypeError(f"Invalid value for default permission: {value}")
94
+
95
+ permission = Permission(name, **permission)
96
+ results.append(permission)
97
+ return results
98
+
99
+ def load_plugin(self, file: str) -> Plugin | None:
100
+ env = os.environ.copy()
101
+ env.pop("LD_PRELOAD", "")
102
+
103
+ dist_name = pkginfo.Wheel(file).name
104
+ subprocess.run(
105
+ [
106
+ sys.executable,
107
+ "-m",
108
+ "pip",
109
+ "install",
110
+ file,
111
+ "--prefix",
112
+ self._prefix,
113
+ "--quiet",
114
+ "--no-warn-script-location",
115
+ "--disable-pip-version-check",
116
+ ],
117
+ env=env,
118
+ )
119
+
120
+ eps = distribution(dist_name).entry_points.select(group="endstone")
121
+ for ep in eps:
122
+ plugin = self._load_plugin_from_ep(ep)
123
+ if plugin:
124
+ return plugin
125
+
126
+ return None
127
+
128
+ def load_plugins(self, directory: str) -> list[Plugin]:
129
+ loaded_plugins = []
130
+
131
+ if not self._plugins:
132
+ eps = entry_points(group="endstone")
133
+ for ep in eps:
134
+ plugin = self._load_plugin_from_ep(ep)
135
+ if plugin:
136
+ loaded_plugins.append(plugin)
137
+
138
+ for file in glob.glob(os.path.join(directory, "*.whl")):
139
+ plugin = self.load_plugin(file)
140
+ if plugin:
141
+ loaded_plugins.append(plugin)
142
+
143
+ return loaded_plugins
144
+
145
+ def _load_plugin_from_ep(self, ep: EntryPoint) -> Plugin | None:
146
+ # enforce naming convention
147
+ if not ep.dist.name.replace("_", "-").startswith("endstone-"):
148
+ self.server.logger.error(
149
+ f"Error occurred when trying to load plugin from entry point '{ep.name}': Invalid name."
150
+ )
151
+ self.server.logger.error(
152
+ f"The name of distribution ({ep.dist.name}) does not start with 'endstone-' or 'endstone_'."
153
+ )
154
+ return None
155
+
156
+ dist_name = "endstone-" + ep.name.replace("_", "-")
157
+ if ep.dist.name.replace("_", "-") != dist_name:
158
+ self.server.logger.error(
159
+ f"Error occurred when trying to load plugin from entry point '{ep.name}': Invalid name."
160
+ )
161
+ self.server.logger.error("You need to make **ONE** of the following changes.")
162
+ self.server.logger.error(
163
+ f"* If you intend to use the current entry point name ({ep.name}), "
164
+ f"please change the distribution name from '{ep.dist.name}' to '{dist_name}'."
165
+ )
166
+ self.server.logger.error(
167
+ f"* If not, " f"please change the entry point name from '{ep.name}' to '{ep.dist.name[9:]}'."
168
+ )
169
+ return None
170
+
171
+ # get distribution metadata
172
+ try:
173
+ plugin_metadata = metadata(ep.dist.name).json
174
+ cls = ep.load()
175
+ except Exception as e:
176
+ self.server.logger.error(f"Error occurred when trying to load plugin from entry point '{ep.name}': {e}")
177
+ return None
178
+
179
+ # prepare plugin description
180
+ cls_attr = dict(cls.__dict__)
181
+ name = cls_attr.pop("name", ep.name.replace("-", "_"))
182
+ version = cls_attr.pop("version", plugin_metadata["version"])
183
+
184
+ api_version = cls_attr.pop("api_version", None)
185
+ if api_version is None:
186
+ self.server.logger.warning(
187
+ f"Plugin '{name}' does not specify an API version. This may prevent the plugin from loading in "
188
+ f"future releases."
189
+ )
190
+ elif api_version not in self.SUPPORTED_API:
191
+ self.server.logger.error(
192
+ f"Error occurred when trying to load plugin '{name}': plugin was designed for API version: "
193
+ f"{api_version} which is not compatible with this server."
194
+ )
195
+ return None
196
+
197
+ load = cls_attr.pop("load", None)
198
+ if load is not None:
199
+ if isinstance(load, str):
200
+ load = PluginLoadOrder.__members__[load.strip().replace(" ", "_").upper()]
201
+ elif not isinstance(load, PluginLoadOrder):
202
+ raise TypeError(f"Invalid value for load order: {load}")
203
+
204
+ description = cls_attr.pop("description", plugin_metadata.get("summary", None))
205
+ authors = cls_attr.pop("authors", plugin_metadata.get("author_email", "").split(","))
206
+ website = cls_attr.pop("website", "; ".join(plugin_metadata.get("project_url", [])))
207
+
208
+ commands = cls_attr.pop("commands", {})
209
+ commands = self._build_commands(commands)
210
+
211
+ permissions = cls_attr.pop("permissions", {})
212
+ permissions = self._build_permissions(permissions)
213
+
214
+ plugin_description = PluginDescription(
215
+ name=name,
216
+ version=version,
217
+ load=load,
218
+ description=description,
219
+ authors=authors,
220
+ website=website,
221
+ commands=commands,
222
+ permissions=permissions,
223
+ **cls_attr,
224
+ )
225
+
226
+ # instantiate plugin
227
+ plugin = cls()
228
+ if not isinstance(plugin, Plugin):
229
+ self.server.logger.error(f"Main class {ep.value} does not extend endstone.plugin.Plugin")
230
+ return None
231
+
232
+ plugin._description = plugin_description
233
+ self._plugins.append(plugin)
234
+ return plugin
@@ -0,0 +1,14 @@
1
+ TYPE_CHECKING = False
2
+ if TYPE_CHECKING:
3
+ from typing import Tuple, Union
4
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
5
+ else:
6
+ VERSION_TUPLE = object
7
+
8
+ version: str
9
+ __version__: str
10
+ __version_tuple__: VERSION_TUPLE
11
+ version_tuple: VERSION_TUPLE
12
+
13
+ __version__ = version = '0.6.1'
14
+ __version_tuple__ = version_tuple = (0, 6, 1, '')
endstone/actor.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Actor, Mob
2
+
3
+ __all__ = ["Actor", "Mob"]
endstone/ban.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import IpBanEntry, IpBanList, PlayerBanEntry, PlayerBanList
2
+
3
+ __all__ = ["IpBanEntry", "IpBanList", "PlayerBanEntry", "PlayerBanList"]
endstone/block.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Block, BlockData, BlockFace, BlockState
2
+
3
+ __all__ = ["Block", "BlockData", "BlockFace", "BlockState"]
endstone/boss.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import BarColor, BarFlag, BarStyle, BossBar
2
+
3
+ __all__ = ["BossBar", "BarColor", "BarFlag", "BarStyle"]
endstone/command.py ADDED
@@ -0,0 +1,11 @@
1
+ from endstone._internal.endstone_python import (
2
+ Command,
3
+ CommandExecutor,
4
+ CommandSender,
5
+ CommandSenderWrapper,
6
+ ConsoleCommandSender,
7
+ ProxiedCommandSender,
8
+ )
9
+
10
+ __all__ = ["Command", "CommandExecutor", "CommandSender", "CommandSenderWrapper", "ConsoleCommandSender",
11
+ "ProxiedCommandSender", ]
endstone/damage.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import DamageSource
2
+
3
+ __all__ = ["DamageSource"]
endstone/event.py ADDED
@@ -0,0 +1,99 @@
1
+ from endstone._internal.endstone_python import (
2
+ ActorDamageEvent,
3
+ ActorDeathEvent,
4
+ ActorEvent,
5
+ ActorExplodeEvent,
6
+ ActorKnockbackEvent,
7
+ ActorRemoveEvent,
8
+ ActorSpawnEvent,
9
+ ActorTeleportEvent,
10
+ BlockBreakEvent,
11
+ BlockEvent,
12
+ BlockPlaceEvent,
13
+ BroadcastMessageEvent,
14
+ Cancellable,
15
+ Event,
16
+ EventPriority,
17
+ MobEvent,
18
+ PlayerChatEvent,
19
+ PlayerCommandEvent,
20
+ PlayerDeathEvent,
21
+ PlayerEmoteEvent,
22
+ PlayerEvent,
23
+ PlayerGameModeChangeEvent,
24
+ PlayerInteractActorEvent,
25
+ PlayerInteractEvent,
26
+ PlayerJoinEvent,
27
+ PlayerKickEvent,
28
+ PlayerLoginEvent,
29
+ PlayerQuitEvent,
30
+ PlayerRespawnEvent,
31
+ PlayerTeleportEvent,
32
+ PluginDisableEvent,
33
+ PluginEnableEvent,
34
+ ScriptMessageEvent,
35
+ ServerCommandEvent,
36
+ ServerEvent,
37
+ ServerListPingEvent,
38
+ ServerLoadEvent,
39
+ ThunderChangeEvent,
40
+ WeatherChangeEvent,
41
+ WeatherEvent,
42
+ )
43
+
44
+ __all__ = [
45
+ "event_handler",
46
+ "ActorEvent",
47
+ "ActorDamageEvent",
48
+ "ActorDeathEvent",
49
+ "ActorExplodeEvent",
50
+ "ActorKnockbackEvent",
51
+ "ActorRemoveEvent",
52
+ "ActorSpawnEvent",
53
+ "ActorTeleportEvent",
54
+ "BlockEvent",
55
+ "BlockBreakEvent",
56
+ "BlockPlaceEvent",
57
+ "Cancellable",
58
+ "Event",
59
+ "EventPriority",
60
+ "MobEvent",
61
+ "PlayerEvent",
62
+ "PlayerChatEvent",
63
+ "PlayerCommandEvent",
64
+ "PlayerDeathEvent",
65
+ "PlayerEmoteEvent",
66
+ "PlayerGameModeChangeEvent",
67
+ "PlayerInteractEvent",
68
+ "PlayerInteractActorEvent",
69
+ "PlayerJoinEvent",
70
+ "PlayerKickEvent",
71
+ "PlayerLoginEvent",
72
+ "PlayerQuitEvent",
73
+ "PlayerRespawnEvent",
74
+ "PlayerTeleportEvent",
75
+ "BroadcastMessageEvent",
76
+ "PluginEnableEvent",
77
+ "PluginDisableEvent",
78
+ "ScriptMessageEvent",
79
+ "ServerEvent",
80
+ "ServerCommandEvent",
81
+ "ServerListPingEvent",
82
+ "ServerLoadEvent",
83
+ "ThunderChangeEvent",
84
+ "WeatherEvent",
85
+ "WeatherChangeEvent",
86
+ ]
87
+
88
+
89
+ def event_handler(func=None, *, priority: EventPriority = EventPriority.NORMAL, ignore_cancelled: bool = False):
90
+ def decorator(f):
91
+ setattr(f, "_is_event_handler", True)
92
+ setattr(f, "_priority", priority)
93
+ setattr(f, "_ignore_cancelled", ignore_cancelled)
94
+ return f
95
+
96
+ if func:
97
+ return decorator(func)
98
+
99
+ return decorator
endstone/form.py ADDED
@@ -0,0 +1,13 @@
1
+ from endstone._internal.endstone_python import (
2
+ ActionForm,
3
+ Dropdown,
4
+ Label,
5
+ MessageForm,
6
+ ModalForm,
7
+ Slider,
8
+ StepSlider,
9
+ TextInput,
10
+ Toggle,
11
+ )
12
+
13
+ __all__ = ["ActionForm", "MessageForm", "ModalForm", "Dropdown", "Label", "Slider", "StepSlider", "TextInput", "Toggle"]
endstone/inventory.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Inventory, ItemStack, PlayerInventory
2
+
3
+ __all__ = ["ItemStack", "Inventory", "PlayerInventory"]
endstone/lang.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Language, Translatable
2
+
3
+ __all__ = ["Language", "Translatable"]
endstone/level.py ADDED
@@ -0,0 +1,9 @@
1
+ from endstone._internal.endstone_python import Chunk, Dimension, Level, Location, Position
2
+
3
+ __all__ = [
4
+ "Chunk",
5
+ "Dimension",
6
+ "Level",
7
+ "Location",
8
+ "Position",
9
+ ]
endstone/network.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Packet, PacketType, SpawnParticleEffectPacket
2
+
3
+ __all__ = ["Packet", "PacketType", "SpawnParticleEffectPacket"]
@@ -0,0 +1,9 @@
1
+ from endstone._internal.endstone_python import (
2
+ Permissible,
3
+ Permission,
4
+ PermissionAttachment,
5
+ PermissionAttachmentInfo,
6
+ PermissionDefault,
7
+ )
8
+
9
+ __all__ = ["Permission", "Permissible", "PermissionAttachment", "PermissionAttachmentInfo", "PermissionDefault"]
endstone/plugin.py ADDED
@@ -0,0 +1,137 @@
1
+ import inspect
2
+ import os
3
+ import shutil
4
+ import typing
5
+ from pathlib import Path
6
+
7
+ import tomlkit
8
+ from importlib_resources import as_file, files
9
+
10
+ from endstone._internal import endstone_python
11
+ from endstone._internal.endstone_python import (
12
+ PluginCommand,
13
+ PluginDescription,
14
+ PluginLoader,
15
+ PluginLoadOrder,
16
+ PluginManager,
17
+ )
18
+ from endstone.event import Event
19
+
20
+ __all__ = [
21
+ "Plugin",
22
+ "PluginCommand",
23
+ "PluginDescription",
24
+ "PluginLoader",
25
+ "PluginLoadOrder",
26
+ "PluginManager",
27
+ ]
28
+
29
+
30
+ class Plugin(endstone_python.Plugin):
31
+ # Metadata
32
+ name = None
33
+ version = None
34
+ api_version = None
35
+
36
+ # Optional metadata
37
+ description = None
38
+ load = None
39
+ authors = None
40
+ contributors = None
41
+ website = None
42
+ prefix = None
43
+
44
+ # Dependencies
45
+ provides = None
46
+ depend = None
47
+ soft_depend = None
48
+ load_before = None
49
+
50
+ # Command
51
+ commands = None
52
+
53
+ # Permissions
54
+ default_permission = None
55
+ permissions = None
56
+
57
+ def __init__(self):
58
+ endstone_python.Plugin.__init__(self)
59
+ self._description: typing.Optional[PluginDescription] = None
60
+ self._config = None
61
+ self._listeners = []
62
+
63
+ def _get_description(self) -> PluginDescription:
64
+ return self._description
65
+
66
+ def register_events(self, listener: object) -> None:
67
+ if not self.is_enabled:
68
+ raise RuntimeError(f"Plugin {self.name} attempted to register events while not enabled")
69
+
70
+ if listener is None:
71
+ raise ValueError("Listener cannot be None")
72
+
73
+ self._listeners.append(listener)
74
+
75
+ for attr_name in dir(listener):
76
+ if attr_name == "config":
77
+ continue
78
+
79
+ func = getattr(listener, attr_name)
80
+ if not callable(func) or not getattr(func, "_is_event_handler", False):
81
+ continue
82
+
83
+ sig = inspect.signature(func)
84
+ params = list(sig.parameters.values())
85
+ if (
86
+ len(params) != 1
87
+ or not inspect.isclass(params[0].annotation)
88
+ or not issubclass(params[0].annotation, Event)
89
+ ):
90
+ self.logger.error(
91
+ f"Plugin {self.name} attempted to register an invalid "
92
+ f"event handler signature: {attr_name}: {sig}"
93
+ )
94
+ continue
95
+
96
+ event_cls = params[0].annotation
97
+ priority = getattr(func, "_priority")
98
+ ignore_cancelled = getattr(func, "_ignore_cancelled")
99
+ self.server.plugin_manager.register_event(
100
+ getattr(event_cls, "NAME", event_cls.__name__), func, priority, self, ignore_cancelled
101
+ )
102
+
103
+ @property
104
+ def config(self) -> dict:
105
+ if self._config is None:
106
+ self._config = self.reload_config()
107
+
108
+ return self._config
109
+
110
+ def reload_config(self) -> dict:
111
+ with (Path(self.data_folder) / "config.toml").open("r", encoding="utf-8") as f:
112
+ self._config = tomlkit.load(f)
113
+
114
+ return self._config
115
+
116
+ def save_config(self) -> None:
117
+ if self._config is None:
118
+ return
119
+
120
+ with (Path(self.data_folder) / "config.toml").open("w", encoding="utf-8") as f:
121
+ tomlkit.dump(self._config, f)
122
+
123
+ def save_default_config(self) -> None:
124
+ if not (Path(self.data_folder) / "config.toml").exists():
125
+ self.save_resources("config.toml", False)
126
+
127
+ def save_resources(self, path: str, replace: bool = False) -> None:
128
+ path = path.replace(os.pathsep, "/")
129
+ out_path = Path(self.data_folder) / path
130
+ if not out_path.exists() or replace:
131
+ out_path.parent.mkdir(exist_ok=True)
132
+ resource = files(self.__class__.__module__).joinpath(path)
133
+ with as_file(resource) as f:
134
+ shutil.copy(f, out_path)
135
+ else:
136
+ self.logger.warning(f"Could not save {out_path.name} to {out_path}: file already exists.")
137
+ return
endstone/scheduler.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Scheduler, Task
2
+
3
+ __all__ = ["Scheduler", "Task"]
endstone/scoreboard.py ADDED
@@ -0,0 +1,3 @@
1
+ from endstone._internal.endstone_python import Criteria, DisplaySlot, Objective, ObjectiveSortOrder, Score, Scoreboard
2
+
3
+ __all__ = ["Criteria", "DisplaySlot", "Objective", "ObjectiveSortOrder", "Score", "Scoreboard"]