diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e800d48..92a92a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,5 +28,5 @@ jobs: - run: ./gradlew build - uses: actions/upload-artifact@v4 with: - name: HouseRules - path: build/libs/HouseRules-*.jar + name: Mayor + path: build/libs/Mayor-*.jar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afe91c6..d1828ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,4 +35,4 @@ jobs: GH_TOKEN: ${{ github.token }} run: | notes=$(awk -v v="${GITHUB_REF_NAME#v}" '$0 ~ "^## " v {f=1; next} /^## /{f=0} f' CHANGELOG.md) - gh release create "$GITHUB_REF_NAME" build/libs/HouseRules-*.jar --title "HouseRules ${GITHUB_REF_NAME#v}" --notes "$notes" --prerelease + gh release create "$GITHUB_REF_NAME" build/libs/Mayor-*.jar --title "Mayor ${GITHUB_REF_NAME#v}" --notes "$notes" --prerelease diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bb83d4..c22ef93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.5.0 + +- **A new name: Mayor** (D198). The mayor sets how folks in town talk to each other. How it works is unchanged. As the owner asked (D196), no old names are kept: the package, plugin, jar, commands, permissions and data folder all use Mayor. +- `/hud share` filtering follows BlueMapMini, the map plugin's new name (D194). Mayor hooks only BlueMapMini's share event; the hooks for its earlier names are gone. +- Install it together with BlueMapMini 0.3.0, BlueMapAuth 0.2.0 and OneVoid, which find it by its new name. BlueMapMini keeps sharing off until Mayor 0.5.0 is installed, so shares can never skip chat circles. +- To install over the previous version, with the server stopped: rename its plugin folder to `plugins/Mayor`, move its LuckPerms permissions to the matching `mayor.*` nodes (circles, bypass and filter bypass), then swap the jar. +- Tested: unit tests pass, and the live share check uses a stand-in named BlueMapMini. The real BlueMapMini 0.3.0 and Mayor 0.5.0 haven't been run together yet. + ## 0.4.0 - `ReachService.sharesCircle(viewerHas, to)`: for a viewer who isn't in game, such as someone on Mapkeeper's web map, only a shared circle counts (owner decision D54, 2026-11-10). World, world group and bypass don't, and with no circles set up nobody is reached. diff --git a/README.md b/README.md index 6978295..cc36ea4 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# HouseRules +# Mayor Chat rules for family and community Paper servers. Players hear people in the same world, plus anyone who shares a "circle" with them, so groups such as families or age groups keep their own conversations across every world. -**Status:** pre-release. It passes its acceptance tests on throwaway Paper 26.2 and 26.3 servers, using protocol bots with LuckPerms: chat and private messages for every pair of players, replies, blocked commands, the word filter and its bypass, books, `/circles`, reloads, a broken config, and 20 bots chatting at once. BlueMinimap's share was tested with a stand-in plugin here, and with the real BlueMinimap 0.2.0 and HouseRules 0.2.0 jars together on Paper 26.1.2, 26.2 and 26.3. Signs and anvil renames weren't tested. It hasn't been seen in a real game client yet, so the wording and colours haven't been checked by a person. See [the live tests](test/live/README.md) and the [test audit](docs/test-audit.md). +**Status:** pre-release. It passes its acceptance tests on throwaway Paper 26.2 and 26.3 servers, using protocol bots with LuckPerms: chat and private messages for every pair of players, replies, blocked commands, the word filter and its bypass, books, `/circles`, reloads, a broken config, and 20 bots chatting at once. BlueMapMini's share was tested with a stand-in plugin here; the real BlueMapMini 0.3.0 and Mayor 0.5.0 haven't been run together yet. Signs and anvil renames weren't tested. It hasn't been seen in a real game client yet, so the wording and colours haven't been checked by a person. See [the live tests](test/live/README.md) and the [test audit](docs/test-audit.md). - Requires Paper 26.1 or newer and Java 25. Paper 1.21.11 and older aren't supported. - With no circles set up it changes nothing about who hears whom, so it's safe to install first and configure after. ## What it does -**Circles.** A circle is a permission, `houserules.circle.`, and the circle names are listed in `config.yml`. A player can be in several. Two players can hear each other if: +**Circles.** A circle is a permission, `mayor.circle.`, and the circle names are listed in `config.yml`. A player can be in several. Two players can hear each other if: 1. they're in the same world group, unless it's a circle-only world, or 2. they share a circle. @@ -24,11 +24,11 @@ Otherwise they can't. A world group is a world with its Nether and End (`surviva - Private messages (`/msg`, `/tell`, `/w` and the others in `private-message-commands`) to someone out of reach aren't sent, and you're told who you can talk to instead. - `/r` and `/reply` are checked again when you reply, because the other player may have moved. - Commands that would reach the whole server, `/me` and `/teammsg` by default, are off while circles are in use. -- BlueMinimap's `/hud share`, if BlueMinimap is installed, goes only to players who could hear you in chat. BlueMinimap was called BlueMapHUD up to 0.1.0; HouseRules 0.2.x still filters shares from BlueMapHUD 0.1.x, and that goes in the next release. Update the two together: BlueMinimap turns sharing off if HouseRules is older than 0.2. +- BlueMapMini's `/hud share`, if BlueMapMini is installed, goes only to players who could hear you in chat. BlueMapMini keeps sharing off until Mayor is 0.5.0 or newer. **Blocked commands.** Any command can be turned off, each with its own message. `sort` also blocks `anyplugin:sort`, in any letter case. -**Word filter.** `filter.yml` lists words and phrases to keep out of chat, private messages, signs, books and item names. It matches whole words, so "class" and "Scunthorpe" are fine, and it sees through `sh1t`, `$` for s and stretched letters. Spaced-out spellings get through. Blocked text is never logged or stored: the player is told kindly and can try again. Players with `houserules.filter.bypass`, such as an adults-only group, aren't filtered anywhere. +**Word filter.** `filter.yml` lists words and phrases to keep out of chat, private messages, signs, books and item names. It matches whole words, so "class" and "Scunthorpe" are fine, and it sees through `sh1t`, `$` for s and stretched letters. Spaced-out spellings get through. Blocked text is never logged or stored: the player is told kindly and can try again. Players with `mayor.filter.bypass`, such as an adults-only group, aren't filtered anywhere. **Nobody heard you.** If you chat and nobody online can hear you, you get a light-hearted note, such as "A squid nods politely. It didn't understand a word." There are 75 in `quips.yml`, and you see them all before any comes round again. At most one every 3 minutes. @@ -36,41 +36,41 @@ Every message players read is in `config.yml`, in [MiniMessage](https://docs.adv ## Install -1. Put `HouseRules-.jar` in `plugins/` and start the server. -2. List your circles in `plugins/HouseRules/config.yml`, and any circle-only worlds. -3. Give each player their circle permission with your permission plugin, for example with LuckPerms: `/lp group family permission set houserules.circle.family`. -4. Run `/houserules reload`. +1. Put `Mayor-.jar` in `plugins/` and start the server. +2. List your circles in `plugins/Mayor/config.yml`, and any circle-only worlds. +3. Give each player their circle permission with your permission plugin, for example with LuckPerms: `/lp group family permission set mayor.circle.family`. +4. Run `/mayor reload`. Permission changes take effect straight away, with no relog. -If the config can't be read when the server starts, there are no rules to follow, so chat and private messages are paused, and admins are told when they join. Fix the file and run `/houserules reload`. A reload with mistakes keeps the rules that were working, and says what's wrong. +If the config can't be read when the server starts, there are no rules to follow, so chat and private messages are paused, and admins are told when they join. Fix the file and run `/mayor reload`. A reload with mistakes keeps the rules that were working, and says what's wrong. ## Commands | Command | What it does | | --- | --- | | `/circles` | Shows your circles and who online can hear you right now. | -| `/houserules reload` | Reads `config.yml`, `filter.yml` and `quips.yml` again. | -| `/houserules status` | Shows the circles, whether the filter is on, and any config warnings. | -| `/houserules check ` | Says whether the first can reach the second, and why. | -| `/houserules describe` | Lists these admin commands. | +| `/mayor reload` | Reads `config.yml`, `filter.yml` and `quips.yml` again. | +| `/mayor status` | Shows the circles, whether the filter is on, and any config warnings. | +| `/mayor check ` | Says whether the first can reach the second, and why. | +| `/mayor describe` | Lists these admin commands. | -Every `/houserules` command also takes `--json ` at the end, for scripts and the Leaf Juice app. It then answers with one `ea-result {...}` line in the shared [Signalbox](https://github.com/ExtantApe/Signalbox) format: `status` gives the circles, the filter and any problems; `reload` whether the new rules were applied; `check` both players with their UUIDs, `reaches` and a reason code such as `shared-circle`; `describe` the commands. Players are named or given by UUID; selectors like `@p` aren't accepted. +Every `/mayor` command also takes `--json ` at the end, for scripts and the Leaf Juice app. It then answers with one `ea-result {...}` line in the shared [Signalbox](https://github.com/ExtantApe/Signalbox) format: `status` gives the circles, the filter and any problems; `reload` whether the new rules were applied; `check` both players with their UUIDs, `reaches` and a reason code such as `shared-circle`; `describe` the commands. Players are named or given by UUID; selectors like `@p` aren't accepted. ## Permissions | Permission | Default | Allows | | --- | --- | --- | -| `houserules.circle.` | nobody | Being in that circle. | -| `houserules.circles` | everyone | `/circles`. | -| `houserules.circles.names` | everyone | Names in `/circles`. Without it, only a count. | -| `houserules.bypass` | nobody, ops included | Reaching and being reached by everyone, for moderators. It doesn't show anyone else's chat. | -| `houserules.filter.bypass` | nobody, ops included | Skipping the word filter everywhere it applies, for adult groups. It doesn't change who hears whom. | -| `houserules.admin` | ops | `/houserules`, and config warnings on joining. | +| `mayor.circle.` | nobody | Being in that circle. | +| `mayor.circles` | everyone | `/circles`. | +| `mayor.circles.names` | everyone | Names in `/circles`. Without it, only a count. | +| `mayor.bypass` | nobody, ops included | Reaching and being reached by everyone, for moderators. It doesn't show anyone else's chat. | +| `mayor.filter.bypass` | nobody, ops included | Skipping the word filter everywhere it applies, for adult groups. It doesn't change who hears whom. | +| `mayor.admin` | ops | `/mayor`, and config warnings on joining. | ## For other plugins -HouseRules registers a `ReachService` with Bukkit's services manager: +Mayor registers a `ReachService` with Bukkit's services manager: ```java ReachService rules = Bukkit.getServicesManager().load(ReachService.class); @@ -83,9 +83,9 @@ if (rules == null || rules.mayReach(from, to)) { /* send it */ } ## Limits -- Signs are readable by anyone who walks past, and a book or named item can be carried to another world. HouseRules only checks them when they're written. +- Signs are readable by anyone who walks past, and a book or named item can be carried to another world. Mayor only checks them when they're written. - Chat plugins that send messages their own way, rather than through Paper's chat event, aren't covered. Add their message commands to `private-message-commands`. -- Only the commands listed in `private-message-commands` and `reply-commands` are checked. `/r` is checked against the last conversation HouseRules saw. +- Only the commands listed in `private-message-commands` and `reply-commands` are checked. `/r` is checked against the last conversation Mayor saw. ## Building @@ -93,7 +93,7 @@ if (rules == null || rules.mayReach(from, to)) { /* send it */ } ./gradlew build ``` -The jar is `build/libs/HouseRules-.jar`. Live tests with bots are in `test/live`. +The jar is `build/libs/Mayor-.jar`. Live tests with bots are in `test/live`. ## License diff --git a/build.gradle.kts b/build.gradle.kts index 99086c2..a94386d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.extantape" -version = "0.4.0" +version = "0.5.0" java { toolchain.languageVersion.set(JavaLanguageVersion.of(25)) @@ -23,8 +23,8 @@ extra["paperApi"] = paperApi dependencies { compileOnly(paperApi) - // BlueMapHUD's share event, for the optional hook. Supplied at runtime by BlueMapHUD itself. - compileOnly(project(":bluemaphud-stub")) + // BlueMapMini's share event, for the optional hook. Supplied at runtime by BlueMapMini itself. + compileOnly(project(":bluemapmini-stub")) // The shared command format (--json, describe), shaded in below. implementation("com.github.ExtantApe:Signalbox:v0.3.1") @@ -56,10 +56,10 @@ tasks.test { // Signalbox goes inside the jar, relocated, so it can never clash with another plugin's copy. The shaded jar is the // only jar built. tasks.shadowJar { - archiveBaseName.set("HouseRules") + archiveBaseName.set("Mayor") archiveClassifier.set("") from("LICENSE") - relocate("com.extantape.signalbox", "com.extantape.houserules.libs.signalbox") + relocate("com.extantape.signalbox", "com.extantape.mayor.libs.signalbox") } tasks.jar { enabled = false } tasks.assemble { dependsOn(tasks.shadowJar) } diff --git a/docs/decisions.md b/docs/decisions.md index 6fe6026..94a027e 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -15,3 +15,8 @@ Owner decisions for HouseRules, newest first. | 2026-11-10 | A viewer who isn't in game, such as someone on the web map, sees only players who share a HouseRules circle with them (owner, D54). | Chat's world rule needs a world, and an away viewer has none. | | 2026-11-07 | Every list is in config, including circles, circle-only worlds, world groups, the dimension suffixes, message and reply commands, blocked commands and every message. With no circles, chat is vanilla. | Generic and publishable, with no server's names in the code. | - **BlueMinimap (2026-11-08, owner rename of BlueMapHUD):** 0.2.0 listens for `com.extantape.blueminimap.api.CoordinateShareEvent` when BlueMinimap is installed, and still for the old `com.extantape.bluemaphud.api` event when only BlueMapHUD 0.1.x is, for one release. Released together with BlueMinimap 0.2.0, which turns sharing off when HouseRules is older than 0.2, so no order of upgrades lets a share skip the circles. + +## 2026-11-14 + +- **Map plugin's new name (owner, D194, D196):** the share hook follows BlueMapMini only. No hooks or stand-ins for its earlier names are kept. +- **Name (owner, D198):** the plugin is renamed Mayor. The owner chose "Mayor (Recommended)". Released as 0.5.0 with no old names kept, installed together with BlueMapMini 0.3.0. diff --git a/docs/test-audit.md b/docs/test-audit.md index 55a6d54..7fc660c 100644 --- a/docs/test-audit.md +++ b/docs/test-audit.md @@ -4,7 +4,7 @@ Kept by this repo's owning session (Minecraft: Plugins Lead), per `~/Code/CLAUDE ## 2026-11-10: the --json console -What's new: every `/houserules` command takes `--json ` (Signalbox format 1), and there's `/houserules describe`. Since the first audit, only CI changes had landed, so this covers the console alone. +What's new: every `/mayor` command takes `--json ` (Signalbox format 1), and there's `/mayor describe`. Since the first audit, only CI changes had landed, so this covers the console alone. | Promise | Covered by | | --- | --- | @@ -14,7 +14,7 @@ What's new: every `/houserules` command takes `--json ` (Signalbox format 1) | While chat is paused, `check` refuses rather than guess | unit `whilePausedCheckRefusesInsteadOfGuessing` | | An offline player is said plainly | unit `anOfflinePlayerIsSaidPlainly`; live | | `status` and `reload` report circles, filter, problems and whether a reload applied | unit; live `status` | -| A player without `houserules.admin` gets nothing, `--json` or not | live "a player without houserules.admin gets no answer line" | +| A player without `mayor.admin` gets nothing, `--json` or not | live "a player without mayor.admin gets no answer line" | | Without `--json`, the commands read as before | the existing live checks (`check explains a shared circle`, the reload checks), unchanged | Live suite: 67/67 on Paper 26.3 (2 skipped, as before). The test bots' two-letter names aren't valid Minecraft names, so the `--json` checks name them by UUID, which the format allows. @@ -33,19 +33,19 @@ Each promise in the README, and the test that holds it to it. "Live" is `test/li | Private messages out of reach aren't sent; told who instead; selectors refused | live private-message matrix (every command), "/msg with a selector is refused" | | `/r` re-checked on reply | unit `replies`; live "Replies" | | `/me` and `/teammsg` off while circles are in use; bypass keeps them | unit `broadcastCommandsOffWhileCirclesAreOn`; live "Blocked and broadcast commands" | -| BlueMinimap shares follow chat; old BlueMapHUD event for one release | live share section with `HR_SHARE_PLUGIN=new` and `old`; BlueMinimap's `test/live/houserules.js` with the real jars | +| BlueMapMini shares follow chat | live share section, with a stand-in BlueMapMini firing the share event; BlueMapMini's own guard keeps sharing off with a Mayor older than 0.5.0 | | Blocked commands in every form, own message | unit `blockedCommandsInEveryForm`; live | | Filter: whole words, disguises, allow list; Scunthorpe passes | unit `wholeWordsOnly`, `seesThroughDisguises`, `allowListWins`, `phrases`, `defaultList*`; live | | **Blocked text is never logged or stored** | **new** live "filtered words never reach the server log" (chat and `/msg`) | | Filter bypass, granted and removed without a relog; others still filtered | unit `filterBypass*`; live | -| `houserules.bypass` and `.filter.bypass` off for everyone, ops included | unit `bypassesAreOffForEveryoneOpsIncluded` | +| `mayor.bypass` and `.filter.bypass` off for everyone, ops included | unit `bypassesAreOffForEveryoneOpsIncluded` | | Nobody-heard notes: all before any repeat, cooldown | unit `everyNoteBeforeAnyRepeatAndNeverTwiceInARow`, `cooldown`, `emptyIsQuiet`; live | | Permission changes take effect without a relog | live "Circles granted and removed with LuckPerms, no relog" | | Broken config at startup pauses chat and messages; console says why | unit `pausedBlocksMessagesButNotOtherCommands`; live | | **Admins are told about a broken config when they join** | **new** live "an admin joining is told…", and "a player who isn't an admin isn't" | | A broken reload keeps the working rules | unit `brokenYamlIsAnErrorWithNoSettings`; live | | `/circles`, names only with permission | live `/circles` section | -| `/houserules check` explains | live | +| `/mayor check` explains | live | | 20 players chatting at once stay quick | live "Load" (TPS, 95th percentile under 500 ms) | **Gaps added this audit:** the two in bold. Both passed on 26.2 and 26.3 (61/61, 2 real-client skips). diff --git a/settings.gradle.kts b/settings.gradle.kts index 34d45fa..02826f0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,11 +1,10 @@ -rootProject.name = "HouseRules" +rootProject.name = "Mayor" -// Compile-only copies of BlueMinimap's share event, under its new and old (BlueMapHUD) names, so HouseRules builds without BlueMapHUD. Never shipped. -include("bluemaphud-stub") -project(":bluemaphud-stub").projectDir = file("test/stub") +// A compile-only copy of BlueMapMini's share event, so Mayor builds without BlueMapMini. Never shipped. +include("bluemapmini-stub") +project(":bluemapmini-stub").projectDir = file("test/stub") // Test-only plugins for the live tests in test/live. Never shipped. -include("live-test-helper", "live-test-fakehud", "live-test-fakeminimap") +include("live-test-helper", "live-test-fakeminimap") project(":live-test-helper").projectDir = file("test/live/helper") -project(":live-test-fakehud").projectDir = file("test/live/fakehud") project(":live-test-fakeminimap").projectDir = file("test/live/fakeminimap") diff --git a/src/main/java/com/extantape/houserules/listen/LegacyShareHook.java b/src/main/java/com/extantape/houserules/listen/LegacyShareHook.java deleted file mode 100644 index a40cd3a..0000000 --- a/src/main/java/com/extantape/houserules/listen/LegacyShareHook.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.extantape.houserules.listen; - -import com.extantape.bluemaphud.api.CoordinateShareEvent; -import com.extantape.houserules.HouseRulesPlugin; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -/** - * The same as {@link ShareHook}, for BlueMapHUD 0.1.x, BlueMinimap's name before 2026-11-09. Kept for one release so a - * server that updates HouseRules before BlueMinimap still filters shares. Only loaded when BlueMapHUD is installed. - */ -public final class LegacyShareHook implements Listener { - private final HouseRulesPlugin plugin; - - public LegacyShareHook(HouseRulesPlugin plugin) { - this.plugin = plugin; - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void onShare(CoordinateShareEvent event) { - Player sender = event.getSender(); - if (!plugin.maySpeak(sender)) { - event.setCancelled(true); - plugin.tell(sender, plugin.paused() ? "chat-paused" : "share-closed-here"); - return; - } - event.getRecipients().removeIf(to -> !plugin.mayReach(sender, to)); - } -} diff --git a/src/main/java/com/extantape/houserules/HouseRulesPlugin.java b/src/main/java/com/extantape/mayor/MayorPlugin.java similarity index 76% rename from src/main/java/com/extantape/houserules/HouseRulesPlugin.java rename to src/main/java/com/extantape/mayor/MayorPlugin.java index 9e47848..8c3d3c0 100644 --- a/src/main/java/com/extantape/houserules/HouseRulesPlugin.java +++ b/src/main/java/com/extantape/mayor/MayorPlugin.java @@ -1,19 +1,18 @@ -package com.extantape.houserules; - -import com.extantape.houserules.api.ReachService; -import com.extantape.houserules.command.HouseRulesCommands; -import com.extantape.houserules.config.Settings; -import com.extantape.houserules.config.SettingsLoader; -import com.extantape.houserules.listen.ChatListener; -import com.extantape.houserules.listen.CommandListener; -import com.extantape.houserules.listen.PlayerListener; -import com.extantape.houserules.listen.LegacyShareHook; -import com.extantape.houserules.listen.ShareHook; -import com.extantape.houserules.listen.TextListener; -import com.extantape.houserules.rules.Reach; -import com.extantape.houserules.rules.Subject; -import com.extantape.houserules.text.Messages; -import com.extantape.houserules.text.Quips; +package com.extantape.mayor; + +import com.extantape.mayor.api.ReachService; +import com.extantape.mayor.command.MayorCommands; +import com.extantape.mayor.config.Settings; +import com.extantape.mayor.config.SettingsLoader; +import com.extantape.mayor.listen.ChatListener; +import com.extantape.mayor.listen.CommandListener; +import com.extantape.mayor.listen.PlayerListener; +import com.extantape.mayor.listen.ShareHook; +import com.extantape.mayor.listen.TextListener; +import com.extantape.mayor.rules.Reach; +import com.extantape.mayor.rules.Subject; +import com.extantape.mayor.text.Messages; +import com.extantape.mayor.text.Quips; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; @@ -31,13 +30,13 @@ import java.util.concurrent.ConcurrentHashMap; /** - * HouseRules: who can hear whom, which commands are off, and a word filter. See README.md. + * Mayor: who can hear whom, which commands are off, and a word filter. See README.md. *

* If the config is broken at startup there are no rules to follow, so chat and private messages are paused, and * admins are told why, until it's fixed and reloaded. A broken reload keeps the rules that were working. */ -public final class HouseRulesPlugin extends JavaPlugin implements ReachService { - public static final String ADMIN = "houserules.admin"; +public final class MayorPlugin extends JavaPlugin implements ReachService { + public static final String ADMIN = "mayor.admin"; /** Everything that changes on reload, swapped in one go so the chat thread never sees half of it. */ public record State(Settings settings, Messages messages, Quips quips) { @@ -65,23 +64,16 @@ public void onEnable() { plugins.registerEvents(new TextListener(this), this); plugins.registerEvents(new PlayerListener(this), this); // Only touch a share event's class when its plugin is installed. The hooks are the only classes that name them. - if (plugins.getPlugin("BlueMinimap") != null) { + if (plugins.getPlugin("BlueMapMini") != null) { try { plugins.registerEvents(new ShareHook(this), this); - getLogger().info("BlueMinimap found: coordinate shares follow the same rules as chat."); + getLogger().info("BlueMapMini found: coordinate shares follow the same rules as chat."); } catch (LinkageError e) { - getLogger().warning("BlueMinimap is installed but has no share event this version can use, so shares aren't filtered. Update BlueMinimap and HouseRules together."); - } - } else if (plugins.getPlugin("BlueMapHUD") != null) { - try { - plugins.registerEvents(new LegacyShareHook(this), this); - getLogger().info("BlueMapHUD found: coordinate shares follow the same rules as chat. BlueMapHUD is now called BlueMinimap; support for the old name goes in the next release."); - } catch (LinkageError e) { - getLogger().warning("BlueMapHUD is installed but has no share event this version can use, so shares aren't filtered. Update it to BlueMinimap."); + getLogger().warning("BlueMapMini is installed but has no share event this version can use, so shares aren't filtered. Update BlueMapMini and Mayor together."); } } getServer().getServicesManager().register(ReachService.class, this, this, ServicePriority.Normal); - HouseRulesCommands commands = new HouseRulesCommands(this); + MayorCommands commands = new MayorCommands(this); getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> commands.register(event.registrar())); } @@ -99,9 +91,9 @@ public boolean reload(CommandSender sender) { for (String error : result.errors()) getLogger().severe(error); problems = result.errors(); if (state == null) { - getLogger().severe("HouseRules couldn't read its config, so chat and private messages are paused until it's fixed. Then run /houserules reload."); + getLogger().severe("Mayor couldn't read its config, so chat and private messages are paused until it's fixed. Then run /mayor reload."); } else { - getLogger().severe("The config has problems, so HouseRules kept the rules it already had."); + getLogger().severe("The config has problems, so Mayor kept the rules it already had."); } return false; } diff --git a/src/main/java/com/extantape/houserules/api/ReachService.java b/src/main/java/com/extantape/mayor/api/ReachService.java similarity index 84% rename from src/main/java/com/extantape/houserules/api/ReachService.java rename to src/main/java/com/extantape/mayor/api/ReachService.java index aa513dc..5311d45 100644 --- a/src/main/java/com/extantape/houserules/api/ReachService.java +++ b/src/main/java/com/extantape/mayor/api/ReachService.java @@ -1,11 +1,11 @@ -package com.extantape.houserules.api; +package com.extantape.mayor.api; import org.bukkit.entity.Player; /** - * HouseRules' answer to "may A reach B?", for other plugins. Get it from Bukkit's services manager: + * Mayor' answer to "may A reach B?", for other plugins. Get it from Bukkit's services manager: * {@code Bukkit.getServicesManager().load(ReachService.class)}. Safe to call from any thread. - * Every answer follows the same rules as chat, including circles, circle-only worlds and houserules.bypass. + * Every answer follows the same rules as chat, including circles, circle-only worlds and mayor.bypass. */ public interface ReachService { /** Whether anything {@code from} says or sends can reach {@code to}. Always true for the same player. */ diff --git a/src/main/java/com/extantape/houserules/command/Console.java b/src/main/java/com/extantape/mayor/command/Console.java similarity index 90% rename from src/main/java/com/extantape/houserules/command/Console.java rename to src/main/java/com/extantape/mayor/command/Console.java index 14152e0..0dac07f 100644 --- a/src/main/java/com/extantape/houserules/command/Console.java +++ b/src/main/java/com/extantape/mayor/command/Console.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.command; +package com.extantape.mayor.command; import com.extantape.signalbox.Answer; import com.extantape.signalbox.Args; @@ -12,14 +12,14 @@ import java.util.Map; /** - * The machine-readable side of /houserules (Signalbox format 1): {@code status}, {@code reload}, {@code check} and + * The machine-readable side of /mayor (Signalbox format 1): {@code status}, {@code reload}, {@code check} and * {@code describe}, each answering exactly one {@code ea-result} line. It's worked out here without a server, so the * answers can be tested on their own; the plugin supplies a {@link Host}. */ final class Console { private Console() {} - static final String ADMIN = "houserules.admin"; + static final String ADMIN = "mayor.admin"; record Status(boolean paused, List circles, boolean filter, List problems) {} record Reloaded(boolean applied, boolean paused, List problems) {} @@ -52,9 +52,9 @@ static String answer(Host host, Request r) { return switch (sub) { case "status" -> { none(rest, "status"); yield status(r.id(), host.status()); } case "reload" -> { none(rest, "reload"); yield reloaded(r.id(), host.reload()); } - case "describe" -> { none(rest, "describe"); yield Answer.ok(r.id(), Describe.body("HouseRules", host.version(), described())); } + case "describe" -> { none(rest, "describe"); yield Answer.ok(r.id(), Describe.body("Mayor", host.version(), described())); } case "check" -> check(host, r.id(), rest); - default -> Answer.error(r.id(), Errors.UNKNOWN_COMMAND, "There's no /houserules " + sub + ". Try /houserules describe."); + default -> Answer.error(r.id(), Errors.UNKNOWN_COMMAND, "There's no /mayor " + sub + ". Try /mayor describe."); }; } catch (Request.Bad bad) { return Answer.error(r.id(), bad.code, bad.getMessage()); @@ -70,7 +70,7 @@ private static final class Refused extends Exception { } private static void none(List rest, String sub) throws Refused { - if (!rest.isEmpty()) throw new Refused(Errors.BAD_ARGUMENT, "/houserules " + sub + " takes no arguments."); + if (!rest.isEmpty()) throw new Refused(Errors.BAD_ARGUMENT, "/mayor " + sub + " takes no arguments."); } private static String status(String id, Status s) { @@ -91,10 +91,10 @@ private static String reloaded(String id, Reloaded r) { } private static String check(Host host, String id, List rest) throws Request.Bad, Refused { - if (rest.size() != 2) throw new Refused(Errors.BAD_ARGUMENT, "Usage: /houserules check "); + if (rest.size() != 2) throw new Refused(Errors.BAD_ARGUMENT, "Usage: /mayor check "); String from = Args.player(rest.get(0)), to = Args.player(rest.get(1)); Status s = host.status(); - if (s.paused()) return Answer.error(id, "paused", "Chat is paused until the config is fixed, so nobody reaches anyone. See /houserules status."); + if (s.paused()) return Answer.error(id, "paused", "Chat is paused until the config is fixed, so nobody reaches anyone. See /mayor status."); Checked c = host.check(from, to); if (c == null) return Answer.error(id, Errors.NEEDS_ONLINE_PLAYER, "Both players need to be online."); Map m = new LinkedHashMap<>(); diff --git a/src/main/java/com/extantape/houserules/command/HouseRulesCommands.java b/src/main/java/com/extantape/mayor/command/MayorCommands.java similarity index 86% rename from src/main/java/com/extantape/houserules/command/HouseRulesCommands.java rename to src/main/java/com/extantape/mayor/command/MayorCommands.java index 7b33501..a983877 100644 --- a/src/main/java/com/extantape/houserules/command/HouseRulesCommands.java +++ b/src/main/java/com/extantape/mayor/command/MayorCommands.java @@ -1,9 +1,9 @@ -package com.extantape.houserules.command; +package com.extantape.mayor.command; -import com.extantape.houserules.HouseRulesPlugin; -import com.extantape.houserules.rules.Reach; -import com.extantape.houserules.rules.Subject; -import com.extantape.houserules.rules.WordFilter; +import com.extantape.mayor.MayorPlugin; +import com.extantape.mayor.rules.Reach; +import com.extantape.mayor.rules.Subject; +import com.extantape.mayor.rules.WordFilter; import com.extantape.signalbox.Answer; import com.extantape.signalbox.Describe; import com.extantape.signalbox.Request; @@ -25,13 +25,13 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; -/** /circles for players, and /houserules status, reload, check and describe for admins, each with a --json form. */ -public final class HouseRulesCommands { - public static final String CIRCLES = "houserules.circles"; - public static final String CIRCLES_NAMES = "houserules.circles.names"; - private final HouseRulesPlugin plugin; +/** /circles for players, and /mayor status, reload, check and describe for admins, each with a --json form. */ +public final class MayorCommands { + public static final String CIRCLES = "mayor.circles"; + public static final String CIRCLES_NAMES = "mayor.circles.names"; + private final MayorPlugin plugin; - public HouseRulesCommands(HouseRulesPlugin plugin) { + public MayorCommands(MayorPlugin plugin) { this.plugin = plugin; } @@ -42,14 +42,14 @@ public void register(Commands commands) { .build(), "Shows your circles and who can hear you right now."); // One greedy argument, read by Signalbox, so every subcommand also takes --json (format 1). Players without - // houserules.admin don't see the command at all; the console always can. - commands.register(Commands.literal("houserules") - .requires(src -> src.getSender().hasPermission(HouseRulesPlugin.ADMIN)) + // mayor.admin don't see the command at all; the console always can. + commands.register(Commands.literal("mayor") + .requires(src -> src.getSender().hasPermission(MayorPlugin.ADMIN)) .executes(ctx -> status(ctx.getSource().getSender())) .then(Commands.argument("args", StringArgumentType.greedyString()) .suggests((ctx, builder) -> suggest(builder)) .executes(ctx -> run(ctx.getSource().getSender(), ctx.getArgument("args", String.class)))) - .build(), "HouseRules admin: status, reload, check whether one player can reach another, and describe."); + .build(), "Mayor admin: status, reload, check whether one player can reach another, and describe."); } private CompletableFuture suggest(SuggestionsBuilder builder) { @@ -84,18 +84,18 @@ private int run(CommandSender sender, String input) { case "reload" -> reload(sender); case "check" -> { Player from = t.size() == 3 ? online(t.get(1)) : null, to = t.size() == 3 ? online(t.get(2)) : null; - if (t.size() != 3) plugin.tell(sender, Component.text("Usage: /houserules check ", NamedTextColor.RED)); + if (t.size() != 3) plugin.tell(sender, Component.text("Usage: /mayor check ", NamedTextColor.RED)); else if (from == null || to == null) plugin.tell(sender, Component.text("Both players need to be online.", NamedTextColor.RED)); else check(sender, from, to); } case "describe" -> { for (Describe.Command d : Console.described()) { - StringBuilder line = new StringBuilder("/houserules " + d.name()); + StringBuilder line = new StringBuilder("/mayor " + d.name()); for (Describe.Arg a : d.args()) line.append(" <").append(a.name()).append('>'); plugin.tell(sender, Component.text(line.toString(), NamedTextColor.GRAY)); } } - default -> plugin.tell(sender, Component.text("There's no /houserules " + sub + ". Try status, reload, check or describe.", NamedTextColor.RED)); + default -> plugin.tell(sender, Component.text("There's no /mayor " + sub + ". Try status, reload, check or describe.", NamedTextColor.RED)); } return Command.SINGLE_SUCCESS; } @@ -114,7 +114,7 @@ static Player online(String nameOrUuid) { @Override public String version() { return plugin.getPluginMeta().getVersion(); } @Override public Console.Status status() { - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state == null) return new Console.Status(true, List.of(), false, plugin.problems()); return new Console.Status(false, state.settings().reach().circles(), !state.settings().filter().isEmpty(), plugin.problems()); } @@ -126,7 +126,7 @@ static Player online(String nameOrUuid) { @Override public Console.Checked check(String fromArg, String toArg) { Player from = online(fromArg), to = online(toArg); - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (from == null || to == null || state == null) return null; Reach reach = state.settings().reach(); Reach.Why why = reach.why(Subject.of(from), Subject.of(to)); @@ -138,7 +138,7 @@ static Player online(String nameOrUuid) { }; private int circles(Player player) { - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state == null) { plugin.tell(player, "chat-paused"); return Command.SINGLE_SUCCESS; @@ -183,7 +183,7 @@ private int reload(CommandSender sender) { } private int status(CommandSender sender) { - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state == null) plugin.tell(sender, "admin-paused"); else { Reach reach = state.settings().reach(); @@ -196,7 +196,7 @@ private int status(CommandSender sender) { } private void check(CommandSender sender, Player from, Player to) { - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state == null) { plugin.tell(sender, "admin-paused"); return; @@ -216,7 +216,7 @@ private static String reason(Reach reach, Reach.Why why, Player from, Player to) return switch (why) { case RULES_OFF -> "no circles are set up, so everyone reaches everyone"; case SELF -> "that's the same player"; - case BYPASS -> "one of them has houserules.bypass"; + case BYPASS -> "one of them has mayor.bypass"; case SAME_WORLD -> "they're in the same world group, " + reach.group(from.getWorld().getName()); case SHARED_CIRCLE -> "they share the circle " + reach.sharedCircle(a, b); case CIRCLE_ONLY_WORLD -> "they're in a circle-only world and share no circle"; diff --git a/src/main/java/com/extantape/houserules/config/Settings.java b/src/main/java/com/extantape/mayor/config/Settings.java similarity index 82% rename from src/main/java/com/extantape/houserules/config/Settings.java rename to src/main/java/com/extantape/mayor/config/Settings.java index 5656440..ba7f673 100644 --- a/src/main/java/com/extantape/houserules/config/Settings.java +++ b/src/main/java/com/extantape/mayor/config/Settings.java @@ -1,8 +1,8 @@ -package com.extantape.houserules.config; +package com.extantape.mayor.config; -import com.extantape.houserules.rules.CommandRules; -import com.extantape.houserules.rules.Reach; -import com.extantape.houserules.rules.WordFilter; +import com.extantape.mayor.rules.CommandRules; +import com.extantape.mayor.rules.Reach; +import com.extantape.mayor.rules.WordFilter; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/extantape/houserules/config/SettingsLoader.java b/src/main/java/com/extantape/mayor/config/SettingsLoader.java similarity index 96% rename from src/main/java/com/extantape/houserules/config/SettingsLoader.java rename to src/main/java/com/extantape/mayor/config/SettingsLoader.java index b9898d9..a45e7c6 100644 --- a/src/main/java/com/extantape/houserules/config/SettingsLoader.java +++ b/src/main/java/com/extantape/mayor/config/SettingsLoader.java @@ -1,8 +1,8 @@ -package com.extantape.houserules.config; +package com.extantape.mayor.config; -import com.extantape.houserules.rules.CommandRules; -import com.extantape.houserules.rules.Reach; -import com.extantape.houserules.rules.WordFilter; +import com.extantape.mayor.rules.CommandRules; +import com.extantape.mayor.rules.Reach; +import com.extantape.mayor.rules.WordFilter; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; @@ -74,7 +74,7 @@ public Result load(String config, String filterText, String quips) { for (String circle : strings(CONFIG, base, "circles")) { String lower = circle.trim().toLowerCase(Locale.ROOT); if (!NAME.matcher(lower).matches()) { - errors.add(CONFIG + ": circle \"" + circle + "\" can only use letters, numbers, - and _, because it becomes the permission houserules.circle.."); + errors.add(CONFIG + ": circle \"" + circle + "\" can only use letters, numbers, - and _, because it becomes the permission mayor.circle.."); } else if (circles.contains(lower)) { warnings.add(CONFIG + ": circle \"" + lower + "\" is listed twice."); } else circles.add(lower); @@ -129,7 +129,7 @@ public Result load(String config, String filterText, String quips) { ConfigurationSection own = base.getConfigurationSection("messages"); if (own != null) { for (String key : own.getKeys(false)) { - if (!messages.containsKey(key)) warnings.add(CONFIG + ": messages." + key + " isn't a message HouseRules uses."); + if (!messages.containsKey(key)) warnings.add(CONFIG + ": messages." + key + " isn't a message Mayor uses."); else if (!own.isString(key)) errors.add(CONFIG + ": messages." + key + " must be text."); else messages.put(key, own.getString(key)); } @@ -191,7 +191,7 @@ private YamlConfiguration parseDefault(String name) { private void unknownKeys(String file, YamlConfiguration yaml, Set known) { for (String key : yaml.getKeys(false)) { - if (!known.contains(key)) warnings.add(file + ": \"" + key + "\" isn't a setting HouseRules uses, so it's ignored."); + if (!known.contains(key)) warnings.add(file + ": \"" + key + "\" isn't a setting Mayor uses, so it's ignored."); } } diff --git a/src/main/java/com/extantape/houserules/listen/ChatListener.java b/src/main/java/com/extantape/mayor/listen/ChatListener.java similarity index 81% rename from src/main/java/com/extantape/houserules/listen/ChatListener.java rename to src/main/java/com/extantape/mayor/listen/ChatListener.java index 8e3ef01..0d4d5fc 100644 --- a/src/main/java/com/extantape/houserules/listen/ChatListener.java +++ b/src/main/java/com/extantape/mayor/listen/ChatListener.java @@ -1,11 +1,11 @@ -package com.extantape.houserules.listen; +package com.extantape.mayor.listen; -import com.extantape.houserules.HouseRulesPlugin; -import com.extantape.houserules.config.Settings; -import com.extantape.houserules.rules.Reach; -import com.extantape.houserules.rules.Subject; -import com.extantape.houserules.rules.WordFilter; -import com.extantape.houserules.text.Messages; +import com.extantape.mayor.MayorPlugin; +import com.extantape.mayor.config.Settings; +import com.extantape.mayor.rules.Reach; +import com.extantape.mayor.rules.Subject; +import com.extantape.mayor.rules.WordFilter; +import com.extantape.mayor.text.Messages; import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; @@ -18,16 +18,16 @@ /** Public chat goes only to the players the speaker can reach. Runs on the async chat thread and never blocks it. */ public final class ChatListener implements Listener { - private final HouseRulesPlugin plugin; + private final MayorPlugin plugin; - public ChatListener(HouseRulesPlugin plugin) { + public ChatListener(MayorPlugin plugin) { this.plugin = plugin; } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onChat(AsyncChatEvent event) { Player from = event.getPlayer(); - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state == null) { event.setCancelled(true); plugin.tell(from, "chat-paused"); diff --git a/src/main/java/com/extantape/houserules/listen/CommandListener.java b/src/main/java/com/extantape/mayor/listen/CommandListener.java similarity index 83% rename from src/main/java/com/extantape/houserules/listen/CommandListener.java rename to src/main/java/com/extantape/mayor/listen/CommandListener.java index 0b9e5c9..500be95 100644 --- a/src/main/java/com/extantape/houserules/listen/CommandListener.java +++ b/src/main/java/com/extantape/mayor/listen/CommandListener.java @@ -1,9 +1,9 @@ -package com.extantape.houserules.listen; +package com.extantape.mayor.listen; -import com.extantape.houserules.HouseRulesPlugin; -import com.extantape.houserules.rules.CommandRules; -import com.extantape.houserules.rules.Subject; -import com.extantape.houserules.text.Messages; +import com.extantape.mayor.MayorPlugin; +import com.extantape.mayor.rules.CommandRules; +import com.extantape.mayor.rules.Subject; +import com.extantape.mayor.text.Messages; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -18,16 +18,16 @@ * before any plugin sees it, so it covers other plugins' commands and their namespaced forms. */ public final class CommandListener implements Listener { - private final HouseRulesPlugin plugin; + private final MayorPlugin plugin; - public CommandListener(HouseRulesPlugin plugin) { + public CommandListener(MayorPlugin plugin) { this.plugin = plugin; } @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onCommand(PlayerCommandPreprocessEvent event) { Player from = event.getPlayer(); - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); boolean paused = state == null; CommandRules rules = (paused ? plugin.fallback() : state).settings().commands(); CommandRules.Decision decision = rules.decide(Subject.of(from), event.getMessage(), paused, diff --git a/src/main/java/com/extantape/houserules/listen/PlayerListener.java b/src/main/java/com/extantape/mayor/listen/PlayerListener.java similarity index 76% rename from src/main/java/com/extantape/houserules/listen/PlayerListener.java rename to src/main/java/com/extantape/mayor/listen/PlayerListener.java index 8cdf600..f824652 100644 --- a/src/main/java/com/extantape/houserules/listen/PlayerListener.java +++ b/src/main/java/com/extantape/mayor/listen/PlayerListener.java @@ -1,6 +1,6 @@ -package com.extantape.houserules.listen; +package com.extantape.mayor.listen; -import com.extantape.houserules.HouseRulesPlugin; +import com.extantape.mayor.MayorPlugin; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -11,16 +11,16 @@ /** Tells admins about config problems when they join, and forgets a player's reply partner when they leave. */ public final class PlayerListener implements Listener { - private final HouseRulesPlugin plugin; + private final MayorPlugin plugin; - public PlayerListener(HouseRulesPlugin plugin) { + public PlayerListener(MayorPlugin plugin) { this.plugin = plugin; } @EventHandler public void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - if (!player.hasPermission(HouseRulesPlugin.ADMIN)) return; + if (!player.hasPermission(MayorPlugin.ADMIN)) return; if (plugin.paused()) plugin.tell(player, "admin-paused"); else if (!plugin.problems().isEmpty()) plugin.tell(player, "admin-warnings", Map.of("count", String.valueOf(plugin.problems().size()))); } @@ -28,7 +28,7 @@ public void onJoin(PlayerJoinEvent event) { @EventHandler public void onQuit(PlayerQuitEvent event) { plugin.partners().remove(event.getPlayer().getUniqueId()); - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state != null) state.quips().forget(event.getPlayer().getUniqueId()); } } diff --git a/src/main/java/com/extantape/houserules/listen/ShareHook.java b/src/main/java/com/extantape/mayor/listen/ShareHook.java similarity index 60% rename from src/main/java/com/extantape/houserules/listen/ShareHook.java rename to src/main/java/com/extantape/mayor/listen/ShareHook.java index b0a0889..68c7326 100644 --- a/src/main/java/com/extantape/houserules/listen/ShareHook.java +++ b/src/main/java/com/extantape/mayor/listen/ShareHook.java @@ -1,22 +1,21 @@ -package com.extantape.houserules.listen; +package com.extantape.mayor.listen; -import com.extantape.blueminimap.api.CoordinateShareEvent; -import com.extantape.houserules.HouseRulesPlugin; +import com.extantape.bluemapmini.api.CoordinateShareEvent; +import com.extantape.mayor.MayorPlugin; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; /** - * BlueMinimap's /hud share follows the same rules as chat: recipients the sender can't reach are removed, and a sender - * who can't chat where they are sends nothing. BlueMinimap tells the sender who got it. Only loaded when BlueMinimap is - * installed, because this is the only class that names its event. {@link LegacyShareHook} does the same for - * BlueMapHUD 0.1.x, its name before 2026-11-09. + * BlueMapMini's /hud share follows the same rules as chat: recipients the sender can't reach are removed, and a sender + * who can't chat where they are sends nothing. BlueMapMini tells the sender who got it. Only loaded when BlueMapMini is + * installed, because this is the only class that names its event. */ public final class ShareHook implements Listener { - private final HouseRulesPlugin plugin; + private final MayorPlugin plugin; - public ShareHook(HouseRulesPlugin plugin) { + public ShareHook(MayorPlugin plugin) { this.plugin = plugin; } diff --git a/src/main/java/com/extantape/houserules/listen/TextListener.java b/src/main/java/com/extantape/mayor/listen/TextListener.java similarity index 90% rename from src/main/java/com/extantape/houserules/listen/TextListener.java rename to src/main/java/com/extantape/mayor/listen/TextListener.java index ebac5e0..b785c73 100644 --- a/src/main/java/com/extantape/houserules/listen/TextListener.java +++ b/src/main/java/com/extantape/mayor/listen/TextListener.java @@ -1,9 +1,9 @@ -package com.extantape.houserules.listen; +package com.extantape.mayor.listen; -import com.extantape.houserules.HouseRulesPlugin; -import com.extantape.houserules.config.Settings; -import com.extantape.houserules.rules.Subject; -import com.extantape.houserules.rules.WordFilter; +import com.extantape.mayor.MayorPlugin; +import com.extantape.mayor.config.Settings; +import com.extantape.mayor.rules.Subject; +import com.extantape.mayor.rules.WordFilter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.entity.Player; @@ -31,10 +31,10 @@ public final class TextListener implements Listener { private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText(); /** The anvil updates on every key press, so its note is shown at most this often. */ private static final long ANVIL_NOTE_MILLIS = 5000; - private final HouseRulesPlugin plugin; + private final MayorPlugin plugin; private final Map anvilNotes = new ConcurrentHashMap<>(); - public TextListener(HouseRulesPlugin plugin) { + public TextListener(MayorPlugin plugin) { this.plugin = plugin; } @@ -80,7 +80,7 @@ public void onAnvil(PrepareAnvilEvent event) { /** The message key explaining why this text can't be written, or null if it's fine. */ private String refusal(Player player, List text, java.util.function.Predicate check, String filteredKey) { - HouseRulesPlugin.State state = plugin.state(); + MayorPlugin.State state = plugin.state(); if (state == null) return "writing-paused"; Settings settings = state.settings(); if (!settings.reach().maySpeak(Subject.of(player))) return "writing-closed-here"; diff --git a/src/main/java/com/extantape/houserules/rules/CommandRules.java b/src/main/java/com/extantape/mayor/rules/CommandRules.java similarity index 97% rename from src/main/java/com/extantape/houserules/rules/CommandRules.java rename to src/main/java/com/extantape/mayor/rules/CommandRules.java index c3f77ec..132cc5e 100644 --- a/src/main/java/com/extantape/houserules/rules/CommandRules.java +++ b/src/main/java/com/extantape/mayor/rules/CommandRules.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import java.util.Arrays; import java.util.List; @@ -10,7 +10,7 @@ /** * Decides what happens to a command a player types: blocked, a private message to check, a reply to check, or - * nothing to do with HouseRules. Labels match in any letter case, and a label without a namespace also matches its + * nothing to do with Mayor. Labels match in any letter case, and a label without a namespace also matches its * namespaced forms: "msg" covers "minecraft:msg" and "other:msg". A label with a namespace matches only itself. */ public final class CommandRules { diff --git a/src/main/java/com/extantape/houserules/rules/Reach.java b/src/main/java/com/extantape/mayor/rules/Reach.java similarity index 94% rename from src/main/java/com/extantape/houserules/rules/Reach.java rename to src/main/java/com/extantape/mayor/rules/Reach.java index 7144189..8124c02 100644 --- a/src/main/java/com/extantape/houserules/rules/Reach.java +++ b/src/main/java/com/extantape/mayor/rules/Reach.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import java.util.ArrayList; import java.util.HashSet; @@ -11,10 +11,10 @@ * world, or if they share a circle. With no circles configured, everyone reaches everyone, as in vanilla. */ public final class Reach { - public static final String CIRCLE_PERMISSION = "houserules.circle."; - public static final String BYPASS = "houserules.bypass"; + public static final String CIRCLE_PERMISSION = "mayor.circle."; + public static final String BYPASS = "mayor.bypass"; - /** Why a player can or can't reach another, for /houserules check. */ + /** Why a player can or can't reach another, for /mayor check. */ public enum Why { RULES_OFF(true), SELF(true), BYPASS(true), SAME_WORLD(true), SHARED_CIRCLE(true), CIRCLE_ONLY_WORLD(false), NOT_SHARED(false); @@ -32,7 +32,7 @@ public enum Why { private final Set circleOnly = new HashSet<>(); /** - * @param circles circle names; each is the permission {@code houserules.circle.} + * @param circles circle names; each is the permission {@code mayor.circle.} * @param circleOnlyWorlds worlds where only a shared circle counts * @param groupOf world name to world group name, for worlds listed under world-groups * @param suffixes suffixes that join a world to its base world, such as {@code _nether} diff --git a/src/main/java/com/extantape/houserules/rules/Subject.java b/src/main/java/com/extantape/mayor/rules/Subject.java similarity index 96% rename from src/main/java/com/extantape/houserules/rules/Subject.java rename to src/main/java/com/extantape/mayor/rules/Subject.java index 63b45d1..b2ef5f0 100644 --- a/src/main/java/com/extantape/houserules/rules/Subject.java +++ b/src/main/java/com/extantape/mayor/rules/Subject.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import org.bukkit.entity.Player; diff --git a/src/main/java/com/extantape/houserules/rules/WordFilter.java b/src/main/java/com/extantape/mayor/rules/WordFilter.java similarity index 97% rename from src/main/java/com/extantape/houserules/rules/WordFilter.java rename to src/main/java/com/extantape/mayor/rules/WordFilter.java index fd52df3..752320f 100644 --- a/src/main/java/com/extantape/houserules/rules/WordFilter.java +++ b/src/main/java/com/extantape/mayor/rules/WordFilter.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import java.text.Normalizer; import java.util.ArrayList; @@ -15,7 +15,7 @@ */ public final class WordFilter { /** Players with this permission aren't filtered anywhere. Off for everyone by default, ops included. */ - public static final String BYPASS = "houserules.filter.bypass"; + public static final String BYPASS = "mayor.filter.bypass"; public static final WordFilter NONE = new WordFilter(List.of(), Set.of()); private static final Pattern SPLIT = Pattern.compile("[^\\p{L}\\p{N}@$]+"); private static final Pattern MARKS = Pattern.compile("\\p{M}+"); diff --git a/src/main/java/com/extantape/houserules/text/Messages.java b/src/main/java/com/extantape/mayor/text/Messages.java similarity index 97% rename from src/main/java/com/extantape/houserules/text/Messages.java rename to src/main/java/com/extantape/mayor/text/Messages.java index c6f9a55..d5ce80f 100644 --- a/src/main/java/com/extantape/houserules/text/Messages.java +++ b/src/main/java/com/extantape/mayor/text/Messages.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.text; +package com.extantape.mayor.text; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; diff --git a/src/main/java/com/extantape/houserules/text/Quips.java b/src/main/java/com/extantape/mayor/text/Quips.java similarity index 98% rename from src/main/java/com/extantape/houserules/text/Quips.java rename to src/main/java/com/extantape/mayor/text/Quips.java index b80ec37..76ea2f5 100644 --- a/src/main/java/com/extantape/houserules/text/Quips.java +++ b/src/main/java/com/extantape/mayor/text/Quips.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.text; +package com.extantape.mayor.text; import java.util.ArrayDeque; import java.util.ArrayList; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b4789ab..39c6e72 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,10 +1,10 @@ -# HouseRules settings. After editing, run /houserules reload. +# Mayor settings. After editing, run /mayor reload. # Messages use MiniMessage: https://docs.advntr.dev/minimessage/format.html config-version: 1 # Chat circles. Players who share a circle hear each other from any world. -# A player is in a circle if they have the permission houserules.circle., and can be in more than one. -# With no circles listed, HouseRules doesn't limit chat at all: everyone hears everyone, as in vanilla. +# A player is in a circle if they have the permission mayor.circle., and can be in more than one. +# With no circles listed, Mayor doesn't limit chat at all: everyone hears everyone, as in vanilla. # For example: # circles: # - family @@ -35,14 +35,14 @@ private-message-commands: - whisper - pm - dm -# Commands that reply to the last person you messaged. HouseRules checks again at the moment you reply, +# Commands that reply to the last person you messaged. Mayor checks again at the moment you reply, # because the other player may have moved. reply-commands: - r - reply # Commands that reach everyone on the server regardless of world. While circles are in use they're off for -# players without houserules.bypass, because they'd go past the circles. +# players without mayor.bypass, because they'd go past the circles. broadcast-commands: - me - teammsg @@ -86,12 +86,12 @@ messages: circles-nobody: "Nobody else online can hear you right now." circles-hearers: "People online who can hear you (): " circles-hearers-count: "People online who can hear you: " - admin-paused: "[HouseRules] Chat and private messages are paused because the config couldn't be read. Fix it, then run /houserules reload. The console says what's wrong." - admin-warnings: "[HouseRules] The config has warning(s). Run /houserules status to see them." - admin-reloaded: "[HouseRules] Reloaded." - admin-reload-failed: "[HouseRules] The config has problems, so nothing changed. The old rules are still in place." - admin-reload-failed-paused: "[HouseRules] The config still has problems, so chat stays paused." + admin-paused: "[Mayor] Chat and private messages are paused because the config couldn't be read. Fix it, then run /mayor reload. The console says what's wrong." + admin-warnings: "[Mayor] The config has warning(s). Run /mayor status to see them." + admin-reloaded: "[Mayor] Reloaded." + admin-reload-failed: "[Mayor] The config has problems, so nothing changed. The old rules are still in place." + admin-reload-failed-paused: "[Mayor] The config still has problems, so chat stays paused." admin-problem: " - " - admin-status: "[HouseRules] Circles: . Word filter: ." + admin-status: "[Mayor] Circles: . Word filter: ." admin-check-yes: " can reach : ." admin-check-no: " can't reach : ." diff --git a/src/main/resources/filter.yml b/src/main/resources/filter.yml index 319b1cd..20a6327 100644 --- a/src/main/resources/filter.yml +++ b/src/main/resources/filter.yml @@ -1,4 +1,4 @@ -# HouseRules word filter. After editing, run /houserules reload. +# Mayor word filter. After editing, run /mayor reload. # # How words match: # - Whole words only, so "class", "assume" and "Scunthorpe" are fine. diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 421273c..6efc02d 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,35 +1,30 @@ -name: HouseRules +name: Mayor version: '${version}' -main: com.extantape.houserules.HouseRulesPlugin +main: com.extantape.mayor.MayorPlugin api-version: '26.1' description: Sets who can hear whom in chat and private messages, with chat circles, blocked commands and a word filter. -website: https://github.com/ExtantApe/HouseRules +website: https://github.com/ExtantApe/Mayor # Commands are registered with Brigadier in code, so there is no commands section. dependencies: server: - # Optional. Only so HouseRules can see BlueMinimap's share event class and filter /hud share like chat. - BlueMinimap: - load: BEFORE - required: false - join-classpath: true - # BlueMinimap's name before 2026-11-09. Kept for one release. - BlueMapHUD: + # Optional. Only so Mayor can see BlueMapMini's share event class and filter /hud share like chat. + BlueMapMini: load: BEFORE required: false join-classpath: true permissions: - houserules.bypass: + mayor.bypass: description: Reach, and be reached by, everyone, for moderators. It doesn't show anyone else's chat. default: false - houserules.filter.bypass: + mayor.filter.bypass: description: Skip the word filter in chat, private messages, signs, books and item names, for adult groups. default: false - houserules.circles: + mayor.circles: description: Use /circles to see your circles and who can hear you. default: true - houserules.circles.names: + mayor.circles.names: description: See names, not just a count, in /circles. default: true - houserules.admin: - description: Use /houserules reload, status and check, and hear about config problems on joining. + mayor.admin: + description: Use /mayor reload, status and check, and hear about config problems on joining. default: op diff --git a/src/main/resources/quips.yml b/src/main/resources/quips.yml index 8a8b194..2cd3615 100644 --- a/src/main/resources/quips.yml +++ b/src/main/resources/quips.yml @@ -1,6 +1,6 @@ # Notes for players who chat when nobody online can hear them. One is picked at a time, in a shuffled # order, and a player sees every note before any comes round again. Keep them light-hearted, never mean. -# MiniMessage formatting works. After editing, run /houserules reload. How often is set in config.yml. +# MiniMessage formatting works. After editing, run /mayor reload. How often is set in config.yml. notes: - "Your words float off into the void. The void says thanks." - "A passing cloud heard that. Sadly, clouds don't type back." diff --git a/src/test/java/com/extantape/houserules/PluginYmlTest.java b/src/test/java/com/extantape/mayor/PluginYmlTest.java similarity index 88% rename from src/test/java/com/extantape/houserules/PluginYmlTest.java rename to src/test/java/com/extantape/mayor/PluginYmlTest.java index b6a270a..ec2064e 100644 --- a/src/test/java/com/extantape/houserules/PluginYmlTest.java +++ b/src/test/java/com/extantape/mayor/PluginYmlTest.java @@ -1,7 +1,7 @@ -package com.extantape.houserules; +package com.extantape.mayor; -import com.extantape.houserules.rules.Reach; -import com.extantape.houserules.rules.WordFilter; +import com.extantape.mayor.rules.Reach; +import com.extantape.mayor.rules.WordFilter; import org.bukkit.configuration.file.YamlConfiguration; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/extantape/houserules/command/ConsoleTest.java b/src/test/java/com/extantape/mayor/command/ConsoleTest.java similarity index 95% rename from src/test/java/com/extantape/houserules/command/ConsoleTest.java rename to src/test/java/com/extantape/mayor/command/ConsoleTest.java index 99249f3..d8c1b13 100644 --- a/src/test/java/com/extantape/houserules/command/ConsoleTest.java +++ b/src/test/java/com/extantape/mayor/command/ConsoleTest.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.command; +package com.extantape.mayor.command; import com.extantape.signalbox.Request; import org.junit.jupiter.api.Test; @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.*; -/** The --json answers for /houserules, with a stand-in for the server. */ +/** The --json answers for /mayor, with a stand-in for the server. */ class ConsoleTest { static final Console.Who ANN = new Console.Who("Ann", "00000000-0000-0000-0000-00000000000a"); static final Console.Who BEN = new Console.Who("Ben", "00000000-0000-0000-0000-00000000000b"); @@ -30,7 +30,7 @@ class ConsoleTest { String a = ask("describe --json d1"); assertTrue(a.startsWith("ea-result d1 {"), a); for (String c : List.of("\"status\"", "\"reload\"", "\"check\"", "\"describe\"")) assertTrue(a.contains(c), c + " in " + a); - assertTrue(a.contains("\"format\":1") && a.contains("\"plugin\":\"HouseRules\"") && a.contains("\"version\":\"0.3.0\""), a); + assertTrue(a.contains("\"format\":1") && a.contains("\"plugin\":\"Mayor\"") && a.contains("\"version\":\"0.3.0\""), a); assertTrue(a.contains("{\"name\":\"from\",\"type\":\"player\"}"), a); } diff --git a/src/test/java/com/extantape/houserules/config/SettingsLoaderTest.java b/src/test/java/com/extantape/mayor/config/SettingsLoaderTest.java similarity index 97% rename from src/test/java/com/extantape/houserules/config/SettingsLoaderTest.java rename to src/test/java/com/extantape/mayor/config/SettingsLoaderTest.java index 006621a..9b8af6c 100644 --- a/src/test/java/com/extantape/houserules/config/SettingsLoaderTest.java +++ b/src/test/java/com/extantape/mayor/config/SettingsLoaderTest.java @@ -1,6 +1,6 @@ -package com.extantape.houserules.config; +package com.extantape.mayor.config; -import com.extantape.houserules.rules.Reach; +import com.extantape.mayor.rules.Reach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/com/extantape/houserules/rules/CommandRulesTest.java b/src/test/java/com/extantape/mayor/rules/CommandRulesTest.java similarity index 97% rename from src/test/java/com/extantape/houserules/rules/CommandRulesTest.java rename to src/test/java/com/extantape/mayor/rules/CommandRulesTest.java index e2a60f0..630f3c7 100644 --- a/src/test/java/com/extantape/houserules/rules/CommandRulesTest.java +++ b/src/test/java/com/extantape/mayor/rules/CommandRulesTest.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import org.junit.jupiter.api.Test; @@ -9,8 +9,8 @@ import java.util.Set; import java.util.UUID; -import static com.extantape.houserules.rules.Players.circle; -import static com.extantape.houserules.rules.Players.player; +import static com.extantape.mayor.rules.Players.circle; +import static com.extantape.mayor.rules.Players.player; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/extantape/houserules/rules/Players.java b/src/test/java/com/extantape/mayor/rules/Players.java similarity index 92% rename from src/test/java/com/extantape/houserules/rules/Players.java rename to src/test/java/com/extantape/mayor/rules/Players.java index 148c018..2f4b6a1 100644 --- a/src/test/java/com/extantape/houserules/rules/Players.java +++ b/src/test/java/com/extantape/mayor/rules/Players.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import java.util.Set; import java.util.UUID; diff --git a/src/test/java/com/extantape/houserules/rules/ReachTest.java b/src/test/java/com/extantape/mayor/rules/ReachTest.java similarity index 96% rename from src/test/java/com/extantape/houserules/rules/ReachTest.java rename to src/test/java/com/extantape/mayor/rules/ReachTest.java index 856abd3..e11399b 100644 --- a/src/test/java/com/extantape/houserules/rules/ReachTest.java +++ b/src/test/java/com/extantape/mayor/rules/ReachTest.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; import org.junit.jupiter.api.Test; @@ -6,8 +6,8 @@ import java.util.Map; import java.util.Set; -import static com.extantape.houserules.rules.Players.circle; -import static com.extantape.houserules.rules.Players.player; +import static com.extantape.mayor.rules.Players.circle; +import static com.extantape.mayor.rules.Players.player; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/com/extantape/houserules/rules/WordFilterTest.java b/src/test/java/com/extantape/mayor/rules/WordFilterTest.java similarity index 96% rename from src/test/java/com/extantape/houserules/rules/WordFilterTest.java rename to src/test/java/com/extantape/mayor/rules/WordFilterTest.java index 48c3cb2..a696bf8 100644 --- a/src/test/java/com/extantape/houserules/rules/WordFilterTest.java +++ b/src/test/java/com/extantape/mayor/rules/WordFilterTest.java @@ -1,6 +1,6 @@ -package com.extantape.houserules.rules; +package com.extantape.mayor.rules; -import com.extantape.houserules.config.SettingsLoader; +import com.extantape.mayor.config.SettingsLoader; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/src/test/java/com/extantape/houserules/text/QuipsTest.java b/src/test/java/com/extantape/mayor/text/QuipsTest.java similarity index 97% rename from src/test/java/com/extantape/houserules/text/QuipsTest.java rename to src/test/java/com/extantape/mayor/text/QuipsTest.java index 5985459..3cf559c 100644 --- a/src/test/java/com/extantape/houserules/text/QuipsTest.java +++ b/src/test/java/com/extantape/mayor/text/QuipsTest.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.text; +package com.extantape.mayor.text; import org.junit.jupiter.api.Test; diff --git a/test/live/README.md b/test/live/README.md index f675c60..125d974 100644 --- a/test/live/README.md +++ b/test/live/README.md @@ -1,6 +1,6 @@ # Live tests -These run HouseRules on a real Paper server and drive it with protocol bots. The unit tests in `src/test` cover the rules on their own; these cover everything that needs a server: chat, commands, permissions, reloads and load. +These run Mayor on a real Paper server and drive it with protocol bots. The unit tests in `src/test` cover the rules on their own; these cover everything that needs a server: chat, commands, permissions, reloads and load. `run.sh` builds one throwaway server on 127.0.0.1, runs the whole suite, and deletes the server afterwards, including when a test fails. Only one server runs at a time. The worlds are small, flat and made up, and so are the players and circles. @@ -8,7 +8,7 @@ These run HouseRules on a real Paper server and drive it with protocol bots. The - **Real:** Paper, LuckPerms, the plugin jar, Paper's chat and command handling, and private messages through vanilla `/msg`, `/tell` and `/w`. - **A bot, not the game:** the players are [mineflayer](https://github.com/PrismarineJS/mineflayer) bots, which speak the 1.21.11 protocol and join 26.x through ViaVersion and ViaBackwards. They read chat as plain text, so colours and how the messages look aren't checked. -- **Mocked:** BlueMinimap's share. A stand-in plugin fires a copy of its `CoordinateShareEvent`: by default one named BlueMinimap (`fakeminimap/`, the new event), or with `HR_SHARE_PLUGIN=old` one named BlueMapHUD (`fakehud/`, the event under its 0.1.x name). The new copy matches the event in BlueMinimap 0.2.0, compared with `javap` on 2026-11-09. The real BlueMinimap isn't run here. A separate check with the real BlueMapHUD 0.1.0 and HouseRules 0.1.0 jars passed on Paper 26.2 on 2026-11-08 (11 of 11, bots, permissions set without LuckPerms); that test isn't in this repo. +- **Mocked:** BlueMapMini's share. A stand-in plugin named BlueMapMini (`fakeminimap/`) fires a copy of its `CoordinateShareEvent`. - **Not covered:** signs and anvil renames, which the bot can't do reliably. They go through the same check as books, which is covered. Paper has no `/r` of its own, so a reply that passes is seen as reaching the server, not as delivered. ## Setting up @@ -25,13 +25,13 @@ test/live/fetch.sh 26.2 26.3 ## Running -Running a server means accepting the [Minecraft EULA](https://aka.ms/MinecraftEULA), so `run.sh` won't start until you set `HR_ACCEPT_EULA=1`. +Running a server means accepting the [Minecraft EULA](https://aka.ms/MinecraftEULA), so `run.sh` won't start until you set `MAYOR_ACCEPT_EULA=1`. ```bash -HR_ACCEPT_EULA=1 test/live/run.sh 26.3 +MAYOR_ACCEPT_EULA=1 test/live/run.sh 26.3 ``` -It takes about 4 minutes. The summary lists failures and skipped checks. The full output is in `test/live/work/server-.log`, and the server console in `server--console.log`. The game port is 25619; change it with `HR_PORT`. +It takes about 4 minutes. The summary lists failures and skipped checks. The full output is in `test/live/work/server-.log`, and the server console in `server--console.log`. The game port is 25619; change it with `MAYOR_PORT`. ## What the suite checks @@ -39,12 +39,12 @@ It takes about 4 minutes. The summary lists failures and skipped checks. The ful | --- | --- | | No config | A fresh install changes nothing: everyone hears everyone. | | Reach matrix | Eight players in two circles, with no circle, and with bypass, across two world groups (one by suffix, one listed), their Nether and End, and a circle-only world. Public chat from each, and 336 private messages over six commands, match an independent model of the rule. | -| `/circles` | Lists your circles and exactly who can hear you, a count without `houserules.circles.names`, and an explanation in a circle-only world. | +| `/circles` | Lists your circles and exactly who can hear you, a count without `mayor.circles.names`, and an explanation in a circle-only world. | | Commands | Blocked commands in plain, namespaced and mixed-case forms, unlisted ones passing, `/me` off except with bypass. | | Replies | `/r` with nobody to reply to, and after the other player moves out of reach. | | Word filter | Chat and private messages with a blocked word aren't delivered; "Scunthorpe has a classic assassin" is. | | Filter bypass | Granted with LuckPerms, a player's chat and private messages aren't filtered while another player's still are; removed, they're filtered again straight away, with no relog. | -| Books | A clean edit is kept; a filtered one, or one in a circle-only world with no circle, is refused with an explanation. A filtered word is kept for a player with `houserules.filter.bypass`. | +| Books | A clean edit is kept; a filtered one, or one in a circle-only world with no circle, is refused with an explanation. A filtered word is kept for a player with `mayor.filter.bypass`. | | Nobody heard you | A note from `quips.yml`, a different one next time, and none when someone heard. | | Share (mocked) | Recipients filtered exactly as chat, and cancelled for a no-circle sender in a circle-only world. | | LuckPerms | A circle granted or removed takes effect with no relog. | @@ -53,4 +53,4 @@ It takes about 4 minutes. The summary lists failures and skipped checks. The ful ## The test-only plugins -`helper/` makes worlds, moves players, gives items and reports TPS, with one console command, `/hrt`. It also stops test players taking damage, since a bot that spawns inside a block in a made-up Nether would otherwise die and respawn in the wrong world. `fakehud/` is the BlueMapHUD stand-in. Both are built by `./gradlew build` and never shipped. Never install either on a real server. +`helper/` makes worlds, moves players, gives items and reports TPS, with one console command, `/mt`. It also stops test players taking damage, since a bot that spawns inside a block in a made-up Nether would otherwise die and respawn in the wrong world. `fakeminimap/` is the BlueMapMini stand-in. Both are built by `./gradlew build` and never shipped. Never install either on a real server. diff --git a/test/live/fakehud/build.gradle.kts b/test/live/fakehud/build.gradle.kts deleted file mode 100644 index c8ac3a0..0000000 --- a/test/live/fakehud/build.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -// Test only, for the live tests. Never shipped. -plugins { - java -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(25)) -} - -repositories { - mavenCentral() - maven("https://repo.papermc.io/repository/maven-public/") -} - -dependencies { - compileOnly(rootProject.extra["paperApi"] as String) -} - -// Ships the same copy of the share event that HouseRules compiles against. -sourceSets.main { - java.srcDir(rootProject.file("test/stub/src/main/java")) -} - -tasks.jar { - archiveBaseName.set("LiveTestFakeHud") -} diff --git a/test/live/fakehud/src/main/java/com/extantape/houserules/livetest/fakehud/FakeHud.java b/test/live/fakehud/src/main/java/com/extantape/houserules/livetest/fakehud/FakeHud.java deleted file mode 100644 index 0a9df37..0000000 --- a/test/live/fakehud/src/main/java/com/extantape/houserules/livetest/fakehud/FakeHud.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.extantape.houserules.livetest.fakehud; - -import com.extantape.bluemaphud.api.CoordinateShareEvent; -import com.mojang.brigadier.arguments.StringArgumentType; -import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.TreeSet; - -/** - * Test only. Console command "fakeshare " fires CoordinateShareEvent the way BlueMapHUD does, with every - * other online player as a candidate, then logs "FAKESHARE cancelled= to=". - */ -public final class FakeHud extends JavaPlugin { - @Override - public void onEnable() { - getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(Commands.literal("fakeshare") - .requires(src -> !(src.getSender() instanceof Player)) - .then(Commands.argument("player", StringArgumentType.word()).executes(ctx -> { - Player sender = Bukkit.getPlayerExact(StringArgumentType.getString(ctx, "player")); - if (sender == null) { - getLogger().info("FAKESHARE FAILED no such player"); - return 0; - } - Set recipients = new LinkedHashSet<>(Bukkit.getOnlinePlayers()); - recipients.remove(sender); - Location at = sender.getLocation(); - CoordinateShareEvent share = new CoordinateShareEvent(sender, sender.getWorld(), at.getBlockX(), at.getBlockY(), at.getBlockZ(), recipients); - Bukkit.getPluginManager().callEvent(share); - Set names = new TreeSet<>(); - for (Player p : share.getRecipients()) names.add(p.getName()); - getLogger().info("FAKESHARE " + sender.getName() + " cancelled=" + share.isCancelled() + " listeners=" + CoordinateShareEvent.hasListeners() + " to=" + String.join(",", names)); - return 1; - })) - .build())); - } -} diff --git a/test/live/fakehud/src/main/resources/paper-plugin.yml b/test/live/fakehud/src/main/resources/paper-plugin.yml deleted file mode 100644 index b3adeca..0000000 --- a/test/live/fakehud/src/main/resources/paper-plugin.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Test only. A stand-in for BlueMapHUD 0.1.x (BlueMinimap's old name) that fires its old share event. -# Named BlueMapHUD so HouseRules finds it exactly as it would find the real plugin. Never install on a real server. -name: BlueMapHUD -version: '0.0.0-test' -main: com.extantape.houserules.livetest.fakehud.FakeHud -api-version: '26.1' diff --git a/test/live/fakeminimap/build.gradle.kts b/test/live/fakeminimap/build.gradle.kts index 0ec4759..8433c1d 100644 --- a/test/live/fakeminimap/build.gradle.kts +++ b/test/live/fakeminimap/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { compileOnly(rootProject.extra["paperApi"] as String) } -// Ships the same copy of the share event that HouseRules compiles against. +// Ships the same copy of the share event that Mayor compiles against. sourceSets.main { java.srcDir(rootProject.file("test/stub/src/main/java")) } diff --git a/test/live/fakeminimap/src/main/java/com/extantape/houserules/livetest/fakeminimap/FakeMinimap.java b/test/live/fakeminimap/src/main/java/com/extantape/mayor/livetest/fakeminimap/FakeMinimap.java similarity index 92% rename from test/live/fakeminimap/src/main/java/com/extantape/houserules/livetest/fakeminimap/FakeMinimap.java rename to test/live/fakeminimap/src/main/java/com/extantape/mayor/livetest/fakeminimap/FakeMinimap.java index 1dc62f5..4136e9f 100644 --- a/test/live/fakeminimap/src/main/java/com/extantape/houserules/livetest/fakeminimap/FakeMinimap.java +++ b/test/live/fakeminimap/src/main/java/com/extantape/mayor/livetest/fakeminimap/FakeMinimap.java @@ -1,6 +1,6 @@ -package com.extantape.houserules.livetest.fakeminimap; +package com.extantape.mayor.livetest.fakeminimap; -import com.extantape.blueminimap.api.CoordinateShareEvent; +import com.extantape.bluemapmini.api.CoordinateShareEvent; import com.mojang.brigadier.arguments.StringArgumentType; import io.papermc.paper.command.brigadier.Commands; import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; @@ -14,7 +14,7 @@ import java.util.TreeSet; /** - * Test only. Console command "fakeshare " fires CoordinateShareEvent the way BlueMinimap does, with every + * Test only. Console command "fakeshare " fires CoordinateShareEvent the way BlueMapMini does, with every * other online player as a candidate, then logs "FAKESHARE cancelled= to=". */ public final class FakeMinimap extends JavaPlugin { diff --git a/test/live/fakeminimap/src/main/resources/paper-plugin.yml b/test/live/fakeminimap/src/main/resources/paper-plugin.yml index 3192903..a5b2192 100644 --- a/test/live/fakeminimap/src/main/resources/paper-plugin.yml +++ b/test/live/fakeminimap/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ -# Test only. A stand-in for BlueMinimap that fires its share event, the same way fakehud does for its old name. -# Named BlueMinimap so HouseRules finds it exactly as it would find the real plugin. Never install on a real server. -name: BlueMinimap +# Test only. A stand-in for BlueMapMini that fires its share event. +# Named BlueMapMini so Mayor finds it exactly as it would find the real plugin. Never install on a real server. +name: BlueMapMini version: '0.0.0-test' -main: com.extantape.houserules.livetest.fakeminimap.FakeMinimap +main: com.extantape.mayor.livetest.fakeminimap.FakeMinimap api-version: '26.1' diff --git a/test/live/fetch.sh b/test/live/fetch.sh index 7bf0f3e..290a36f 100755 --- a/test/live/fetch.sh +++ b/test/live/fetch.sh @@ -3,9 +3,9 @@ # Usage: test/live/fetch.sh ... e.g. test/live/fetch.sh 26.2 26.3 set -euo pipefail HERE=$(cd "$(dirname "$0")" && pwd) -WORK=${HR_LIVE_DIR:-$HERE/work} +WORK=${MAYOR_LIVE_DIR:-$HERE/work} mkdir -p "$WORK/downloads" -UA="houserules-live-tests" +UA="mayor-live-tests" get() { # url, file, algorithm, expected hash local url=$1 file=$2 algo=$3 want=$4 got diff --git a/test/live/helper/src/main/java/com/extantape/houserules/livetest/LiveTestHelper.java b/test/live/helper/src/main/java/com/extantape/mayor/livetest/LiveTestHelper.java similarity index 90% rename from test/live/helper/src/main/java/com/extantape/houserules/livetest/LiveTestHelper.java rename to test/live/helper/src/main/java/com/extantape/mayor/livetest/LiveTestHelper.java index 1280156..262b951 100644 --- a/test/live/helper/src/main/java/com/extantape/houserules/livetest/LiveTestHelper.java +++ b/test/live/helper/src/main/java/com/extantape/mayor/livetest/LiveTestHelper.java @@ -1,4 +1,4 @@ -package com.extantape.houserules.livetest; +package com.extantape.mayor.livetest; import com.mojang.brigadier.arguments.StringArgumentType; import io.papermc.paper.command.brigadier.Commands; @@ -17,12 +17,12 @@ import java.util.Locale; /** - * Test only. One console command, /hrt: - * hrt world makes a small flat world (a name ending in _nether or _the_end gets that environment) - * hrt tp moves a player to a world's spawn - * hrt tps prints "HRT tps <1m> mspt " - * hrt worlds prints "HRT worlds " - * hrt give puts one item in the player's main hand + * Test only. One console command, /mt: + * mt world makes a small flat world (a name ending in _nether or _the_end gets that environment) + * mt tp moves a player to a world's spawn + * mt tps prints "HRT tps <1m> mspt " + * mt worlds prints "HRT worlds " + * mt give puts one item in the player's main hand */ public final class LiveTestHelper extends JavaPlugin implements Listener { /** Bots stand still in made-up worlds, so a death only means a bad spawn point. Keep them alive, and say if one dies anyway. */ @@ -39,7 +39,7 @@ public void onDeath(PlayerDeathEvent event) { @Override public void onEnable() { getServer().getPluginManager().registerEvents(this, this); - getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(Commands.literal("hrt") + getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(Commands.literal("mt") .requires(src -> src.getSender().isOp() || !(src.getSender() instanceof Player)) .then(Commands.literal("world").then(Commands.argument("name", StringArgumentType.word()).executes(ctx -> { String name = StringArgumentType.getString(ctx, "name"); diff --git a/test/live/helper/src/main/resources/paper-plugin.yml b/test/live/helper/src/main/resources/paper-plugin.yml index 3378f66..806f9bb 100644 --- a/test/live/helper/src/main/resources/paper-plugin.yml +++ b/test/live/helper/src/main/resources/paper-plugin.yml @@ -1,5 +1,5 @@ -name: HouseRulesLiveTest +name: MayorLiveTest version: '1' -main: com.extantape.houserules.livetest.LiveTestHelper +main: com.extantape.mayor.livetest.LiveTestHelper api-version: '26.1' -description: Test only. Makes small worlds, moves players and reports tick times for the HouseRules live tests. Never install on a real server. +description: Test only. Makes small worlds, moves players and reports tick times for the Mayor live tests. Never install on a real server. diff --git a/test/live/lib.js b/test/live/lib.js index e7e6f83..430b5f2 100644 --- a/test/live/lib.js +++ b/test/live/lib.js @@ -1,7 +1,7 @@ -// Live test kit for HouseRules: server control and protocol bots. See README.md in this folder. +// Live test kit for Mayor: server control and protocol bots. See README.md in this folder. const path = require('path'), fs = require('fs'), cp = require('child_process'); const mineflayer = require('mineflayer'); -const ROOT = path.resolve(process.env.HR_LIVE_DIR || path.join(__dirname, 'work')); +const ROOT = path.resolve(process.env.MAYOR_LIVE_DIR || path.join(__dirname, 'work')); const JAVA = process.env.JAVA || (process.env.JAVA_HOME ? path.join(process.env.JAVA_HOME, 'bin', 'java') : 'java'); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); @@ -57,10 +57,10 @@ class Server { await sleep(500); } errors(from = 0) { - return this.log.slice(from).split('\n').filter((l) => /\bERROR\b|Exception|\bat com\.extantape|WARN\]: \[HouseRules\]/.test(l) && !/Advanced terminal/.test(l)); + return this.log.slice(from).split('\n').filter((l) => /\bERROR\b|Exception|\bat com\.extantape|WARN\]: \[Mayor\]/.test(l) && !/Advanced terminal/.test(l)); } path(...p) { return path.join(this.dir, ...p); } - writeConfig(name, text) { fs.writeFileSync(this.path('plugins/HouseRules', name), text); } + writeConfig(name, text) { fs.writeFileSync(this.path('plugins/Mayor', name), text); } } // ---- Bot ---- diff --git a/test/live/package.json b/test/live/package.json index 3d3b613..f5ce734 100644 --- a/test/live/package.json +++ b/test/live/package.json @@ -1,7 +1,7 @@ { - "name": "houserules-live-tests", + "name": "mayor-live-tests", "private": true, - "description": "Live tests for HouseRules: throwaway Paper servers driven by protocol bots.", + "description": "Live tests for Mayor: throwaway Paper servers driven by protocol bots.", "license": "MIT", "type": "commonjs", "engines": { diff --git a/test/live/run.sh b/test/live/run.sh index 302d238..05d343c 100755 --- a/test/live/run.sh +++ b/test/live/run.sh @@ -1,36 +1,30 @@ #!/usr/bin/env bash -# Builds one throwaway Paper server on 127.0.0.1, runs the HouseRules acceptance tests, and deletes it, even when +# Builds one throwaway Paper server on 127.0.0.1, runs the Mayor acceptance tests, and deletes it, even when # a test fails. See README.md in this folder. -# Usage: HR_ACCEPT_EULA=1 test/live/run.sh +# Usage: MAYOR_ACCEPT_EULA=1 test/live/run.sh set -euo pipefail HERE=$(cd "$(dirname "$0")" && pwd) REPO=$(cd "$HERE/../.." && pwd) -WORK=$(mkdir -p "${HR_LIVE_DIR:-$HERE/work}" && cd "${HR_LIVE_DIR:-$HERE/work}" && pwd) -PORT=${HR_PORT:-25619} +WORK=$(mkdir -p "${MAYOR_LIVE_DIR:-$HERE/work}" && cd "${MAYOR_LIVE_DIR:-$HERE/work}" && pwd) +PORT=${MAYOR_PORT:-25619} VERSION=${1:?Usage: run.sh } -if [ "${HR_ACCEPT_EULA:-}" != "1" ]; then +if [ "${MAYOR_ACCEPT_EULA:-}" != "1" ]; then echo "This starts a Minecraft server, which means accepting the Minecraft EULA (https://aka.ms/MinecraftEULA)." - echo "Set HR_ACCEPT_EULA=1 to accept it." + echo "Set MAYOR_ACCEPT_EULA=1 to accept it." exit 2 fi -JAR=$(ls "$REPO"/build/libs/HouseRules-*.jar 2>/dev/null | head -1) +JAR=$(ls "$REPO"/build/libs/Mayor-*.jar 2>/dev/null | head -1) HELPER=$REPO/test/live/helper/build/libs/LiveTestHelper.jar -# Which share stand-in to install: "new" (named BlueMinimap, the default) or "old" (named BlueMapHUD, 0.1.x). -export HR_SHARE_PLUGIN=${HR_SHARE_PLUGIN:-new} -case "$HR_SHARE_PLUGIN" in - new) FAKEHUD=$REPO/test/live/fakeminimap/build/libs/LiveTestFakeMinimap.jar ;; - old) FAKEHUD=$REPO/test/live/fakehud/build/libs/LiveTestFakeHud.jar ;; - *) echo "HR_SHARE_PLUGIN must be new or old"; exit 2 ;; -esac +FAKEMAP=$REPO/test/live/fakeminimap/build/libs/LiveTestFakeMinimap.jar missing=0 for f in "$WORK/paper-$VERSION.jar" "$WORK/ViaVersion.jar" "$WORK/ViaBackwards.jar" "$WORK/LuckPerms.jar"; do [ -f "$f" ] || { echo "Missing $f. Run test/live/fetch.sh $VERSION first."; missing=1; } done -for f in "$JAR" "$HELPER" "$FAKEHUD"; do [ -n "$f" ] && [ -f "$f" ] || { echo "Missing a built jar. Run ./gradlew build first."; missing=1; }; done +for f in "$JAR" "$HELPER" "$FAKEMAP"; do [ -n "$f" ] && [ -f "$f" ] || { echo "Missing a built jar. Run ./gradlew build first."; missing=1; }; done [ -d "$HERE/node_modules/mineflayer" ] || { echo "Run npm ci in test/live first."; missing=1; } [ $missing = 0 ] || exit 2 -if pgrep -f "server-[0-9.]*/.*paper-|paper-.*\.jar nogui" >/dev/null && [ -z "${HR_ALLOW_OTHER:-}" ]; then +if pgrep -f "server-[0-9.]*/.*paper-|paper-.*\.jar nogui" >/dev/null && [ -z "${MAYOR_ALLOW_OTHER:-}" ]; then echo "Note: another Paper server seems to be running on this machine. Port $PORT is used for this one." fi @@ -58,10 +52,10 @@ level-type=minecraft\:flat generate-structures=false view-distance=4 simulation-distance=4 -motd=HouseRules throwaway test server +motd=Mayor throwaway test server PROPS -cp "$WORK/ViaVersion.jar" "$WORK/ViaBackwards.jar" "$WORK/LuckPerms.jar" "$HELPER" "$FAKEHUD" "$SERVER/plugins/" -cp "$JAR" "$SERVER/plugins/HouseRules.jar" +cp "$WORK/ViaVersion.jar" "$WORK/ViaBackwards.jar" "$WORK/LuckPerms.jar" "$HELPER" "$FAKEMAP" "$SERVER/plugins/" +cp "$JAR" "$SERVER/plugins/Mayor.jar" cd "$HERE" LOG=$WORK/$NAME.log echo "== Running the suite (log: $LOG)" diff --git a/test/live/suite.js b/test/live/suite.js index 3f6a7e6..06ad4df 100644 --- a/test/live/suite.js +++ b/test/live/suite.js @@ -1,4 +1,4 @@ -// The HouseRules acceptance tests, on one throwaway server. See README.md in this folder. +// The Mayor acceptance tests, on one throwaway server. See README.md in this folder. // Usage: node suite.js

, relative to the work folder. const fs = require('fs'), path = require('path'); const { sleep, check, skip, section, summary, Server, Bot } = require('./lib.js'); @@ -53,15 +53,15 @@ async function join(names) { for (const n of names) { bots[n] = new Bot(n, +port); await bots[n].spawned; await sleep(250); } } async function place() { - for (const [n, p] of Object.entries(PEOPLE)) await s.run(`hrt tp ${n} ${p.world}`, new RegExp(`HRT tp ${n} ${p.world} true`), 10000); + for (const [n, p] of Object.entries(PEOPLE)) await s.run(`mt tp ${n} ${p.world}`, new RegExp(`HRT tp ${n} ${p.world} true`), 10000); await sleep(1500); } async function worlds() { - for (const w of ['meadow', 'orchard', 'orchard_nether', 'hall', 'island']) await s.run(`hrt world ${w}`, new RegExp(`HRT world (${w}|FAILED)`), 60000); - const m = await s.run('hrt worlds', /HRT worlds (.*)/); + for (const w of ['meadow', 'orchard', 'orchard_nether', 'hall', 'island']) await s.run(`mt world ${w}`, new RegExp(`HRT world (${w}|FAILED)`), 60000); + const m = await s.run('mt worlds', /HRT worlds (.*)/); return m ? m[1].trim().split(',') : []; } -async function reload() { return s.run('houserules reload', /\[HouseRules\] (Reloaded|The config has problems, so nothing changed|The config still has problems)[^\n]*/, 10000); } +async function reload() { return s.run('mayor reload', /\[Mayor\] (Reloaded|The config has problems, so nothing changed|The config still has problems)[^\n]*/, 10000); } async function lp(cmd) { await s.run(`lp ${cmd}`, null); await sleep(400); } /** Each sender says one line; returns who heard it. */ @@ -133,10 +133,10 @@ async function circlesCommand() { const n = bots.N2.mark(); bots.N2.say('/circles'); await sleep(1000); check('/circles in a circle-only world with no circle explains why', bots.N2.heard("You're not in any circles", n) && bots.N2.heard('Chat opens here', n), bots.N2.lines(n)); - await lp('user R1 permission set houserules.circles.names false'); + await lp('user R1 permission set mayor.circles.names false'); const k = bots.R1.mark(); bots.R1.say('/circles'); - check('without houserules.circles.names, only a count', await bots.R1.waitFor(/People online who can hear you: \d+$/, 3000, k), bots.R1.lines(k)); - await lp('user R1 permission unset houserules.circles.names'); + check('without mayor.circles.names, only a count', await bots.R1.waitFor(/People online who can hear you: \d+$/, 3000, k), bots.R1.lines(k)); + await lp('user R1 permission unset mayor.circles.names'); } async function blockedCommands() { @@ -159,15 +159,15 @@ async function blockedCommands() { async function replies() { section('Replies'); - // Paper has no /r of its own, so "passes" means HouseRules let it through to the server. + // Paper has no /r of its own, so "passes" means Mayor let it through to the server. bots.R1.say('/msg N1 start'); await sleep(800); let m = bots.N1.mark(); bots.N1.say('/r still here'); await sleep(1200); check('/r to someone still in reach passes', !bots.N1.heard(/reply wasn't sent/, m), bots.N1.lines(m)); - await s.run('hrt tp R1 meadow', /HRT tp R1 meadow true/); await sleep(800); + await s.run('mt tp R1 meadow', /HRT tp R1 meadow true/); await sleep(800); m = bots.N1.mark(); bots.N1.say('/reply are you there'); check('/reply after the other player moves out of reach is blocked and explained', await bots.N1.waitFor("R1 has moved somewhere they can't hear you", 3000, m), bots.N1.lines(m)); - await s.run(`hrt tp R1 ${PEOPLE.R1.world}`, /HRT tp R1 world true/); await sleep(800); + await s.run(`mt tp R1 ${PEOPLE.R1.world}`, /HRT tp R1 world true/); await sleep(800); } async function filter() { @@ -183,14 +183,14 @@ async function filter() { check('filtered private message is not delivered', !bots.R2.heard('b1tch', marks.R2) && bots.R1.heard("a word we don't use here", m)); section('Filter bypass for adult groups, granted and removed with LuckPerms, no relog'); - await lp('user R1 permission set houserules.filter.bypass true'); + await lp('user R1 permission set mayor.filter.bypass true'); let k = bots.R2.mark(); bots.R1.say('this is sh1t'); await sleep(1200); - check('with houserules.filter.bypass, chat is not filtered', bots.R2.heard('this is sh1t', k), bots.R2.lines(k)); + check('with mayor.filter.bypass, chat is not filtered', bots.R2.heard('this is sh1t', k), bots.R2.lines(k)); k = bots.R2.mark(); bots.R1.say('/msg R2 that was sh1t'); await sleep(1200); - check('with houserules.filter.bypass, private messages are not filtered', bots.R2.heard('that was sh1t', k), bots.R2.lines(k)); + check('with mayor.filter.bypass, private messages are not filtered', bots.R2.heard('that was sh1t', k), bots.R2.lines(k)); k = bots.R1.mark(); const own = bots.R2.mark(); bots.R2.say('this is sh1t too'); await sleep(1200); check('players without it are still filtered', !bots.R1.heard('sh1t too', k) && bots.R2.heard("a word we don't use here", own), bots.R2.lines(own)); - await lp('user R1 permission unset houserules.filter.bypass'); + await lp('user R1 permission unset mayor.filter.bypass'); k = bots.R2.mark(); const told = bots.R1.mark(); bots.R1.say('this is sh1t again'); await sleep(1200); check('removing it filters again straight away', !bots.R2.heard('sh1t again', k) && bots.R1.heard("a word we don't use here", told)); } @@ -199,9 +199,9 @@ async function writing() { section('Signs, books and item names'); // Book: give a writable book and edit it, as the client does. for (const [who, text, wantBlocked, why, bypass] of [['N1', 'a lovely story', false, 'clean text'], ['R2', 'what the fuck', true, 'filtered word'], - ['R1', 'what the fuck', false, 'filtered word with houserules.filter.bypass', true], ['N2', 'hello from the hall', true, 'circle-only world, no circle']]) { - if (bypass) await lp(`user ${who} permission set houserules.filter.bypass true`); - await s.run(`hrt give ${who} writable_book`, /HRT give/); + ['R1', 'what the fuck', false, 'filtered word with mayor.filter.bypass', true], ['N2', 'hello from the hall', true, 'circle-only world, no circle']]) { + if (bypass) await lp(`user ${who} permission set mayor.filter.bypass true`); + await s.run(`mt give ${who} writable_book`, /HRT give/); for (let i = 0; i < 40 && !bots[who].bot.inventory.slots[36]; i++) await sleep(100); const m = bots[who].mark(); try { @@ -212,7 +212,7 @@ async function writing() { const told = bots[who].heard(/book has a word|Writing on signs, books and item names opens/, m); check(`book edit, ${why}: ${wantBlocked ? 'refused and explained' : 'allowed'}`, told === wantBlocked, bots[who].lines(m)); } catch (e) { skip(`book edit, ${why}`, 'the bot could not edit a book: ' + e.message); } - if (bypass) await lp(`user ${who} permission unset houserules.filter.bypass`); + if (bypass) await lp(`user ${who} permission unset mayor.filter.bypass`); } // Signs and anvils need the client's own block placing and window handling, which the bot can't do reliably // through ViaBackwards. The rules for them are the same code path as books. @@ -223,8 +223,8 @@ async function writing() { async function nobodyHeard() { section('Nobody heard you'); // The moderator's bypass would let them hear N3, which is right, so it's off for this part. - await lp('user M1 permission unset houserules.bypass'); - await s.run('hrt tp N3 island', /HRT tp N3 island true/, 10000); await sleep(800); + await lp('user M1 permission unset mayor.bypass'); + await s.run('mt tp N3 island', /HRT tp N3 island true/, 10000); await sleep(800); const m = bots.N3.mark(); bots.N3.say('anyone out there'); await sleep(1500); const note = bots.N3.lines(m).find((l) => NOTES.includes(l)); @@ -236,13 +236,12 @@ async function nobodyHeard() { check('the next note is a different one', !!second && second !== note, `${note} / ${second}`); const r = bots.R1.mark(); bots.R1.say('someone hears this'); await sleep(1200); check('no note when someone heard you', !bots.R1.lines(r).some((l) => NOTES.includes(l))); - await s.run(`hrt tp N3 ${PEOPLE.N3.world}`, /HRT tp N3 meadow true/, 10000); await sleep(800); - await lp('user M1 permission set houserules.bypass true'); + await s.run(`mt tp N3 ${PEOPLE.N3.world}`, /HRT tp N3 meadow true/, 10000); await sleep(800); + await lp('user M1 permission set mayor.bypass true'); } async function share() { - const old = process.env.HR_SHARE_PLUGIN === 'old'; - section(`${old ? 'BlueMapHUD (old name)' : 'BlueMinimap'} share (stand-in plugin, mocked)`); + section('BlueMapMini share (stand-in plugin, mocked)'); let bad = []; for (const a of NAMES) { const m = await s.run(`fakeshare ${a}`, new RegExp(`FAKESHARE ${a} cancelled=(\\w+) listeners=(\\w+) to=(.*)`)); @@ -252,28 +251,28 @@ async function share() { if (want === null ? !cancelled : cancelled || JSON.stringify(to) !== JSON.stringify(want)) bad.push(`${a}: cancelled=${cancelled} to=${to} want ${want ?? 'cancelled'}`); } check('share recipients are filtered exactly as chat, and a no-circle sender in a circle-only world is cancelled', bad.length === 0, bad.join('; ')); - check(`HouseRules registered for ${old ? 'the old' : 'the new'} share event`, (old ? /BlueMapHUD found/ : /BlueMinimap found/).test(s.log)); + check('Mayor registered for the share event', /BlueMapMini found/.test(s.log)); } async function liveCircles() { section('Circles granted and removed with LuckPerms, no relog'); - await lp('user N3 permission set houserules.circle.red true'); + await lp('user N3 permission set mayor.circle.red true'); let { heard } = await chatRound(['N3'], 'lp1'); check('N3 reaches the red circle as soon as it is granted', heard.N3.includes('R1') && heard.N3.includes('R2'), heard.N3); - await lp('user N3 permission unset houserules.circle.red'); + await lp('user N3 permission unset mayor.circle.red'); ({ heard } = await chatRound(['N3'], 'lp2')); check('and stops as soon as it is removed', !heard.N3.includes('R1'), heard.N3); } async function adminCheck() { - section('/houserules check'); - let m = await s.run('houserules check R1 R2', /\w+ can(?:'t)? reach R2: [^\n]*/); + section('/mayor check'); + let m = await s.run('mayor check R1 R2', /\w+ can(?:'t)? reach R2: [^\n]*/); check('check explains a shared circle', m && /R1 can reach R2: they share the circle red/.test(m[0]), m && m[0]); - m = await s.run('houserules check N1 N3', /\w+ can(?:'t)? reach N3: [^\n]*/); + m = await s.run('mayor check N1 N3', /\w+ can(?:'t)? reach N3: [^\n]*/); check('check explains different worlds', m && /can't reach N3: they're in different world groups/.test(m[0]), m && m[0]); - section('/houserules --json (Signalbox format 1)'); - const json = (c, id) => s.run(`houserules ${c} --json ${id}`, new RegExp(`ea-result ${id} (\\{[^\\n]*\\})`)).then((x) => x && JSON.parse(x[1])); + section('/mayor --json (Signalbox format 1)'); + const json = (c, id) => s.run(`mayor ${c} --json ${id}`, new RegExp(`ea-result ${id} (\\{[^\\n]*\\})`)).then((x) => x && JSON.parse(x[1])); const d = await json('describe', 'hr1'); check('describe lists the four commands, format 1', d && d.ok && d.format === 1 && ['status', 'reload', 'check', 'describe'].every((n) => d.commands.some((c) => c.name === n)), JSON.stringify(d)); // The test bots' two-letter names aren't valid Minecraft names (3 to 16), so --json gets their offline UUIDs. @@ -286,14 +285,14 @@ async function adminCheck() { check('status lists the circles and no problems', st && st.ok && st.paused === false && st.circles.includes('red'), JSON.stringify(st)); const bad = await json('frobnicate', 'hr5'); check('an unknown subcommand still gets one answer', bad && bad.ok === false && bad.error.code === 'unknown-command', JSON.stringify(bad)); - // A player without houserules.admin: the command doesn't exist for them, --json or not. - const k = bots.N2.mark(); bots.N2.say('/houserules status --json hr6'); - check('a player without houserules.admin gets no answer line', await bots.N2.waitFor(/Unknown or incomplete command/, 3000, k) && !s.log.includes('ea-result hr6'), bots.N2.lines(k)); + // A player without mayor.admin: the command doesn't exist for them, --json or not. + const k = bots.N2.mark(); bots.N2.say('/mayor status --json hr6'); + check('a player without mayor.admin gets no answer line', await bots.N2.waitFor(/Unknown or incomplete command/, 3000, k) && !s.log.includes('ea-result hr6'), bots.N2.lines(k)); } async function brokenReload() { section('Config: a broken reload keeps the old rules'); - const good = fs.readFileSync(s.path('plugins/HouseRules/config.yml'), 'utf8'); + const good = fs.readFileSync(s.path('plugins/Mayor/config.yml'), 'utf8'); s.writeConfig('config.yml', 'circles: [red\nnope: : :'); const m = await reload(); check('a broken reload is reported and nothing changes', m && /The config has problems, so nothing changed/.test(m[0]), m && m[0]); @@ -308,7 +307,7 @@ async function load() { const extra = []; for (let i = 0; i < 20; i++) { const b = new Bot(`L${String(i).padStart(2, '0')}`, +port); extra.push(b); } await Promise.all(extra.map((b) => b.spawned)); - for (let i = 0; i < extra.length; i++) await lp(`user ${extra[i].name} permission set houserules.circle.${i % 2 ? 'red' : 'blue'} true`); + for (let i = 0; i < extra.length; i++) await lp(`user ${extra[i].name} permission set mayor.circle.${i % 2 ? 'red' : 'blue'} true`); await sleep(1000); const start = Date.now(), sent = []; for (let round = 0; round < 15; round++) { @@ -316,7 +315,7 @@ async function load() { await sleep(1000); } await sleep(2000); - const tps = await s.run('hrt tps', /HRT tps ([\d.]+) mspt ([\d.]+)/); + const tps = await s.run('mt tps', /HRT tps ([\d.]+) mspt ([\d.]+)/); const latencies = []; for (const m of sent) { const i = +m.from.slice(1), mate = extra.find((b) => b.name !== m.from && (+b.name.slice(1)) % 2 === i % 2); @@ -340,7 +339,7 @@ async function pausedOnBrokenStart() { section('Config: broken at startup pauses chat'); all().forEach((b) => b.quit()); bots = {}; await s.stop(); - const good = fs.readFileSync(s.path('plugins/HouseRules/config.yml'), 'utf8'); + const good = fs.readFileSync(s.path('plugins/Mayor/config.yml'), 'utf8'); s.writeConfig('config.yml', 'circles: [red\nnope: : :'); await s.start(); check('the console explains the pause', /chat and private messages are paused until it's fixed/.test(s.log)); @@ -366,7 +365,7 @@ async function pausedOnBrokenStart() { await s.start(); const have = await worlds(); check('test worlds exist', ['world', 'world_nether', 'world_the_end', 'meadow', 'orchard_nether', 'hall'].every((w) => have.includes(w)), have); - check('HouseRules starts with no config as vanilla chat', /No circles are set up, so chat reaches everyone/.test(s.log)); + check('Mayor starts with no config as vanilla chat', /No circles are set up, so chat reaches everyone/.test(s.log)); await join(NAMES); await place(); await vanilla(); @@ -374,8 +373,8 @@ async function pausedOnBrokenStart() { s.writeConfig('config.yml', CONFIG); check('reload applies the new config', /Reloaded/.test((await reload() || [''])[0])); for (const [n, p] of Object.entries(PEOPLE)) { - for (const c of p.circles) await lp(`user ${n} permission set houserules.circle.${c} true`); - if (p.bypass) await lp(`user ${n} permission set houserules.bypass true`); + for (const c of p.circles) await lp(`user ${n} permission set mayor.circle.${c} true`); + if (p.bypass) await lp(`user ${n} permission set mayor.bypass true`); } await sleep(1000); @@ -398,7 +397,7 @@ async function pausedOnBrokenStart() { // README: "Blocked text is never logged or stored." The words the filter test used must not be in the server log. const leaked = ['sh1t', 'b1tch'].filter((w) => s.log.toLowerCase().includes(w)); check('filtered words never reach the server log', leaked.length === 0, leaked.join(', ')); - const errors = s.errors().filter((l) => !/config.yml: isn't valid YAML|couldn't read its config|The config has problems|SEVERE.*HouseRules.*(YAML|paused|kept)/.test(l)); + const errors = s.errors().filter((l) => !/config.yml: isn't valid YAML|couldn't read its config|The config has problems|SEVERE.*Mayor.*(YAML|paused|kept)/.test(l)); check('no errors or exceptions in the log', errors.length === 0, errors.slice(0, 8).join(' || ')); check('no test player died and respawned elsewhere', !/HRT death /.test(s.log), (s.log.match(/.*HRT death .*/) || [''])[0]); const kicked = all().filter((b) => b.kicked); diff --git a/test/stub/build.gradle.kts b/test/stub/build.gradle.kts index 862f18c..2610102 100644 --- a/test/stub/build.gradle.kts +++ b/test/stub/build.gradle.kts @@ -1,4 +1,4 @@ -// A copy of BlueMapHUD's CoordinateShareEvent, compile-only. Never shipped: BlueMapHUD supplies the real class. +// A copy of BlueMapMini's CoordinateShareEvent, compile-only. Never shipped: BlueMapMini supplies the real class. plugins { java } diff --git a/test/stub/src/main/java/com/extantape/bluemaphud/api/CoordinateShareEvent.java b/test/stub/src/main/java/com/extantape/bluemaphud/api/CoordinateShareEvent.java deleted file mode 100644 index 8e6929f..0000000 --- a/test/stub/src/main/java/com/extantape/bluemaphud/api/CoordinateShareEvent.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.extantape.bluemaphud.api; - -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; - -import java.util.Set; - -/** - * A copy of BlueMapHUD's share event, matching the API BlueMapHUD's session gave for 0.1.0, so HouseRules compiles - * without BlueMapHUD. The live tests also ship it in a stand-in plugin. Fired on the main thread just before a share. - */ -public final class CoordinateShareEvent extends Event implements Cancellable { - private static final HandlerList HANDLERS = new HandlerList(); - private final Player sender; - private final World world; - private final int x, y, z; - private final Set recipients; - private boolean cancelled; - - public CoordinateShareEvent(Player sender, World world, int x, int y, int z, Set recipients) { - this.sender = sender; - this.world = world; - this.x = x; - this.y = y; - this.z = z; - this.recipients = recipients; - } - - public Player getSender() { return sender; } - public World getWorld() { return world; } - public int getX() { return x; } - public int getY() { return y; } - public int getZ() { return z; } - public Set getRecipients() { return recipients; } - @Override public boolean isCancelled() { return cancelled; } - @Override public void setCancelled(boolean cancel) { cancelled = cancel; } - @Override public HandlerList getHandlers() { return HANDLERS; } - public static HandlerList getHandlerList() { return HANDLERS; } - public static boolean hasListeners() { return HANDLERS.getRegisteredListeners().length > 0; } -} diff --git a/test/stub/src/main/java/com/extantape/blueminimap/api/CoordinateShareEvent.java b/test/stub/src/main/java/com/extantape/bluemapmini/api/CoordinateShareEvent.java similarity index 87% rename from test/stub/src/main/java/com/extantape/blueminimap/api/CoordinateShareEvent.java rename to test/stub/src/main/java/com/extantape/bluemapmini/api/CoordinateShareEvent.java index 233d6b6..0272bba 100644 --- a/test/stub/src/main/java/com/extantape/blueminimap/api/CoordinateShareEvent.java +++ b/test/stub/src/main/java/com/extantape/bluemapmini/api/CoordinateShareEvent.java @@ -1,4 +1,4 @@ -package com.extantape.blueminimap.api; +package com.extantape.bluemapmini.api; import org.bukkit.World; import org.bukkit.entity.Player; @@ -9,8 +9,8 @@ import java.util.Set; /** - * A copy of BlueMinimap's share event, matching the API BlueMinimap's session gave for 0.1.0, so HouseRules compiles - * without BlueMinimap. The live tests also ship it in a stand-in plugin. Fired on the main thread just before a share. + * A copy of BlueMapMini's share event, so Mayor compiles + * without BlueMapMini. The live tests also ship it in a stand-in plugin. Fired on the main thread just before a share. */ public final class CoordinateShareEvent extends Event implements Cancellable { private static final HandlerList HANDLERS = new HandlerList();