feat(event): add ProtectionCreateEvent for player protection actions#17
feat(event): add ProtectionCreateEvent for player protection actions#17TheBjoRedCraft wants to merge 11 commits intoversion/1.21.11from
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new Bukkit event intended to signal when a player successfully creates a protection, and updates Gradle version management to be driven via gradle.properties.
Changes:
- Introduce
ProtectionCreateEvent(customPlayerEvent) for protection creation. - Hook the event into the successful protection purchase/creation flow.
- Move project version configuration to
gradle.propertiesand read it from the build script.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/main/kotlin/dev/slne/surf/protect/paper/region/ProtectionRegion.kt |
Attempts to emit ProtectionCreateEvent after successful protection creation. |
src/main/kotlin/dev/slne/surf/protect/paper/event/ProtectionCreateEvent.kt |
Adds the new custom Bukkit event class. |
gradle.properties |
Adds centralized version property (and Kotlin code style setting). |
build.gradle.kts |
Reads version from Gradle properties instead of hardcoding it. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/main/kotlin/dev/slne/surf/protect/paper/region/ProtectionRegion.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/dev/slne/surf/protect/paper/event/ProtectionCreateEvent.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Adds a new Bukkit/Paper event to signal when a player successfully creates a protection region, and wires it into the region purchase/creation flow (with additional spawn-protection handling and build versioning updates).
Changes:
- Introduce
ProtectionCreateEventand fire it after a successful protection purchase. - Add a new region creation state for spawn-protected areas and introduce checks for “too near to spawn”.
- Move project versioning into
gradle.propertiesand read it frombuild.gradle.kts.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/kotlin/dev/slne/surf/protect/paper/region/info/RegionCreationState.kt |
Adds TOO_NEAR_FROM_SPAWN state for region creation outcomes. |
src/main/kotlin/dev/slne/surf/protect/paper/region/ProtectionRegion.kt |
Adds spawn-distance validation, fires ProtectionCreateEvent on success, and runs the event call on the global dispatcher. |
src/main/kotlin/dev/slne/surf/protect/paper/event/ProtectionCreateEvent.kt |
New Bukkit event class emitted on protection creation. |
gradle.properties |
Adds kotlin.code.style=official and defines a shared version property. |
build.gradle.kts |
Uses the version Gradle property instead of a hardcoded string. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/main/kotlin/dev/slne/surf/protect/paper/region/info/RegionCreationState.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/main/kotlin/dev/slne/surf/protect/paper/region/info/RegionCreationState.kt
Show resolved
Hide resolved
src/main/kotlin/dev/slne/surf/protect/paper/menu/dialog/ProtectionAddMemberDialog.kt
Show resolved
Hide resolved
…ags and ProtectionEditFlagsView
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/main/kotlin/dev/slne/surf/protect/paper/menu/view/flags/ProtectionEditFlagsView.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/dev/slne/surf/protect/paper/menu/view/flags/ProtectionEditFlagsView.kt
Outdated
Show resolved
Hide resolved
src/main/kotlin/dev/slne/surf/protect/paper/menu/view/flags/ProtectionEditFlagsView.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| val state = flag.initialState ?: StateFlag.State.ALLOW | ||
|
|
||
| if (flag.isPlayerRelated) { | ||
| region.setFlag(flag.flag.regionGroupFlag, RegionGroup.NON_MEMBERS) | ||
| region.setFlag(flag.flag, state) | ||
|
|
||
| region.setFlag(flag.flag.regionGroupFlag, RegionGroup.MEMBERS) | ||
| region.setFlag(flag.flag, StateFlag.State.ALLOW) | ||
|
|
||
| continue | ||
| } | ||
|
|
||
| region.setFlag(flag.flag, state) | ||
| } |
There was a problem hiding this comment.
For non-player-related flags where initialState is null, applyDefaultFlags falls back to ALLOW and explicitly sets the flag. Previously those flags were left unset (null) during region creation, allowing parent/global region settings to apply. Explicitly forcing ALLOW changes behavior in setups that rely on global denies and can unintentionally override higher-level protection policies. Consider skipping setFlag when initialState is null (or only applying defaults for isPlayerRelated flags here).
| render.layoutSlot('P') | ||
| .updateOnStateChange(paginationState) | ||
| .displayIf { _ -> | ||
| pagination.canBack() | ||
| } | ||
| .onRender { slotRender -> | ||
| if (pagination.canBack()) { | ||
| slotRender.item = previousItem | ||
| } else { | ||
| slotRender.item = outlineItem | ||
| } | ||
| .onRender { | ||
| if (pagination.canBack()) previousItem else outlineItem | ||
| } |
There was a problem hiding this comment.
The .onRender { if (pagination.canBack()) previousItem else outlineItem } block doesn't assign the computed item to the slot (contrast with other views that do slotRender.item = ...). If onRender here is the usual (SlotRender) -> Unit, this will result in the slot not updating. Consider using the slotRender parameter and setting slotRender.item explicitly (and same for the next-page slot).
| private fun applyState( | ||
| region: ProtectedRegion, | ||
| flag: EditableProtectionFlags, | ||
| state: StateFlag.State | ||
| ) { | ||
| if (flag.isPlayerRelated) { | ||
| region.setFlag(flag.flag, StateFlag.State.ALLOW) | ||
| if (state == StateFlag.State.ALLOW) { | ||
| region.setFlag(flag.flag.regionGroupFlag, null) | ||
| } else { | ||
| region.setFlag(flag.flag.regionGroupFlag, RegionGroup.MEMBERS) | ||
| } | ||
| } else { | ||
| region.setFlag(flag.flag, state) | ||
| } |
There was a problem hiding this comment.
applyState for player-related flags sets the flag value to ALLOW and only toggles regionGroupFlag between null and MEMBERS. With WorldGuard's group flag semantics, setting group=MEMBERS + ALLOW does not deny non-members (it just makes the flag not apply to them, so they can still be allowed by defaults/parents). To enforce “members-only”, set StateFlag=DENY with regionGroupFlag=NON_MEMBERS; to allow everyone, set StateFlag=ALLOW with regionGroupFlag=null/ALL (or clear both).
| if (playerName.isEmpty() || playerName.isBlank() || playerName.length > 16) { | ||
| player.sendText { | ||
| appendErrorPrefix() | ||
| error("Der Spielername ist ungültig.") | ||
| } | ||
| player.closeDialog() | ||
| viewFrame.open( | ||
| ProtectionMemberListView::class.java, | ||
| player, | ||
| mapOf("protection" to protection) | ||
| ) | ||
| return | ||
| } |
There was a problem hiding this comment.
With the new invalid-name validation, closing the dialog (which currently triggers onClose -> handleAdd(...)) will now show an error (“Spielername ist ungültig.”) and reopen the member list even if the user simply cancelled. Consider changing onClose to behave like a cancel action (navigate back without attempting to add), and keep handleAdd only for onSearch.
| private fun createMemberItem(member: OfflinePlayer) = buildItem(Material.PLAYER_HEAD) { | ||
| displayName { | ||
| protectColored(member.displayName.toSmallCaps(), TextDecoration.BOLD) | ||
| protectColored(member.name?.toSmallCaps() ?: "#Unknown", TextDecoration.BOLD) |
There was a problem hiding this comment.
The fallback name "#Unknown" is English while the rest of the UI/messages in this view are German (and elsewhere you use #Unbekannt). Consider using a consistent localized fallback string here as well.
| protectColored(member.name?.toSmallCaps() ?: "#Unknown", TextDecoration.BOLD) | |
| protectColored(member.name?.toSmallCaps() ?: "#Unbekannt", TextDecoration.BOLD) |
| withContext(plugin.globalRegionDispatcher) { | ||
| protectionUser.bukkitPlayer?.let { | ||
| ProtectionCreateEvent(it).callEvent() | ||
| } | ||
| } |
There was a problem hiding this comment.
ProtectionCreateEvent is a PlayerEvent, so it should be fired from the correct Folia thread context for that player. globalRegionDispatcher doesn't guarantee entity-thread safety for player-bound operations; prefer plugin.entityDispatcher(player) (or another player/region-appropriate dispatcher) when calling Bukkit/Paper APIs like event dispatch.
| region.setFlag(flag.flag.regionGroupFlag, RegionGroup.MEMBERS) | ||
| region.setFlag(flag.flag, StateFlag.State.ALLOW) | ||
|
|
There was a problem hiding this comment.
applyDefaultFlags currently sets the RegionGroup flag twice for player-related flags (NON_MEMBERS and then MEMBERS), and also overwrites the StateFlag value (state then ALLOW). The second assignment wins, so the final configuration is effectively group=MEMBERS and state=ALLOW, which does not enforce “deny non-members / allow members” as intended. Suggestion: for player-related flags, set regionGroupFlag = NON_MEMBERS and keep the flag state as DENY (or whatever initialState is), without later overwriting it; this makes the deny apply only to non-members while members are unaffected.
| region.setFlag(flag.flag.regionGroupFlag, RegionGroup.MEMBERS) | |
| region.setFlag(flag.flag, StateFlag.State.ALLOW) |
| private fun getCurrentState( | ||
| region: ProtectedRegion, | ||
| flag: EditableProtectionFlags | ||
| ): StateFlag.State { | ||
| return if (flag.isPlayerRelated) { | ||
| val group = region.getFlag(flag.flag.regionGroupFlag) | ||
| if (group == RegionGroup.MEMBERS) StateFlag.State.DENY else StateFlag.State.ALLOW | ||
| } else { | ||
| region.getFlag(flag.flag) ?: flag.initialState ?: StateFlag.State.ALLOW | ||
| } |
There was a problem hiding this comment.
getCurrentState for isPlayerRelated flags is derived from regionGroupFlag == MEMBERS, but (a) the default setup in applyDefaultFlags uses NON_MEMBERS for the “members-only” intent, and (b) the actual StateFlag value is ignored. This will make the UI show the wrong state and toggle from an incorrect baseline. Consider mapping the UI state from the combination of StateFlag value + regionGroupFlag (e.g. DENY + NON_MEMBERS => “Nur für Mitglieder”, otherwise => “Für alle erlaubt”).
| viewFrame.open( | ||
| ProtectionInfoView::class.java, | ||
| player, | ||
| mapOf("protection" to protection) | ||
| ) |
There was a problem hiding this comment.
On validation failures you open ProtectionInfoView, but the dialog isn't closed in these early-return branches. This can leave the search dialog still open (or re-openable) on top of the inventory UI. Consider calling player.closeDialog() before opening the view (same applies to the other early-return validations in this function).
No description provided.