aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2023-10-17 14:55:30 +0800
committer简律纯 <i@jyunko.cn>2023-10-17 14:55:30 +0800
commitf7e8f6f166114b9ab9e05852f5cb80d3d36eab2f (patch)
treec72f096792ff7343f9744d20ae9c5afd2d340884
parent7967a1317b54a17d8039ad7e25a03681bf6aacb2 (diff)
downloadinfini-f7e8f6f166114b9ab9e05852f5cb80d3d36eab2f.tar.gz
infini-f7e8f6f166114b9ab9e05852f5cb80d3d36eab2f.zip
feat(status): add deprecated and new status
-rw-r--r--.vscode/settings.json4
-rw-r--r--docs/Standard/Term/check/ability.md5
-rw-r--r--mkdocs.yml6
-rw-r--r--pdm.lock159
-rw-r--r--pyproject.toml2
-rw-r--r--src/hydrorollcore/__init__.py6
-rw-r--r--src/hydrorollcore/config.py77
-rw-r--r--src/hydrorollcore/core.py96
-rw-r--r--src/hydrorollcore/exceptions.py34
-rw-r--r--src/hydrorollcore/rule.py78
-rw-r--r--src/hydrorollcore/typing.py46
-rw-r--r--src/hydrorollcore/utils.py4
-rw-r--r--tests/MyRule/Rule/ThePool.py29
-rw-r--r--tests/MyRule/Rule/__init__.py0
-rw-r--r--tests/MyRule/Rule/wiki.py242
-rw-r--r--tests/MyRule/Rule/池 THE POOL.pdfbin0 -> 318327 bytes
-rw-r--r--tests/MyRule/__init__.py0
-rw-r--r--tests/MyRule/config.toml4
-rw-r--r--tests/MyRule/core.py43
19 files changed, 563 insertions, 272 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 69746e86..0fc7b202 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,5 @@
{
- "restructuredtext.preview.docutils.disabled": true
+ "restructuredtext.preview.docutils.disabled": true,
+ "python.analysis.typeCheckingMode": "basic",
+ "iis.configDir": ""
} \ No newline at end of file
diff --git a/docs/Standard/Term/check/ability.md b/docs/Standard/Term/check/ability.md
index aaa4088e..64233562 100644
--- a/docs/Standard/Term/check/ability.md
+++ b/docs/Standard/Term/check/ability.md
@@ -1,11 +1,10 @@
---
title: ability 检定
-status: attention
+status: deprecated
---
!!! info
关于这部分内容请[移步正式术语][redirect]: [`check 检定`](/Standard/Term/check/)
[redirect]: https://HydroRollCore.retrofor.space "我为什么会看到此项内容?"
-
-这部分其实已经移除。 \ No newline at end of file
+这部分其实已经移除。
diff --git a/mkdocs.yml b/mkdocs.yml
index 123c0bc2..652ad6ad 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -62,6 +62,9 @@ theme:
text: Roboto
code: Roboto Mono
extra:
+ status:
+ new: 最近添加
+ deprecated: 已被废弃
version:
provider: mike
homepage: https://grps.retrofor.space # hydroroll-team.github.io
@@ -215,3 +218,6 @@ nav:
extra_css:
- stylesheets/extra.css
+
+extra_javascript:
+ - "https://samuelcolvin.github.io/mkdocs-run-code/run_code_main.js"
diff --git a/pdm.lock b/pdm.lock
index 3fb41438..b1bf15b8 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -6,7 +6,20 @@ groups = ["default"]
cross_platform = true
static_urls = false
lock_version = "4.3"
-content_hash = "sha256:5d7c1be81b0f4501c46e7c5f40f7b9b62ceedf2f3fa0b28003d5981dc3e1d0d9"
+content_hash = "sha256:860461057289e7a545cf024ca51cc9776a6e9b4588aadf9893bbdb9814d53d73"
+
+[[package]]
+name = "annotated-types"
+version = "0.6.0"
+requires_python = ">=3.8"
+summary = "Reusable constraint types to use with typing.Annotated"
+dependencies = [
+ "typing-extensions>=4.0.0; python_version < \"3.9\"",
+]
+files = [
+ {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
+ {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
+]
[[package]]
name = "anyio"
@@ -484,6 +497,20 @@ files = [
]
[[package]]
+name = "loguru"
+version = "0.7.2"
+requires_python = ">=3.5"
+summary = "Python logging made (stupidly) simple"
+dependencies = [
+ "colorama>=0.3.4; sys_platform == \"win32\"",
+ "win32-setctime>=1.0.0; sys_platform == \"win32\"",
+]
+files = [
+ {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
+ {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
+]
+
+[[package]]
name = "lxml"
version = "4.9.3"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
@@ -1034,6 +1061,126 @@ files = [
]
[[package]]
+name = "pydantic"
+version = "2.4.2"
+requires_python = ">=3.7"
+summary = "Data validation using Python type hints"
+dependencies = [
+ "annotated-types>=0.4.0",
+ "pydantic-core==2.10.1",
+ "typing-extensions>=4.6.1",
+]
+files = [
+ {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"},
+ {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"},
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.10.1"
+requires_python = ">=3.7"
+summary = ""
+dependencies = [
+ "typing-extensions!=4.7.0,>=4.6.0",
+]
+files = [
+ {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"},
+ {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"},
+ {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"},
+ {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"},
+ {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"},
+ {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"},
+ {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"},
+ {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"},
+ {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"},
+ {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"},
+ {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"},
+ {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"},
+ {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"},
+ {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"},
+ {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"},
+ {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"},
+ {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"},
+ {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"},
+ {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"},
+ {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"},
+ {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"},
+ {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"},
+ {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"},
+]
+
+[[package]]
name = "pygments"
version = "2.16.1"
requires_python = ">=3.7"
@@ -1439,6 +1586,16 @@ files = [
]
[[package]]
+name = "win32-setctime"
+version = "1.1.0"
+requires_python = ">=3.5"
+summary = "A small Python utility to set file creation time on Windows"
+files = [
+ {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
+ {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
+]
+
+[[package]]
name = "zipp"
version = "3.17.0"
requires_python = ">=3.8"
diff --git a/pyproject.toml b/pyproject.toml
index e214593d..e52d3d6e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,6 +22,8 @@ dependencies = [
"mkdocstrings>=0.23.0",
"distribute>=0.7.3",
"reportlab[pycairo]>=4.0.5",
+ "pydantic>=2.4.2",
+ "loguru>=0.7.2",
]
requires-python = ">=3.8"
readme = "README.md"
diff --git a/src/hydrorollcore/__init__.py b/src/hydrorollcore/__init__.py
index 13f83e86..93c042eb 100644
--- a/src/hydrorollcore/__init__.py
+++ b/src/hydrorollcore/__init__.py
@@ -1,6 +1,6 @@
from HydroRollCore.cli import Cli
-from HydroRollCore.config import ConfigModel
-from HydroRollCore.rule import Rule, RuleLoadType
+from HydroRollCore.config import Config
+from HydroRollCore.rule import Rule
from HydroRollCore.core import Core
-__all__ = ['Core', 'Rule', 'ConfigModel', 'RuleLoadType', 'Cli']
+__all__ = ['Core', 'Rule', 'Config', 'Cli']
diff --git a/src/hydrorollcore/config.py b/src/hydrorollcore/config.py
index 4bee29df..c7697d15 100644
--- a/src/hydrorollcore/config.py
+++ b/src/hydrorollcore/config.py
@@ -1,76 +1,7 @@
-"""HydroRollCore 配置。
+from pydantic import BaseModel
-HydroRollCore 使用 [pydantic](https://pydantic-docs.helpmanual.io/) 来读取配置。
-"""
-from typing import Set, Union
-from pydantic import Extra, Field, BaseModel, DirectoryPath
+class Config(BaseModel):
+ rule_dir: list = []
+ rules: list = []
-__all__ = [
- "ConfigModel",
- "LogConfig",
- "CoreConfig",
- "RuleConfig",
- "MainConfig",
-]
-
-
-class ConfigModel(BaseModel):
- """HydroRollCore 配置模型。
-
- Attributes:
- __config_name__: 配置名称。
- """
-
- __config_name__: str
-
- class Config:
- extra = Extra.allow
-
-
-class LogConfig(ConfigModel):
- """HydroRollCore 日志相关设置。
-
- Attributes:
- level: 日志级别。
- verbose_exception: 详细的异常记录,设置为 True 时会在日志中添加异常的 Traceback。
- """
-
- level: Union[str, int] = "DEBUG"
- verbose_exception: bool = False
-
-
-class CoreConfig(ConfigModel):
- """Core 配置。
-
- Attributes:
- rules: 将被加载的规则书列表,将被 `Core` 类的 `load_rules()` 方法加载。
- rule_dirs: 将被加载的规则书目录列表,将被 `Core` 类的 `load_rules_from_dirs()` 方法加载。
- log: HydroRollCore 日志相关设置。
- """
-
- rules: Set[str] = Field(default_factory=set)
- rule_dirs: Set[DirectoryPath] = Field(default_factory=set)
- log: LogConfig = LogConfig() # type: ignore
-
-
-class RuleConfig(ConfigModel):
- """规则书配置。"""
-
-
-class DebugConfig(ConfigModel):
- """是否打印事件配置。"""
-
-
-class MainConfig(ConfigModel):
- """HydroRollCore 配置。
-
- Attributes:
- core: HydroRollCore 的主要配置。
- """
-
- core: CoreConfig = CoreConfig() # type: ignore
- rule: RuleConfig = RuleConfig() # type: ignore
-
- class Config:
- extra = Extra.allow \ No newline at end of file
diff --git a/src/hydrorollcore/core.py b/src/hydrorollcore/core.py
index 9016eda8..f3c83989 100644
--- a/src/hydrorollcore/core.py
+++ b/src/hydrorollcore/core.py
@@ -1,76 +1,26 @@
-"""HydroRollCore 核心程序"""
+import importlib
+from typing import List
+from .exceptions import RuleLoadError
+from .rule import Rule
-import asyncio
-from collections import defaultdict
-import sys
-
-from typing import Any, List, Optional, Type, Dict
-
-from HydroRollCore.config import MainConfig
-from HydroRollCore.rule import Rule, RuleLoadType
-
-__all__ = ['Core']
class Core:
- """HydroRollCore 核心对象,定义了核心的基本方法。
- 读取并储存配置 `Config`,加载规则包 `Rule`,并进行事件分发。
-
- Attributes:
- config: 核心配置。
- should_exit: 核心是否应该进入准备退出状态。
- rules: 当前已经加载的规则包的列表。
- rules_priority_dict: 规则包优先级字典。
- rule_state: 规则包状态。
- global_state: 全局状态。
- """
-
- config: MainConfig
- should_exit: asyncio.Event
- rules: List[Rule]
- rules_priority_dict: Dict[int, List[Type[Rule]]]
- rule_state: Dict[str, Any]
- global_state: Dict[Any, Any]
-
- def __init__(
- self,
- *,
- config_file: Optional[str] = "config.toml",
- config_dict: Optional[Dict] = None,
- hot_reload: bool = False,
- ):
- """初始化 iamai ,读取配置文件,创建配置,加载规则包。
-
- Args:
- config_file: 配置文件,如不指定则使用默认的 `config.toml`。
- 若指定为 None,则不加载配置文件。
- config_dict: 配置字典,默认为 None。
- 若指定字典,则会忽略 config_file 配置,不再读取配置文件。
- hot_reload: 热重载。
- 启用后将自动检查 `rule_dir` 中的规则包文件更新,并在更新时自动重新加载。
- """
- self.config = MainConfig() # type: ignore[assignment]
- self.plugins_priority_dict = defaultdict(list)
- self.plugin_state = defaultdict(type(None)) # type: ignore[assignment]
- self.global_state = {}
-
- self.adapters = []
- self._restart_flag = False
- self._module_path_finder = ModulePathFinder()
- self._raw_config_dict = {}
-
- self._config_file = config_file
- self._config_dict = config_dict
- self._hot_reload = hot_reload
-
- self._extend_plugins = []
- self._extend_plugin_dirs = []
- self._extend_adapters = []
- self._bot_run_hooks = []
- self._bot_exit_hooks = []
- self._adapter_startup_hooks = []
- self._adapter_run_hooks = []
- self._adapter_shutdown_hooks = []
- self._event_preprocessor_hooks = []
- self._event_postprocessor_hooks = []
-
- sys.meta_path.insert(0, self._module_path_finder) \ No newline at end of file
+ def __init__(self, config):
+ self.rule_dir = config.rule_dir
+ self.rules = config.rules
+
+ async def load_rules(self) -> List[Rule]:
+ loaded_rules = []
+ for rule in self.rules:
+ try:
+ module = importlib.import_module(rule)
+ except ImportError as e:
+ raise RuleLoadError(f'Failed to load rule {rule}: {e}')
+ try:
+ rule_cls = getattr(module, rule.split('.')[-1])
+ if not issubclass(rule_cls, Rule):
+ raise RuleLoadError(f"Class '{rule_cls.__name__}' is not a subclass of 'Rule'")
+ except AttributeError as e:
+ raise RuleLoadError(f"Failed to get rule class from module '{rule}': {e}")
+ loaded_rules.append(rule_cls())
+ return loaded_rules
diff --git a/src/hydrorollcore/exceptions.py b/src/hydrorollcore/exceptions.py
index ae49fed2..a7cf2c0b 100644
--- a/src/hydrorollcore/exceptions.py
+++ b/src/hydrorollcore/exceptions.py
@@ -1,32 +1,2 @@
-"""HydroRollCore 异常。
-
-下列是 HydroRollCore 运行过程中可能会抛出的异常。这些异常大部分不需要用户处理,HydroRollCore 会自动捕获并处理。
-"""
-
-
-class EventException(BaseException):
- """事件处理过程中由插件抛出的异常,用于控制事件的传播,会被 iamai 自动捕获并处理。"""
-
-
-class SkipException(EventException):
- """跳过当前插件继续当前事件传播。"""
-
-
-class StopException(EventException):
- """停止当前事件传播。"""
-
-
-class iamaiException(Exception):
- """所有 iamai 发生的异常的基类。"""
-
-
-class GetEventTimeout(iamaiException):
- """当 get 方法超时使被抛出。"""
-
-
-class AdapterException(iamaiException):
- """由适配器抛出的异常基类,所有适配器抛出的异常都应该继承自此类。"""
-
-
-class LoadModuleError(iamaiException):
- """加载模块错误,在指定模块中找不到特定类型的类或模块中存在多个符合条件的类时抛出。""" \ No newline at end of file
+class RuleLoadError(Exception):
+ pass
diff --git a/src/hydrorollcore/rule.py b/src/hydrorollcore/rule.py
index e48b6ce2..6a89b68b 100644
--- a/src/hydrorollcore/rule.py
+++ b/src/hydrorollcore/rule.py
@@ -1,71 +1,17 @@
-from abc import ABC, abstractmethod
-import os
-import os.path
-from enum import Enum
-from iamai.config import ConfigModel
-from iamai.utils import is_config_class
-from typing import Generic, NoReturn, Optional, Type, TYPE_CHECKING
+from abc import ABCMeta, abstractmethod
-if TYPE_CHECKING:
- from iamai.bot import Bot
-
-
-class RuleLoadType(Enum):
- """插件加载类型。"""
-
- DIR = "dir"
- NAME = "name"
- FILE = "file"
- CLASS = "class"
-
-
-class Rule(ABC):
- """所有 iamai 插件的基类。
-
- Attributes:
- event: 当前正在被此插件处理的事件。
- priority: 插件的优先级,数字越小表示优先级越高,默认为 0。
- block: 插件执行结束后是否阻止事件的传播。True 表示阻止。
- __rule_load_type__: 插件加载类型,由 iamai 自动设置,反映了此插件是如何被加载的。
- __rule_file_path__: 当插件加载类型为 `RuleLoadType.CLASS` 时为 `None`,
- 否则为定义插件在的 Python 模块的位置。
- """
-
- priority: int = 0
- Config: Type[ConfigModel]
-
- __rule_load_type__: RuleLoadType
- __rule_file_path__: Optional[str]
-
- def __init__(self, event):
- self.event = event
-
- if not hasattr(self, "priority"):
- self.priority = 0
-
- self.get = self.bot.get
-
- self.__post_init__()
-
- def __post_init__(self):
- """用于初始化后处理,被 `__init__()` 方法调用。"""
+class Rule(metaclass=ABCMeta):
+ @abstractmethod
+ def __init__(self):
pass
- @property
- def name(self) -> str:
- """规则类名称。"""
- return self.__class__.__name__
-
- @property
- def bot(self) -> "Bot":
- """机器人对象。"""
- return self.event.adapter.bot
+ @classmethod
+ def __subclasshook__(cls, other):
+ if cls is Rule:
+ return hasattr(other, 'run') and callable(getattr(other, 'run'))
+ return NotImplemented
- @property
- def config(self):
- """规则包配置。"""
- config_class: ConfigModel = getattr(self, "Rule", None)
- if is_config_class(config_class):
- return getattr(self.config.rule, config_class.__config_name__, None)
- return None
+ @abstractmethod
+ async def run(self):
+ pass
diff --git a/src/hydrorollcore/typing.py b/src/hydrorollcore/typing.py
index 0b4a78ba..4a29d3c2 100644
--- a/src/hydrorollcore/typing.py
+++ b/src/hydrorollcore/typing.py
@@ -1,23 +1,33 @@
-"""HydroRollCore 类型提示支持。
+from pydantic import BaseModel
-此模块定义了部分 HydroRollCore 使用的类型。
-"""
-from typing import TYPE_CHECKING, TypeVar
+class Config(BaseModel):
+ rule_dir: list = []
+ rules: list = []
-if TYPE_CHECKING:
- from HydroRollCore.core import Core # noqa
- from HydroRollCore.rule import Rule # noqa
- from HydroRollCore.config import ConfigModel # noqa
-__all__ = [
- "T_State",
- "T_Core",
- "T_Rule",
- "T_Config"
-]
+class DiceConfig(BaseModel):
+ sides: int
+ counts: int
+ init_dice_pool: int
-T_State = TypeVar("T_State")
-T_Core = TypeVar("T_Core", bound="Core")
-T_Rule = TypeVar("T_Rule", bound="Rule")
-T_Config = TypeVar("T_Config", bound="ConfigModel") \ No newline at end of file
+
+class PlayerCard(BaseModel):
+ name: str
+ traits: list
+
+
+class Bonus(BaseModel):
+ level: int
+ cost: int
+
+
+class WikiPage(BaseModel):
+ title: str
+ content: str
+ tags: list = []
+
+
+class WikiModel:
+ class Setting(BaseModel):
+ desc: str
diff --git a/src/hydrorollcore/utils.py b/src/hydrorollcore/utils.py
index c69fdd48..71187856 100644
--- a/src/hydrorollcore/utils.py
+++ b/src/hydrorollcore/utils.py
@@ -15,7 +15,7 @@ from importlib.abc import MetaPathFinder
from importlib.machinery import PathFinder
from typing import Any, List, Type, Tuple, TypeVar, Callable, Iterable, Coroutine
-from HydroRollCore.config import ConfigModel
+from HydroRollCore.config import Config
__all__ = [
"ModulePathFinder",
@@ -55,7 +55,7 @@ def is_config_class(config_class: Any) -> bool:
"""
return (
inspect.isclass(config_class)
- and issubclass(config_class, ConfigModel)
+ and issubclass(config_class, Config)
and isinstance(getattr(config_class, "__config_name__", None), str)
and ABC not in config_class.__bases__
and not inspect.isabstract(config_class)
diff --git a/tests/MyRule/Rule/ThePool.py b/tests/MyRule/Rule/ThePool.py
new file mode 100644
index 00000000..c3c01317
--- /dev/null
+++ b/tests/MyRule/Rule/ThePool.py
@@ -0,0 +1,29 @@
+from HydroRollCore import Rule
+from .wiki import Wiki
+
+
+class Config:
+ __config_name__ = "ThePool"
+
+
+class ThePool(Rule):
+ config: Config
+ wiki: Wiki
+
+ class DefaultDice:
+ _sides = 6
+ _counts = 1
+ _init_dice_pool = 15
+
+ class Bonus:
+ """奖励骰
+
+ 要分配奖励,你需要花费起始骰池中的骰子。消耗的数量等于奖励骰的数量乘以它自身。
+ """
+
+ class PlayerCard:
+ """角色卡"""
+
+ def Trait(self):
+ """特质标准判断"""
+ return self.__str__() if self.__str__() != "" else None
diff --git a/tests/MyRule/Rule/__init__.py b/tests/MyRule/Rule/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/MyRule/Rule/__init__.py
diff --git a/tests/MyRule/Rule/wiki.py b/tests/MyRule/Rule/wiki.py
new file mode 100644
index 00000000..8cff35cf
--- /dev/null
+++ b/tests/MyRule/Rule/wiki.py
@@ -0,0 +1,242 @@
+from HydroRollCore.rule import WikiModel
+
+
+class Wiki(WikiModel):
+
+ class THE_RULES(WikiModel.Setting):
+ @property
+ def desc(self):
+ return self.__str__()
+
+ def __str__(self):
+ return """The Pool is a role-playing system geared toward player and GM narrative
+ collaboration. You can use it for any setting you like. One person in your
+ group needs to be the Game Master, or GM—this is the person who
+ runs the game. To play you need a lot of d6s (six-sided dice) including a
+ handful of “GM dice” that look different from the rest.
+ Before character creation begins, each player needs 15 dice for their
+ starting Pool. The rest of the dice go into a common pool. """
+
+ def __repr__(self):
+ return self.__str__()
+
+ class CHARACTER_CREATION:
+ @property
+ def character_creation(self) -> str:
+ return """Once your group has decided on a setting you can begin creating
+ characters.
+ Making a character is simple: just write a 50 word Story. Pretend you’re
+ writing a book and this is the introduction of your main character.
+ You only have 50 words to play with, so focus on the most important
+ elements of your new character and how the character fits into the setting
+ your group has chosen. Mentioning your character’s name does not
+ count towards the word limit."""
+
+ @property
+ def example(self) -> str:
+ return """\
+I’ve created my first character for The Pool. The setting is a world of
+darkish magical fantasy.
+“Damart is a sorcerer trained in elemental magic by the secret Lost Land order.
+He was expelled from the Order after falling in love with a young initiate who
+died when he tried to teach her a spell she could not control. Now Damart
+seeks the means to bring her back to life.” """
+
+ class ASSIGNING_TRAITS_AND_BONUSES:
+ @property
+ def desc(self) -> str:
+ return """\
+Now pick the most important elements of your Story. These are Traits
+that will help you gain narrative control during play.
+Traits can be anything from friends and enemies to a good horse or a
+knack for attracting trouble. Whatever is important about your character
+can be a Trait. Though you can word a Trait any way you wish, make sure
+it doesn’t contradict or expand your Story.
+
+For example, Damart’s Story reads “seeks the means to bring her back to
+life” so a Trait based on that statement could be called “Searching for a way
+to bring his love back from the dead” or “Trying to find a way to raise his
+love from the dead” or something similar. But calling the Trait “Has vast
+knowledge of death magic and resurrection” would not work because the
+Story does not relate any special death-related skills or knowledge.
+
+Make sure your Traits are specific enough to avoid game conflicts over
+vagueness. For example, Damart is an elemental sorcerer. When he uses
+magic it is elemental magic, not death magic or shooting stars from his
+fingers. Avoid listing Traits as vague as “Magic” or “Scholar”—be specific.
+
+You can assign Bonuses to important traits, in the form of dice. Bonuses
+increase the effectiveness of traits during play. You do not have to assign
+a Bonus to every Trait. To assign a Bonus, spend dice from your starting
+Pool. The cost is the Bonus times itself. Thus, a +2 would cost 4 dice and
+a +3 would cost 9 dice and so on. It is very important to leave some dice in
+your Pool — at least 3 or 4"""
+
+ def example(self) -> str:
+ return """\
+After writing Damart’s Story, I choose the Traits I want and assign Bonuses
+to them. These Bonuses cost a total of 9 dice, leaving 6 dice in my Pool.
+• Elemental sorcerer of the Lost Land Order +2
+• Outcast of the Lost Land Order
+• He is driven by love +2
+• Searching for the means to raise his love from the dead +1"""
+
+ class CASTING_THE_DICE:
+ @property
+ def desc(self) -> str:
+ return """\
+Dice are cast to determine the general outcome of conflicts. This is not
+the same as rolling when you simply want to take an action. The swing of
+a sword can be achieved through simple dialogue with the GM, without
+throwing dice. The effect of a die roll in The Pool is much broader than the
+swing of a sword.
+
+Anyone can call for a die roll whenever a conflict is apparent or when
+someone wants to introduce a new conflict. Just broadly state your
+intention and roll.
+
+To win a die roll, roll a 1 on any of the dice you cast. Ignore any other
+results. If you don’t roll a 1, you fail the roll.
+When you roll, the GM will provide 1-3 GM dice to add to the throw. If
+you can show an obvious connection between your intention and one of
+your character’s Traits, you can add Bonus dice to your roll if that Trait
+has a Bonus.
+
+In addition, you can gamble up to 9 dice from your Pool. Adding dice to
+your roll greatly increases your chances of getting a 1. But if you fail a roll
+you lose all the dice you gambled. A bad throw can instantly reduce your
+Pool to nothing. """
+
+ @property
+ def example(self) -> str:
+ return """\
+Damart is in an ancient library. I want him to find a piece of knowledge
+that will help him on his quest, so I ask for a roll based on the Trait
+“searching for the means to bring his love from the dead +1”. The GM
+hands me 1 GM die (for my +1 Trait) and decides to give me 2 more to roll
+as well (he can give me an extra 1-3, remember). I still have 6 dice in my
+Pool, so I add 4 of them to the roll as a gamble to increase my chances.
+
+I cast all 7 dice and, luckily, I get a 1. If I had not rolled a 1 I would have lost
+the 4 gambled dice from my Pool, leaving me with only 2"""
+
+ class SUCCESS_AND_FAILURE:
+ @property
+ def desc(self) -> str:
+ return """\
+When you roll successfully, you have two options: add a die to your Pool,
+or make a Monologue of Victory.
+
+If you chose to add a die to your Pool then the GM will narrate a positive
+outcome to the conflict, but he will do so any way he chooses. This
+means things might not go exactly the way you wanted.
+
+Making a Monologue of Victory (or MOV) is the only way to ensure
+that the conflict results in what you want. Giving an MOV is like taking
+control of the game for a few moments. You can describe your character’s
+actions, the actions of those around him, and the outcome of those
+actions. You can even focus on less direct elements of the conflict such as
+what’s happening in the next room or who’s entering the scene.
+
+You can do just about anything. In fact, these are the only real limitations
+you must observe:
+ 1) Don’t make alterations to the characters of other players (such as killing
+them). You can add complications for them and affect the things
+around them, but don’t intrude on the creation of a fellow player.
+ 2) Keep your narration in synch with the established facts and tone
+of the game. If you need to ask the GM questions or prompt the
+other players for responses during your MOV, do so.
+ 3) Keep your narration reasonably short.
+Observing these rules of courtesy and continuity will help everyone enjoy
+the game even more. If you ignore these rules, the GM may end your
+MOV at any time.
+
+If you fail a die roll two things will happen. First, you will lose any dice
+you gambled. Second, the GM will narrate an outcome that is not what
+you intended. The details of the outcome are entirely up to him. He may
+introduce new complications for your character or simply narrate a scene
+that is opposite of what you wanted. """
+
+ @property
+ def example(self) -> str:
+ return """\
+With my successful die roll from the previous example, I choose to give an
+MOV. The GM turns it over to me, everyone listens...
+“After a frustrating couple of hours searching through ancient tomes,
+Damart is ready to give it up. There’s nothing here. But then he notices
+a very strange thing. In a darkened corner a book is leaning against the
+wall. But it isn’t just leaning, its moving! He takes a closer look and the
+book scurries under a table. It can walk! He crawls under the table and
+manages to get his hands on it. The book squirms, but isn’t strong enough
+to break free. On it’s cover are letters from a very old language he has
+some familiarity with. They read ‘Land of the Dead’. There are bloodstains
+on the edges of the pages.”
+I decide that’s a good stopping point. Everyone is very curious about this
+walking book and now the GM resumes control of the game, taking into
+consideration this new element I have just invented. """
+
+ class THE_CONTINUING_STORY:
+ @property
+ def desc(self) -> str:
+ return """\
+If you have 9 dice or more left in your Pool at the end of a session, you
+start the next session with the same number. If you have less than that,
+you start the next session with 9 dice in your Pool.
+At the end of each session, you may add up to 15 new words to your
+character’s Story. They can be new lines or additions to old lines. You can
+also save them until the end of the next session and then write a total of
+30 new words.
+You may add new Traits when you choose. You may add or increase
+Bonuses to Traits anytime you wish the same way you did when you
+created your character: the desired Bonus times itself (+2 costs 4 dice,
++3 costs 9 dice, etc.)."""
+
+ class AT_DEATHS_DOOR:
+ @property
+ def desc(self) -> str:
+ return """\
+Your character does not have “hit points” or any other measure of life.
+But he can die. If your character fails a die roll in a situation the GM
+deems utterly lethal, you can either accept death and make a final MOV
+to describe it (no rolling required), or make a final roll to save his life. In
+this roll you cannot use any Traits and the GM cannot grant you any extra
+dice. All dice must be gambled. Your fellow players may pitch in up to 9
+dice each to help your character survive.
+No matter what the outcome of the roll, all the dice you cast are lost--
+even dice gambled by other players.
+If you win this roll your character has survived the incident, but you do
+not get a MOV nor do you get to add any dice to your Pool. The GM will
+describe how death was cheated.
+If you fail the roll, your character dies. In this case, you get to make a final
+MOV in which you describe your character’s death in detail. Make it a
+good one."""
+
+ class END_NOTES:
+ @property
+ def desc(self) -> str:
+ return """\
+This game was designed more-or-less in a single night back around
+2001 or so. I presented it to the community of the lamentably defunct
+indierpgs.com website and got quite a bit of positive feedback,
+including this from Ron Edwards (Sorcerer):
+“Here is a two-page freebie available on the internet that may be the beginning of
+a whole new wave in RPG design. It presents an amazing concept, astonishingly
+strong, and so pure. My players, hardened RPG veterans, cannot stop gushing
+about it.”
+Here is a video of Ron Edwards delivering a talk about The Pool at the
+University of Milan: Game Designers on Stage YouTube link
+I did eventually develop the system into a finished RPG called The
+Questing Beast, also thanks to much feedback at The Forge. So my
+special thanks goes to Ron Edwards, Scott Knipe, Paul Czege, Mike
+Holmes, Blake Hutchins, Nathan E. Banks, Rene Vernon, Tim Denee
+(and rpg.net), bankuei, David Farmer (and the other folks at Collector
+Comics...now called Little Monsters.), Shawn Martin, James Perrin,
+Phillip Keeney, Dawna Keeney (wink), etc.
+There were many variations of this game created by the RPG
+community as well as some non-English translations (French, Italian
+and Portuguese). I have made these available in a zipped file at
+www.jwarts.com/thepoolvariations.zip.
+I’m always eager to hear about other people’s experiences with The
+Pool. Write a review on one of the great RPG websites, such as rpg.net.
+Or send me an email with your thoughts, comments, criticisms, etc.
+Thanks for checking out The Pool!"""
diff --git a/tests/MyRule/Rule/池 THE POOL.pdf b/tests/MyRule/Rule/池 THE POOL.pdf
new file mode 100644
index 00000000..50570456
--- /dev/null
+++ b/tests/MyRule/Rule/池 THE POOL.pdf
Binary files differ
diff --git a/tests/MyRule/__init__.py b/tests/MyRule/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/MyRule/__init__.py
diff --git a/tests/MyRule/config.toml b/tests/MyRule/config.toml
new file mode 100644
index 00000000..e1c853e5
--- /dev/null
+++ b/tests/MyRule/config.toml
@@ -0,0 +1,4 @@
+[plugins]
+[HydroRollCore]
+rules = []
+rule_dir = ["rules"]
diff --git a/tests/MyRule/core.py b/tests/MyRule/core.py
new file mode 100644
index 00000000..acebe644
--- /dev/null
+++ b/tests/MyRule/core.py
@@ -0,0 +1,43 @@
+import os
+import importlib
+from typing import List
+from pydantic import BaseModel
+import asyncio
+
+class Rule(BaseModel):
+ name: str
+ config_name: str
+
+ async def success(self):
+ # 处理成功事件的逻辑
+ pass
+
+ async def fail(self):
+ # 处理失败事件的逻辑
+ pass
+
+async def load_rules_from_folder(folder_path: str) -> List[Rule]:
+ rules = []
+ for file_name in os.listdir(folder_path):
+ if file_name.endswith(".py"):
+ module_name = file_name[:-3]
+ module = importlib.import_module(module_name)
+ for name, obj in module.__dict__.items():
+ if isinstance(obj, type) and issubclass(obj, Rule) and obj != Rule:
+ rules.append(obj())
+ return rules
+
+async def process_event(rules: List[Rule], event: str):
+ tasks = []
+ for rule in rules:
+ if hasattr(rule, event):
+ task = getattr(rule, event)()
+ tasks.append(task)
+ await asyncio.gather(*tasks)
+
+async def main():
+ folder_path = "tests/MyRule/Rule"
+ rules = await load_rules_from_folder(folder_path)
+ await process_event(rules, "success")
+
+asyncio.run(main())