endstone-papi 0.2.0__cp315-cp315-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.
- endstone_papi/__init__.py +59 -0
- endstone_papi/_native_loader.py +185 -0
- endstone_papi/_papi.cp315-win_amd64.pyd +0 -0
- endstone_papi/_papi.pyi +215 -0
- endstone_papi/_toolchain_provenance.txt +4 -0
- endstone_papi/include/endstone_papi/events.h +71 -0
- endstone_papi/include/endstone_papi/expansion_info.h +54 -0
- endstone_papi/include/endstone_papi/papi.h +8 -0
- endstone_papi/include/endstone_papi/placeholder_api.h +177 -0
- endstone_papi/include/endstone_papi/placeholder_expansion.h +168 -0
- endstone_papi/include/endstone_papi/unregister_reason.h +52 -0
- endstone_papi/include/endstone_papi/version.h +22 -0
- endstone_papi/plugin.py +147 -0
- endstone_papi-0.2.0.dist-info/DELVEWHEEL +2 -0
- endstone_papi-0.2.0.dist-info/METADATA +221 -0
- endstone_papi-0.2.0.dist-info/RECORD +20 -0
- endstone_papi-0.2.0.dist-info/WHEEL +5 -0
- endstone_papi-0.2.0.dist-info/entry_points.txt +3 -0
- endstone_papi-0.2.0.dist-info/licenses/LICENSE +21 -0
- endstone_papi.libs/msvcp140-aa30f46a44704dd3d8207f5fe859b4f9.dll +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Endstone PlaceholderAPI.
|
|
2
|
+
|
|
3
|
+
A native C++20 PlaceholderAPI framework for Endstone and Minecraft Bedrock
|
|
4
|
+
Dedicated Server. Provides a bracket placeholder parser, an owner-aware
|
|
5
|
+
expansion registry, and a service that resolves ``{identifier:params}``
|
|
6
|
+
placeholders through expansions supplied by C++ or Python plugins.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# start delvewheel patch
|
|
11
|
+
def _delvewheel_patch_1_13_1():
|
|
12
|
+
import os
|
|
13
|
+
if os.path.isdir(libs_dir := os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'endstone_papi.libs'))):
|
|
14
|
+
os.add_dll_directory(libs_dir)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_delvewheel_patch_1_13_1()
|
|
18
|
+
del _delvewheel_patch_1_13_1
|
|
19
|
+
# end delvewheel patch
|
|
20
|
+
|
|
21
|
+
# Load PAPI's native extension before Endstone's Python bindings so provider
|
|
22
|
+
# exceptions are translated by the module that owns them.
|
|
23
|
+
try:
|
|
24
|
+
from ._native_loader import load_native as _load_native
|
|
25
|
+
from ._native_loader import should_use_shadow as _should_use_shadow
|
|
26
|
+
|
|
27
|
+
if _should_use_shadow(__file__):
|
|
28
|
+
_load_native(__name__, __file__)
|
|
29
|
+
|
|
30
|
+
from ._papi import (
|
|
31
|
+
SERVICE_NAME,
|
|
32
|
+
ExpansionInfo,
|
|
33
|
+
ExpansionRegisteredEvent,
|
|
34
|
+
ExpansionUnregisteredEvent,
|
|
35
|
+
PlaceholderAPI,
|
|
36
|
+
PlaceholderExpansion,
|
|
37
|
+
UnregisterReason,
|
|
38
|
+
__version__,
|
|
39
|
+
)
|
|
40
|
+
except ImportError as _e:
|
|
41
|
+
raise ImportError(
|
|
42
|
+
f"Failed to load the native PAPI extension: {_e}. "
|
|
43
|
+
"PAPI requires Endstone >=0.11.8,<0.12 (API 0.11). "
|
|
44
|
+
"After updating PAPI or Endstone, restart the server before trying again."
|
|
45
|
+
) from _e
|
|
46
|
+
|
|
47
|
+
from .plugin import PlaceholderAPIPlugin
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"SERVICE_NAME",
|
|
51
|
+
"ExpansionInfo",
|
|
52
|
+
"ExpansionRegisteredEvent",
|
|
53
|
+
"ExpansionUnregisteredEvent",
|
|
54
|
+
"PlaceholderAPI",
|
|
55
|
+
"PlaceholderAPIPlugin",
|
|
56
|
+
"PlaceholderExpansion",
|
|
57
|
+
"UnregisterReason",
|
|
58
|
+
"__version__",
|
|
59
|
+
]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Windows content-addressed loading for the native PAPI extension."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import importlib.machinery
|
|
7
|
+
import importlib.util
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
import types
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
PERSISTENT_MODULE_NAME = "_endstone_papi_native._papi"
|
|
15
|
+
PERSISTENT_DIGEST_ATTRIBUTE = "_papi_payload_sha256"
|
|
16
|
+
_PERSISTENT_PARENT_NAME = "_endstone_papi_native"
|
|
17
|
+
_PACKAGE_SUFFIX = ("plugins", ".local", "Lib", "site-packages", "endstone_papi")
|
|
18
|
+
_RESTART_REQUIRED = "native binary changed while server runs; restart server to apply update"
|
|
19
|
+
_SHADOW_TARGET_RE = re.compile(r"endstone_papi-[0-9a-f]{64}\.pyd\Z", re.IGNORECASE)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _package_dir(package_path: str | os.PathLike[str]) -> Path:
|
|
23
|
+
path = Path(package_path)
|
|
24
|
+
if path.name == "__init__.py" or (path.suffix and not path.is_dir()):
|
|
25
|
+
path = path.parent
|
|
26
|
+
return path.resolve()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def shadow_root(package_path: str | os.PathLike[str]) -> Path | None:
|
|
30
|
+
"""Return the Endstone local-plugin root for an eligible package path."""
|
|
31
|
+
package_dir = _package_dir(package_path)
|
|
32
|
+
expected = tuple(part.casefold() for part in _PACKAGE_SUFFIX)
|
|
33
|
+
actual = tuple(part.casefold() for part in package_dir.parts[-len(expected) :])
|
|
34
|
+
if actual != expected:
|
|
35
|
+
return None
|
|
36
|
+
return package_dir.parents[2]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def should_use_shadow(package_path: str | os.PathLike[str]) -> bool:
|
|
40
|
+
return os.name == "nt" and shadow_root(package_path) is not None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _find_native_payload(package_dir: Path) -> Path:
|
|
44
|
+
matches: list[Path] = []
|
|
45
|
+
seen: set[str] = set()
|
|
46
|
+
for suffix in importlib.machinery.EXTENSION_SUFFIXES:
|
|
47
|
+
candidate = package_dir / f"_papi{suffix}"
|
|
48
|
+
key = str(candidate).casefold()
|
|
49
|
+
if key in seen or not candidate.is_file():
|
|
50
|
+
continue
|
|
51
|
+
seen.add(key)
|
|
52
|
+
matches.append(candidate)
|
|
53
|
+
|
|
54
|
+
if not matches:
|
|
55
|
+
suffixes = ", ".join(importlib.machinery.EXTENSION_SUFFIXES)
|
|
56
|
+
raise ImportError(f"could not find packaged native PAPI payload (_papi + one of: {suffixes})")
|
|
57
|
+
if len(matches) != 1:
|
|
58
|
+
names = ", ".join(str(path) for path in matches)
|
|
59
|
+
raise ImportError(f"ambiguous packaged native PAPI payloads: {names}")
|
|
60
|
+
return matches[0]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _payload_digest(payload: bytes) -> str:
|
|
64
|
+
return hashlib.sha256(payload).hexdigest()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _target_path(root: Path, digest: str) -> Path:
|
|
68
|
+
return root / f"endstone_papi-{digest}.pyd"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _ensure_shadow_copy(target: Path, payload: bytes, digest: str) -> None:
|
|
72
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
try:
|
|
74
|
+
stream = target.open("xb")
|
|
75
|
+
except FileExistsError:
|
|
76
|
+
try:
|
|
77
|
+
existing = target.read_bytes()
|
|
78
|
+
except OSError as error:
|
|
79
|
+
raise ImportError(f"could not verify existing native shadow target {target}") from error
|
|
80
|
+
if _payload_digest(existing) != digest:
|
|
81
|
+
raise ImportError(f"existing native shadow target does not match payload digest: {target}")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
with stream:
|
|
86
|
+
stream.write(payload)
|
|
87
|
+
stream.flush()
|
|
88
|
+
except Exception as error:
|
|
89
|
+
try:
|
|
90
|
+
target.unlink()
|
|
91
|
+
except OSError:
|
|
92
|
+
pass
|
|
93
|
+
raise ImportError(f"failed to create native shadow target {target}") from error
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _make_persistent_parent() -> tuple[types.ModuleType, bool]:
|
|
97
|
+
existing = sys.modules.get(_PERSISTENT_PARENT_NAME)
|
|
98
|
+
if existing is not None:
|
|
99
|
+
if not isinstance(existing, types.ModuleType):
|
|
100
|
+
raise ImportError(f"persistent native namespace is not a module: {_PERSISTENT_PARENT_NAME}")
|
|
101
|
+
return existing, False
|
|
102
|
+
|
|
103
|
+
parent = types.ModuleType(_PERSISTENT_PARENT_NAME)
|
|
104
|
+
parent.__path__ = []
|
|
105
|
+
parent.__package__ = _PERSISTENT_PARENT_NAME
|
|
106
|
+
sys.modules[_PERSISTENT_PARENT_NAME] = parent
|
|
107
|
+
return parent, True
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _execute_extension(target: Path) -> types.ModuleType:
|
|
111
|
+
loader = importlib.machinery.ExtensionFileLoader(PERSISTENT_MODULE_NAME, str(target))
|
|
112
|
+
spec = importlib.util.spec_from_file_location(PERSISTENT_MODULE_NAME, str(target), loader=loader)
|
|
113
|
+
if spec is None:
|
|
114
|
+
raise ImportError(f"could not create import spec for native PAPI payload: {target}")
|
|
115
|
+
|
|
116
|
+
parent, parent_created = _make_persistent_parent()
|
|
117
|
+
module: types.ModuleType | None = None
|
|
118
|
+
try:
|
|
119
|
+
module = importlib.util.module_from_spec(spec)
|
|
120
|
+
sys.modules[PERSISTENT_MODULE_NAME] = module
|
|
121
|
+
loader.exec_module(module)
|
|
122
|
+
return module
|
|
123
|
+
except BaseException:
|
|
124
|
+
if module is not None and sys.modules.get(PERSISTENT_MODULE_NAME) is module:
|
|
125
|
+
del sys.modules[PERSISTENT_MODULE_NAME]
|
|
126
|
+
if parent_created and sys.modules.get(_PERSISTENT_PARENT_NAME) is parent:
|
|
127
|
+
del sys.modules[_PERSISTENT_PARENT_NAME]
|
|
128
|
+
raise
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _cleanup_stale_targets(root: Path, current: Path) -> None:
|
|
132
|
+
current_key = str(current.absolute()).casefold()
|
|
133
|
+
try:
|
|
134
|
+
candidates = root.glob("endstone_papi-*.pyd")
|
|
135
|
+
for candidate in candidates:
|
|
136
|
+
if _SHADOW_TARGET_RE.fullmatch(candidate.name) is None:
|
|
137
|
+
continue
|
|
138
|
+
if str(candidate.absolute()).casefold() == current_key:
|
|
139
|
+
continue
|
|
140
|
+
try:
|
|
141
|
+
candidate.unlink()
|
|
142
|
+
except OSError:
|
|
143
|
+
pass
|
|
144
|
+
except OSError:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _mismatch_error() -> ImportError:
|
|
149
|
+
return ImportError(_RESTART_REQUIRED)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def load_native(package_name: str, package_path: str | os.PathLike[str]) -> types.ModuleType:
|
|
153
|
+
"""Load and alias the native extension for an eligible Windows package."""
|
|
154
|
+
package_dir = _package_dir(package_path)
|
|
155
|
+
root = shadow_root(package_dir)
|
|
156
|
+
if os.name != "nt" or root is None:
|
|
157
|
+
raise ImportError("native shadow loading is only available for Endstone local plugins on Windows")
|
|
158
|
+
|
|
159
|
+
source = _find_native_payload(package_dir)
|
|
160
|
+
payload = source.read_bytes()
|
|
161
|
+
digest = _payload_digest(payload)
|
|
162
|
+
|
|
163
|
+
persistent = sys.modules.get(PERSISTENT_MODULE_NAME)
|
|
164
|
+
if persistent is not None:
|
|
165
|
+
if getattr(persistent, PERSISTENT_DIGEST_ATTRIBUTE, None) != digest:
|
|
166
|
+
raise _mismatch_error()
|
|
167
|
+
module = persistent
|
|
168
|
+
else:
|
|
169
|
+
target = _target_path(root, digest)
|
|
170
|
+
_ensure_shadow_copy(target, payload, digest)
|
|
171
|
+
module = _execute_extension(target)
|
|
172
|
+
_make_persistent_parent()
|
|
173
|
+
sys.modules[PERSISTENT_MODULE_NAME] = module
|
|
174
|
+
setattr(module, PERSISTENT_DIGEST_ATTRIBUTE, digest)
|
|
175
|
+
_cleanup_stale_targets(root, target)
|
|
176
|
+
|
|
177
|
+
alias_name = f"{package_name}._papi"
|
|
178
|
+
sys.modules[alias_name] = module
|
|
179
|
+
package = sys.modules.get(package_name)
|
|
180
|
+
if package is not None:
|
|
181
|
+
package._papi = module
|
|
182
|
+
parent = sys.modules.get(_PERSISTENT_PARENT_NAME)
|
|
183
|
+
if parent is not None:
|
|
184
|
+
parent._papi = module
|
|
185
|
+
return module
|
|
Binary file
|
endstone_papi/_papi.pyi
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Type stubs for the native PlaceholderAPI core."""
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
|
|
5
|
+
from endstone import OfflinePlayer, Player
|
|
6
|
+
from endstone.event import Event
|
|
7
|
+
from endstone.plugin import Plugin, Service, ServiceManager
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"SERVICE_NAME",
|
|
11
|
+
"ExpansionInfo",
|
|
12
|
+
"ExpansionRegisteredEvent",
|
|
13
|
+
"ExpansionUnregisteredEvent",
|
|
14
|
+
"PlaceholderAPI",
|
|
15
|
+
"PlaceholderExpansion",
|
|
16
|
+
"UnregisterReason",
|
|
17
|
+
"__version__",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
__version__: str
|
|
21
|
+
|
|
22
|
+
SERVICE_NAME: str
|
|
23
|
+
|
|
24
|
+
class UnregisterReason(enum.Enum):
|
|
25
|
+
"""Why an expansion was removed from the registry."""
|
|
26
|
+
|
|
27
|
+
EXPLICIT = 0
|
|
28
|
+
OWNER_DISABLED = 1
|
|
29
|
+
REQUIRED_PLUGIN_DISABLED = 2
|
|
30
|
+
PAPI_SHUTDOWN = 3
|
|
31
|
+
|
|
32
|
+
class ExpansionInfo:
|
|
33
|
+
"""An immutable copy of a registered expansion's metadata.
|
|
34
|
+
|
|
35
|
+
Stays valid and unchanged after the expansion it describes is unregistered and
|
|
36
|
+
destroyed. It holds no reference to the expansion, its owner, or the registry.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def identifier(self) -> str:
|
|
41
|
+
"""The canonical, lowercase identifier."""
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def name(self) -> str:
|
|
45
|
+
"""The expansion's display name."""
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def author(self) -> str:
|
|
49
|
+
"""The expansion's author."""
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def version(self) -> str:
|
|
53
|
+
"""The expansion's version."""
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def owner(self) -> str:
|
|
57
|
+
"""The name of the plugin that registered the expansion."""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def required_plugin(self) -> str | None:
|
|
61
|
+
"""The plugin the expansion requires, or None."""
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def relational(self) -> bool:
|
|
65
|
+
"""Whether the expansion handles relational placeholders."""
|
|
66
|
+
|
|
67
|
+
class PlaceholderExpansion:
|
|
68
|
+
"""Base class for placeholder providers.
|
|
69
|
+
|
|
70
|
+
Subclass this and register it through the PlaceholderAPI service. C++ and Python
|
|
71
|
+
expansions share one registry, so a consumer never learns which language a
|
|
72
|
+
provider was written in.
|
|
73
|
+
|
|
74
|
+
Metadata is read once at registration and copied, so an implementation must report
|
|
75
|
+
stable values. Arguments are borrowed for the duration of a call only; do not keep
|
|
76
|
+
the player past the callback.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self) -> None: ...
|
|
80
|
+
@property
|
|
81
|
+
def identifier(self) -> str:
|
|
82
|
+
"""The identifier this expansion answers to.
|
|
83
|
+
|
|
84
|
+
Must match ``[A-Za-z0-9][A-Za-z0-9-]*``, and is canonicalized to lowercase.
|
|
85
|
+
The colon separates the identifier from parameters. Dots and underscores may
|
|
86
|
+
be used in parameters, but are not allowed in an identifier.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def author(self) -> str:
|
|
91
|
+
"""The expansion's author. Must not be empty."""
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def version(self) -> str:
|
|
95
|
+
"""The expansion's version. Must not be empty."""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def name(self) -> str:
|
|
99
|
+
"""The expansion's display name. Defaults to the identifier."""
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def required_plugin(self) -> str | None:
|
|
103
|
+
"""The plugin this expansion requires, or None.
|
|
104
|
+
|
|
105
|
+
Matched case-sensitively. It must be enabled at registration, and the expansion
|
|
106
|
+
is removed automatically if it is later disabled.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def can_register(self) -> bool:
|
|
110
|
+
"""Final chance to refuse registration. Defaults to True."""
|
|
111
|
+
|
|
112
|
+
def supports_relational_placeholders(self) -> bool:
|
|
113
|
+
"""Whether ``on_relational_request`` is implemented. Defaults to False."""
|
|
114
|
+
|
|
115
|
+
def supports_player_cleanup(self) -> bool:
|
|
116
|
+
"""Whether ``on_player_quit`` is implemented. Defaults to False."""
|
|
117
|
+
|
|
118
|
+
def on_request(self, player: OfflinePlayer | None, params: str) -> str | None:
|
|
119
|
+
"""Resolves an ordinary ``{identifier:params}`` placeholder.
|
|
120
|
+
|
|
121
|
+
``params`` contains everything after the first colon exactly as written.
|
|
122
|
+
Return a ``str``, or None to leave the placeholder text untouched. Any other
|
|
123
|
+
type is a provider error and is not coerced.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def on_relational_request(self, one: Player, two: Player, params: str) -> str | None:
|
|
127
|
+
"""Resolves a relational ``{rel:identifier:params}`` placeholder.
|
|
128
|
+
|
|
129
|
+
``params`` contains everything after the second colon exactly as written.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def on_player_quit(self, player: Player) -> None:
|
|
133
|
+
"""Called when a player leaves, if this expansion opted into cleanup."""
|
|
134
|
+
|
|
135
|
+
def on_unregister(self, reason: UnregisterReason) -> None:
|
|
136
|
+
"""Called once after this expansion has been removed from the registry."""
|
|
137
|
+
|
|
138
|
+
class PlaceholderAPI(Service):
|
|
139
|
+
"""Resolves placeholders through registered expansions.
|
|
140
|
+
|
|
141
|
+
Obtained from Endstone's service manager; it cannot be constructed or subclassed in
|
|
142
|
+
Python. A retained instance becomes permanently inert once PAPI is disabled:
|
|
143
|
+
``active`` turns False, parsing returns its input unchanged, queries come back
|
|
144
|
+
empty, and mutations fail.
|
|
145
|
+
|
|
146
|
+
Parsing and register/unregister mutations require the primary server thread, and
|
|
147
|
+
provider callbacks execute there. ``contains_placeholders``, ``active``, and registry
|
|
148
|
+
queries that return copied state are safe from any thread.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def load(service_manager: ServiceManager) -> PlaceholderAPI | None:
|
|
153
|
+
"""Load the active PlaceholderAPI service, or None when it is unavailable."""
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def active(self) -> bool:
|
|
157
|
+
"""Whether this service is still usable."""
|
|
158
|
+
|
|
159
|
+
def set_placeholders(self, player: OfflinePlayer | None, text: str) -> str:
|
|
160
|
+
"""Replaces every resolvable ``{identifier:params}`` in text."""
|
|
161
|
+
|
|
162
|
+
def set_relational_placeholders(self, one: Player, two: Player, text: str) -> str:
|
|
163
|
+
"""Replaces every resolvable ``{rel:identifier:params}`` in text."""
|
|
164
|
+
|
|
165
|
+
def contains_placeholders(self, text: str) -> bool:
|
|
166
|
+
"""Whether text lexically contains a placeholder-shaped substring."""
|
|
167
|
+
|
|
168
|
+
def is_registered(self, identifier: str) -> bool:
|
|
169
|
+
"""Whether an identifier currently has an expansion registered.
|
|
170
|
+
|
|
171
|
+
``rel`` is reserved by relational syntax and cannot be registered.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def registered_identifiers(self) -> tuple[str, ...]:
|
|
176
|
+
"""Every registered identifier, sorted canonically."""
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def expansions(self) -> tuple[ExpansionInfo, ...]:
|
|
180
|
+
"""Metadata for every registered expansion, sorted by identifier."""
|
|
181
|
+
|
|
182
|
+
def register_expansion(self, owner: Plugin, expansion: PlaceholderExpansion) -> bool:
|
|
183
|
+
"""Registers an expansion on behalf of a plugin."""
|
|
184
|
+
|
|
185
|
+
def unregister_expansion(self, owner: Plugin, identifier: str) -> bool:
|
|
186
|
+
"""Unregisters one expansion owned by a plugin."""
|
|
187
|
+
|
|
188
|
+
def unregister_expansions(self, owner: Plugin) -> int:
|
|
189
|
+
"""Unregisters every expansion owned by a plugin."""
|
|
190
|
+
|
|
191
|
+
class ExpansionRegisteredEvent(Event):
|
|
192
|
+
"""Fired after an expansion has been added to the registry."""
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def expansion_info(self) -> ExpansionInfo:
|
|
196
|
+
"""Metadata describing the expansion that was registered."""
|
|
197
|
+
|
|
198
|
+
class ExpansionUnregisteredEvent(Event):
|
|
199
|
+
"""Fired after an expansion has been removed from the registry."""
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def expansion_info(self) -> ExpansionInfo:
|
|
203
|
+
"""Metadata describing the expansion that was removed."""
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def reason(self) -> UnregisterReason:
|
|
207
|
+
"""Why the expansion was removed."""
|
|
208
|
+
|
|
209
|
+
class _PapiBootstrap:
|
|
210
|
+
"""Internal native lifecycle state owned by the PAPI plugin."""
|
|
211
|
+
|
|
212
|
+
def start(self, plugin: Plugin) -> bool: ...
|
|
213
|
+
def stop(self) -> None: ...
|
|
214
|
+
@property
|
|
215
|
+
def service(self) -> PlaceholderAPI | None: ...
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <utility>
|
|
4
|
+
|
|
5
|
+
#include <endstone/event/event.h>
|
|
6
|
+
#include <endstone/event/server/server_event.h>
|
|
7
|
+
|
|
8
|
+
#include "endstone_papi/expansion_info.h"
|
|
9
|
+
#include "endstone_papi/unregister_reason.h"
|
|
10
|
+
|
|
11
|
+
namespace papi {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @brief Fired after an expansion has been added to the registry.
|
|
15
|
+
*
|
|
16
|
+
* Synchronous, non-cancellable, and dispatched after the registry has committed,
|
|
17
|
+
* so a listener querying PlaceholderAPI already observes the new expansion.
|
|
18
|
+
*
|
|
19
|
+
* Only copied metadata is exposed; the expansion object is never reachable from
|
|
20
|
+
* the event.
|
|
21
|
+
*/
|
|
22
|
+
class ExpansionRegisteredEvent : public endstone::ServerEvent {
|
|
23
|
+
public:
|
|
24
|
+
ENDSTONE_EVENT(ExpansionRegisteredEvent);
|
|
25
|
+
|
|
26
|
+
explicit ExpansionRegisteredEvent(ExpansionInfo info) : info_(std::move(info)) {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @brief Metadata describing the expansion that was registered.
|
|
30
|
+
*/
|
|
31
|
+
[[nodiscard]] const ExpansionInfo &getExpansionInfo() const { return info_; }
|
|
32
|
+
|
|
33
|
+
private:
|
|
34
|
+
ExpansionInfo info_;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @brief Fired after an expansion has been removed from the registry.
|
|
39
|
+
*
|
|
40
|
+
* Synchronous, non-cancellable, and dispatched after the registry has removed the
|
|
41
|
+
* entry and the expansion's cleanup callback has returned, so a listener querying
|
|
42
|
+
* PlaceholderAPI no longer observes the expansion.
|
|
43
|
+
*
|
|
44
|
+
* This event is suppressed during PAPI's own shutdown, because the event system
|
|
45
|
+
* and its listeners are being torn down at that point.
|
|
46
|
+
*/
|
|
47
|
+
class ExpansionUnregisteredEvent : public endstone::ServerEvent {
|
|
48
|
+
public:
|
|
49
|
+
ENDSTONE_EVENT(ExpansionUnregisteredEvent);
|
|
50
|
+
|
|
51
|
+
ExpansionUnregisteredEvent(ExpansionInfo info, const UnregisterReason reason)
|
|
52
|
+
: info_(std::move(info)), reason_(reason)
|
|
53
|
+
{
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @brief Metadata describing the expansion that was removed.
|
|
58
|
+
*/
|
|
59
|
+
[[nodiscard]] const ExpansionInfo &getExpansionInfo() const { return info_; }
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @brief Why the expansion was removed.
|
|
63
|
+
*/
|
|
64
|
+
[[nodiscard]] UnregisterReason getReason() const { return reason_; }
|
|
65
|
+
|
|
66
|
+
private:
|
|
67
|
+
ExpansionInfo info_;
|
|
68
|
+
UnregisterReason reason_;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
} // namespace papi
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <optional>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
namespace papi {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @brief An immutable copy of a registered expansion's metadata.
|
|
10
|
+
*
|
|
11
|
+
* Instances are pure values. They contain no plugin pointer, expansion pointer,
|
|
12
|
+
* callback, or registry reference, so they stay valid and unchanged after the
|
|
13
|
+
* expansion they describe is unregistered and destroyed.
|
|
14
|
+
*/
|
|
15
|
+
struct ExpansionInfo {
|
|
16
|
+
/**
|
|
17
|
+
* @brief The canonical identifier, always ASCII lowercase.
|
|
18
|
+
*/
|
|
19
|
+
std::string identifier;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @brief The human-readable name reported by the expansion.
|
|
23
|
+
*/
|
|
24
|
+
std::string name;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @brief The author reported by the expansion.
|
|
28
|
+
*/
|
|
29
|
+
std::string author;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @brief The version reported by the expansion.
|
|
33
|
+
*/
|
|
34
|
+
std::string version;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @brief The name of the Endstone plugin that registered the expansion.
|
|
38
|
+
*/
|
|
39
|
+
std::string owner;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @brief The plugin the expansion requires, if it declared one.
|
|
43
|
+
*/
|
|
44
|
+
std::optional<std::string> required_plugin;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @brief Whether the expansion handles relational placeholders.
|
|
48
|
+
*/
|
|
49
|
+
bool relational = false;
|
|
50
|
+
|
|
51
|
+
[[nodiscard]] friend bool operator==(const ExpansionInfo &, const ExpansionInfo &) = default;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
} // namespace papi
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "endstone_papi/events.h"
|
|
4
|
+
#include "endstone_papi/expansion_info.h"
|
|
5
|
+
#include "endstone_papi/placeholder_api.h"
|
|
6
|
+
#include "endstone_papi/placeholder_expansion.h"
|
|
7
|
+
#include "endstone_papi/unregister_reason.h"
|
|
8
|
+
#include "endstone_papi/version.h"
|