Initial commit

This commit is contained in:
Gabriel Tofvesson 2021-07-06 21:04:43 +02:00
commit 42789e012b
37 changed files with 2279 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Project exclude paths
/.gradle/
/build/

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

19
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Gradle Imported" enabled="true">
<outputRelativeToContentRoot value="true" />
<option name="bungeeAnnotationResultPath" value="C:\Users\Lenovo\IdeaProjects\SpigotChunkProtector\build\spigradle\bungee_main" />
<option name="nukkitAnnotationResultPath" value="C:\Users\Lenovo\IdeaProjects\SpigotChunkProtector\build\spigradle\nukkit_main" />
<option name="pluginAnnotationResultPath" value="C:\Users\Lenovo\IdeaProjects\SpigotChunkProtector\build\spigradle\plugin_main" />
<option name="spigotAnnotationResultPath" value="C:\Users\Lenovo\IdeaProjects\SpigotChunkProtector\build\spigradle\spigot_main" />
<processorPath useClasspath="false">
<entry name="$USER_HOME$/gradle-7.1/caches/modules-2/files-2.1/kr.entree/spigradle-annotations/2.1.1/140e14d5fb373843420f0719f6f83cacc07d8cd1/spigradle-annotations-2.1.1.jar" />
</processorPath>
<module name="SpigotChunkProtector.main" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="16" />
</component>
</project>

20
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="16" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

75
.idea/jarRepositories.xml generated Normal file
View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="https://papermc.io/repo/repository/maven-public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven4" />
<option name="name" value="maven4" />
<option name="url" value="https://hub.spigotmc.org/nexus/content/repositories/snapshots/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven9" />
<option name="name" value="maven9" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="maven2" />
<option name="name" value="maven2" />
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven6" />
<option name="name" value="maven6" />
<option name="url" value="https://oss.sonatype.org/content/repositories/central" />
</remote-repository>
<remote-repository>
<option name="id" value="maven8" />
<option name="name" value="maven8" />
<option name="url" value="https://repo.dmulloy2.net/nexus/repository/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven5" />
<option name="name" value="maven5" />
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots" />
</remote-repository>
<remote-repository>
<option name="id" value="maven5" />
<option name="name" value="maven5" />
<option name="url" value="https://papermc.io/repo/repository/maven-public" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$MAVEN_REPOSITORY$/" />
</remote-repository>
<remote-repository>
<option name="id" value="Gradle Central Plugin Repository" />
<option name="name" value="Gradle Central Plugin Repository" />
<option name="url" value="https://plugins.gradle.org/m2" />
</remote-repository>
</component>
</project>

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="16" />
</component>
</project>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="libraries-with-intellij-classes">
<option name="intellijApiContainingLibraries">
<list>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIU" />
<option name="groupId" value="com.jetbrains.intellij.idea" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIU" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIC" />
<option name="groupId" value="com.jetbrains.intellij.idea" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIC" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPY" />
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPY" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPC" />
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPC" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="clion" />
<option name="groupId" value="com.jetbrains.intellij.clion" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="clion" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="riderRD" />
<option name="groupId" value="com.jetbrains.intellij.rider" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="riderRD" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="goland" />
<option name="groupId" value="com.jetbrains.intellij.goland" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="goland" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
</list>
</option>
</component>
</project>

15
.idea/libraries/KotlinJavaRuntime.xml generated Normal file
View File

@ -0,0 +1,15 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime">
<CLASSES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
</SOURCES>
</library>
</component>

9
.idea/libraries/SpigotWizCompat.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="SpigotWizCompat">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/SpigotWizCompat.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" default="true" project-jdk-name="16" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

139
README.md Normal file
View File

@ -0,0 +1,139 @@
# Spigot Chunk Protector
by Gabriel Tofvesson (IKEA_Jesus)
## Index
* [Permissions](#Permissions)
* [Commands](#Commands)
* [claim](#claim)
* [unclaim](#unclaim)
* [claims](#claims)
* [invite](#invite)
* [uninvite](#uninvite)
* [showclaim](#showclaim)
* [claimowner](#claimowner)
* [claimoption](#claimoption)
* [Options](#Options)
## Permissions
|Permission Node|Description|Applies to (by default)|
|:---|:---|:---:|
|`chunkprotector.claim`|Allows access to [`/claim`](#claim)|All players|
|`chunkprotector.unclaim`|Allows access to [`/unclaim`](#unclaim)|All players|
|`chunkprotector.invite`|Allows access to [`/invite`](#invite)|All players|
|`chunkprotector.uninvite`|Allows access to [`/uninvite`](#uninvite)|All players|
|`chunkprotector.listclaims`|Allows access to [`/claims`](#claims)|All players|
|`chunkprotector.claimowner`|Allows access to [`/claimowner`](#claimowner)|All players|
|`chunkprotector.showclaim`|Allows access to [`/showclaim`](#showclaim)|All players|
|`chunkprotector.claimoption`|Allows access to [`/claimoption`](#claimoption)|All players|
|`chunkprotector.bypass`|Allows for modification of otherp player's claims|Operators|
|`chunkprotector.ignore`|Allows bypassing of all physical restrictions|Operators|
## Commands
### claim
*Claim an area and give it a specified name*
| Action |Command|
|:--- | :---: |
|Select area|`/claim [name]`|
|Cancel selection|`/claim`|
### unclaim
*Remove a claim with a given name for a specific player*
|Who|Command|
|:--- | :---:|
|All players|`/unclaim [claim]`|
|Bypass permission + Console|`/unclaim [claim] [player]`|
### claims
*List all claimed areas for a player*
|Who|Command|
|:--- | :---:|
|All players|`/claims`|
|Bypass permission + Console|`/claims [player]`|
### invite
*Invite a player to a claim*
|Who|Command|Context|
|:--- | :---:|:---|
|All players|`/invite [player]`|Invite player to claim that owner is standing in|
|All players|`/invite [player] [claim]`|Invite player to given claim|
|Bypass permission + Console|`/invite [player] [claim] [claimOwner]`|Invite player to another players claim|
### uninvite
*Un-invite a player to a claim*
|Who|Command|Context|
|:--- | :---:|:---|
|All players|`/uninvite [player]`|Un-invite player to claim that owner is standing in|
|All players|`/uninvite [player] [claim]`|Un-invite player to given claim|
|Bypass permission + Console|`/uninvite [player] [claim] [claimOwner]`|Un-invite player to another players claim|
### showclaim
*Show the boundaries of a claimed area*
|Action|Command|
|:--- | :---:|
|Current claim|`/claims`|
|Specific claim|`/claims [claim]`|
|Specific claim|`/claims [claim] [player]`|
### claimowner
*Get the name and owner of the claim the command sender is standing in*
|Where|Command|
|:--- | :---:|
|Current claim|`/claimowner`|
### claimoption
*Set a configuration option for a given claim. Values will always be either `true` or `false`*
|Who|Command|Context|
|:--- | :---:|:---|
|All players|`/claimoption [option] [value]`|Set option for the claim the command sender is standing in|
|All players|`/claimoption [claim] [option] [value]`|Set option for the given claim|
|Bypass permission + Console|`/claimoption [player] [claim] [option] [value]`|Set option for the claim of the given player|
### Options
|Option name|Description|
|:---|:---|
|`allowAllLiquids`|Allow all liquids to enter a claimed area|
|`allowEntityInteract`|Allow entities to interact with the environment (e.g. creepers break blocks, mobs can hurt each other). When this is false, mobs will act as if players that are not invited to a claim do not exist|
|`allowGuestLiquids`|Allow liquids to enter claimed area if they originate from a claim owned by an invited player|
|`allowPlayerEntityInteract`|Allow all players (invited or not) to interact with non-hostile mobs in a claim|
|`allowTNT`|Allow TNT explosions to break blocks|
|`disablePVP`|Disable player-versus-player interactions|
**NOTE:** Regarding `allowPlayerEntityInteract`, players can always interact with hostile mobs, so long as said mobs do not have a custom name.

195
build.gradle Normal file
View File

@ -0,0 +1,195 @@
plugins {
id 'java'
id 'kr.entree.spigradle' version '2.2.4'
id 'org.jetbrains.kotlin.jvm' version '1.5.20'
}
group 'dev.w1zzrd.spigot.chunkprotector'
version '1.0-SNAPSHOT'
repositories {
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
// As of Gradle 5.1, you can limit this to only those
// dependencies you expect from it
content {
includeGroup 'org.bukkit'
includeGroup 'org.spigotmc'
}
}
/*
As Spigot-API depends on the Bungeecord ChatComponent-API,
we need to add the Sonatype OSS repository, as Gradle,
in comparison to maven, doesn't want to understand the ~/.m2
directory unless added using mavenLocal(). Maven usually just gets
it from there, as most people have run the BuildTools at least once.
This is therefore not needed if you're using the full Spigot/CraftBukkit,
or if you're using the Bukkit API.
*/
maven {
url = "https://papermc.io/repo/repository/maven-public"
content {
includeGroup 'io.papermc.paper'
}
}
maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' }
maven { url = 'https://oss.sonatype.org/content/repositories/central' }
mavenLocal() // This is needed for CraftBukkit and Spigot.
mavenCentral()
protocolLib()
jitpack() // For vault
}
dependencies {
// Pick only one of these and read the comment in the repositories block.
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" // The Spigot API with no shadowing. Requires the OSS repo.
implementation files('lib/SpigotWizCompat.jar')
compileOnly spigot('1.17') // Or spigot()
//compileOnly protocolLib()
//compileOnly vault()
}
spigot {
authors = ['IKEAJesus']
depends = ['WizCompat', 'Kotlin']
apiVersion = '1.16'
load = STARTUP
commands {
claim {
description = 'Start claiming land'
permission = 'chunkprotector.claim'
permissionMessage = 'You are not allowed to claim land!'
usage = '/<command> [claim]'
}
unclaim {
description = 'Un-claim a claimed region'
permission = 'chunkprotector.unclaim'
permissionMessage = 'You are not allowed to unclaim land!'
usage = '/<command> [claim]'
}
invite {
description = 'Invite a player to a claimed area'
permission = 'chunkprotector.invite'
permissionMessage = 'You are not allowed to invite players!'
usage = '/<command> [player] [claim]'
}
uninvite {
description = 'Un-invite a player from a claimed area'
permission = 'chunkprotector.uninvite'
permissionMessage = 'You are not allowed to un-invite players!'
usage = '/<command> [player] [claim]'
}
claims {
description = 'Get a list of all owned claims'
permission = 'chunkprotector.listclaims'
permissionMessage = 'You are not allowed get a list of your claims!'
usage = '/<command>'
}
claimowner {
description = 'Get the owner of the current chunk'
permission = 'chunkprotector.claimowner'
permissionMessage = 'You are not allowed to check who owns this chunk!'
usage = '/<command>'
}
showclaim {
description = 'Show the outline of a claimed region'
permission = 'chunkprotector.showclaim'
permissionMessage = 'You are not allowed to see the claimed region!'
usage = '/<command> {claim}'
}
claimoption {
description = 'Change properties of a claimed area'
permission = 'chunkprotector.claimoption'
permissionMessage = 'You are not allowed to change the properties of a claimed area!'
usage = '/<command> {claim} [claimOption] [true/false]'
}
}
permissions {
'chunkprotector.claim' {
description = 'Allows access to /claim'
defaults = 'true'
}
'chunkprotector.unclaim' {
description = 'Allows access to /unclaim'
defaults = 'true'
}
'chunkprotector.invite' {
description = 'Allows access to /invite'
defaults = 'true'
}
'chunkprotector.uninvite' {
description = 'Allows access to /uninvite'
defaults = 'true'
}
'chunkprotector.listclaims' {
description = 'Allows access to /claims'
defaults = 'true'
}
'chunkprotector.bypass' {
description = 'Allows bypassing of all command-related claim restrictions'
defaults = 'op'
}
'chunkprotector.ignore' {
description = 'Allows bypassing of all physical claim restrictions'
defaults = 'op'
}
'chunkprotector.claimowner' {
description = 'Allows access to /claimowner'
defaults = 'true'
}
'chunkprotector.showclaim' {
description = 'Allows access to /showclaim'
defaults = 'true'
}
'chunkprotector.claimoption' {
description = 'Allows access to /claimoption'
defaults = 'true'
}
}
/*
commands {
give {
aliases = ['i']
description = 'Give command.'
permission = 'test.foo'
permissionMessage = 'You do not have permission!'
usage = '/<command> [test|stop]'
}
}
permissions {
'test.foo' {
description = 'Allows foo command'
defaults = 'true'
}
'test.*' {
description = 'Wildcard permission'
defaults = 'op'
children = ['test.foo': true]
}
}
*/
}
compileKotlin {
kotlinOptions {
jvmTarget = "16"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "16"
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,86 @@
package dev.w1zzrd.spigot.chunkprotector
import dev.w1zzrd.spigot.chunkprotector.claim.Claim
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimChunk
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.chunkprotector.collection.SerializableBinaryList
import dev.w1zzrd.spigot.chunkprotector.command.*
import dev.w1zzrd.spigot.chunkprotector.freecam.FreeCamManager
import dev.w1zzrd.spigot.chunkprotector.listener.PlayerActionListener
import dev.w1zzrd.spigot.chunkprotector.listener.TabCompleteListener
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData
import dev.w1zzrd.spigot.wizcompat.serialization.UUIDList
import kr.entree.spigradle.annotations.SpigotPlugin
import org.bukkit.configuration.serialization.ConfigurationSerialization
import org.bukkit.event.HandlerList
import org.bukkit.plugin.java.JavaPlugin
@SpigotPlugin
class ChunkProtectorPlugin: JavaPlugin() {
private val freeCamManager = FreeCamManager()
private val tabCompleteListener = TabCompleteListener()
// Properties that should be initialized only after serializers are enabled
private val persistentData: PersistentData by lazy { PersistentData("data", this) }
private val claimManager: ClaimManager by lazy { ClaimManager(freeCamManager, persistentData) }
private val actionListener: PlayerActionListener by lazy {
PlayerActionListener(claimManager)
}
override fun onEnable() {
super.onEnable()
enableSerializers()
freeCamManager.onEnable(this)
claimManager.onEnable(this)
enableListeners()
enableCommands()
}
private fun enableSerializers() {
ConfigurationSerialization.registerClass(UUIDList::class.java)
ConfigurationSerialization.registerClass(SerializableBinaryList::class.java)
ConfigurationSerialization.registerClass(ClaimChunk::class.java)
ConfigurationSerialization.registerClass(Claim::class.java)
}
private fun enableListeners() {
server.pluginManager.registerEvents(tabCompleteListener, this)
server.pluginManager.registerEvents(actionListener, this)
}
private fun enableCommands() {
getCommand("claim")!!.setExecutor(ClaimCommand(claimManager))
getCommand("unclaim")!!.setExecutor(UnClaimCommand(claimManager))
getCommand("invite")!!.setExecutor(InviteCommand(claimManager))
getCommand("uninvite")!!.setExecutor(UnInviteCommand(claimManager))
getCommand("claims")!!.setExecutor(ListClaimsCommand(claimManager))
getCommand("claimowner")!!.setExecutor(ClaimOwnerCommand(claimManager))
getCommand("showclaim")!!.setExecutor(ShowClaimCommand(claimManager, this))
getCommand("claimoption")!!.setExecutor(
ClaimOptionCommand(claimManager).also { tabCompleteListener.registerTabCompleter("claimoption", it.completionProcessor) }
)
}
override fun onDisable() {
disableListeners()
claimManager.onDisable()
freeCamManager.onDisable()
persistentData.saveData()
disableSerializers()
super.onDisable()
}
private fun disableSerializers() {
ConfigurationSerialization.unregisterClass(Claim::class.java)
ConfigurationSerialization.unregisterClass(ClaimChunk::class.java)
ConfigurationSerialization.unregisterClass(SerializableBinaryList::class.java)
ConfigurationSerialization.unregisterClass(UUIDList::class.java)
}
private fun disableListeners() {
HandlerList.unregisterAll(actionListener)
tabCompleteListener.unRegisterAll()
HandlerList.unregisterAll(tabCompleteListener)
}
}

View File

@ -0,0 +1,217 @@
package dev.w1zzrd.spigot.chunkprotector.claim
import dev.w1zzrd.spigot.chunkprotector.collection.BinaryList
import dev.w1zzrd.spigot.wizcompat.packet.EntityCreator.*
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem
import dev.w1zzrd.spigot.wizcompat.serialization.UUIDList
import org.bukkit.Chunk
import org.bukkit.Location
import org.bukkit.OfflinePlayer
import org.bukkit.World
import org.bukkit.entity.Player
import java.lang.Integer.max
import java.lang.Integer.min
import java.util.*
class ClaimBuilder(val who: Player, val world: World, val name: String): Comparable<ClaimBuilder> {
private val first = CornerSelection()
private val second = CornerSelection()
val isValidRegion: Boolean
get() = second.corner != null
val built: Claim
get() = Claim(who.uniqueId, world.uid, name, ClaimChunk(first.corner!!.x, first.corner!!.z), ClaimChunk(second.corner!!.x, second.corner!!.z))
fun toggleCorner(corner: Chunk) {
when (corner) {
first.corner -> first.corner = null
second.corner -> second.corner = null
else -> {
if ((second.corner != null && first.corner != null) || first.corner == null) {
second.corner = null
first.corner = corner
} else {
second.corner = corner
}
}
}
first.render()
second.render()
}
fun clearRenders() {
first.clearRender()
second.clearRender()
}
override fun compareTo(other: ClaimBuilder) = who.uniqueId.compareTo(other.who.uniqueId)
override fun equals(other: Any?) = other is ClaimBuilder && hashCode() == other.hashCode()
override fun hashCode() = who.hashCode()
private inner class CornerSelection {
private var render = -1
private var location: Location? = null
var isDirty: Boolean = true
private set
var corner: Chunk? = null
set(value) {
isDirty = isDirty || (field != value)
field = value
adjustHeight()
}
fun adjustHeight() {
val currentCorner = corner
if (currentCorner != null) {
location = Location(
who.world,
(currentCorner.x shl 4) + 8.5,
kotlin.math.max(who.location.y - 3.0, 0.0),
(currentCorner.z shl 4) + 8.5,
0f,
0f
)
}
}
fun render() {
if (isDirty) {
clearRender()
if (corner != null) {
val renderShulker = createFakeShulker(who)
setEntityInvisible(renderShulker, true)
setEntityInvulnerable(renderShulker, true)
setEntityLocation(
renderShulker,
location!!.x,
location!!.y,
location!!.z,
location!!.yaw,
location!!.pitch
)
setEntityCollision(renderShulker, false)
setEntityGlowing(renderShulker, true)
sendEntitySpawnPacket(who, renderShulker)
sendEntityMetadataPacket(who, renderShulker)
render = getEntityID(renderShulker)
}
isDirty = false
}
}
fun clearRender() {
if (render != -1) {
sendEntityDespawnPacket(who, render)
render = -1
}
}
}
}
class Claim: SimpleReflectiveConfigItem, Comparable<Claim> {
@Transient
var owner: UUID
private set
private lateinit var ownerString: String
@Transient
var world: UUID
private set
private lateinit var worldString: String
lateinit var name: String
private set
lateinit var topLeft: ClaimChunk
private set
lateinit var bottomRight: ClaimChunk
private set
private lateinit var guests: UUIDList
// Per-claim settings
var allowPlayerEntityInteract = false
var allowTNT = false
var allowEntityInteract = false
var allowAllLiquids = false
var allowGuestLiquids = true
var disablePVP = false
constructor(map: Map<String, Any?>): super(map) {
owner = UUID.fromString(ownerString)
world = UUID.fromString(worldString)
}
constructor(owner: UUID, world: UUID, name: String, corner1: ClaimChunk, corner2: ClaimChunk): super(Collections.emptyMap()) {
this.owner = owner
ownerString = owner.toString()
this.world = world
worldString = world.toString()
this.name = name
this.topLeft = ClaimChunk(min(corner1.chunkX, corner2.chunkX), max(corner1.chunkZ, corner2.chunkZ))
this.bottomRight = ClaimChunk(max(corner1.chunkX, corner2.chunkX), min(corner1.chunkZ, corner2.chunkZ))
guests = UUIDList(BinaryList.newBinaryList())
}
fun addGuest(player: OfflinePlayer) = guests.uuids.add(player.uniqueId)
fun removeGuest(player: OfflinePlayer) = guests.uuids.remove(player.uniqueId)
fun hasGuest(player: OfflinePlayer) = guests.uuids.contains(player.uniqueId)
fun isAccessible(player: OfflinePlayer) = owner == player.uniqueId || guests.uuids.contains(player.uniqueId)
fun overlaps(other: Claim) =
!(topLeft.chunkX > other.bottomRight.chunkX || other.topLeft.chunkX > bottomRight.chunkZ) &&
!(bottomRight.chunkZ > other.topLeft.chunkZ || other.bottomRight.chunkZ > topLeft.chunkZ)
fun contains(chunk: Chunk) = contains(chunk.x, chunk.z)
fun contains(chunkX: Int, chunkZ: Int) =
!(topLeft.chunkX > chunkX || chunkX > bottomRight.chunkX) &&
!(bottomRight.chunkZ > chunkZ || chunkZ > topLeft.chunkZ)
override fun compareTo(other: Claim) = compareRaw(other.owner, other.name)
fun compareRaw(owner: UUID, name: String): Int {
val comp1 = this.owner.compareTo(owner)
return if (comp1 == 0) this.name.compareTo(name) else comp1
}
fun compareFindFirst(owner: UUID): Int {
val comp1 = this.owner.compareTo(owner)
return if (comp1 == 0) 1 else comp1
}
fun compareFindLast(owner: UUID): Int {
val comp1 = this.owner.compareTo(owner)
return if (comp1 == 0) -1 else comp1
}
}
fun BinaryList<Claim>.getByName(owner: UUID, name: String): Claim? {
val index = binarySearch { it.compareRaw(owner, name) }
if (index >= 0)
return this[index]
return null
}
fun BinaryList<Claim>.getAllForOwner(owner: UUID): List<Claim> {
val startIndex = -(binarySearch { it.compareFindFirst(owner) } + 1)
val endIndex = -(binarySearch { it.compareFindLast(owner) } + 1)
if (startIndex >= endIndex)
return emptyList()
return subList(startIndex, endIndex)
}

View File

@ -0,0 +1,27 @@
package dev.w1zzrd.spigot.chunkprotector.claim
import dev.w1zzrd.spigot.wizcompat.serialization.SimpleReflectiveConfigItem
import java.util.*
class ClaimChunk: SimpleReflectiveConfigItem, Comparable<ClaimChunk> {
var chunkX: Int = 0
private set
var chunkZ: Int = 0
private set
constructor(map: Map<String, Any?>): super(map)
constructor(chunkX: Int, chunkZ: Int): super(Collections.emptyMap()) {
this.chunkX = chunkX
this.chunkZ = chunkZ
}
private val longCoordinate: Long
get() = chunkX.toLong().shl(32) or chunkZ.toLong().and(0xFFFFFFFFL)
override fun compareTo(other: ClaimChunk) =
longCoordinate.compareTo(other.longCoordinate)
override fun equals(other: Any?) = other is ClaimChunk && compareTo(other) == 0
}

View File

@ -0,0 +1,132 @@
package dev.w1zzrd.spigot.chunkprotector.claim
import dev.w1zzrd.spigot.chunkprotector.collection.BinaryCache
import dev.w1zzrd.spigot.chunkprotector.collection.BinaryList.Companion.newBinaryList
import dev.w1zzrd.spigot.chunkprotector.collection.SerializableBinaryList
import dev.w1zzrd.spigot.chunkprotector.freecam.FreeCamManager
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.errorMessage
import dev.w1zzrd.spigot.wizcompat.serialization.PersistentData
import org.bukkit.Chunk
import org.bukkit.OfflinePlayer
import org.bukkit.World
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.HandlerList
import org.bukkit.event.Listener
import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerChangedWorldEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.plugin.Plugin
private val Chunk.longChunkCoordinate: Long
get() = (x.toLong() shl 32) or (z.toLong() and 0xFFFFFFFFL)
private fun Chunk.compareTo(other: Chunk): Int {
val comp1 = world.uid.compareTo(other.world.uid)
return if (comp1 == 0) longChunkCoordinate.compareTo(other.longChunkCoordinate) else comp1
}
private data class ClaimNameCacheEntry(val player: OfflinePlayer, val name: String): Comparable<ClaimNameCacheEntry> {
override fun compareTo(other: ClaimNameCacheEntry): Int {
val comp1 = player.uniqueId.compareTo(player.uniqueId)
return if (comp1 == 0) name.compareTo(other.name) else comp1
}
}
class ClaimManager(private val freeCamManager: FreeCamManager, persistentData: PersistentData) {
private val claimSelectListener = ClaimSelectListener()
private val claimBuilders = newBinaryList<ClaimBuilder>()
private val claims: SerializableBinaryList<Claim> = persistentData.loadData("claims") { SerializableBinaryList(newBinaryList()) }
private val chunkPermCache = BinaryCache.makeCache(512, Chunk::compareTo) { chunk ->
claims.list.find { it.world == chunk.world.uid && it.contains(chunk) }
}
private val namePermCache = BinaryCache.makeCache<ClaimNameCacheEntry, Claim>(64, ClaimNameCacheEntry::compareTo) {
claims.list.getByName(it.player.uniqueId, it.name)
}
init {
freeCamManager.addOnPlayerExitFreeCam { player ->
val builderIndex = claimBuilders.binarySearch { it.who.uniqueId.compareTo(player.uniqueId) }
if (builderIndex >= 0) {
val builder = claimBuilders.removeAt(builderIndex)
builder.clearRenders()
}
}
}
fun onEnable(plugin: Plugin) = plugin.server.pluginManager.registerEvents(claimSelectListener, plugin)
fun onDisable() = HandlerList.unregisterAll(claimSelectListener)
fun toggleClaim(player: Player, name: String) =
if (isClaiming(player)) {
freeCamManager.disableFreeCam(player)
false
} else {
freeCamManager.enableFreeCam(player)
claimBuilders.add(ClaimBuilder(player, player.world, name))
true
}
fun isClaiming(player: Player) = claimBuilders.contains(player.uniqueId) { who.uniqueId }
fun addClaim(claim: Claim): Boolean {
if (claims.list.any { it.overlaps(claim) })
return false
claims.list.add(claim)
return true
}
fun removeClaim(claim: Claim): Boolean {
chunkPermCache.clearValues(claim)
namePermCache.clearValues(claim)
return claims.list.remove(claim)
}
fun invitePlayer(owner: Player, name: String, invitedPlayer: Player) =
claims.list.getByName(owner.uniqueId, name)?.addGuest(invitedPlayer) ?: false
fun unInvitePlayer(owner: Player, name: String, unInvitedPlayer: Player) =
claims.list.getByName(owner.uniqueId, name)?.removeGuest(unInvitedPlayer) ?: false
// This is slow, because a sequential search must be done
fun getClaimAt(chunk: Chunk) = chunkPermCache[chunk]
fun getClaimByName(owner: OfflinePlayer, name: String) = namePermCache[ClaimNameCacheEntry(owner, name)]
fun getClaimsForOwner(owner: OfflinePlayer) = claims.list.getAllForOwner(owner.uniqueId)
private inner class ClaimSelectListener: Listener {
@EventHandler
fun onFreeCamHit(event: PlayerInteractEvent) {
if ((event.action == Action.LEFT_CLICK_AIR || event.action == Action.LEFT_CLICK_BLOCK) && isClaiming(event.player)) {
val claimBuilder = claimBuilders.find { it.who == event.player }!!
claimBuilder.toggleCorner(event.player.location.chunk)
if (claimBuilder.isValidRegion) {
event.player.spigot()
.sendMessage(CommandUtils.successMessage("Region selected! Right-click a block to confirm selections"))
}
} else if (event.action == Action.RIGHT_CLICK_BLOCK && isClaiming(event.player)) {
val claimBuilder = claimBuilders.find { it.who == event.player }!!
if (assertTrue(claimBuilder.isValidRegion, "You have not selected a region", event.player) ||
assertTrue(addClaim(claimBuilder.built), "This claim overlaps with another claim", event.player))
return
freeCamManager.disableFreeCam(event.player)
event.player.spigot().sendMessage(CommandUtils.successMessage("Region claimed!"))
}
}
@EventHandler
fun onPlayerWorldChange(event: PlayerChangedWorldEvent) {
if (isClaiming(event.player)) {
freeCamManager.disableFreeCam(event.player)
event.player.spigot().sendMessage(errorMessage("Cancelled claiming!"))
}
}
}
}

View File

@ -0,0 +1,112 @@
package dev.w1zzrd.spigot.chunkprotector.collection
import java.util.*
import kotlin.Comparator
class BinaryCache<K, V> (cacheSize: Int, private val comparator: Comparator<K>, private val cacheMiss: (K) -> V?, private val keyType: Class<K>) {
companion object {
inline fun <reified K, V> makeCache(cacheSize: Int, comparator: Comparator<K>, noinline cacheMiss: (K) -> V?) =
BinaryCache(cacheSize, comparator, cacheMiss, K::class.java)
inline fun <reified K: Comparable<K>, V> makeCache(cacheSize: Int, noinline cacheMiss: (K) -> V?) =
BinaryCache(cacheSize, Comparable<K>::compareTo, cacheMiss, K::class.java)
}
private val keys = java.lang.reflect.Array.newInstance(keyType, cacheSize) as Array<K>
private val values = Array<Any?>(cacheSize) { null }
private val ages = java.lang.reflect.Array.newInstance(keyType, cacheSize) as Array<K> // Essentially a fifo queue
private var entryCount = 0
private var oldest = 0
fun clearValues(value: V) {
val scratch1 = java.lang.reflect.Array.newInstance(keyType, ages.size) as Array<K>
// Since ages softly depend on key/value entries, it's easiest to process them first
var copyIndex = 0
for (index in oldest until entryCount)
if (values[indexOf(ages[index])] != value)
scratch1[copyIndex++] = ages[index]
if (entryCount == ages.size) {
for (index in 0 until oldest)
if (values[indexOf(ages[index])] != value)
scratch1[copyIndex++] = ages[index]
}
// No change
if (copyIndex == entryCount)
return
// Just re-index the queue so that the oldest entry lies at index 0
System.arraycopy(scratch1, 0, ages, 0, copyIndex)
oldest = 0
copyIndex = 0
val scratch2 = Array<Any?>(values.size){ null }
for (index in 0 until entryCount)
if (values[index] != value) {
scratch1[copyIndex] = keys[index]
scratch2[copyIndex++] = values[index]
}
System.arraycopy(scratch1, 0, keys, 0, copyIndex)
System.arraycopy(scratch2, 0, values, 0, copyIndex)
entryCount -= copyIndex
}
operator fun get(key: K): V? {
var index = indexOf(key)
// Cache hit
if (index >= 0)
return values[index] as V
// Cache miss
index = -(index + 1)
val value = cacheMiss(key) ?: return null
if (entryCount < keys.size) {
System.arraycopy(keys, index, keys, index + 1, entryCount - index)
System.arraycopy(values, index, values, index + 1, entryCount - index)
ages[(oldest + entryCount).rem(ages.size)] = key
++entryCount
} else {
// We're out of spaces. This works
if (index > 0)
--index
// Find oldest entry
val oldestIndex = indexOf(ages[oldest])
if (oldestIndex > index) {
System.arraycopy(keys, index, keys, index + 1, oldestIndex - index)
System.arraycopy(values, index, values, index + 1, oldestIndex - index)
} else if (oldestIndex < index) {
System.arraycopy(keys, oldestIndex + 1, keys, oldestIndex, index - oldestIndex)
System.arraycopy(values, oldestIndex + 1, values, oldestIndex, index - oldestIndex)
}
// Overwrite oldest entry with new entry
ages[oldest] = key
// Re-index age list so that current oldest entry becomes youngest
oldest = (oldest + 1).rem(ages.size)
}
keys[index] = key
values[index] = value
return value
}
private fun indexOf(key: K) =
keys.binarySearch(key, comparator, 0, entryCount)
}

View File

@ -0,0 +1,82 @@
package dev.w1zzrd.spigot.chunkprotector.collection
import dev.w1zzrd.spigot.chunkprotector.collection.BinaryList.Companion.wrapSortedList
import org.bukkit.configuration.serialization.ConfigurationSerializable
class SerializableBinaryList<T>: ConfigurationSerializable {
lateinit var list: BinaryList<T>
private set
constructor(map: Map<String, Any?>) {
if (map.containsKey("list"))
list = wrapSortedList({ a, b -> (a as Comparable<T>).compareTo(b) }, map["list"] as MutableList<T>)
}
constructor(list: BinaryList<T>) {
this.list = list
}
override fun serialize() = mutableMapOf("list" to list.toMutableList())
}
class BinaryList<T> private constructor(private val backing: MutableList<T>, private val comparator: Comparator<in T>): MutableList<T> by backing {
companion object {
fun <K> newBinaryList(comparator: Comparator<in K>, backingFactory: () -> MutableList<K> = ::ArrayList) =
BinaryList(backingFactory(), comparator)
fun <K: Comparable<K>> newBinaryList(backingFactory: () -> MutableList<K> = ::ArrayList) =
newBinaryList({ a, b -> a.compareTo(b) }, backingFactory)
fun <K> wrapSortedList(comparator: Comparator<in K>, backingList: MutableList<K>) =
BinaryList(backingList, comparator)
fun <K: Comparable<K>> wrapSortedList(backingList: MutableList<K>) =
wrapSortedList({ a, b -> a.compareTo(b) }, backingList)
}
override fun add(element: T): Boolean {
val index = binarySearch(element, comparator)
if (index >= 0)
return false
backing.add(-(index + 1), element)
return true
}
override fun addAll(elements: Collection<T>) = elements.map(this::add).reduce(Boolean::or)
override fun addAll(index: Int, elements: Collection<T>): Boolean {
throw UnsupportedOperationException("Sorted list does not support inserting elements at a specific index")
}
override fun remove(element: T): Boolean {
val index = binarySearch(element, comparator)
if (index < 0)
return false
backing.removeAt(index)
return true
}
override fun removeAll(elements: Collection<T>) = elements.map(this::remove).reduce(Boolean::or)
override fun contains(element: T) = binarySearch(element, comparator) >= 0
fun <K : Comparable<K>> contains(element: K, convert: T.() -> K) = binarySearch { it.convert().compareTo(element) } >= 0
override fun indexOf(element: T): Int {
val index = binarySearch(element, comparator)
if (index < 0)
return -1
return index
}
fun getOrAdd(element: T): T {
val index = binarySearch(element, comparator)
return if (index >= 0)
backing[index]
else {
backing.add(-(index + 1), element)
element
}
}
}

View File

@ -0,0 +1,32 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.chunkprotector.listener.TabCompleteListener
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class ClaimCommand(private val claimManager: ClaimManager): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
if (assertTrue(sender is Player, "Only players can claim land", sender))
return true
val isClaiming = claimManager.isClaiming(sender as Player)
if (assertTrue(isClaiming || args.size == 1, "Expected exactly one argument", sender) ||
assertTrue(!isClaiming || args.isEmpty(), "You are already claiming a region!", sender) ||
assertTrue(isClaiming || claimManager.getClaimByName(sender, args[0]) == null, "You already have a claim with that name", sender))
return true
if (claimManager.toggleClaim(sender, if(isClaiming) "" else args[0]))
sender.spigot().sendMessage(CommandUtils.successMessage("Enabled claim mode"))
else
sender.spigot().sendMessage(CommandUtils.successMessage("Cancelled claim!"))
return true
}
}

View File

@ -0,0 +1,61 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.Claim
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.chunkprotector.kotlin.assertNotNull
import dev.w1zzrd.spigot.chunkprotector.listener.CompletionProcessor
import dev.w1zzrd.spigot.wizcompat.OfflinePlayers
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.successMessage
import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
private val OPTIONS = arrayOf(
Claim::allowAllLiquids,
Claim::allowEntityInteract,
Claim::allowGuestLiquids,
Claim::allowPlayerEntityInteract,
Claim::allowTNT,
Claim::disablePVP
)
class ClaimOptionCommand(private val claimManager: ClaimManager): TabCompletionCommandExecutor() {
override val completionProcessor: CompletionProcessor
get() = { sender, args ->
if (args.isEmpty() || (args.size == 1 && args[0].isBlank()))
if(sender is Player && !sender.hasPermission("chunkprotector.bypass")) claimManager.getClaimsForOwner(sender).map(Claim::name)
else OfflinePlayers.getAllKnownPlayers(
Bukkit.getServer(),
true
).mapNotNull { Bukkit.getOfflinePlayer(it).name }
else emptyList()
}
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player || args.size == 4, "Command can only be run by players", sender) && return true
assertTrue(
(sender is Player && (args.size in 2..3 || (sender.hasPermission("chunkprotector.bypass") && args.size in 2..4))) || (sender !is Player && args.size == 4),
"Wrong number of arguments",
sender
) && return true
// It has been written. Let it nevermore be read
(assertNotNull(OPTIONS.firstOrNull { it.name.equals(args[args.size - 2], ignoreCase = true) }, "Unknown option", sender) ?: return true).set(
if(args.size == 2)
assertNotNull(claimManager.getClaimAt((sender as Player).location.chunk), "This location is unclaimed", sender) ?: return true
else
assertNotNull(claimManager.getClaimByName(assertNotNull(
if(args.size == 3) sender as Player
else OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[0]), "Could not find a player with the given name", sender
) ?: return true, args[1]), "No claim with the given name could be found", sender) ?: return true,
args[args.size - 1].lowercase().toBoolean()
)
sender.spigot().sendMessage(successMessage("Option has been updated!"))
return true
}
}

View File

@ -0,0 +1,24 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.*
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class ClaimOwnerCommand(private val claimManager: ClaimManager): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player, "Only players can check the owner of a chunk", sender) && return true
assertTrue(args.isEmpty(), "No arguments expected for this command", sender) && return true
sender as Player
val claim = claimManager.getClaimAt(sender.location.chunk)
assertTrue(claim != null, "No one owns this chunk", sender) && return true
claim!!
sender.spigot().sendMessage(successMessage("Chunk is owned by ${if(claim.owner == sender.uniqueId) "you" else Bukkit.getOfflinePlayer(claim.owner).name} (name: ${claim.name})"))
return true
}
}

View File

@ -0,0 +1,38 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.wizcompat.OfflinePlayers
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.successMessage
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class InviteCommand(private val claimManager: ClaimManager): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player || args.size == 3, "Command can only be issued by players", sender) && true
assertTrue(
(sender is Player && args.size in 1..2) || ((sender !is Player || sender.hasPermission("chunkprotector.bypass")) && args.size == 3),
"Wrong number of arguments supplied!",
sender
) && return true
val targetPlayer = if(args.size == 3) OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[2]) else sender as Player
assertTrue(targetPlayer != null, "Could not find given player", sender) && return true
assertTrue(args[0] != targetPlayer!!.name, "The owner of a claim cannot be invited to said claim", sender) && return true
val claim = if(args.size == 1) claimManager.getClaimAt((targetPlayer as Player).location.chunk) else claimManager.getClaimByName(targetPlayer, args[1])
assertTrue(claim != null && claim.owner == targetPlayer.uniqueId, "Could not find a claim with that name", sender) && return true
val guest = OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[0])
assertTrue(guest != null, "Cannot find a player with that name", sender) && return true
if (claim!!.addGuest(guest!!) && guest.isOnline)
(guest as Player).spigot().sendMessage(successMessage("You have been invited to \"${claim.name}\" (owned by ${targetPlayer.name})"))
sender.spigot().sendMessage(successMessage("${guest.name} has been invited to ${claim.name}"))
return true
}
}

View File

@ -0,0 +1,34 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.Claim
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.wizcompat.OfflinePlayers
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.successMessage
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class ListClaimsCommand(private val claimManager: ClaimManager): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player || args.size == 1, "Command can only be issued by players", sender) && return true
assertTrue(
((sender !is Player || sender.hasPermission("chunkprotector.bypass")) && args.size == 1) || (sender is Player && args.isEmpty()),
"This command expects no arguments",
sender
) && return true
val targetPlayer = if(args.isNotEmpty()) OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[0]) else sender as Player
assertTrue(targetPlayer != null, "Could not find given player", sender) && return true
val owned = claimManager.getClaimsForOwner(targetPlayer!!)
if (owned.isEmpty())
sender.spigot().sendMessage(successMessage("${if(targetPlayer != sender) "${targetPlayer.name} has" else "You have" } no claims"))
else
sender.spigot().sendMessage(successMessage("Claims: ${owned.map(Claim::name).joinToString(", ")}"))
return true
}
}

View File

@ -0,0 +1,79 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimChunk
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.wizcompat.OfflinePlayers
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import org.bukkit.Bukkit
import org.bukkit.Color
import org.bukkit.Particle
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
private val inaccessible = Particle.DustOptions(Color.fromRGB(255, 0, 0), 10.0F)
private val accessible = Particle.DustOptions(Color.fromRGB(0, 255, 0), 10.0F)
class ShowClaimCommand(private val claimManager: ClaimManager, private val plugin: Plugin): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player, "Claims can only be visualised by players", sender) && return true
assertTrue(args.size <= 2, "Too many arguments supplied", sender) && return true
sender as Player
val targetPlayer = if(args.size == 2) OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[1]) else sender
assertTrue(targetPlayer != null, "Specified player could not be found", sender) && return true
targetPlayer!!
val targetRegion = if(args.isEmpty()) claimManager.getClaimAt(sender.location.chunk) else claimManager.getClaimByName(targetPlayer, args[0])
assertTrue(targetRegion != null, "Region is either not claimed, or a claim with the given name does not exist", sender) && return true
targetRegion!!
fun ClaimChunk.asCoords() = (chunkX shl 4).toDouble() to (chunkZ shl 4).toDouble()
fun Pair<Double, Double>.lerp(other: Pair<Double, Double>) = (first + other.first).div(2.0) to (second + other.second).div(2.0)
val baseVerts = arrayOf(
ClaimChunk(targetRegion.topLeft.chunkX, targetRegion.topLeft.chunkZ + 1).asCoords(),
ClaimChunk(targetRegion.topLeft.chunkX, targetRegion.bottomRight.chunkZ).asCoords(),
ClaimChunk(targetRegion.bottomRight.chunkX + 1, targetRegion.bottomRight.chunkZ).asCoords(),
ClaimChunk(targetRegion.bottomRight.chunkX + 1, targetRegion.topLeft.chunkZ + 1).asCoords()
)
val allVerts = arrayOf(
*baseVerts,
baseVerts[0].lerp(baseVerts[1]),
baseVerts[1].lerp(baseVerts[2]),
baseVerts[2].lerp(baseVerts[3]),
baseVerts[3].lerp(baseVerts[0])
)
var count = 0
var task: BukkitTask? = null
val particleData = if (targetRegion.isAccessible(sender)) accessible else inaccessible
task = Bukkit.getScheduler().runTaskTimer(
plugin,
Runnable {
if (count == 40)
task!!.cancel()
count += 1
for (vert in allVerts)
sender.spawnParticle(
Particle.REDSTONE,
vert.first,
sender.location.y + 1.0,
vert.second,
10,
particleData
)
},
0,
10
)
return true
}
}

View File

@ -0,0 +1,8 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.listener.CompletionProcessor
import org.bukkit.command.CommandExecutor
abstract class TabCompletionCommandExecutor: CommandExecutor {
abstract val completionProcessor: CompletionProcessor
}

View File

@ -0,0 +1,29 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.wizcompat.OfflinePlayers
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.successMessage
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class UnClaimCommand(private val claimManager: ClaimManager): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player || args.size == 2, "Command can only be run by players", sender) && return true
assertTrue((sender is Player && args.size in 0..1) || ((sender !is Player || sender.hasPermission("chunkprotector.bypass")) && args.size == 2), "Wrong number of arguments", sender) && return true
val targetPlayer = if (args.size == 2) OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[1]) else sender as Player
assertTrue(targetPlayer != null, "Could not find target player", sender) && return true
val claim = if(args.isEmpty()) claimManager.getClaimAt((targetPlayer!! as Player).location.chunk) else claimManager.getClaimByName(targetPlayer!!, args[0])
assertTrue(claim != null, "Could not find a claim with that name", sender) && return true
claimManager.removeClaim(claim!!)
sender.spigot().sendMessage(successMessage("Unclaimed ${args[0]}"))
return true
}
}

View File

@ -0,0 +1,38 @@
package dev.w1zzrd.spigot.chunkprotector.command
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import dev.w1zzrd.spigot.wizcompat.OfflinePlayers
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.*
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class UnInviteCommand(private val claimManager: ClaimManager): CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
assertTrue(sender is Player || args.size == 3, "Command can only be issued by players", sender) && return true
assertTrue(
args.size in 1..2 || ((sender !is Player || sender.hasPermission("chunkprotector.bypass")) && args.size == 3),
"Wrong number of arguments supplied!",
sender
) && return true
val targetPlayer = if(args.size == 3) OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[2]) else sender as Player
assertTrue(targetPlayer != null, "Could not find given player", sender) && return true
assertTrue(args[0] != targetPlayer!!.name, "The owner of a claim cannot be uninvited from said claim", sender) && return true
val claim = if(args.size == 1) claimManager.getClaimAt((targetPlayer as Player).location.chunk) else claimManager.getClaimByName(targetPlayer, args[1])
assertTrue(claim != null && claim.owner == targetPlayer.uniqueId, "Could not find a claim with that name", sender) && return true
val guest = OfflinePlayers.getKnownPlayer(Bukkit.getServer(), args[0])
assertTrue(guest != null, "Cannot find a player with that name", sender) && return true
if (claim!!.removeGuest(guest!!) && guest.isOnline)
(guest as Player).spigot().sendMessage(errorMessage("You have been uninvited from \"${claim.name}\" (owned by ${targetPlayer.name})"))
sender.spigot().sendMessage(successMessage("${guest.name} is no longer invited to ${claim.name}"))
return true
}
}

View File

@ -0,0 +1,101 @@
package dev.w1zzrd.spigot.chunkprotector.freecam
import dev.w1zzrd.spigot.wizcompat.packet.Players
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.HandlerList
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.event.player.PlayerTeleportEvent
import org.bukkit.plugin.Plugin
class FreeCamManager {
private val freeCammers = ArrayList<FreeCammer>()
private val freeCamEventListener = FreeCamEventListener()
private val freeCamEnableListeners = ArrayList<(Player) -> Unit>()
private val freeCamDisableListeners = ArrayList<(Player) -> Unit>()
fun disableFreeCam(player: Player): Boolean {
val index = freeCammers.binarySearch(FreeCammer(player))
if (index < 0)
return false
// Disable before removal to prevent TOCTOU (in case I decide to multi-thread this)
freeCammers[index].disable()
freeCammers.removeAt(index)
return true
}
fun enableFreeCam(player: Player): Boolean {
val freeCammer = FreeCammer(player, player.location.clone(), player.gameMode)
val index = freeCammers.binarySearch(freeCammer)
if (index >= 0)
return false
// Enable after insertion to prevent TOCTOU (in case I decide to multi-thread this)
freeCammers.add(-(index + 1), freeCammer)
freeCammer.enable()
return true
}
fun toggleFreeCam(player: Player): Boolean {
val freeCammer = FreeCammer(player, player.location.clone(), player.gameMode)
val index = freeCammers.binarySearch(freeCammer)
return if (index >= 0) {
freeCammers[index].disable()
freeCammers.removeAt(index)
false
} else {
freeCammers.add(-(index + 1), freeCammer)
freeCammer.enable()
true
}
}
fun isFreeCamming(player: Player) = freeCammers.binarySearch(FreeCammer(player)) >= 0
fun addOnPlayerEnterFreeCam(callback: (Player) -> Unit) = freeCamEnableListeners.add(callback)
fun addOnPlayerExitFreeCam(callback: (Player) -> Unit) = freeCamDisableListeners.add(callback)
fun onEnable(plugin: Plugin) = plugin.server.pluginManager.registerEvents(freeCamEventListener, plugin)
fun onDisable() {
HandlerList.unregisterAll(freeCamEventListener)
freeCammers.forEach(FreeCammer::disable)
freeCammers.clear()
}
private inner class FreeCamEventListener: Listener {
@EventHandler
fun onPlayerQuit(event: PlayerQuitEvent) = disableFreeCam(event.player)
}
inner class FreeCammer(val player: Player, val startLocation: Location? = null, val originalMode: GameMode? = null): Comparable<FreeCammer> {
fun enable() {
player.gameMode = GameMode.SPECTATOR
Players.sendPlayerGameModePacket(player, GameMode.CREATIVE)
freeCamEnableListeners.forEach { it(player) }
}
fun disable() {
player.teleport(startLocation!!, PlayerTeleportEvent.TeleportCause.PLUGIN)
player.gameMode = originalMode!!
Players.sendPlayerGameModePacket(player, originalMode)
freeCamDisableListeners.forEach { it(player) }
}
override fun compareTo(other: FreeCammer) = player.uniqueId.compareTo(other.player.uniqueId)
}
}

View File

@ -0,0 +1,20 @@
package dev.w1zzrd.spigot.chunkprotector.freecam
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.entity.Player
import org.bukkit.event.player.PlayerTeleportEvent
data class FreeCammer(val player: Player, val startLocation: Location? = null, val originalMode: GameMode? = null): Comparable<FreeCammer> {
fun enable() {
player.gameMode = GameMode.SPECTATOR
}
fun disable() {
player.teleport(startLocation!!, PlayerTeleportEvent.TeleportCause.PLUGIN)
player.gameMode = originalMode!!
}
override fun compareTo(other: FreeCammer) = player.uniqueId.compareTo(other.player.uniqueId)
}

View File

@ -0,0 +1,15 @@
package dev.w1zzrd.spigot.chunkprotector.kotlin
import dev.w1zzrd.spigot.wizcompat.command.CommandUtils.assertTrue
import org.bukkit.command.CommandSender
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
fun <T> assertNotNull(value: T?, errorMessage: String, sender: CommandSender): T? {
contract { returnsNotNull() implies (value != null) }
assertTrue(value != null, errorMessage, sender)
return value
}

View File

@ -0,0 +1,143 @@
package dev.w1zzrd.spigot.chunkprotector.listener
import dev.w1zzrd.spigot.chunkprotector.claim.Claim
import dev.w1zzrd.spigot.chunkprotector.claim.ClaimManager
import org.bukkit.Bukkit
import org.bukkit.Chunk
import org.bukkit.Location
import org.bukkit.block.Block
import org.bukkit.entity.Entity
import org.bukkit.entity.Monster
import org.bukkit.entity.Player
import org.bukkit.event.Cancellable
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.BlockBreakEvent
import org.bukkit.event.block.BlockExplodeEvent
import org.bukkit.event.block.BlockFromToEvent
import org.bukkit.event.block.BlockPlaceEvent
import org.bukkit.event.entity.EntityDamageByEntityEvent
import org.bukkit.event.entity.EntityExplodeEvent
import org.bukkit.event.entity.EntityTargetEvent
import org.bukkit.event.entity.PlayerLeashEntityEvent
import org.bukkit.event.player.PlayerInteractEntityEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.event.player.PlayerShearEntityEvent
@Suppress("unused")
class PlayerActionListener(private val claimManager: ClaimManager): Listener {
@EventHandler
fun onPlayerInteract(event: PlayerInteractEvent) =
doConditionalCancellation(event.player, event)
@EventHandler
fun onPlayerBreakBlock(event: BlockBreakEvent) =
doConditionalCancellation(event.player, event, event.block.location)
@EventHandler
fun onPlayerPlaceBlock(event: BlockPlaceEvent) =
doConditionalCancellation(event.player, event, event.blockPlaced.location)
@EventHandler
fun onEntityExplode(event: EntityExplodeEvent) {
if (event.isCancelled)
return
filterProtectedBlocks(event.blockList(), Claim::allowEntityInteract)
}
@EventHandler
fun onBlockExplode(event: BlockExplodeEvent) {
if (event.isCancelled)
return
filterProtectedBlocks(event.blockList(), Claim::allowTNT)
}
@EventHandler
fun onLiquidMove(event: BlockFromToEvent) {
if (event.isCancelled)
return
// Cancel the event if a liquid is moving into a claimed area from an area that is not owned by the claimer
val toClaim = claimManager.getClaimAt(event.toBlock.chunk) ?: return
val fromClaim = claimManager.getClaimAt(event.toBlock.location.subtract(event.face.direction).chunk)
if (fromClaim == null) {
event.isCancelled = true
return
}
if (toClaim.allowAllLiquids || toClaim.owner == fromClaim.owner || (toClaim.allowGuestLiquids && toClaim.hasGuest(Bukkit.getPlayer(fromClaim.owner)!!)))
return
event.isCancelled = true
}
@EventHandler
fun onPlayerInteractEntity(event: PlayerInteractEntityEvent) {
if (!event.isCancelled && checkEntityInteraction(event.player, event.rightClicked, Claim::allowPlayerEntityInteract))
event.isCancelled = true
}
@EventHandler
fun onPlayerShearEntity(event: PlayerShearEntityEvent) {
if (!event.isCancelled && checkEntityInteraction(event.player, event.entity, Claim::allowPlayerEntityInteract))
event.isCancelled = true
}
@EventHandler
fun onPlayerLeashEntity(event: PlayerLeashEntityEvent) {
if (!event.isCancelled && checkEntityInteraction(event.player, event.entity, Claim::allowPlayerEntityInteract))
event.isCancelled = true
}
@EventHandler
fun onPlayerAttractAnimal(event: EntityTargetEvent) {
if (!event.isCancelled && event.target is Player && event.reason == EntityTargetEvent.TargetReason.TEMPT && checkEntityInteraction(event.target as Player, event.entity, Claim::allowPlayerEntityInteract))
event.isCancelled = true
}
@EventHandler
fun onPlayerAttackMob(event: EntityDamageByEntityEvent) {
if (!event.isCancelled && checkEntityInteraction(
event.damager,
event.entity,
if(event.damager is Player) Claim::allowPlayerEntityInteract
else Claim::allowEntityInteract
))
event.isCancelled = true
}
private fun checkEntityInteraction(source: Entity, entity: Entity, permissionType: Claim.() -> Boolean): Boolean {
if (entity is Monster && entity.customName == null)
return false
val claim = claimManager.getClaimAt(entity.location.chunk)
return (claim != null && claim.disablePVP && source is Player && entity is Player) || !(entity is Player || claim == null || claim.permissionType() || (source is Player && (claim.isAccessible(source) || source.hasPermission("chunkprotector.ignore"))))
}
private fun filterProtectedBlocks(changedBlocks: MutableList<Block>, configCheck: Claim.() -> Boolean) {
val chunks = HashMap<Chunk, MutableList<Block>>()
for (block in changedBlocks) {
if (block.chunk !in chunks)
chunks[block.chunk] = ArrayList()
chunks[block.chunk]!!.add(block)
}
for ((chunk, blocks) in chunks) {
val claim = claimManager.getClaimAt(chunk)
if (claim != null && !claim.configCheck())
changedBlocks.removeAll(blocks)
}
}
private fun doConditionalCancellation(player: Player, event: Cancellable, eventLocation: Location = player.location) {
if (event.isCancelled || player.hasPermission("chunkprotector.ignore"))
return
if (claimManager.getClaimAt(eventLocation.chunk)?.isAccessible(player) == false)
event.isCancelled = true
}
}

View File

@ -0,0 +1,34 @@
package dev.w1zzrd.spigot.chunkprotector.listener
import org.bukkit.command.CommandSender
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.server.TabCompleteEvent
typealias CompletionProcessor = (CommandSender, List<String>) -> Collection<String>
private val SPLITTER = Regex(" +")
class TabCompleteListener: Listener {
private val tabCompleters = HashMap<String, CompletionProcessor>()
@EventHandler
fun onTabComplete(event: TabCompleteEvent) {
if (!event.isCancelled) {
val split = event.buffer.split(SPLITTER)
event.completions.addAll((tabCompleters[split[0]] ?: return).invoke(event.sender, if(split.size > 1) split.subList(1, split.size) else emptyList()))
}
}
fun registerTabCompleter(command: String, processor: CompletionProcessor) {
tabCompleters[command] = processor
}
fun unRegisterTabCompleter(command: String) {
tabCompleters.remove(command)
}
fun unRegisterAll() {
tabCompleters.clear()
}
}