├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── META-INF └── MANIFEST.MF ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── src ├── net │ └── deltik │ │ └── mc │ │ └── signedit │ │ ├── ArgParser.java │ │ ├── ArgParserArgs.java │ │ ├── ChatComms.java │ │ ├── ChatCommsModule.java │ │ ├── Configuration.java │ │ ├── ConfigurationWatcher.java │ │ ├── CraftBukkitReflector.java │ │ ├── LineSelectorParser.java │ │ ├── ResourceList.java │ │ ├── SignEditPlugin.java │ │ ├── SignEditPluginComponent.java │ │ ├── SignText.java │ │ ├── SignTextClipboardManager.java │ │ ├── SignTextHistory.java │ │ ├── SignTextHistoryManager.java │ │ ├── UserComms.java │ │ ├── commands │ │ ├── SignCommand.java │ │ ├── SignCommandModule.java │ │ └── SignCommandTabCompleter.java │ │ ├── exceptions │ │ ├── BlockStateNotPlacedException.java │ │ ├── ForbiddenSignEditException.java │ │ ├── ForbiddenWaxedSignEditException.java │ │ ├── LineSelectionException.java │ │ ├── MissingLineSelectionException.java │ │ ├── NullClipboardException.java │ │ ├── NumberParseLineSelectionException.java │ │ ├── OutOfBoundsLineSelectionException.java │ │ ├── RangeOrderLineSelectionException.java │ │ ├── RangeParseLineSelectionException.java │ │ ├── SignEditorInvocationException.java │ │ └── SignTextHistoryStackBoundsException.java │ │ ├── integrations │ │ ├── BreakReplaceSignEditValidator.java │ │ ├── NoopSignEditValidator.java │ │ ├── SignEditValidator.java │ │ ├── SignEditValidatorModule.java │ │ └── StandardSignEditValidator.java │ │ ├── interactions │ │ ├── BookUiSignEditInteraction.java │ │ ├── CopySignEditInteraction.java │ │ ├── CutSignEditInteraction.java │ │ ├── InteractionCommand.java │ │ ├── PasteSignEditInteraction.java │ │ ├── SetSignEditInteraction.java │ │ ├── SignEditInteraction.java │ │ ├── SignEditInteractionManager.java │ │ ├── SignEditInteractionModule.java │ │ ├── UiSignEditInteraction.java │ │ └── WaxSignEditInteraction.java │ │ ├── listeners │ │ ├── BookUiSignEditListener.java │ │ ├── CoreSignEditListener.java │ │ ├── SignEditListener.java │ │ └── SignEditListenerModule.java │ │ ├── shims │ │ ├── CalculatedBlockHitResult.java │ │ ├── IBlockHitResult.java │ │ ├── ISignSide.java │ │ ├── PreciseBlockHitResult.java │ │ ├── SideShim.java │ │ ├── SignHelpers.java │ │ ├── SignShim.java │ │ └── SignSideShim.java │ │ └── subcommands │ │ ├── CancelSignSubcommand.java │ │ ├── ClearSignSubcommand.java │ │ ├── CopySignSubcommand.java │ │ ├── CutSignSubcommand.java │ │ ├── HelpSignSubcommand.java │ │ ├── PasteSignSubcommand.java │ │ ├── PerSubcommand.java │ │ ├── RedoSignSubcommand.java │ │ ├── SetSignSubcommand.java │ │ ├── SignSubcommand.java │ │ ├── SignSubcommandComponent.java │ │ ├── StatusSignSubcommand.java │ │ ├── Subcommand.java │ │ ├── SubcommandName.java │ │ ├── UiSignSubcommand.java │ │ ├── UndoSignSubcommand.java │ │ ├── UnwaxSignSubcommand.java │ │ ├── VersionSignSubcommand.java │ │ └── WaxSignSubcommand.java └── resources │ ├── Comms.properties │ ├── Comms_de.properties │ ├── Comms_en.properties │ ├── Comms_nl.properties │ ├── Comms_zh.properties │ ├── Comms_zh_CN.properties │ ├── Comms_zh_HK.properties │ ├── Comms_zh_TW.properties │ ├── README.Comms.txt │ ├── config.yml.j2 │ └── plugin.yml └── test ├── net └── deltik │ └── mc │ └── signedit │ ├── ArgParserTest.java │ ├── ChatCommsTest.java │ ├── LineSelectorParserTest.java │ ├── SignTextTest.java │ ├── UserCommsTest.java │ ├── commands │ └── SignCommandTabCompleterTest.java │ └── subcommands │ ├── HelpSignSubcommandTest.java │ └── VersionSignSubcommandTest.java └── resources └── mockito-extensions └── org.mockito.plugins.MockMaker /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | env: 14 | JVM_OPTS: -Xmx3200m 15 | TERM: dumb 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up JDK 17 23 | uses: actions/setup-java@v4 24 | with: 25 | distribution: 'adopt' 26 | java-version: '17' 27 | 28 | - name: Grant execute permission for gradlew 29 | run: chmod +x gradlew 30 | 31 | - name: Cache Gradle and Maven dependencies 32 | uses: actions/cache@v4 33 | with: 34 | path: | 35 | ~/.m2 36 | ~/.gradle/caches 37 | ~/.gradle/wrapper 38 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle/wrapper/gradle-wrapper.properties') }} 39 | restore-keys: | 40 | ${{ runner.os }}-gradle- 41 | 42 | - name: Download dependencies 43 | run: ./gradlew dependencies 44 | 45 | - name: Run tests 46 | run: ./gradlew test 47 | 48 | - name: Clean build environment 49 | run: ./gradlew clean 50 | 51 | - name: Build JAR 52 | run: ./gradlew jar 53 | 54 | - name: Archive artifacts 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: JAR Artifact(s) 58 | path: build/libs/*.jar 59 | 60 | - name: Extract release notes from CHANGELOG.md 61 | if: startsWith(github.ref, 'refs/tags/') 62 | id: extract_release_notes 63 | uses: actions/github-script@v7 64 | with: 65 | result-encoding: string 66 | script: | 67 | const fs = require('fs'); 68 | const tag = context.ref.replace('refs/tags/', ''); 69 | const tagPattern = '^##\\s*' + tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\s*'; 70 | 71 | const versionPattern = new RegExp(tagPattern); 72 | 73 | const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); 74 | 75 | const lines = changelog.split('\n'); 76 | 77 | let inSection = false; 78 | let releaseTitle = ''; 79 | let releaseBodyLines = []; 80 | for (let i = 0; i < lines.length; i++) { 81 | const line = lines[i]; 82 | if (versionPattern.test(line)) { 83 | releaseTitle = line.replace(/^##\s+/, '').trim(); 84 | inSection = true; 85 | continue; 86 | } 87 | if (inSection) { 88 | if (/^##\s+/.test(line)) { 89 | break; 90 | } else { 91 | releaseBodyLines.push(line); 92 | } 93 | } 94 | } 95 | const releaseBody = releaseBodyLines.join('\n').trim(); 96 | 97 | core.setOutput('release_title', releaseTitle); 98 | core.setOutput('release_body', releaseBody); 99 | 100 | - name: Upload release asset 101 | if: startsWith(github.ref, 'refs/tags/') 102 | uses: softprops/action-gh-release@v1 103 | with: 104 | files: build/libs/*.jar 105 | body: ${{ steps.extract_release_notes.outputs.release_body }} 106 | name: ${{ steps.extract_release_notes.outputs.release_title }} 107 | env: 108 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | .gradle/* 3 | *.iml 4 | .idea 5 | out 6 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | buildscript { 21 | repositories { 22 | mavenCentral() 23 | } 24 | } 25 | 26 | plugins { 27 | id 'java' 28 | id 'java-library' 29 | id 'application' 30 | id 'com.palantir.git-version' version '0.12.3' 31 | } 32 | 33 | def name = 'SignEdit' 34 | 35 | def verDetails = versionDetails() 36 | def versionWithoutLeadingV = gitVersion().replaceFirst(/^v/, "") 37 | if (verDetails.isCleanTag) { 38 | version versionWithoutLeadingV 39 | } else { 40 | version versionWithoutLeadingV + '-SNAPSHOT' 41 | } 42 | 43 | java { 44 | toolchain { 45 | languageVersion.set(JavaLanguageVersion.of(8)) 46 | } 47 | } 48 | 49 | repositories { 50 | mavenCentral() 51 | maven { 52 | url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" 53 | } 54 | } 55 | 56 | configurations { 57 | extraLibs 58 | } 59 | 60 | application { 61 | mainClass.set("net.deltik.mc.signedit.SignEditPlugin") 62 | } 63 | 64 | dependencies { 65 | implementation group: 'org.bukkit', name: 'bukkit', version: '1.13-R0.1-SNAPSHOT' 66 | // implementation group: 'org.spigotmc', name: 'spigot-api', version: '1.20.1-R0.1-SNAPSHOT' 67 | implementation 'com.intellij:annotations:12.0@jar' 68 | 69 | implementation group: 'com.google.dagger', name: 'dagger', version: '2.36' 70 | extraLibs group: 'com.google.dagger', name: 'dagger', version: '2.36' 71 | annotationProcessor group: 'com.google.dagger', name: 'dagger-compiler', version: '2.36' 72 | extraLibs group: 'javax.inject', name: 'javax.inject', version: '1' 73 | 74 | testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.7.2' 75 | testImplementation group: 'org.junit.vintage', name: 'junit-vintage-engine', version: '5.7.2' 76 | testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.10.0' 77 | } 78 | 79 | sourceSets { 80 | main { 81 | java { 82 | srcDirs = ['src'] 83 | } 84 | resources { 85 | srcDirs = ['src/resources'] 86 | } 87 | } 88 | 89 | test { 90 | java { 91 | srcDir 'test' 92 | } 93 | resources { 94 | srcDirs = ['test/resources'] 95 | } 96 | } 97 | } 98 | 99 | jar { 100 | from { 101 | configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) } 102 | } 103 | } 104 | 105 | processResources { 106 | def expandProperties = [ 107 | pluginName : name, 108 | pluginVersion: version, 109 | mainClass : application.mainClass, 110 | ] 111 | inputs.properties(expandProperties) 112 | with copySpec { 113 | from 'src/resources' 114 | expand(expandProperties) 115 | duplicatesStrategy(DuplicatesStrategy.INCLUDE) 116 | } 117 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltik/SignEdit/146b3f6ebba7efbc7be4ac130ce46924ad681715/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | plugins { 21 | id "org.gradle.toolchains.foojay-resolver-convention" version "0.5.0" 22 | } 23 | 24 | rootProject.name = 'SignEdit' 25 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/ArgParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import net.deltik.mc.signedit.commands.SignCommand; 23 | import net.deltik.mc.signedit.exceptions.LineSelectionException; 24 | import net.deltik.mc.signedit.exceptions.NumberParseLineSelectionException; 25 | import net.deltik.mc.signedit.subcommands.SubcommandName; 26 | 27 | import javax.inject.Inject; 28 | import java.util.Arrays; 29 | import java.util.LinkedList; 30 | import java.util.List; 31 | import java.util.Set; 32 | 33 | import static net.deltik.mc.signedit.LineSelectorParser.NO_LINES_SELECTED; 34 | 35 | public class ArgParser { 36 | private final Configuration config; 37 | private final Set subcommandNames; 38 | 39 | String subcommand; 40 | int[] selectedLines = NO_LINES_SELECTED; 41 | LineSelectionException selectedLinesError; 42 | List remainder; 43 | 44 | @Inject 45 | public ArgParser( 46 | Configuration config, 47 | @ArgParserArgs String[] args, 48 | @SubcommandName Set subcommandNames 49 | ) { 50 | this.config = config; 51 | this.subcommandNames = subcommandNames; 52 | parseArgs(args); 53 | } 54 | 55 | public String getSubcommand() { 56 | return subcommand; 57 | } 58 | 59 | public int[] getLinesSelection() { 60 | return selectedLines; 61 | } 62 | 63 | public LineSelectionException getLinesSelectionError() { 64 | return selectedLinesError; 65 | } 66 | 67 | public List getRemainder() { 68 | return remainder; 69 | } 70 | 71 | private void parseArgs(String[] args) { 72 | List argsArray = new LinkedList<>(Arrays.asList(args)); 73 | selectedLines = NO_LINES_SELECTED; 74 | remainder = argsArray; 75 | 76 | if (argsArray.size() == 0) { 77 | subcommand = SignCommand.SUBCOMMAND_NAME_HELP; 78 | return; 79 | } 80 | 81 | String maybeSubcommandOrShorthandLines = remainder.remove(0); 82 | String maybeSubcommand = maybeSubcommandOrShorthandLines.toLowerCase(); 83 | if (subcommandNames.contains(maybeSubcommand)) { 84 | subcommand = maybeSubcommand; 85 | } 86 | if (subcommand == null) { 87 | try { 88 | parseLineSelection(maybeSubcommandOrShorthandLines); 89 | if (selectedLines.length > 0) { 90 | if (remainder.size() == 0) subcommand = "clear"; 91 | else subcommand = "set"; 92 | } 93 | return; 94 | } catch (NumberParseLineSelectionException e) { 95 | remainder.add(0, maybeSubcommandOrShorthandLines); 96 | } catch (LineSelectionException e) { 97 | remainder.add(0, maybeSubcommandOrShorthandLines); 98 | subcommand = "set"; 99 | } 100 | } 101 | if (subcommand == null) { 102 | subcommand = SignCommand.SUBCOMMAND_NAME_HELP; 103 | } 104 | if (remainder.size() == 0 || subcommand.equals(SignCommand.SUBCOMMAND_NAME_HELP)) { 105 | return; 106 | } 107 | 108 | try { 109 | parseLineSelection(remainder.get(0)); 110 | remainder.remove(0); 111 | } catch (LineSelectionException e) { 112 | selectedLines = NO_LINES_SELECTED; 113 | selectedLinesError = e; 114 | } 115 | } 116 | 117 | private void parseLineSelection(String rawLineGroups) { 118 | LineSelectorParser lineSelectorParser = new LineSelectorParser(config); 119 | this.selectedLines = lineSelectorParser.toSelectedLines(rawLineGroups); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/ArgParserArgs.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import javax.inject.Qualifier; 23 | import java.lang.annotation.Documented; 24 | import java.lang.annotation.Retention; 25 | import java.lang.annotation.RetentionPolicy; 26 | 27 | @Qualifier 28 | @Documented 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface ArgParserArgs { 31 | } 32 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/ChatCommsModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import dagger.BindsInstance; 23 | import dagger.Module; 24 | import dagger.Subcomponent; 25 | import org.bukkit.command.CommandSender; 26 | 27 | @Module(subcomponents = { 28 | ChatCommsModule.ChatCommsComponent.class 29 | }) 30 | public abstract class ChatCommsModule { 31 | @Subcomponent 32 | public interface ChatCommsComponent { 33 | ChatComms comms(); 34 | 35 | @Subcomponent.Builder 36 | abstract class Builder { 37 | public abstract ChatCommsComponent build(); 38 | 39 | @BindsInstance 40 | public abstract Builder commandSender(CommandSender commandSender); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/ConfigurationWatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import org.bukkit.plugin.Plugin; 23 | 24 | import javax.inject.Inject; 25 | import javax.inject.Singleton; 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.nio.file.*; 29 | import java.nio.file.attribute.FileTime; 30 | import java.util.concurrent.atomic.AtomicBoolean; 31 | 32 | import static org.bukkit.Bukkit.getLogger; 33 | 34 | @Singleton 35 | public class ConfigurationWatcher extends Thread { 36 | private final Configuration config; 37 | private final WatchService watcher; 38 | private final AtomicBoolean halt = new AtomicBoolean(false); 39 | private final SignEditPlugin plugin; 40 | 41 | private Path configPath; 42 | private FileTime lastModifiedTime; 43 | 44 | @Inject 45 | public ConfigurationWatcher(Plugin plugin, Configuration config) { 46 | this.plugin = (SignEditPlugin) plugin; 47 | this.config = config; 48 | File file = this.config.getConfigFile(); 49 | 50 | WatchService watcher; 51 | try { 52 | watcher = FileSystems.getDefault().newWatchService(); 53 | 54 | Path fileBasedir = file.toPath().getParent(); 55 | fileBasedir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); 56 | 57 | configPath = file.toPath(); 58 | lastModifiedTime = Files.getLastModifiedTime(configPath); 59 | } catch (IOException e) { 60 | watcher = null; 61 | end(); 62 | 63 | getLogger().warning( 64 | "Could not set up SignEdit configuration watcher. " + 65 | "Configuration changes will require a Bukkit server restart to apply." 66 | ); 67 | getLogger().warning(SignEditPlugin.getStackTrace(e)); 68 | } 69 | this.watcher = watcher; 70 | } 71 | 72 | public boolean isHalted() { 73 | return halt.get(); 74 | } 75 | 76 | public void end() { 77 | halt.set(true); 78 | } 79 | 80 | @Override 81 | public void run() { 82 | while (!isHalted()) { 83 | WatchKey watchKey; 84 | try { 85 | watchKey = watcher.take(); 86 | } catch (InterruptedException e) { 87 | return; 88 | } 89 | if (watchKey == null) { 90 | Thread.yield(); 91 | continue; 92 | } 93 | try { 94 | // noinspection BusyWait 95 | Thread.sleep(50); 96 | } catch (InterruptedException e) { 97 | return; 98 | } 99 | for (WatchEvent watchEvent : watchKey.pollEvents()) { 100 | Object context = watchEvent.context(); 101 | if (!(context instanceof Path)) continue; 102 | 103 | Path pathContext = (Path) context; 104 | if (!pathContext.equals(configPath.getFileName())) continue; 105 | try { 106 | FileTime newLastModifiedTime = Files.getLastModifiedTime(configPath); 107 | if (newLastModifiedTime.equals(lastModifiedTime) && newLastModifiedTime.toMillis() != 0) continue; 108 | 109 | lastModifiedTime = newLastModifiedTime; 110 | } catch (IOException e) { 111 | getLogger().warning( 112 | "SignEdit could not determine the file modification time of configuration file: " 113 | + configPath.toString() 114 | ); 115 | } 116 | 117 | if (isHalted()) return; 118 | getLogger().info("SignEdit detected a configuration file change. Reloading configuration..."); 119 | this.config.reloadConfig(); 120 | plugin.reregisterListeners(); 121 | break; 122 | } 123 | watchKey.reset(); 124 | Thread.yield(); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/CraftBukkitReflector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import org.bukkit.Bukkit; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import javax.inject.Inject; 26 | import java.lang.reflect.Field; 27 | import java.lang.reflect.Method; 28 | import java.util.Arrays; 29 | 30 | public class CraftBukkitReflector { 31 | public String BUKKIT_SERVER_VERSION; 32 | 33 | @Inject 34 | public CraftBukkitReflector() { 35 | BUKKIT_SERVER_VERSION = getBukkitServerVersion(); 36 | } 37 | 38 | private static String getBukkitServerVersion() { 39 | String bukkitPackageName = Bukkit.getServer().getClass().getPackage().getName(); 40 | return bukkitPackageName.substring(bukkitPackageName.lastIndexOf('.') + 1); 41 | } 42 | 43 | public static Method getDeclaredMethodRecursive(Class whateverClass, String name, Class... parameterTypes) 44 | throws NoSuchMethodException { 45 | try { 46 | Method method = whateverClass.getDeclaredMethod(name, parameterTypes); 47 | method.setAccessible(true); 48 | return method; 49 | } catch (NoSuchMethodException e) { 50 | Class superClass = whateverClass.getSuperclass(); 51 | if (superClass != null) { 52 | return getDeclaredMethodRecursive(superClass, name, parameterTypes); 53 | } 54 | throw e; 55 | } 56 | } 57 | 58 | /** 59 | * Find the first method that accepts the provided parameters 60 | *

61 | * Performs a breadth-first search up the class ancestry until there are no more parents 62 | * 63 | * @param whateverClass The class to check for a matching method 64 | * @param parameterTypes The parameter types that the method accepts 65 | * @return The first matching method 66 | * @throws NoSuchMethodException if no methods match 67 | */ 68 | public static Method findMethodByParameterTypes(Class whateverClass, Class... parameterTypes) 69 | throws NoSuchMethodException { 70 | try { 71 | for (Method maybeMethod : whateverClass.getMethods()) { 72 | Class[] candidateParameterTypes = maybeMethod.getParameterTypes(); 73 | if (Arrays.equals(parameterTypes, candidateParameterTypes)) { 74 | maybeMethod.setAccessible(true); 75 | return maybeMethod; 76 | } 77 | } 78 | throw new NoSuchMethodException(); 79 | } catch (NoSuchMethodException e) { 80 | Class superClass = whateverClass.getSuperclass(); 81 | if (superClass != null) { 82 | return findMethodByParameterTypes(superClass, parameterTypes); 83 | } 84 | throw e; 85 | } 86 | } 87 | 88 | public static Field getFirstFieldOfType(Object source, Class desiredType) throws NoSuchFieldException { 89 | return getFirstFieldOfType(source, desiredType, ~0x0); 90 | } 91 | 92 | public static Field getFirstFieldOfType( 93 | Object source, 94 | Class desiredType, 95 | int modifierMask 96 | ) throws NoSuchFieldException { 97 | return getFirstFieldOfType(source.getClass(), desiredType, modifierMask); 98 | } 99 | 100 | public static Field getFirstFieldOfType( 101 | @NotNull Class source, 102 | @NotNull Class desiredType, 103 | int modifierMask 104 | ) throws NoSuchFieldException { 105 | Class ancestor = source; 106 | while (ancestor != null) { 107 | Field[] fields = ancestor.getDeclaredFields(); 108 | for (Field field : fields) { 109 | Class candidateType = field.getType(); 110 | if (desiredType.isAssignableFrom(candidateType) && (field.getModifiers() & modifierMask) > 0) { 111 | field.setAccessible(true); 112 | return field; 113 | } 114 | } 115 | ancestor = ancestor.getSuperclass(); 116 | } 117 | throw new NoSuchFieldException("Cannot match " + desiredType.getName() + " in ancestry of " + source.getName()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/ResourceList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.Enumeration; 27 | import java.util.regex.Pattern; 28 | import java.util.zip.ZipEntry; 29 | import java.util.zip.ZipFile; 30 | 31 | // https://web.archive.org/web/20191230162038/http://forums.devx.com/showthread.php?153784-how-to-list-resources-in-a-package&p=457686#post457686 32 | public class ResourceList { 33 | 34 | /** 35 | * for all elements of java.class.path get a Collection of resources Pattern 36 | * pattern = Pattern.compile(".*"); gets all resources 37 | * 38 | * @param pattern the pattern to match 39 | * @return the resources in the order they are found 40 | */ 41 | public static Collection getResources( 42 | final Pattern pattern) { 43 | final ArrayList retval = new ArrayList<>(); 44 | final String classPath = System.getProperty("java.class.path", "."); 45 | final String[] classPathElements = classPath.split(System.getProperty("path.separator")); 46 | for (final String element : classPathElements) { 47 | retval.addAll(getResources(element, pattern)); 48 | } 49 | return retval; 50 | } 51 | 52 | public static Collection getResources( 53 | final String element, 54 | final Pattern pattern) { 55 | final ArrayList retval = new ArrayList<>(); 56 | final File file = new File(element); 57 | if (file.isDirectory()) { 58 | retval.addAll(getResourcesFromDirectory(file, pattern)); 59 | } else { 60 | retval.addAll(getResourcesFromJarFile(file, pattern)); 61 | } 62 | return retval; 63 | } 64 | 65 | private static Collection getResourcesFromJarFile( 66 | final File file, 67 | final Pattern pattern) { 68 | final ArrayList retval = new ArrayList<>(); 69 | ZipFile zf; 70 | try { 71 | zf = new ZipFile(file); 72 | } catch (final IOException e) { 73 | throw new Error(e); 74 | } 75 | final Enumeration e = zf.entries(); 76 | while (e.hasMoreElements()) { 77 | final ZipEntry ze = e.nextElement(); 78 | final String fileName = ze.getName(); 79 | final boolean accept = pattern.matcher(fileName).matches(); 80 | if (accept) { 81 | retval.add(fileName); 82 | } 83 | } 84 | try { 85 | zf.close(); 86 | } catch (final IOException e1) { 87 | throw new Error(e1); 88 | } 89 | return retval; 90 | } 91 | 92 | private static Collection getResourcesFromDirectory( 93 | final File directory, 94 | final Pattern pattern) { 95 | final ArrayList retval = new ArrayList<>(); 96 | final File[] fileList = directory.listFiles(); 97 | if (fileList == null) return retval; 98 | 99 | for (final File file : fileList) { 100 | if (file.isDirectory()) { 101 | retval.addAll(getResourcesFromDirectory(file, pattern)); 102 | } else { 103 | try { 104 | final String fileName = file.getCanonicalPath(); 105 | final boolean accept = pattern.matcher(fileName).matches(); 106 | if (accept) { 107 | retval.add(fileName); 108 | } 109 | } catch (final IOException e) { 110 | throw new Error(e); 111 | } 112 | } 113 | } 114 | return retval; 115 | } 116 | 117 | /** 118 | * list the resources that match args[0] 119 | * 120 | * @param args args[0] is the pattern to match, or list all resources if 121 | * there are no args 122 | */ 123 | public static void main(final String[] args) { 124 | Pattern pattern; 125 | if (args.length < 1) { 126 | pattern = Pattern.compile(".*"); 127 | } else { 128 | pattern = Pattern.compile(args[0]); 129 | } 130 | final Collection list = ResourceList.getResources(pattern); 131 | for (final String name : list) { 132 | System.out.println(name); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/SignEditPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import net.deltik.mc.signedit.commands.SignCommand; 23 | import net.deltik.mc.signedit.commands.SignCommandTabCompleter; 24 | import net.deltik.mc.signedit.listeners.SignEditListener; 25 | import org.bukkit.command.PluginCommand; 26 | import org.bukkit.event.HandlerList; 27 | import org.bukkit.plugin.java.JavaPlugin; 28 | 29 | import javax.inject.Inject; 30 | import javax.inject.Provider; 31 | import java.io.IOException; 32 | import java.io.PrintWriter; 33 | import java.io.StringWriter; 34 | import java.util.Set; 35 | 36 | public class SignEditPlugin extends JavaPlugin { 37 | @Inject 38 | public Configuration config; 39 | @Inject 40 | public ConfigurationWatcher configWatcher; 41 | @Inject 42 | UserComms userComms; 43 | 44 | @Inject 45 | public Provider> listenersProvider; 46 | 47 | @Inject 48 | public SignCommand signCommand; 49 | @Inject 50 | public SignCommandTabCompleter signCommandTabCompleter; 51 | 52 | @Override 53 | public void onEnable() { 54 | DaggerSignEditPluginComponent.builder().plugin(this).build().injectSignEditPlugin(this); 55 | 56 | for (String alias : new String[]{"sign", "signedit", "editsign", "se"}) { 57 | PluginCommand pluginCommand = this.getCommand(alias); 58 | pluginCommand.setExecutor(signCommand); 59 | pluginCommand.setTabCompleter(signCommandTabCompleter); 60 | } 61 | 62 | reregisterListeners(); 63 | 64 | try { 65 | userComms.deploy(); 66 | } catch (IOException e) { 67 | getLogger().warning("Cannot enable user-defined locales due to error:"); 68 | getLogger().warning(getStackTrace(e)); 69 | } 70 | 71 | configWatcher.start(); 72 | } 73 | 74 | @Override 75 | public void onDisable() { 76 | try { 77 | configWatcher.end(); 78 | config.configHighstate(); 79 | } catch (IOException e) { 80 | getLogger().severe(getStackTrace(e)); 81 | throw new IllegalStateException("Unrecoverable error while sanity checking plugin configuration"); 82 | } 83 | } 84 | 85 | public void reregisterListeners() { 86 | HandlerList.unregisterAll(this); 87 | 88 | Set listeners = listenersProvider.get(); 89 | for (SignEditListener listener : listeners) { 90 | getServer().getPluginManager().registerEvents(listener, this); 91 | } 92 | } 93 | 94 | /** 95 | * Returns the stack trace of the given {@link Throwable} as a {@link String} 96 | * 97 | * @param throwable the {@link Throwable} to be examined 98 | * @return the stack trace as generated by {@link Throwable#printStackTrace(PrintWriter)} 99 | */ 100 | public static String getStackTrace(final Throwable throwable) { 101 | final StringWriter sw = new StringWriter(); 102 | final PrintWriter pw = new PrintWriter(sw, true); 103 | throwable.printStackTrace(pw); 104 | return sw.getBuffer().toString(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/SignEditPluginComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import dagger.BindsInstance; 23 | import dagger.Component; 24 | import net.deltik.mc.signedit.commands.SignCommandModule; 25 | import net.deltik.mc.signedit.listeners.SignEditListenerModule; 26 | import org.bukkit.plugin.Plugin; 27 | 28 | import javax.inject.Singleton; 29 | 30 | @Component(modules = { 31 | SignEditListenerModule.class, 32 | SignCommandModule.class, 33 | ChatCommsModule.class 34 | }) 35 | @Singleton 36 | public interface SignEditPluginComponent { 37 | void injectSignEditPlugin(SignEditPlugin plugin); 38 | 39 | @Component.Builder 40 | interface Builder { 41 | SignEditPluginComponent build(); 42 | 43 | @BindsInstance 44 | Builder plugin(Plugin plugin); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/SignTextClipboardManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import org.bukkit.entity.Player; 23 | 24 | import javax.inject.Inject; 25 | import javax.inject.Singleton; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | @Singleton 30 | public class SignTextClipboardManager { 31 | private final Map playerSignTextMap = new HashMap<>(); 32 | 33 | @Inject 34 | public SignTextClipboardManager() { 35 | } 36 | 37 | public void forgetPlayer(Player player) { 38 | playerSignTextMap.remove(player); 39 | } 40 | 41 | public SignText getClipboard(Player player) { 42 | return playerSignTextMap.get(player); 43 | } 44 | 45 | public void setClipboard(Player player, SignText clipboard) { 46 | playerSignTextMap.put(player, clipboard); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/SignTextHistory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import net.deltik.mc.signedit.exceptions.SignTextHistoryStackBoundsException; 23 | import org.bukkit.entity.Player; 24 | 25 | import java.util.LinkedList; 26 | 27 | public class SignTextHistory { 28 | private final Player player; 29 | private final LinkedList history = new LinkedList<>(); 30 | int tailPosition = 0; 31 | 32 | public SignTextHistory(Player player) { 33 | this.player = player; 34 | } 35 | 36 | public void push(SignText signText) { 37 | while (history.size() > tailPosition) { 38 | history.removeLast(); 39 | } 40 | history.addLast(signText); 41 | tailPosition = history.size(); 42 | } 43 | 44 | public int undosRemaining() { 45 | return tailPosition; 46 | } 47 | 48 | public int redosRemaining() { 49 | return history.size() - tailPosition; 50 | } 51 | 52 | public SignText undo(ChatComms comms) { 53 | if (tailPosition <= 0) { 54 | throw new SignTextHistoryStackBoundsException("nothing_to_undo"); 55 | } 56 | SignText previousSignText = history.get(tailPosition - 1); 57 | previousSignText.applySignAutoWax(player, comms, previousSignText::revertSign); 58 | tailPosition--; 59 | return previousSignText; 60 | } 61 | 62 | public SignText redo(ChatComms comms) { 63 | if (tailPosition == history.size()) { 64 | throw new SignTextHistoryStackBoundsException("nothing_to_redo"); 65 | } 66 | SignText nextSignText = history.get(tailPosition); 67 | nextSignText.applySignAutoWax(player, comms, nextSignText::applySign); 68 | tailPosition++; 69 | return nextSignText; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/SignTextHistoryManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import org.bukkit.entity.Player; 23 | 24 | import javax.inject.Inject; 25 | import javax.inject.Singleton; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | @Singleton 30 | public class SignTextHistoryManager { 31 | private final Map playerHistoryMap = new HashMap<>(); 32 | 33 | @Inject 34 | public SignTextHistoryManager() { 35 | } 36 | 37 | public void forgetPlayer(Player player) { 38 | playerHistoryMap.remove(player); 39 | } 40 | 41 | public SignTextHistory getHistory(Player player) { 42 | SignTextHistory history = playerHistoryMap.get(player); 43 | if (history == null) { 44 | history = new SignTextHistory(player); 45 | playerHistoryMap.put(player, history); 46 | } 47 | return history; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/commands/SignCommandModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.commands; 21 | 22 | import dagger.Binds; 23 | import dagger.Module; 24 | import dagger.Provides; 25 | import dagger.multibindings.IntoMap; 26 | import dagger.multibindings.StringKey; 27 | import net.deltik.mc.signedit.integrations.SignEditValidatorModule; 28 | import net.deltik.mc.signedit.interactions.InteractionCommand; 29 | import net.deltik.mc.signedit.subcommands.*; 30 | 31 | import java.util.Set; 32 | import java.util.stream.Collectors; 33 | import java.util.stream.Stream; 34 | 35 | @Module(subcomponents = { 36 | SignSubcommandComponent.class, 37 | }, 38 | includes = { 39 | SignEditValidatorModule.class, 40 | }) 41 | public abstract class SignCommandModule { 42 | 43 | /** 44 | * FIXME: Find a way not to hard-code this Set. 45 | */ 46 | @Provides 47 | @SubcommandName 48 | public static Set provideSubcommandNames() { 49 | return Stream.of( 50 | "help", 51 | "set", 52 | "clear", 53 | "ui", 54 | "cancel", 55 | "status", 56 | "copy", 57 | "cut", 58 | "paste", 59 | "undo", 60 | "redo", 61 | "unwax", 62 | "wax", 63 | "version" 64 | ).collect(Collectors.toSet()); 65 | } 66 | 67 | @Binds 68 | @IntoMap 69 | @StringKey("help") 70 | abstract InteractionCommand BindHelpSignSubcommand(HelpSignSubcommand command); 71 | 72 | @Binds 73 | @IntoMap 74 | @StringKey("set") 75 | abstract InteractionCommand BindSetSignSubcommand(SetSignSubcommand command); 76 | 77 | @Binds 78 | @IntoMap 79 | @StringKey("clear") 80 | abstract InteractionCommand BindClearSignSubcommand(ClearSignSubcommand command); 81 | 82 | @Binds 83 | @IntoMap 84 | @StringKey("ui") 85 | abstract InteractionCommand BindUiSignSubcommand(UiSignSubcommand command); 86 | 87 | @Binds 88 | @IntoMap 89 | @StringKey("cancel") 90 | abstract InteractionCommand BindCancelSignSubcommand(CancelSignSubcommand command); 91 | 92 | @Binds 93 | @IntoMap 94 | @StringKey("status") 95 | abstract InteractionCommand BindStatusSignSubcommand(StatusSignSubcommand command); 96 | 97 | @Binds 98 | @IntoMap 99 | @StringKey("copy") 100 | abstract InteractionCommand BindCopySignSubcommand(CopySignSubcommand command); 101 | 102 | @Binds 103 | @IntoMap 104 | @StringKey("cut") 105 | abstract InteractionCommand BindCutSignSubcommand(CutSignSubcommand command); 106 | 107 | @Binds 108 | @IntoMap 109 | @StringKey("paste") 110 | abstract InteractionCommand BindPasteSignSubcommand(PasteSignSubcommand command); 111 | 112 | @Binds 113 | @IntoMap 114 | @StringKey("undo") 115 | abstract InteractionCommand BindUndoSignSubcommand(UndoSignSubcommand command); 116 | 117 | @Binds 118 | @IntoMap 119 | @StringKey("redo") 120 | abstract InteractionCommand BindRedoSignSubcommand(RedoSignSubcommand command); 121 | 122 | @Binds 123 | @IntoMap 124 | @StringKey("unwax") 125 | abstract InteractionCommand BindUnwaxSignSubcommand(UnwaxSignSubcommand command); 126 | 127 | @Binds 128 | @IntoMap 129 | @StringKey("wax") 130 | abstract InteractionCommand BindWaxSignSubcommand(WaxSignSubcommand command); 131 | 132 | @Binds 133 | @IntoMap 134 | @StringKey("version") 135 | abstract InteractionCommand BindVersionSignSubcommand(VersionSignSubcommand command); 136 | } 137 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/BlockStateNotPlacedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class BlockStateNotPlacedException extends RuntimeException { 23 | public BlockStateNotPlacedException() { 24 | super(); 25 | } 26 | 27 | public BlockStateNotPlacedException(String s) { 28 | super(s); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/ForbiddenSignEditException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class ForbiddenSignEditException extends RuntimeException { 23 | public ForbiddenSignEditException() { 24 | super(); 25 | } 26 | 27 | public ForbiddenSignEditException(String s) { 28 | super(s); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/ForbiddenWaxedSignEditException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class ForbiddenWaxedSignEditException extends ForbiddenSignEditException { 23 | } 24 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/LineSelectionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | abstract public class LineSelectionException extends IllegalArgumentException { 23 | public LineSelectionException() { 24 | } 25 | 26 | public LineSelectionException(String var1) { 27 | super(var1); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/MissingLineSelectionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class MissingLineSelectionException extends LineSelectionException { 23 | public MissingLineSelectionException() { 24 | super(); 25 | } 26 | 27 | public MissingLineSelectionException(String s) { 28 | super(s); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/NullClipboardException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class NullClipboardException extends NullPointerException { 23 | public NullClipboardException() { 24 | } 25 | 26 | public NullClipboardException(String s) { 27 | super(s); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/NumberParseLineSelectionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class NumberParseLineSelectionException extends LineSelectionException { 23 | public NumberParseLineSelectionException(String s) { 24 | super(s); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/OutOfBoundsLineSelectionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class OutOfBoundsLineSelectionException extends LineSelectionException { 23 | public OutOfBoundsLineSelectionException(String s) { 24 | super(s); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/RangeOrderLineSelectionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class RangeOrderLineSelectionException extends LineSelectionException { 23 | private final String wholeSelection; 24 | private final String invalidLowerBound; 25 | private final String invalidUpperBound; 26 | 27 | public RangeOrderLineSelectionException(String wholeSelection, String invalidLowerBound, String invalidUpperBound) { 28 | this.wholeSelection = wholeSelection; 29 | this.invalidLowerBound = invalidLowerBound; 30 | this.invalidUpperBound = invalidUpperBound; 31 | } 32 | 33 | @Override 34 | public String getMessage() { 35 | return this.wholeSelection; 36 | } 37 | 38 | public String getInvalidLowerBound() { 39 | return invalidLowerBound; 40 | } 41 | 42 | public String getInvalidUpperBound() { 43 | return invalidUpperBound; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/RangeParseLineSelectionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class RangeParseLineSelectionException extends LineSelectionException { 23 | private final String wholeSelection; 24 | private final String badRange; 25 | 26 | public RangeParseLineSelectionException(String wholeSelection, String badRange) { 27 | this.wholeSelection = wholeSelection; 28 | this.badRange = badRange; 29 | } 30 | 31 | @Override 32 | public String getMessage() { 33 | return this.wholeSelection; 34 | } 35 | 36 | public String getBadRange() { 37 | return badRange; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/SignEditorInvocationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class SignEditorInvocationException extends RuntimeException { 23 | private final Exception originalException; 24 | 25 | public SignEditorInvocationException(Exception originalException) { 26 | this.originalException = originalException; 27 | } 28 | 29 | public Exception getOriginalException() { 30 | return originalException; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/exceptions/SignTextHistoryStackBoundsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.exceptions; 21 | 22 | public class SignTextHistoryStackBoundsException extends IndexOutOfBoundsException { 23 | public SignTextHistoryStackBoundsException(String s) { 24 | super(s); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/integrations/NoopSignEditValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.integrations; 21 | 22 | import net.deltik.mc.signedit.shims.SideShim; 23 | import net.deltik.mc.signedit.shims.SignShim; 24 | import org.bukkit.entity.Player; 25 | import org.bukkit.event.block.SignChangeEvent; 26 | 27 | import javax.inject.Inject; 28 | 29 | public class NoopSignEditValidator implements SignEditValidator { 30 | @Inject 31 | public NoopSignEditValidator() { 32 | } 33 | 34 | @Override 35 | public void validate(SignShim proposedSign, SideShim signSide, Player player) { 36 | } 37 | 38 | @Override 39 | public void validate(SignChangeEvent signChangeEvent) { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/integrations/SignEditValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.integrations; 21 | 22 | import net.deltik.mc.signedit.shims.SideShim; 23 | import net.deltik.mc.signedit.shims.SignShim; 24 | import org.bukkit.block.Sign; 25 | import org.bukkit.entity.Player; 26 | import org.bukkit.event.EventHandler; 27 | import org.bukkit.event.EventPriority; 28 | import org.bukkit.event.block.SignChangeEvent; 29 | 30 | public interface SignEditValidator { 31 | /** 32 | * Ensure that the changed sign passes checks before saving 33 | * 34 | * @param proposedSign A changed sign that has not had {@link Sign#update()} called yet 35 | * @param side The side of the sign that has staged changes 36 | * @param player The player that is trying to edit the sign 37 | */ 38 | void validate(SignShim proposedSign, SideShim side, Player player); 39 | 40 | /** 41 | * Import sign changes from a {@link SignChangeEvent} and ensure that they pass checks before saving 42 | * 43 | * @param signChangeEvent A sign change event in the validation phase (i.e. not passed in by an {@link EventHandler} 44 | * with {@link EventPriority#MONITOR}) 45 | */ 46 | void validate(SignChangeEvent signChangeEvent); 47 | } 48 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/integrations/SignEditValidatorModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.integrations; 21 | 22 | import dagger.Binds; 23 | import dagger.Module; 24 | import dagger.Provides; 25 | import dagger.multibindings.IntoMap; 26 | import dagger.multibindings.StringKey; 27 | import net.deltik.mc.signedit.Configuration; 28 | import org.bukkit.plugin.Plugin; 29 | import org.bukkit.plugin.PluginManager; 30 | 31 | import javax.inject.Provider; 32 | import java.util.Map; 33 | 34 | @Module 35 | public abstract class SignEditValidatorModule { 36 | @Provides 37 | static PluginManager providePluginManager(Plugin plugin) { 38 | return plugin.getServer().getPluginManager(); 39 | } 40 | 41 | @Provides 42 | static SignEditValidator provideSignEditValidator( 43 | Configuration config, 44 | Map> validatorProviders 45 | ) { 46 | return validatorProviders.get(config.getEditValidation().toLowerCase()).get(); 47 | } 48 | 49 | @Binds 50 | @IntoMap 51 | @StringKey("standard") 52 | abstract SignEditValidator bindStandard(StandardSignEditValidator integration); 53 | 54 | @Binds 55 | @IntoMap 56 | @StringKey("extra") 57 | abstract SignEditValidator bindExtra(BreakReplaceSignEditValidator integration); 58 | 59 | @Binds 60 | @IntoMap 61 | @StringKey("none") 62 | abstract SignEditValidator bindNone(NoopSignEditValidator integration); 63 | } 64 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/integrations/StandardSignEditValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.integrations; 21 | 22 | import net.deltik.mc.signedit.exceptions.ForbiddenSignEditException; 23 | import net.deltik.mc.signedit.listeners.CoreSignEditListener; 24 | import net.deltik.mc.signedit.shims.SideShim; 25 | import net.deltik.mc.signedit.shims.SignShim; 26 | import org.bukkit.block.Block; 27 | import org.bukkit.block.Sign; 28 | import org.bukkit.entity.Player; 29 | import org.bukkit.event.block.SignChangeEvent; 30 | import org.bukkit.plugin.PluginManager; 31 | import org.jetbrains.annotations.NotNull; 32 | 33 | import javax.inject.Inject; 34 | import java.lang.reflect.Constructor; 35 | import java.lang.reflect.InvocationTargetException; 36 | import java.lang.reflect.Method; 37 | import java.util.Arrays; 38 | 39 | public class StandardSignEditValidator implements SignEditValidator { 40 | protected final PluginManager pluginManager; 41 | 42 | @Inject 43 | public StandardSignEditValidator( 44 | PluginManager pluginManager 45 | ) { 46 | this.pluginManager = pluginManager; 47 | } 48 | 49 | @Override 50 | public void validate(SignShim proposedSign, SideShim side, Player player) { 51 | SignChangeEvent signChangeEvent; 52 | try { 53 | Constructor constructor = Arrays.stream(SignChangeEvent.class.getConstructors()) 54 | .filter(c -> c.getParameterCount() == 4) 55 | .filter(c -> { 56 | Class[] parameterTypes = c.getParameterTypes(); 57 | return parameterTypes[0].equals(Block.class) 58 | && parameterTypes[1].equals(Player.class) 59 | && parameterTypes[2].equals(String[].class) 60 | && parameterTypes[3].isEnum(); 61 | }) 62 | .findFirst() 63 | .orElseThrow(() -> new NoSuchMethodException("No such constructor exists for SignChangeEvent")); 64 | 65 | Method enumValueOf = constructor.getParameterTypes()[3].getMethod("valueOf", String.class); 66 | Enum enumValue = (Enum) enumValueOf.invoke(null, side.name()); 67 | 68 | signChangeEvent = (SignChangeEvent) constructor.newInstance( 69 | proposedSign.getImplementation().getBlock(), 70 | player, 71 | proposedSign.getSide(side).getLines(), 72 | enumValue 73 | ); 74 | } catch ( 75 | NoSuchMethodException | 76 | IllegalAccessException | 77 | InvocationTargetException | 78 | InstantiationException e 79 | ) { 80 | signChangeEvent = makeOldSignChangeEvent(proposedSign, side, player); 81 | } 82 | 83 | pluginManager.callEvent(signChangeEvent); 84 | validate(signChangeEvent); 85 | } 86 | 87 | /** 88 | * Creates a Bukkit 1.8-compatible {@link SignChangeEvent} without {@link Sign} sides 89 | * 90 | * @param proposedSign The {@link Sign} with new values that hasn't been updated with {@link Sign#update()} yet 91 | * @param side Which side of the {@link Sign} changed 92 | * (must be {@link SideShim#FRONT} because that was the only side before Bukkit 1.20) 93 | * @param player The {@link Player} who intends to change the {@link Sign} 94 | * @return A new {@link SignChangeEvent} that should be sent to 95 | * {@link PluginManager#callEvent(org.bukkit.event.Event)} 96 | */ 97 | @SuppressWarnings("deprecation") 98 | @NotNull 99 | private SignChangeEvent makeOldSignChangeEvent(SignShim proposedSign, SideShim side, Player player) { 100 | if (side != SideShim.FRONT) { 101 | throw new IllegalArgumentException("Bug: Event support missing for editing back of sign"); 102 | } 103 | return new SignChangeEvent( 104 | proposedSign.getImplementation().getBlock(), 105 | player, 106 | proposedSign.getSide(side).getLines() 107 | ); 108 | } 109 | 110 | @Override 111 | public void validate(SignChangeEvent signChangeEvent) { 112 | Sign bukkitSign = CoreSignEditListener.getPlacedSignFromBlockEvent(signChangeEvent); 113 | SignShim proposedSign = new SignShim(bukkitSign); 114 | if (signChangeEvent.isCancelled()) { 115 | throw new ForbiddenSignEditException(); 116 | } 117 | SideShim side = SideShim.fromSignChangeEvent(signChangeEvent); 118 | String[] newLines = signChangeEvent.getLines(); 119 | for (int i = 0; i < newLines.length; i++) { 120 | proposedSign.getSide(side).setLine(i, newLines[i]); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/BookUiSignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import net.deltik.mc.signedit.SignText; 25 | import net.deltik.mc.signedit.SignTextHistoryManager; 26 | import net.deltik.mc.signedit.shims.SideShim; 27 | import net.deltik.mc.signedit.shims.SignShim; 28 | import org.bukkit.Bukkit; 29 | import org.bukkit.Material; 30 | import org.bukkit.entity.Player; 31 | import org.bukkit.event.Event; 32 | import org.bukkit.event.inventory.InventoryClickEvent; 33 | import org.bukkit.event.player.PlayerEditBookEvent; 34 | import org.bukkit.inventory.ItemStack; 35 | import org.bukkit.inventory.PlayerInventory; 36 | import org.bukkit.inventory.meta.BookMeta; 37 | import org.bukkit.plugin.Plugin; 38 | 39 | import javax.inject.Inject; 40 | 41 | public class BookUiSignEditInteraction implements SignEditInteraction { 42 | private final Plugin plugin; 43 | private final SignEditInteractionManager interactionManager; 44 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 45 | private final SignText signText; 46 | private final SignTextHistoryManager historyManager; 47 | protected ItemStack originalItem; 48 | protected int originalItemIndex; 49 | protected Player player; 50 | 51 | @Inject 52 | public BookUiSignEditInteraction( 53 | Plugin plugin, 54 | SignEditInteractionManager interactionManager, 55 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder, 56 | SignText signText, 57 | SignTextHistoryManager historyManager 58 | ) { 59 | this.plugin = plugin; 60 | this.interactionManager = interactionManager; 61 | this.commsBuilder = commsBuilder; 62 | this.signText = signText; 63 | this.historyManager = historyManager; 64 | } 65 | 66 | @Override 67 | public String getName() { 68 | return "open_sign_editor"; 69 | } 70 | 71 | @Override 72 | public void interact(Player player, SignShim sign, SideShim side) { 73 | interactionManager.setPendingInteraction(player, this); 74 | 75 | if (originalItem == null) { 76 | signText.setTargetSign(sign, side); 77 | signText.importSign(); 78 | formatSignTextForEdit(signText); 79 | openSignEditor(player); 80 | return; 81 | } 82 | 83 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 84 | comms.tell(comms.t("right_click_air_to_open_sign_editor")); 85 | } 86 | 87 | @Override 88 | public String getActionHint(ChatComms comms) { 89 | if (originalItem == null) { 90 | return SignEditInteraction.super.getActionHint(comms); 91 | } 92 | return comms.t("right_click_air_to_apply_action_hint"); 93 | } 94 | 95 | protected void openSignEditor(Player player) { 96 | this.player = player; 97 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 98 | PlayerInventory inventory = player.getInventory(); 99 | ItemStack book = new ItemStack(Material.WRITABLE_BOOK, 1); 100 | BookMeta bookMeta = (BookMeta) book.getItemMeta(); 101 | bookMeta.setDisplayName(comms.t("sign_editor_item_name")); 102 | bookMeta.setPages(String.join("\n", signText.getLines())); 103 | book.setItemMeta(bookMeta); 104 | originalItem = inventory.getItemInMainHand(); 105 | originalItemIndex = inventory.getHeldItemSlot(); 106 | inventory.setItemInMainHand(book); 107 | comms.tell(comms.t("right_click_air_to_open_sign_editor")); 108 | interactionManager.removePendingInteraction(player); 109 | interactionManager.setPendingInteraction(player, this); 110 | } 111 | 112 | @Override 113 | public void cleanup(Event event) { 114 | if (originalItem == null) return; 115 | 116 | if (event instanceof InventoryClickEvent) cleanupInventoryClickEvent((InventoryClickEvent) event); 117 | player.getInventory().setItem(originalItemIndex, originalItem); 118 | 119 | if (!(event instanceof PlayerEditBookEvent)) { 120 | return; 121 | } 122 | 123 | PlayerEditBookEvent editBookEvent = (PlayerEditBookEvent) event; 124 | editBookEvent.setCancelled(true); 125 | Bukkit.getScheduler().scheduleSyncDelayedTask( 126 | plugin, 127 | () -> player.getInventory().setItem(originalItemIndex, originalItem), 128 | 0 129 | ); 130 | BookMeta meta = editBookEvent.getNewBookMeta(); 131 | String[] newLines; 132 | if (meta.hasPages()) { 133 | String page = meta.getPage(1); 134 | newLines = page.split("\n"); 135 | } else { 136 | newLines = new String[]{}; 137 | } 138 | for (int i = 0; i < signText.getLines().length; i++) { 139 | String newLine; 140 | if (i >= newLines.length) { 141 | newLine = ""; 142 | } else { 143 | newLine = newLines[i]; 144 | } 145 | signText.setLine(i, newLine); 146 | } 147 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 148 | 149 | signText.applySignAutoWax(player, comms, signText::applySign); 150 | if (signText.signTextChanged()) { 151 | historyManager.getHistory(player).push(signText); 152 | } 153 | 154 | comms.compareSignText(signText); 155 | } 156 | 157 | private void cleanupInventoryClickEvent(InventoryClickEvent event) { 158 | if (originalItemIndex == event.getSlot()) { 159 | interactionManager.setPendingInteraction(player, this); 160 | event.setCancelled(true); 161 | } 162 | } 163 | 164 | protected void formatSignTextForEdit(SignText signText) { 165 | for (int i = 0; i < 4; i++) { 166 | signText.setLineLiteral(i, signText.getLineParsed(i)); 167 | } 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/CopySignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.*; 23 | import net.deltik.mc.signedit.shims.ISignSide; 24 | import net.deltik.mc.signedit.shims.SideShim; 25 | import net.deltik.mc.signedit.shims.SignShim; 26 | import org.bukkit.entity.Player; 27 | 28 | import javax.inject.Inject; 29 | import java.util.Arrays; 30 | 31 | import static net.deltik.mc.signedit.LineSelectorParser.ALL_LINES_SELECTED; 32 | import static net.deltik.mc.signedit.LineSelectorParser.NO_LINES_SELECTED; 33 | 34 | public class CopySignEditInteraction implements SignEditInteraction { 35 | private final ArgParser argParser; 36 | private final SignText signText; 37 | private final SignTextClipboardManager clipboardManager; 38 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 39 | 40 | @Inject 41 | public CopySignEditInteraction( 42 | ArgParser argParser, 43 | SignText signText, 44 | SignTextClipboardManager clipboardManager, 45 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder 46 | ) { 47 | this.argParser = argParser; 48 | this.signText = signText; 49 | this.clipboardManager = clipboardManager; 50 | this.commsBuilder = commsBuilder; 51 | } 52 | 53 | @Override 54 | public void interact(Player player, SignShim sign, SideShim side) { 55 | int[] selectedLines = argParser.getLinesSelection(); 56 | if (Arrays.equals(selectedLines, NO_LINES_SELECTED)) { 57 | selectedLines = ALL_LINES_SELECTED; 58 | } 59 | ISignSide signSide = sign.getSide(side); 60 | for (int selectedLine : selectedLines) { 61 | signText.setLineLiteral(selectedLine, signSide.getLine(selectedLine)); 62 | } 63 | 64 | clipboardManager.setClipboard(player, signText); 65 | 66 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 67 | comms.tell(comms.t("lines_copied_section")); 68 | comms.dumpLines(signText.getLines()); 69 | } 70 | 71 | @Override 72 | public String getName() { 73 | return "copy_sign_text"; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/CutSignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.*; 23 | import net.deltik.mc.signedit.integrations.SignEditValidator; 24 | import net.deltik.mc.signedit.shims.ISignSide; 25 | import net.deltik.mc.signedit.shims.SideShim; 26 | import net.deltik.mc.signedit.shims.SignShim; 27 | import org.bukkit.entity.Player; 28 | 29 | import javax.inject.Inject; 30 | import java.util.Arrays; 31 | 32 | import static net.deltik.mc.signedit.LineSelectorParser.ALL_LINES_SELECTED; 33 | import static net.deltik.mc.signedit.LineSelectorParser.NO_LINES_SELECTED; 34 | 35 | public class CutSignEditInteraction implements SignEditInteraction { 36 | private final ArgParser argParser; 37 | private final SignText sourceSign; 38 | private final SignText clipboard; 39 | private final SignTextClipboardManager clipboardManager; 40 | private final SignTextHistoryManager historyManager; 41 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 42 | 43 | @Inject 44 | public CutSignEditInteraction( 45 | ArgParser argParser, 46 | SignText signText, 47 | SignEditValidator validator, 48 | SignTextClipboardManager clipboardManager, 49 | SignTextHistoryManager historyManager, 50 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder 51 | ) { 52 | this.argParser = argParser; 53 | this.sourceSign = signText; 54 | this.clipboard = new SignText(validator); 55 | this.clipboardManager = clipboardManager; 56 | this.historyManager = historyManager; 57 | this.commsBuilder = commsBuilder; 58 | } 59 | 60 | @Override 61 | public void interact(Player player, SignShim sign, SideShim side) { 62 | int[] selectedLines = argParser.getLinesSelection(); 63 | if (Arrays.equals(selectedLines, NO_LINES_SELECTED)) { 64 | selectedLines = ALL_LINES_SELECTED; 65 | } 66 | 67 | sourceSign.setTargetSign(sign, side); 68 | 69 | ISignSide signSide = sign.getSide(side); 70 | for (int selectedLine : selectedLines) { 71 | clipboard.setLineLiteral(selectedLine, signSide.getLine(selectedLine)); 72 | sourceSign.setLineLiteral(selectedLine, ""); 73 | } 74 | 75 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 76 | 77 | sourceSign.applySignAutoWax(player, comms, sourceSign::applySign); 78 | if (sourceSign.signTextChanged()) { 79 | historyManager.getHistory(player).push(sourceSign); 80 | } 81 | clipboardManager.setClipboard(player, clipboard); 82 | 83 | comms.tell(comms.t("lines_cut_section")); 84 | comms.dumpLines(clipboard.getLines()); 85 | } 86 | 87 | @Override 88 | public String getName() { 89 | return "cut_sign_text"; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/InteractionCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | public interface InteractionCommand { 23 | SignEditInteraction execute(); 24 | 25 | /** 26 | * @return Whether this interaction is permitted to execute 27 | */ 28 | boolean isPermitted(); 29 | } -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/PasteSignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.*; 23 | import net.deltik.mc.signedit.shims.SideShim; 24 | import net.deltik.mc.signedit.shims.SignShim; 25 | import org.bukkit.entity.Player; 26 | 27 | import javax.inject.Inject; 28 | import javax.inject.Provider; 29 | 30 | public class PasteSignEditInteraction implements SignEditInteraction { 31 | private final SignTextClipboardManager clipboardManager; 32 | private final Provider signTextProvider; 33 | private final SignTextHistoryManager historyManager; 34 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 35 | 36 | @Inject 37 | public PasteSignEditInteraction( 38 | SignTextClipboardManager clipboardManager, 39 | Provider signTextProvider, 40 | SignTextHistoryManager historyManager, 41 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder 42 | ) { 43 | this.clipboardManager = clipboardManager; 44 | this.signTextProvider = signTextProvider; 45 | this.historyManager = historyManager; 46 | this.commsBuilder = commsBuilder; 47 | } 48 | 49 | @Override 50 | public void interact(Player player, SignShim sign, SideShim side) { 51 | SignText clipboard = clipboardManager.getClipboard(player); 52 | SignText signText = signTextProvider.get(); 53 | signText.setTargetSign(sign, side); 54 | 55 | for (int i = 0; i < clipboard.getLines().length; i++) { 56 | signText.setLineLiteral(i, clipboard.getLine(i)); 57 | } 58 | 59 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 60 | 61 | signText.applySignAutoWax(player, comms, signText::applySign); 62 | if (signText.signTextChanged()) { 63 | historyManager.getHistory(player).push(signText); 64 | } 65 | 66 | comms.compareSignText(signText); 67 | } 68 | 69 | @Override 70 | public String getName() { 71 | return "paste_lines_from_clipboard"; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/SetSignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import net.deltik.mc.signedit.SignText; 25 | import net.deltik.mc.signedit.SignTextHistoryManager; 26 | import net.deltik.mc.signedit.shims.SideShim; 27 | import net.deltik.mc.signedit.shims.SignShim; 28 | import org.bukkit.entity.Player; 29 | 30 | import javax.inject.Inject; 31 | 32 | public class SetSignEditInteraction implements SignEditInteraction { 33 | private final SignText signText; 34 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 35 | private final SignTextHistoryManager historyManager; 36 | 37 | @Inject 38 | public SetSignEditInteraction( 39 | SignText signText, 40 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder, 41 | SignTextHistoryManager historyManager 42 | ) { 43 | this.signText = signText; 44 | this.commsBuilder = commsBuilder; 45 | this.historyManager = historyManager; 46 | } 47 | 48 | @Override 49 | public String getName() { 50 | return "change_sign_text"; 51 | } 52 | 53 | @Override 54 | public void interact(Player player, SignShim sign, SideShim side) { 55 | signText.setTargetSign(sign, side); 56 | 57 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 58 | 59 | signText.applySignAutoWax(player, comms, signText::applySign); 60 | if (signText.signTextChanged()) { 61 | historyManager.getHistory(player).push(signText); 62 | } 63 | 64 | comms.compareSignText(signText); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/SignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.shims.SideShim; 24 | import net.deltik.mc.signedit.shims.SignShim; 25 | import org.bukkit.entity.Player; 26 | import org.bukkit.event.Event; 27 | import org.bukkit.event.HandlerList; 28 | import org.jetbrains.annotations.NotNull; 29 | 30 | public interface SignEditInteraction { 31 | void interact(Player player, SignShim sign, SideShim side); 32 | 33 | default String getName() { 34 | return this.getClass().getSimpleName(); 35 | } 36 | 37 | default String getActionHint(ChatComms comms) { 38 | return comms.t("right_click_sign_to_apply_action_hint"); 39 | } 40 | 41 | default void cleanup(Event event) { 42 | } 43 | 44 | default void cleanup() { 45 | cleanup(new Event() { 46 | @NotNull 47 | @Override 48 | public HandlerList getHandlers() { 49 | return new HandlerList(); 50 | } 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/SignEditInteractionManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import org.bukkit.entity.Player; 25 | import org.bukkit.event.Event; 26 | 27 | import javax.inject.Inject; 28 | import javax.inject.Provider; 29 | import javax.inject.Singleton; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | @Singleton 34 | public class SignEditInteractionManager { 35 | protected final Provider commsBuilderProvider; 36 | protected final Map pendingInteractions = new HashMap<>(); 37 | 38 | @Inject 39 | public SignEditInteractionManager( 40 | Provider commsBuilderProvider 41 | ) { 42 | this.commsBuilderProvider = commsBuilderProvider; 43 | } 44 | 45 | public void endInteraction(Player player, Event event) { 46 | try { 47 | removePendingInteraction(player).cleanup(event); 48 | } catch (Throwable e) { 49 | ChatComms comms = commsBuilderProvider.get().commandSender(player).build().comms(); 50 | comms.reportException(e); 51 | } 52 | } 53 | 54 | public void setPendingInteraction(Player player, SignEditInteraction interaction) { 55 | if (pendingInteractions.get(player) != null) { 56 | removePendingInteraction(player).cleanup(); 57 | } 58 | pendingInteractions.put(player, interaction); 59 | } 60 | 61 | public boolean isInteractionPending(Player player) { 62 | return pendingInteractions.containsKey(player); 63 | } 64 | 65 | public SignEditInteraction removePendingInteraction(Player player) { 66 | return pendingInteractions.remove(player); 67 | } 68 | 69 | public SignEditInteraction getPendingInteraction(Player player) { 70 | return pendingInteractions.get(player); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/SignEditInteractionModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import dagger.Binds; 23 | import dagger.Module; 24 | import dagger.multibindings.IntoMap; 25 | import dagger.multibindings.StringKey; 26 | 27 | @Module 28 | public abstract class SignEditInteractionModule { 29 | public static final String UI_NATIVE = "NativeUi"; 30 | public static final String UI_EDITABLE_BOOK = "EditableBookUi"; 31 | 32 | @Binds 33 | @IntoMap 34 | @StringKey("Set") 35 | abstract SignEditInteraction bindSet(SetSignEditInteraction interaction); 36 | 37 | @Binds 38 | @IntoMap 39 | @StringKey(UI_EDITABLE_BOOK) 40 | abstract SignEditInteraction bindEditableBookUi(BookUiSignEditInteraction interaction); 41 | 42 | @Binds 43 | @IntoMap 44 | @StringKey(UI_NATIVE) 45 | abstract SignEditInteraction bindNativeUi(UiSignEditInteraction interaction); 46 | 47 | @Binds 48 | @IntoMap 49 | @StringKey("Copy") 50 | abstract SignEditInteraction bindCopy(CopySignEditInteraction interaction); 51 | 52 | @Binds 53 | @IntoMap 54 | @StringKey("Cut") 55 | abstract SignEditInteraction bindCut(CutSignEditInteraction interaction); 56 | 57 | @Binds 58 | @IntoMap 59 | @StringKey("Paste") 60 | abstract SignEditInteraction bindPaste(PasteSignEditInteraction interaction); 61 | 62 | @Binds 63 | @IntoMap 64 | @StringKey("Wax") 65 | abstract SignEditInteraction bindWax(WaxSignEditInteraction interaction); 66 | } 67 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/interactions/WaxSignEditInteraction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.interactions; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import net.deltik.mc.signedit.SignText; 25 | import net.deltik.mc.signedit.exceptions.BlockStateNotPlacedException; 26 | import net.deltik.mc.signedit.exceptions.ForbiddenSignEditException; 27 | import net.deltik.mc.signedit.shims.SideShim; 28 | import net.deltik.mc.signedit.shims.SignHelpers; 29 | import net.deltik.mc.signedit.shims.SignShim; 30 | import org.bukkit.Location; 31 | import org.bukkit.Particle; 32 | import org.bukkit.Sound; 33 | import org.bukkit.World; 34 | import org.bukkit.block.BlockState; 35 | import org.bukkit.block.Sign; 36 | import org.bukkit.entity.Player; 37 | import org.jetbrains.annotations.NotNull; 38 | import org.jetbrains.annotations.Nullable; 39 | 40 | import javax.inject.Inject; 41 | 42 | public class WaxSignEditInteraction implements SignEditInteraction { 43 | private final SignText signText; 44 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 45 | 46 | @Inject 47 | public WaxSignEditInteraction( 48 | SignText signText, 49 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder 50 | ) { 51 | this.signText = signText; 52 | this.commsBuilder = commsBuilder; 53 | } 54 | 55 | @Override 56 | public void interact(Player player, SignShim sign, SideShim side) { 57 | signText.setTargetSign(sign, side); 58 | 59 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 60 | 61 | Sign signImplementation = sign.getImplementation(); 62 | BlockState realSign = signImplementation.getBlock().getState(); 63 | if (!(realSign instanceof Sign)) { 64 | throw new BlockStateNotPlacedException(); 65 | } 66 | 67 | boolean stagedEditable = Boolean.TRUE.equals(signText.shouldBeEditable()); 68 | boolean realEditable = SignHelpers.isEditable((Sign) realSign); 69 | 70 | if (stagedEditable == realEditable) { 71 | comms.tell(comms.t("sign_did_not_change")); 72 | return; 73 | } 74 | 75 | signText.applySign(player); 76 | 77 | realSign = signImplementation.getBlock().getState(); 78 | realEditable = SignHelpers.isEditable((Sign) realSign); 79 | 80 | if (stagedEditable != realEditable) { 81 | throw new ForbiddenSignEditException(); 82 | } else if (realEditable) { 83 | comms.tell(comms.t("wax_removed")); 84 | playWaxOff(signImplementation); 85 | } else { 86 | comms.tell(comms.t("wax_applied")); 87 | playWaxOn(signImplementation); 88 | } 89 | } 90 | 91 | @Override 92 | public String getName() { 93 | if (Boolean.TRUE.equals(signText.shouldBeEditable())) { 94 | return "unwax_sign"; 95 | } else { 96 | return "wax_sign"; 97 | } 98 | } 99 | 100 | /** 101 | * Send a firework-like burst initiating from the middle of the specified {@link BlockState} to its {@link World} 102 | * 103 | * @param block The block state from where the burst should originate 104 | * @param particle The kind of particle to spawn in the firework, or none if null 105 | * @param sound The sound to play at the block, or none if null 106 | * @throws BlockStateNotPlacedException if the block state is not placed or somehow isn't in the {@link World} 107 | */ 108 | private static void sendAudioVisualBurst( 109 | @NotNull BlockState block, 110 | @Nullable Particle particle, 111 | @Nullable Sound sound 112 | ) { 113 | Location signLocation = block.getLocation().add(0.5, 0.5, 0.5); 114 | World world = signLocation.getWorld(); 115 | if (world == null || !block.isPlaced()) { 116 | throw new BlockStateNotPlacedException(); 117 | } 118 | if (particle != null) { 119 | world.spawnParticle(particle, signLocation, 20, 0.3, 0.3, 0.3, 0.5); 120 | } 121 | if (sound != null) { 122 | world.playSound(signLocation, sound, 1, 1); 123 | } 124 | } 125 | 126 | /** 127 | * Play the "wax off" animation on the specified {@link Sign} by spawning particle effects and playing a sound 128 | * 129 | * @param sign The sign on which to play the "wax off" animation 130 | */ 131 | public static void playWaxOff(Sign sign) { 132 | Particle confirmationParticle = Particle.valueOf("WAX_OFF"); 133 | Sound confirmationSound = Sound.valueOf("BLOCK_SIGN_WAXED_INTERACT_FAIL"); 134 | sendAudioVisualBurst(sign, confirmationParticle, confirmationSound); 135 | } 136 | 137 | /** 138 | * Play the "wax on" animation on the specified {@link Sign} by spawning particle effects and playing a sound 139 | * 140 | * @param sign The sign on which to play the "wax on" animation 141 | */ 142 | public static void playWaxOn(Sign sign) { 143 | Particle confirmationParticle = Particle.valueOf("WAX_ON"); 144 | Sound confirmationSound = Sound.valueOf("ITEM_HONEYCOMB_WAX_ON"); 145 | sendAudioVisualBurst(sign, confirmationParticle, confirmationSound); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/listeners/BookUiSignEditListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.listeners; 21 | 22 | import net.deltik.mc.signedit.interactions.BookUiSignEditInteraction; 23 | import net.deltik.mc.signedit.interactions.SignEditInteractionManager; 24 | import org.bukkit.Material; 25 | import org.bukkit.entity.HumanEntity; 26 | import org.bukkit.entity.Player; 27 | import org.bukkit.event.EventHandler; 28 | import org.bukkit.event.inventory.InventoryClickEvent; 29 | import org.bukkit.event.player.PlayerDropItemEvent; 30 | import org.bukkit.event.player.PlayerEditBookEvent; 31 | import org.bukkit.event.player.PlayerItemHeldEvent; 32 | import org.bukkit.inventory.ItemStack; 33 | 34 | import javax.inject.Inject; 35 | import javax.inject.Singleton; 36 | 37 | @Singleton 38 | public class BookUiSignEditListener extends SignEditListener { 39 | private final SignEditInteractionManager interactionManager; 40 | 41 | @Inject 42 | public BookUiSignEditListener( 43 | SignEditInteractionManager interactionManager 44 | ) { 45 | this.interactionManager = interactionManager; 46 | } 47 | 48 | @EventHandler 49 | public void onSignChangeBookMode(PlayerEditBookEvent event) { 50 | Player player = event.getPlayer(); 51 | 52 | if (interactionManager.getPendingInteraction(player) instanceof BookUiSignEditInteraction) { 53 | interactionManager.endInteraction(player, event); 54 | } 55 | } 56 | 57 | @EventHandler 58 | public void onLeaveSignEditorBook(PlayerItemHeldEvent event) { 59 | Player player = event.getPlayer(); 60 | 61 | if (interactionManager.getPendingInteraction(player) instanceof BookUiSignEditInteraction) { 62 | interactionManager.endInteraction(player, event); 63 | } 64 | } 65 | 66 | @EventHandler 67 | public void onDropSignEditorBook(PlayerDropItemEvent event) { 68 | Player player = event.getPlayer(); 69 | if (!interactionManager.isInteractionPending(player)) return; 70 | 71 | ItemStack droppedItem = event.getItemDrop().getItemStack(); 72 | Material droppedItemType = droppedItem.getType(); 73 | if (droppedItemType == Material.WRITABLE_BOOK || droppedItemType == Material.WRITTEN_BOOK) { 74 | ItemStack item = event.getItemDrop().getItemStack(); 75 | event.getItemDrop().remove(); 76 | player.getInventory().setItemInMainHand(item); 77 | interactionManager.endInteraction(player, event); 78 | } 79 | } 80 | 81 | @EventHandler 82 | public void onClickSignEditorBook(InventoryClickEvent event) { 83 | HumanEntity human = event.getWhoClicked(); 84 | if (!(human instanceof Player)) return; 85 | Player player = (Player) human; 86 | if (!interactionManager.isInteractionPending(player)) return; 87 | 88 | interactionManager.endInteraction(player, event); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/listeners/SignEditListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.listeners; 21 | 22 | import org.bukkit.event.Listener; 23 | 24 | import javax.inject.Singleton; 25 | 26 | @Singleton 27 | public abstract class SignEditListener implements Listener { 28 | } 29 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/listeners/SignEditListenerModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.listeners; 21 | 22 | import dagger.Binds; 23 | import dagger.Module; 24 | import dagger.Provides; 25 | import dagger.multibindings.ElementsIntoSet; 26 | import dagger.multibindings.IntoMap; 27 | import dagger.multibindings.StringKey; 28 | import net.deltik.mc.signedit.Configuration; 29 | import net.deltik.mc.signedit.CraftBukkitReflector; 30 | import net.deltik.mc.signedit.interactions.SignEditInteractionModule; 31 | import net.deltik.mc.signedit.subcommands.UiSignSubcommand; 32 | 33 | import javax.inject.Provider; 34 | import java.util.HashSet; 35 | import java.util.Map; 36 | import java.util.Set; 37 | 38 | @Module 39 | public abstract class SignEditListenerModule { 40 | private static final String CORE = "Core"; 41 | 42 | @Provides 43 | @ElementsIntoSet 44 | static Set provideSignEditListeners( 45 | Map> listenerProviders, 46 | Configuration config, 47 | CraftBukkitReflector reflector 48 | ) { 49 | Set listeners = new HashSet<>(); 50 | listeners.add(listenerProviders.get(CORE).get()); 51 | String implementationName = UiSignSubcommand.getImplementationName(config, reflector); 52 | if (listenerProviders.containsKey(implementationName)) { 53 | listeners.add(listenerProviders.get(implementationName).get()); 54 | } 55 | return listeners; 56 | } 57 | 58 | @Binds 59 | @IntoMap 60 | @StringKey(CORE) 61 | abstract SignEditListener bindCore(CoreSignEditListener listener); 62 | 63 | @Binds 64 | @IntoMap 65 | @StringKey(SignEditInteractionModule.UI_EDITABLE_BOOK) 66 | abstract SignEditListener bindBookUi(BookUiSignEditListener listener); 67 | } 68 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/CalculatedBlockHitResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.Location; 23 | import org.bukkit.block.Block; 24 | import org.bukkit.block.BlockFace; 25 | import org.bukkit.entity.LivingEntity; 26 | import org.bukkit.util.Vector; 27 | 28 | public class CalculatedBlockHitResult implements IBlockHitResult { 29 | private final LivingEntity entity; 30 | private final Block targetBlock; 31 | private final Location targetBlockLocation; 32 | 33 | public CalculatedBlockHitResult(LivingEntity entity, int maxDistance) { 34 | this.entity = entity; 35 | this.targetBlock = entity.getTargetBlock(null, maxDistance); 36 | this.targetBlockLocation = targetBlock.getLocation(); 37 | } 38 | 39 | @Override 40 | public Block getHitBlock() { 41 | return targetBlock; 42 | } 43 | 44 | @Override 45 | public BlockFace getHitBlockFace() { 46 | Vector direction = entity.getLocation().toVector().subtract(targetBlockLocation.toVector()).normalize(); 47 | double x = direction.getX(); 48 | double z = direction.getZ(); 49 | 50 | if (Math.abs(x) > Math.abs(z)) { 51 | if (x > 0) { 52 | return BlockFace.WEST; 53 | } else { 54 | return BlockFace.EAST; 55 | } 56 | } else { 57 | if (z > 0) { 58 | return BlockFace.NORTH; 59 | } else { 60 | return BlockFace.SOUTH; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/IBlockHitResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.block.Block; 23 | import org.bukkit.block.BlockFace; 24 | 25 | public interface IBlockHitResult { 26 | Block getHitBlock(); 27 | 28 | BlockFace getHitBlockFace(); 29 | } 30 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/ISignSide.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.DyeColor; 23 | import org.jetbrains.annotations.Nullable; 24 | 25 | public interface ISignSide { 26 | /** 27 | * Gets the line of text at the specified index on this side of the sign. 28 | *

29 | * For example, getLine(0) will return the first line of text. 30 | * 31 | * @param index Line number to get the text from, starting at 0 32 | * @return Text on the given line 33 | * @throws IndexOutOfBoundsException Thrown when the line does not exist 34 | */ 35 | String getLine(int index) throws IndexOutOfBoundsException; 36 | 37 | /** 38 | * Gets all the lines of text currently on this side of the sign. 39 | * 40 | * @return Array of Strings containing each line of text 41 | */ 42 | String[] getLines(); 43 | 44 | /** 45 | * Sets the line of text at the specified index on this side of the sign. 46 | *

47 | * For example, setLine(0, "Line One") will set the first 48 | * line of text to "Line One". 49 | * 50 | * @param index Line number to set the text at, starting from 0 51 | * @param line New text to set at the specified index 52 | * @throws IndexOutOfBoundsException If the index is out of the range 0..3 53 | */ 54 | void setLine(int index, String line) throws IndexOutOfBoundsException; 55 | 56 | /** 57 | * Gets whether this side of the sign has glowing text. 58 | * 59 | * @return if this side of the sign has glowing text 60 | */ 61 | boolean isGlowingText(); 62 | 63 | /** 64 | * Sets whether this side of the sign has glowing text. 65 | * 66 | * @param glowing if this side of the sign has glowing text 67 | */ 68 | void setGlowingText(boolean glowing); 69 | 70 | /** 71 | * Gets the color of this object. 72 | * This may be null to represent the default color of an object if the object has a special default color. 73 | * 74 | * @return The {@link DyeColor} of this object. 75 | */ 76 | @Nullable 77 | DyeColor getColor(); 78 | 79 | /** 80 | * Sets the color of this object to the specified {@link DyeColor}. 81 | * This may be null to represent the default color of an object if the object has a special default color. 82 | * 83 | * @param color The color of the object as a {@link DyeColor} 84 | * @throws NullPointerException if argument is null and this implementation does not support null 85 | */ 86 | void setColor(@Nullable DyeColor color); 87 | } 88 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/PreciseBlockHitResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.block.Block; 23 | import org.bukkit.block.BlockFace; 24 | 25 | import java.lang.reflect.InvocationTargetException; 26 | 27 | public class PreciseBlockHitResult implements IBlockHitResult { 28 | private final Object rayTraceResultLike; 29 | private final Class rayTraceResultLikeClass; 30 | 31 | public PreciseBlockHitResult(Object rayTraceResultLike) { 32 | this.rayTraceResultLike = rayTraceResultLike; 33 | if (rayTraceResultLike == null) { 34 | this.rayTraceResultLikeClass = null; 35 | } else { 36 | this.rayTraceResultLikeClass = rayTraceResultLike.getClass(); 37 | } 38 | } 39 | 40 | @Override 41 | public Block getHitBlock() { 42 | if (rayTraceResultLike == null) { 43 | return null; 44 | } 45 | 46 | try { 47 | return (Block) rayTraceResultLikeClass.getMethod("getHitBlock").invoke(rayTraceResultLike); 48 | } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { 49 | throw new IllegalStateException("Unexpected structure of RayTraceResult-like object"); 50 | } 51 | } 52 | 53 | @Override 54 | public BlockFace getHitBlockFace() { 55 | if (rayTraceResultLike == null) { 56 | return null; 57 | } 58 | 59 | try { 60 | return (BlockFace) rayTraceResultLikeClass.getMethod("getHitBlockFace").invoke(rayTraceResultLike); 61 | } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { 62 | throw new IllegalStateException("Unexpected structure of RayTraceResult-like object"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/SideShim.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.block.BlockFace; 23 | import org.bukkit.block.Sign; 24 | import org.bukkit.block.data.BlockData; 25 | import org.bukkit.block.data.Directional; 26 | import org.bukkit.block.data.Rotatable; 27 | import org.bukkit.entity.LivingEntity; 28 | import org.bukkit.event.block.SignChangeEvent; 29 | import org.bukkit.util.Vector; 30 | import org.jetbrains.annotations.NotNull; 31 | 32 | import java.lang.reflect.InvocationTargetException; 33 | 34 | public enum SideShim { 35 | FRONT, 36 | BACK; 37 | 38 | public static SideShim fromRelativePosition(@NotNull Sign sign, @NotNull LivingEntity entity) { 39 | if (!SignHelpers.hasSignSideFeature()) { 40 | return FRONT; // Compatibility with Bukkit 1.19.4 41 | } 42 | 43 | Vector vector = entity.getEyeLocation().toVector().subtract(sign.getLocation().add(0.5, 0.5, 0.5).toVector()); 44 | BlockData blockData; 45 | try { 46 | blockData = sign.getBlockData(); 47 | } catch (NoSuchMethodError e) { 48 | return FRONT; // Compatibility with Bukkit 1.12.2 and older 49 | } 50 | 51 | Vector signDirection = getSignDirection(blockData); 52 | return vector.dot(signDirection) > 0 ? FRONT : BACK; 53 | } 54 | 55 | public static SideShim fromSignChangeEvent(SignChangeEvent event) { 56 | try { 57 | Object getSide = SignChangeEvent.class.getMethod("getSide").invoke(event); 58 | String enumValue = (String) getSide.getClass().getMethod("name").invoke(getSide); 59 | return valueOf(enumValue); 60 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 61 | return FRONT; 62 | } 63 | } 64 | 65 | @NotNull 66 | private static Vector getSignDirection(BlockData blockData) { 67 | BlockFace signFace; 68 | 69 | if (blockData instanceof Directional) { 70 | Directional directional = (Directional) blockData; 71 | signFace = directional.getFacing(); 72 | } else if (blockData instanceof Rotatable) { 73 | Rotatable rotatable = (Rotatable) blockData; 74 | signFace = rotatable.getRotation(); 75 | } else { 76 | signFace = BlockFace.NORTH; 77 | } 78 | 79 | return new Vector(signFace.getModX(), signFace.getModY(), signFace.getModZ()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/SignShim.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.block.Sign; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.lang.reflect.InvocationTargetException; 26 | 27 | public class SignShim { 28 | @NotNull 29 | private final Sign implementation; 30 | 31 | public SignShim(@NotNull Sign implementation) { 32 | this.implementation = implementation; 33 | } 34 | 35 | @NotNull 36 | public ISignSide getSide(@NotNull SideShim side) { 37 | try { 38 | Object rawGetSide = SignHelpers.rawGetSide(implementation, side.toString()); 39 | if (rawGetSide == null) { 40 | return getFrontSignSide(); 41 | } 42 | return new SignSideShim(rawGetSide); 43 | } catch (InvocationTargetException e) { 44 | throw new IllegalStateException("Unable to call method: getSide", e); 45 | } 46 | } 47 | 48 | @NotNull 49 | public Sign getImplementation() { 50 | return implementation; 51 | } 52 | 53 | /** 54 | * Proxy this {@link Sign} as the front side only for Bukkit 1.19.4 and below 55 | * 56 | * @return The front sign side as the {@link SignSideShim} adapter 57 | */ 58 | @NotNull 59 | private SignSideShim getFrontSignSide() { 60 | return new SignSideShim(implementation); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/shims/SignSideShim.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.shims; 21 | 22 | import org.bukkit.DyeColor; 23 | import org.jetbrains.annotations.NotNull; 24 | import org.jetbrains.annotations.Nullable; 25 | 26 | import java.lang.reflect.InvocationTargetException; 27 | import java.lang.reflect.Method; 28 | 29 | public class SignSideShim implements ISignSide { 30 | private final Object signSideLike; 31 | private final Class signSideLikeClass; 32 | 33 | public SignSideShim(@NotNull Object signSideLike) { 34 | this.signSideLike = signSideLike; 35 | this.signSideLikeClass = signSideLike.getClass(); 36 | } 37 | 38 | @NotNull 39 | public String[] getLines() { 40 | try { 41 | Method getLines = signSideLikeClass.getMethod("getLines"); 42 | return (String[]) getLines.invoke(signSideLike); 43 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 44 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 45 | } 46 | } 47 | 48 | @NotNull 49 | public String getLine(int index) throws IndexOutOfBoundsException { 50 | try { 51 | Method getLine = signSideLikeClass.getMethod("getLine", int.class); 52 | return (String) getLine.invoke(signSideLike, index); 53 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 54 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 55 | } 56 | } 57 | 58 | public void setLine(int index, @NotNull String line) throws IndexOutOfBoundsException { 59 | try { 60 | Method setLine = signSideLikeClass.getMethod("setLine", int.class, String.class); 61 | setLine.invoke(signSideLike, index, line); 62 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 63 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 64 | } 65 | } 66 | 67 | public boolean isGlowingText() { 68 | try { 69 | Method isGlowingText = signSideLikeClass.getMethod("isGlowingText"); 70 | return (boolean) isGlowingText.invoke(signSideLike); 71 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 72 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 73 | } 74 | } 75 | 76 | public void setGlowingText(boolean glowing) { 77 | try { 78 | Method setGlowingText = signSideLikeClass.getMethod("setGlowingText", boolean.class); 79 | setGlowingText.invoke(signSideLike, glowing); 80 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 81 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 82 | } 83 | } 84 | 85 | @Nullable 86 | public DyeColor getColor() { 87 | try { 88 | Method getColor = signSideLikeClass.getMethod("getColor"); 89 | return (DyeColor) getColor.invoke(signSideLike); 90 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 91 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 92 | } 93 | } 94 | 95 | public void setColor(@Nullable DyeColor color) { 96 | try { 97 | Method setColor = signSideLikeClass.getMethod("setColor", DyeColor.class); 98 | setColor.invoke(signSideLike, color); 99 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 100 | throw new IllegalStateException("Unexpected structure of Sign-like object"); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/CancelSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 25 | import net.deltik.mc.signedit.interactions.SignEditInteractionManager; 26 | import org.bukkit.entity.Player; 27 | 28 | import javax.inject.Inject; 29 | 30 | public class CancelSignSubcommand extends SignSubcommand { 31 | private final SignEditInteractionManager interactionManager; 32 | private final Player player; 33 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 34 | 35 | @Inject 36 | public CancelSignSubcommand( 37 | SignEditInteractionManager interactionManager, 38 | Player player, 39 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder 40 | ) { 41 | super(player); 42 | this.interactionManager = interactionManager; 43 | this.player = player; 44 | this.commsBuilder = commsBuilder; 45 | } 46 | 47 | @Override 48 | public SignEditInteraction execute() { 49 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 50 | 51 | SignEditInteraction interaction = interactionManager.removePendingInteraction(player); 52 | if (interaction == null) { 53 | comms.tell(comms.t("no_pending_action_to_cancel")); 54 | } else { 55 | interaction.cleanup(); 56 | comms.tell(comms.t("cancelled_pending_action")); 57 | } 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/ClearSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.ArgParser; 23 | import net.deltik.mc.signedit.LineSelectorParser; 24 | import net.deltik.mc.signedit.SignText; 25 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 26 | import org.bukkit.entity.Player; 27 | 28 | import javax.inject.Inject; 29 | import javax.inject.Provider; 30 | import java.util.Map; 31 | 32 | public class ClearSignSubcommand extends SignSubcommand { 33 | private final Map> interactions; 34 | private final ArgParser argParser; 35 | private final SignText signText; 36 | 37 | @Inject 38 | public ClearSignSubcommand( 39 | Map> interactions, 40 | Player player, 41 | ArgParser argParser, 42 | SignText signText 43 | ) { 44 | super(player); 45 | this.interactions = interactions; 46 | this.argParser = argParser; 47 | this.signText = signText; 48 | } 49 | 50 | @Override 51 | public SignEditInteraction execute() { 52 | int[] selectedLines = argParser.getLinesSelection(); 53 | if (selectedLines.length <= 0) { 54 | selectedLines = LineSelectorParser.ALL_LINES_SELECTED; 55 | } 56 | 57 | for (int selectedLine : selectedLines) { 58 | signText.setLine(selectedLine, ""); 59 | } 60 | 61 | return interactions.get("Set").get(); 62 | } 63 | } -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/CopySignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 23 | import org.bukkit.entity.Player; 24 | 25 | import javax.inject.Inject; 26 | import javax.inject.Provider; 27 | import java.util.Map; 28 | 29 | public class CopySignSubcommand extends SignSubcommand { 30 | private final Map> interactions; 31 | 32 | @Inject 33 | public CopySignSubcommand( 34 | Player player, 35 | Map> interactions 36 | ) { 37 | super(player); 38 | this.interactions = interactions; 39 | } 40 | 41 | @Override 42 | public SignEditInteraction execute() { 43 | return interactions.get("Copy").get(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/CutSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 23 | import org.bukkit.entity.Player; 24 | 25 | import javax.inject.Inject; 26 | import javax.inject.Provider; 27 | import java.util.Map; 28 | 29 | public class CutSignSubcommand extends SignSubcommand { 30 | private final Map> interactions; 31 | 32 | @Inject 33 | public CutSignSubcommand( 34 | Player player, 35 | Map> interactions 36 | ) { 37 | super(player); 38 | this.interactions = interactions; 39 | } 40 | 41 | @Override 42 | public SignEditInteraction execute() { 43 | return interactions.get("Cut").get(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/PasteSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.SignTextClipboardManager; 23 | import net.deltik.mc.signedit.exceptions.NullClipboardException; 24 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 25 | import org.bukkit.entity.Player; 26 | 27 | import javax.inject.Inject; 28 | import javax.inject.Provider; 29 | import java.util.Map; 30 | 31 | public class PasteSignSubcommand extends SignSubcommand { 32 | private final Map> interactions; 33 | private final Player player; 34 | private final SignTextClipboardManager clipboardManager; 35 | 36 | @Inject 37 | public PasteSignSubcommand( 38 | Map> interactions, 39 | Player player, 40 | SignTextClipboardManager clipboardManager 41 | ) { 42 | super(player); 43 | this.interactions = interactions; 44 | this.player = player; 45 | this.clipboardManager = clipboardManager; 46 | } 47 | 48 | @Override 49 | public SignEditInteraction execute() { 50 | if (clipboardManager.getClipboard(player) == null) { 51 | throw new NullClipboardException(); 52 | } 53 | 54 | return interactions.get("Paste").get(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/PerSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import javax.inject.Scope; 23 | import java.lang.annotation.Documented; 24 | import java.lang.annotation.Retention; 25 | 26 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 | 28 | @Scope 29 | @Documented 30 | @Retention(RUNTIME) 31 | public @interface PerSubcommand { 32 | } -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/RedoSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.*; 23 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 24 | import org.bukkit.entity.Player; 25 | 26 | import javax.inject.Inject; 27 | 28 | public class RedoSignSubcommand extends SignSubcommand { 29 | private final Player player; 30 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 31 | private final SignTextHistoryManager historyManager; 32 | 33 | @Inject 34 | public RedoSignSubcommand( 35 | Player player, 36 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder, 37 | SignTextHistoryManager historyManager 38 | ) { 39 | super(player); 40 | this.player = player; 41 | this.commsBuilder = commsBuilder; 42 | this.historyManager = historyManager; 43 | } 44 | 45 | @Override 46 | public SignEditInteraction execute() { 47 | SignTextHistory history = historyManager.getHistory(player); 48 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 49 | SignText redoneSignText = history.redo(comms); 50 | comms.compareSignText(redoneSignText); 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/SetSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.ArgParser; 23 | import net.deltik.mc.signedit.SignText; 24 | import net.deltik.mc.signedit.exceptions.MissingLineSelectionException; 25 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 26 | import org.bukkit.entity.Player; 27 | 28 | import javax.inject.Inject; 29 | import javax.inject.Provider; 30 | import java.util.Map; 31 | 32 | public class SetSignSubcommand extends SignSubcommand { 33 | private final Map> interactions; 34 | private final ArgParser argParser; 35 | private final SignText signText; 36 | 37 | @Inject 38 | public SetSignSubcommand( 39 | Map> interactions, 40 | Player player, 41 | ArgParser argParser, 42 | SignText signText 43 | ) { 44 | super(player); 45 | this.interactions = interactions; 46 | this.argParser = argParser; 47 | this.signText = signText; 48 | } 49 | 50 | @Override 51 | public SignEditInteraction execute() { 52 | int[] selectedLines = argParser.getLinesSelection(); 53 | if (selectedLines.length <= 0) { 54 | throw new MissingLineSelectionException(); 55 | } 56 | 57 | String text = String.join(" ", argParser.getRemainder()); 58 | 59 | for (int selectedLine : selectedLines) { 60 | signText.setLine(selectedLine, text); 61 | } 62 | 63 | return interactions.get("Set").get(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/SignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.commands.SignCommand; 23 | import net.deltik.mc.signedit.interactions.InteractionCommand; 24 | import org.bukkit.entity.Player; 25 | 26 | public abstract class SignSubcommand extends Subcommand implements InteractionCommand { 27 | private final Player player; 28 | 29 | protected SignSubcommand(Player player) { 30 | this.player = player; 31 | } 32 | 33 | @Override 34 | public boolean isPermitted() { 35 | return // Legacy (< 1.4) permissions 36 | player.hasPermission("signedit.use") || 37 | // /sign 38 | player.hasPermission("signedit." + SignCommand.COMMAND_NAME + "." + getName()); 39 | } 40 | 41 | /** 42 | * Get the subcommand name 43 | */ 44 | private String getName() { 45 | String simpleClassName = getClass().getSimpleName(); 46 | int end = simpleClassName.lastIndexOf(SignSubcommand.class.getSimpleName()); 47 | if (end != -1) { 48 | return simpleClassName.substring(0, end).toLowerCase(); 49 | } 50 | return simpleClassName.toLowerCase(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/SignSubcommandComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import dagger.BindsInstance; 23 | import dagger.Subcomponent; 24 | import net.deltik.mc.signedit.ArgParser; 25 | import net.deltik.mc.signedit.ArgParserArgs; 26 | import net.deltik.mc.signedit.interactions.InteractionCommand; 27 | import net.deltik.mc.signedit.interactions.SignEditInteractionModule; 28 | import org.bukkit.entity.Player; 29 | 30 | import javax.inject.Provider; 31 | import java.util.Map; 32 | 33 | @PerSubcommand 34 | @Subcomponent(modules = {SignEditInteractionModule.class}) 35 | public interface SignSubcommandComponent { 36 | 37 | Map> subcommandProviders(); 38 | 39 | ArgParser argParser(); 40 | 41 | @Subcomponent.Builder 42 | abstract class Builder { 43 | public abstract SignSubcommandComponent build(); 44 | 45 | @BindsInstance 46 | public abstract Builder player(Player player); 47 | 48 | @BindsInstance 49 | public abstract Builder commandArgs(@ArgParserArgs String[] args); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/StatusSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.*; 23 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 24 | import net.deltik.mc.signedit.interactions.SignEditInteractionManager; 25 | import org.bukkit.entity.Player; 26 | 27 | import javax.inject.Inject; 28 | 29 | public class StatusSignSubcommand extends SignSubcommand { 30 | private final Player player; 31 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 32 | private final SignEditInteractionManager interactionManager; 33 | private final SignTextClipboardManager clipboardManager; 34 | private final SignTextHistoryManager historyManager; 35 | 36 | @Inject 37 | public StatusSignSubcommand( 38 | Player player, 39 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder, 40 | SignEditInteractionManager interactionManager, 41 | SignTextClipboardManager clipboardManager, 42 | SignTextHistoryManager historyManager 43 | ) { 44 | super(player); 45 | this.player = player; 46 | this.commsBuilder = commsBuilder; 47 | this.interactionManager = interactionManager; 48 | this.clipboardManager = clipboardManager; 49 | this.historyManager = historyManager; 50 | } 51 | 52 | @Override 53 | public SignEditInteraction execute() { 54 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 55 | 56 | reportHistory(comms); 57 | reportPendingAction(comms); 58 | reportClipboard(comms); 59 | 60 | return null; 61 | } 62 | 63 | private void reportPendingAction(ChatComms comms) { 64 | if (!interactionManager.isInteractionPending(player)) { 65 | comms.tell(comms.t("pending_action_section", comms.t("no_pending_action"))); 66 | } else { 67 | SignEditInteraction interaction = interactionManager.getPendingInteraction(player); 68 | comms.tell(comms.t("pending_action_section", comms.t(interaction.getName()))); 69 | comms.tell(interaction.getActionHint(comms)); 70 | } 71 | } 72 | 73 | private void reportHistory(ChatComms comms) { 74 | SignTextHistory history = historyManager.getHistory(player); 75 | int undosRemaining = 0; 76 | int redosRemaining = 0; 77 | 78 | if (history != null) { 79 | undosRemaining = history.undosRemaining(); 80 | redosRemaining = history.redosRemaining(); 81 | } 82 | 83 | comms.tell(comms.t("history_section", 84 | comms.t("history_have", undosRemaining, redosRemaining) 85 | )); 86 | } 87 | 88 | private void reportClipboard(ChatComms comms) { 89 | SignText clipboard = clipboardManager.getClipboard(player); 90 | if (clipboard == null) { 91 | comms.tell(comms.t("clipboard_contents_section", comms.t("empty_clipboard"))); 92 | } else { 93 | comms.tell(comms.t("clipboard_contents_section", "")); 94 | comms.dumpLines(clipboard.getLines()); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/Subcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | public abstract class Subcommand { 23 | } 24 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/SubcommandName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import javax.inject.Qualifier; 23 | import java.lang.annotation.Documented; 24 | import java.lang.annotation.Retention; 25 | import java.lang.annotation.RetentionPolicy; 26 | 27 | @Qualifier 28 | @Documented 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface SubcommandName { 31 | } 32 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/UiSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.Configuration; 23 | import net.deltik.mc.signedit.CraftBukkitReflector; 24 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 25 | import net.deltik.mc.signedit.interactions.SignEditInteractionModule; 26 | import org.bukkit.entity.Player; 27 | 28 | import javax.inject.Inject; 29 | import javax.inject.Provider; 30 | import java.util.Map; 31 | 32 | public class UiSignSubcommand extends SignSubcommand { 33 | /** 34 | * Bug reported for Minecraft 1.16.1: https://bugs.mojang.com/browse/MC-192263 35 | * Bug resolved in Minecraft 1.16.2: https://minecraft.gamepedia.com/Java_Edition_20w30a 36 | */ 37 | private static final String QUIRKY_BUKKIT_SERVER_VERSION = "v1_16_R1"; 38 | 39 | private final Configuration config; 40 | private final Map> interactions; 41 | private final CraftBukkitReflector reflector; 42 | 43 | @Inject 44 | public UiSignSubcommand( 45 | Player player, 46 | Configuration config, 47 | Map> interactions, 48 | CraftBukkitReflector reflector 49 | ) { 50 | super(player); 51 | this.config = config; 52 | this.interactions = interactions; 53 | this.reflector = reflector; 54 | } 55 | 56 | @Override 57 | public SignEditInteraction execute() { 58 | return interactions.get(getImplementationName(config, reflector)).get(); 59 | } 60 | 61 | public static String getImplementationName(Configuration config, CraftBukkitReflector reflector) { 62 | String value = config.getSignUi().toLowerCase(); 63 | 64 | if ("editablebook".equals(value)) { 65 | return SignEditInteractionModule.UI_EDITABLE_BOOK; 66 | } else if ("native".equals(value)) { 67 | return SignEditInteractionModule.UI_NATIVE; 68 | } 69 | 70 | // Auto mode 71 | if (QUIRKY_BUKKIT_SERVER_VERSION.compareTo(reflector.BUKKIT_SERVER_VERSION) == 0) { 72 | return SignEditInteractionModule.UI_EDITABLE_BOOK; 73 | } 74 | return SignEditInteractionModule.UI_NATIVE; 75 | } 76 | } -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/UndoSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.*; 23 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 24 | import org.bukkit.entity.Player; 25 | 26 | import javax.inject.Inject; 27 | 28 | public class UndoSignSubcommand extends SignSubcommand { 29 | private final Player player; 30 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 31 | private final SignTextHistoryManager historyManager; 32 | 33 | @Inject 34 | public UndoSignSubcommand( 35 | Player player, 36 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder, 37 | SignTextHistoryManager historyManager 38 | ) { 39 | super(player); 40 | this.player = player; 41 | this.commsBuilder = commsBuilder; 42 | this.historyManager = historyManager; 43 | } 44 | 45 | @Override 46 | public SignEditInteraction execute() { 47 | SignTextHistory history = historyManager.getHistory(player); 48 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 49 | SignText undoneSignText = history.undo(comms); 50 | comms.compareSignText(undoneSignText); 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/UnwaxSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.SignText; 23 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 24 | import net.deltik.mc.signedit.shims.SignHelpers; 25 | import org.bukkit.entity.Player; 26 | 27 | import javax.inject.Inject; 28 | import javax.inject.Provider; 29 | import java.util.Map; 30 | 31 | public class UnwaxSignSubcommand extends SignSubcommand { 32 | private final Map> interactions; 33 | private final SignText signText; 34 | 35 | @Inject 36 | public UnwaxSignSubcommand( 37 | Map> interactions, 38 | Player player, 39 | SignText signText 40 | ) { 41 | super(player); 42 | this.interactions = interactions; 43 | this.signText = signText; 44 | } 45 | 46 | @Override 47 | public SignEditInteraction execute() { 48 | signText.setShouldBeEditable(true); 49 | return interactions.get("Wax").get(); 50 | } 51 | 52 | @Override 53 | public boolean isPermitted() { 54 | if (!SignHelpers.hasWaxableFeature()) return false; 55 | return super.isPermitted(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/VersionSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 25 | import org.bukkit.entity.Player; 26 | import org.bukkit.plugin.Plugin; 27 | 28 | import javax.inject.Inject; 29 | 30 | public class VersionSignSubcommand extends SignSubcommand { 31 | private final Plugin plugin; 32 | private final ChatCommsModule.ChatCommsComponent.Builder commsBuilder; 33 | private final Player player; 34 | 35 | @Inject 36 | public VersionSignSubcommand( 37 | Plugin plugin, 38 | ChatCommsModule.ChatCommsComponent.Builder commsBuilder, 39 | Player player 40 | ) { 41 | super(player); 42 | this.plugin = plugin; 43 | this.commsBuilder = commsBuilder; 44 | this.player = player; 45 | } 46 | 47 | @Override 48 | public SignEditInteraction execute() { 49 | String version = plugin.getDescription().getVersion(); 50 | ChatComms comms = commsBuilder.commandSender(player).build().comms(); 51 | comms.tell(comms.t("version", version)); 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/net/deltik/mc/signedit/subcommands/WaxSignSubcommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.SignText; 23 | import net.deltik.mc.signedit.interactions.SignEditInteraction; 24 | import net.deltik.mc.signedit.shims.SignHelpers; 25 | import org.bukkit.entity.Player; 26 | 27 | import javax.inject.Inject; 28 | import javax.inject.Provider; 29 | import java.util.Map; 30 | 31 | public class WaxSignSubcommand extends SignSubcommand { 32 | private final Map> interactions; 33 | private final SignText signText; 34 | 35 | @Inject 36 | public WaxSignSubcommand( 37 | Map> interactions, 38 | Player player, 39 | SignText signText 40 | ) { 41 | super(player); 42 | this.interactions = interactions; 43 | this.signText = signText; 44 | } 45 | 46 | @Override 47 | public SignEditInteraction execute() { 48 | signText.setShouldBeEditable(false); 49 | return interactions.get("Wax").get(); 50 | } 51 | 52 | @Override 53 | public boolean isPermitted() { 54 | if (!SignHelpers.hasWaxableFeature()) return false; 55 | return super.isPermitted(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/resources/Comms.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2025 Deltik 3 | # 4 | # This file is part of SignEdit for Bukkit. 5 | # 6 | # SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with SignEdit for Bukkit. If not, see . 18 | # 19 | 20 | # ${pluginName} ${pluginVersion} locale file 21 | 22 | # Theme 23 | reset=§r 24 | primary=§6 25 | primaryDark=§7 26 | primaryLight=§e 27 | secondary=§f 28 | highlightBefore=§4 29 | highlightAfter=§2 30 | strong=§l 31 | italic=§o 32 | strike=§m 33 | error=§c 34 | # Display 35 | ## 0 - {plugin_name} 36 | ## 1 - Any message sent to the player by this plugin 37 | prefix={primaryDark}[{primary}{0}{primaryDark}]{reset} {1} 38 | ## 0 - Highlight formatting 39 | ## 1 - Sign line number 40 | ## 2 - Sign line text 41 | print_line=\\ {0}<{1,number}>{reset} {2} 42 | # General 43 | plugin_name=SignEdit 44 | ## 0 - Command and subcommand as "/command subcommand" 45 | you_cannot_use={error}You are not allowed to use {primaryLight}{0}{error}. 46 | ## 0 - {usage_page_info} 47 | usage_page_heading={secondary}-----{reset} {0}{reset} {secondary}----- 48 | ## 0 - Command 49 | ## 1 - The word "help" as a subcommand 50 | ## 2 - {usage_page_numbering}, only shown if there is more than 1 page 51 | usage_page_info={primary}/{0}{reset} {primaryLight}{1}{reset}{2} 52 | ## 0 - Current page number 53 | ## 1 - Total number of pages 54 | usage_page_numbering=\\ {secondary}{0,number}{reset} {primaryDark}of {1,number} 55 | ## 0 - Command 56 | ## 1 - Subcommand 57 | ## 2 - Subcommand arguments 58 | print_subcommand_usage={primary}/{0} {primaryLight}{1} {primaryDark}{2} 59 | ## 0 - {online_documentation_url} 60 | online_documentation={secondary}{strong}Online Help: {reset}{0} 61 | online_documentation_url=https://git.io/SignEdit-README 62 | sign_did_not_change={primary}Sign did not change 63 | ## 0 - {section_decorator}, only shown if there is something to say about the section 64 | before_section={primary}{strong}Before:{0} 65 | ## 0 - {section_decorator}, only shown if there is something to say about the section 66 | after_section={primary}{strong}After:{0} 67 | ## 0 - A notice about the section 68 | section_decorator={reset} {primaryDark}{italic}({primaryLight}{italic}{0}{primaryDark}{italic}) 69 | right_click_sign_to_apply_action={primary}Right-click a sign to apply the action 70 | right_click_sign_to_apply_action_hint=\\ {italic}Right-click a sign to apply the action 71 | right_click_air_to_apply_action_hint=\\ {italic}Right-click air (nothing) to apply the action 72 | right_click_air_to_open_sign_editor={primary}Look away from the sign, then right-click to open the sign editor 73 | sign_editor_item_name=Sign Editor 74 | must_look_at_sign_to_interact={error}You must be looking at a sign to interact with it! 75 | lines_copied_section={primary}{strong}Lines copied: 76 | lines_cut_section={primary}{strong}Lines cut: 77 | cancelled_pending_action={primary}Cancelled pending action 78 | no_pending_action_to_cancel={error}No pending action to cancel! 79 | wax_removed={primary}Wax removed 80 | wax_applied={primary}Wax applied 81 | # StatusSignSubcommand 82 | no_pending_action=None 83 | empty_clipboard={primaryDark}None 84 | ## 0 - One of the "Interactions" 85 | pending_action_section={primary}{strong}Pending Action: {reset}{0} 86 | ## 0 - {history_have} 87 | history_section={primary}{strong}History: {reset}{0} 88 | ## 0 - Count of items in the undo stack 89 | ## 1 - Count of items in the redo stack 90 | history_have={primary}have {primaryLight}{0,number}{primary} undo{0,choice,0#s|1#|1 3 | # 4 | # This file is part of SignEdit for Bukkit. 5 | # 6 | # SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with SignEdit for Bukkit. If not, see . 18 | # 19 | 20 | # ${pluginName} ${pluginVersion} locale file 21 | 22 | # Comms.properties provides this language. -------------------------------------------------------------------------------- /src/resources/Comms_zh.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2025 Deltik 3 | # 4 | # This file is part of SignEdit for Bukkit. 5 | # 6 | # SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with SignEdit for Bukkit. If not, see . 18 | # 19 | 20 | # ${pluginName} ${pluginVersion} locale file 21 | 22 | # Theme 23 | reset=§r 24 | primary=§6 25 | primaryDark=§7 26 | primaryLight=§e 27 | secondary=§f 28 | highlightBefore=§4 29 | highlightAfter=§2 30 | strong=§l 31 | italic=§o 32 | strike=§m 33 | error=§c 34 | # Display 35 | ## 0 - {plugin_name} 36 | ## 1 - Any message sent to the player by this plugin 37 | prefix={primaryDark}[{primary}{0}{primaryDark}]{reset} {1} 38 | ## 0 - Highlight formatting 39 | ## 1 - Sign line number 40 | ## 2 - Sign line text 41 | print_line=\\ {0}<{1,number}>{reset} {2} 42 | # General 43 | plugin_name=SignEdit 44 | ## 0 - Command and subcommand as "/command subcommand" 45 | you_cannot_use={error}您没有权限使用 {primaryLight}{0}{error}。 46 | ## 0 - {usage_page_info} 47 | usage_page_heading={secondary}-----{reset} {0}{reset} {secondary}----- 48 | ## 0 - Command 49 | ## 1 - The word "help" as a subcommand 50 | ## 2 - {usage_page_numbering}, only shown if there is more than 1 page 51 | usage_page_info={primary}/{0}{reset} {primaryLight}{1}{reset}{2} 52 | ## 0 - Current page number 53 | ## 1 - Total number of pages 54 | usage_page_numbering=\\ {secondary}{0,number}{reset}{primaryDark}/{1,number} 55 | ## 0 - Command 56 | ## 1 - Subcommand 57 | ## 2 - Subcommand arguments 58 | print_subcommand_usage={primary}/{0} {primaryLight}{1} {primaryDark}{2} 59 | ## 0 - {online_documentation_url} 60 | online_documentation={secondary}{strong}在线帮助: {reset}{0} 61 | online_documentation_url=https://git.io/SignEdit-README 62 | sign_did_not_change={primary}告示牌没有变更。 63 | ## 0 - {section_decorator}, only shown if there is something to say about the section 64 | before_section={primary}{strong}变更前:{0} 65 | ## 0 - {section_decorator}, only shown if there is something to say about the section 66 | after_section={primary}{strong}变更后:{0} 67 | ## 0 - A notice about the section 68 | section_decorator={reset}{primaryDark}{italic}({primaryLight}{italic}{0}{primaryDark}{italic}) 69 | right_click_sign_to_apply_action={primary}右键点击要编辑的告示牌 70 | right_click_sign_to_apply_action_hint=\\ {italic}右键点击要编辑的告示牌 71 | right_click_air_to_apply_action_hint=\\ {italic}对空气点击右键以执行动作 72 | right_click_air_to_open_sign_editor={primary}请将视线移离告示牌,然后点击右键,开启告示牌编辑器 73 | sign_editor_item_name=告示牌编辑器 74 | must_look_at_sign_to_interact={error}您必须看着一个告示牌才能编辑它! 75 | lines_copied_section={primary}{strong}已复制的行: 76 | lines_cut_section={primary}{strong}已剪下的行: 77 | cancelled_pending_action={primary}等待中的编辑动作已取消。 78 | no_pending_action_to_cancel={error}没有可以取消的编辑动作! 79 | wax_removed={primary}已给告示牌脱蜡 80 | wax_applied={primary}已给告示牌涂蜡 81 | # StatusSignSubcommand 82 | no_pending_action=无 83 | empty_clipboard={primaryDark}空白 84 | ## 0 - One of the "Interactions" 85 | pending_action_section={primary}{strong}等待中的动作:{reset}{0} 86 | ## 0 - {history_have} 87 | history_section={primary}{strong}编辑历史: {reset}{0} 88 | ## 0 - Count of items in the undo stack 89 | ## 1 - Count of items in the redo stack 90 | history_have={primary}有 {primaryLight}{0,number}{primary} 个撤销和 {primaryLight}{1,number}{primary} 个重做 91 | ## 0 - {empty_clipboard}, shown only if the clipboard is empty 92 | clipboard_contents_section={primary}{strong}剪贴板內容:{reset}{0} 93 | # Interactions 94 | copy_sign_text=复制告示牌的文字 95 | cut_sign_text=剪切告示牌的文字 96 | paste_lines_from_clipboard=粘贴剪贴板里的文字 97 | change_sign_text=更改告示牌的文字 98 | open_sign_editor=开启告示牌编辑器 99 | unwax_sign=刮除告示牌的蜜脾 100 | wax_sign=将蜜脾涂到告示牌上 101 | # VersionSignSubcommand 102 | ## 0 - Version of the plugin 103 | version={primary}版本 {secondary}{0} 104 | # Errors 105 | forbidden_sign_edit={error}有其他插件或策略阻止了告示牌的编辑 106 | forbidden_waxed_sign_edit={error}您不能编辑已涂蜡的告示牌。 107 | bypass_wax_cannot_rewax={error}无法重新涂蜡:您没有重新给告示牌涂蜡的权限! 108 | missing_line_selection_exception={error}必须至少选择一个行。 109 | ## 0 - Invalid input that the player tried to pass as a line selection 110 | number_parse_line_selection_exception={error}"{0}" 不是一个正确的行号。 111 | ## 0 - The index of the first line of a sign 112 | ## 1 - The index of the last line of a sign 113 | ## 2 - The invalid line index that the player inputted 114 | out_of_bounds_line_selection_exception={error}行号必须在 {0,number} 到 {1,number} 之间,却提供了 {2,number}。 115 | ## 0 - The invalid lower bound that the player inputted for a line range selection 116 | ## 1 - The invalid upper bound that the player inputted for a line range selection 117 | ## 2 - The invalid line range selection that the player inputted 118 | range_order_line_selection_exception={error}在选择 {2} 中,下限 {0,number} 不能高于上限 {1,number}。 119 | ## 0 - The invalid line range selection that the player inputted 120 | ## 1 - The full invalid line selection that the player inputted 121 | range_parse_line_selection_exception={error}在选择 {1} 中,"{0}" 并非有效的范围。 122 | nothing_to_undo={error}没有可以撤销的动作 123 | nothing_to_redo={error}没有可以重做的动作 124 | block_state_not_placed_exception={error}命令执行失败:告示牌已不再存在! 125 | null_clipboard_exception={error}剪贴板是空的! 126 | ## 0 - The string representation of a Java Exception 127 | uncaught_error={error}未知的错误:{0} 128 | cannot_open_sign_editor={error}{strong}无法开启告示牌编辑器! 129 | ## 0 - The guessed cause of a sign edit failure, probably {minecraft_server_api_changed} 130 | likely_cause={primaryDark}可能的原因:{reset}{0} 131 | minecraft_server_api_changed=Minecraft 服务器 API 改变了 132 | ## 0 - A message for the player to forward to the server admin 133 | to_server_admin={primaryDark}服务器管理员:{reset}{0} 134 | check_for_updates_to_this_plugin=检查这个插件的更新 135 | ## 0 - The string representation of a Java Exception 136 | error_code={primaryDark}错误代码:{reset}{0} 137 | hint_more_details_with_server_admin={error}(服务器的控制台有更多信息) 138 | # Warnings 139 | modified_by_another_plugin=已被其他插件编辑 140 | -------------------------------------------------------------------------------- /src/resources/Comms_zh_CN.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2025 Deltik 3 | # 4 | # This file is part of SignEdit for Bukkit. 5 | # 6 | # SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with SignEdit for Bukkit. If not, see . 18 | # 19 | 20 | # ${pluginName} ${pluginVersion} locale file 21 | 22 | # Comms_zh.properties provides this language. 23 | -------------------------------------------------------------------------------- /src/resources/Comms_zh_HK.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2025 Deltik 3 | # 4 | # This file is part of SignEdit for Bukkit. 5 | # 6 | # SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with SignEdit for Bukkit. If not, see . 18 | # 19 | 20 | # ${pluginName} ${pluginVersion} locale file 21 | 22 | # Theme 23 | reset=§r 24 | primary=§6 25 | primaryDark=§7 26 | primaryLight=§e 27 | secondary=§f 28 | highlightBefore=§4 29 | highlightAfter=§2 30 | strong=§l 31 | italic=§o 32 | strike=§m 33 | error=§c 34 | # Display 35 | ## 0 - {plugin_name} 36 | ## 1 - Any message sent to the player by this plugin 37 | prefix={primaryDark}[{primary}{0}{primaryDark}]{reset} {1} 38 | ## 0 - Highlight formatting 39 | ## 1 - Sign line number 40 | ## 2 - Sign line text 41 | print_line=\\ {0}<{1,number}>{reset} {2} 42 | # General 43 | plugin_name=SignEdit 44 | ## 0 - Command and subcommand as "/command subcommand" 45 | you_cannot_use={error}你冇權限使用 {primaryLight}{0}{error}。 46 | ## 0 - {usage_page_info} 47 | usage_page_heading={secondary}-----{reset} {0}{reset} {secondary}----- 48 | ## 0 - Command 49 | ## 1 - The word "help" as a subcommand 50 | ## 2 - {usage_page_numbering}, only shown if there is more than 1 page 51 | usage_page_info={primary}/{0}{reset} {primaryLight}{1}{reset}{2} 52 | ## 0 - Current page number 53 | ## 1 - Total number of pages 54 | usage_page_numbering=\\ {secondary}{0,number}{reset}{primaryDark}/{1,number} 55 | ## 0 - Command 56 | ## 1 - Subcommand 57 | ## 2 - Subcommand arguments 58 | print_subcommand_usage={primary}/{0} {primaryLight}{1} {primaryDark}{2} 59 | ## 0 - {online_documentation_url} 60 | online_documentation={secondary}{strong}線上説明:{reset}{0} 61 | online_documentation_url=https://git.io/SignEdit-README 62 | sign_did_not_change={primary}指示牌未有變更。 63 | ## 0 - {section_decorator}, only shown if there is something to say about the section 64 | before_section={primary}{strong}變更前:{0} 65 | ## 0 - {section_decorator}, only shown if there is something to say about the section 66 | after_section={primary}{strong}變更後:{0} 67 | ## 0 - A notice about the section 68 | section_decorator={reset}{primaryDark}{italic}({primaryLight}{italic}{0}{primaryDark}{italic}) 69 | right_click_sign_to_apply_action={primary}右鍵點擊要編輯嘅指示牌 70 | right_click_sign_to_apply_action_hint=\\ {italic}右鍵點擊要編輯嘅指示牌 71 | right_click_air_to_apply_action_hint=\\ {italic}對空氣點擊右鍵以執行操作 72 | right_click_air_to_open_sign_editor={primary}請將視線移離指示牌,然後點擊右鍵,打開指示牌編輯器 73 | sign_editor_item_name=指示牌編輯器 74 | must_look_at_sign_to_interact={error}你必須睇住一個指示牌先可以編輯! 75 | lines_copied_section={primary}{strong}已複製嘅行: 76 | lines_cut_section={primary}{strong}已剪下嘅行: 77 | cancelled_pending_action={primary}等待中嘅編輯動作已取消。 78 | no_pending_action_to_cancel={error}未有可以取消嘅編輯操作! 79 | wax_removed={primary}幫指示牌除咗蠟 80 | wax_applied={primary}幫指示牌打咗蠟 81 | # StatusSignSubcommand 82 | no_pending_action=無 83 | empty_clipboard={primaryDark}空白 84 | ## 0 - One of the "Interactions" 85 | pending_action_section={primary}{strong}等待中嘅操作:{reset}{0} 86 | ## 0 - {history_have} 87 | history_section={primary}{strong}編輯歷史:{reset}{0} 88 | ## 0 - Count of items in the undo stack 89 | ## 1 - Count of items in the redo stack 90 | history_have={primary}有 {primaryLight}{0,number}{primary} 個撤銷同 {primaryLight}{1,number}{primary} 個重做 91 | ## 0 - {empty_clipboard}, shown only if the clipboard is empty 92 | clipboard_contents_section={primary}{strong}剪貼簿內容:{reset}{0} 93 | # Interactions 94 | copy_sign_text=複製指示牌嘅文字 95 | cut_sign_text=剪下指示牌嘅文字 96 | paste_lines_from_clipboard=貼上剪貼簿入邊嘅文字 97 | change_sign_text=更改指示牌嘅文字 98 | open_sign_editor=開啟指示牌編輯器 99 | unwax_sign=刮除指示牌上嘅蜂蠟 100 | wax_sign=喺指示牌塗上蜂蠟 101 | # VersionSignSubcommand 102 | ## 0 - Version of the plugin 103 | version={primary}版本 {secondary}{0} 104 | # Errors 105 | forbidden_sign_edit={error}有其他插件或策略阻止咗指示牌嘅編輯 106 | forbidden_waxed_sign_edit={error}你唔可以編輯打咗蠟嘅指示牌。 107 | bypass_wax_cannot_rewax={error}無法重新打蠟:你冇權幫指示牌重新打蠟! 108 | missing_line_selection_exception={error}必須至少選擇一個行。 109 | ## 0 - Invalid input that the player tried to pass as a line selection 110 | number_parse_line_selection_exception={error}"{0}" 唔係一個正確嘅行號。 111 | ## 0 - The index of the first line of a sign 112 | ## 1 - The index of the last line of a sign 113 | ## 2 - The invalid line index that the player inputted 114 | out_of_bounds_line_selection_exception={error}行號必須喺 {0,number} 至 {1,number} 之間,但提供咗 {2,number}。 115 | ## 0 - The invalid lower bound that the player inputted for a line range selection 116 | ## 1 - The invalid upper bound that the player inputted for a line range selection 117 | ## 2 - The invalid line range selection that the player inputted 118 | range_order_line_selection_exception={error}喺選擇 {2} 中,下限 {0,number} 唔可以高過上限 {1,number}。 119 | ## 0 - The invalid line range selection that the player inputted 120 | ## 1 - The full invalid line selection that the player inputted 121 | range_parse_line_selection_exception={error}喺選擇 {1} 中,"{0}" 並非有效嘅範圍。 122 | nothing_to_undo={error}未有可以撤銷嘅操作 123 | nothing_to_redo={error}未有可以重做嘅操作 124 | block_state_not_placed_exception={error}指令執行失敗:指示牌已不再存在! 125 | null_clipboard_exception={error}剪貼簿係空白嘅! 126 | ## 0 - The string representation of a Java Exception 127 | uncaught_error={error}不明嘅錯誤:{0} 128 | cannot_open_sign_editor={error}{strong}無法打開指示牌編輯器! 129 | ## 0 - The guessed cause of a sign edit failure, probably {minecraft_server_api_changed} 130 | likely_cause={primaryDark}可能嘅原因: {reset}{0} 131 | minecraft_server_api_changed=Minecraft 伺服器 API 改變咗 132 | ## 0 - A message for the player to forward to the server admin 133 | to_server_admin={primaryDark}伺服器管理員:{reset}{0} 134 | check_for_updates_to_this_plugin=檢查依個插件嘅更新 135 | ## 0 - The string representation of a Java Exception 136 | error_code={primaryDark}錯誤代碼:{reset}{0} 137 | hint_more_details_with_server_admin={error}(伺服器嘅控制台有更多訊息) 138 | # Warnings 139 | modified_by_another_plugin=已被其他插件編輯 140 | -------------------------------------------------------------------------------- /src/resources/Comms_zh_TW.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2017-2025 Deltik 3 | # 4 | # This file is part of SignEdit for Bukkit. 5 | # 6 | # SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with SignEdit for Bukkit. If not, see . 18 | # 19 | 20 | # ${pluginName} ${pluginVersion} locale file 21 | 22 | # Theme 23 | reset=§r 24 | primary=§6 25 | primaryDark=§7 26 | primaryLight=§e 27 | secondary=§f 28 | highlightBefore=§4 29 | highlightAfter=§2 30 | strong=§l 31 | italic=§o 32 | strike=§m 33 | error=§c 34 | # Display 35 | ## 0 - {plugin_name} 36 | ## 1 - Any message sent to the player by this plugin 37 | prefix={primaryDark}[{primary}{0}{primaryDark}]{reset} {1} 38 | ## 0 - Highlight formatting 39 | ## 1 - Sign line number 40 | ## 2 - Sign line text 41 | print_line=\\ {0}<{1,number}>{reset} {2} 42 | # General 43 | plugin_name=SignEdit 44 | ## 0 - Command and subcommand as "/command subcommand" 45 | you_cannot_use={error}您沒有權限使用 {primaryLight}{0}{error}。 46 | ## 0 - {usage_page_info} 47 | usage_page_heading={secondary}-----{reset} {0}{reset} {secondary}----- 48 | ## 0 - Command 49 | ## 1 - The word "help" as a subcommand 50 | ## 2 - {usage_page_numbering}, only shown if there is more than 1 page 51 | usage_page_info={primary}/{0}{reset} {primaryLight}{1}{reset}{2} 52 | ## 0 - Current page number 53 | ## 1 - Total number of pages 54 | usage_page_numbering=\\ {secondary}{0,number}{reset}{primaryDark}/{1,number} 55 | ## 0 - Command 56 | ## 1 - Subcommand 57 | ## 2 - Subcommand arguments 58 | print_subcommand_usage={primary}/{0} {primaryLight}{1} {primaryDark}{2} 59 | ## 0 - {online_documentation_url} 60 | online_documentation={secondary}{strong}線上說明:{reset}{0} 61 | online_documentation_url=https://git.io/SignEdit-README 62 | sign_did_not_change={primary}告示牌沒有變更。 63 | ## 0 - {section_decorator}, only shown if there is something to say about the section 64 | before_section={primary}{strong}變更前: {0} 65 | ## 0 - {section_decorator}, only shown if there is something to say about the section 66 | after_section={primary}{strong}變更後: {0} 67 | ## 0 - A notice about the section 68 | section_decorator={reset}{primaryDark}{italic}({primaryLight}{italic}{0}{primaryDark}{italic}) 69 | right_click_sign_to_apply_action={primary}右鍵點擊要編輯的告示牌 70 | right_click_sign_to_apply_action_hint=\\ {italic}右鍵點擊要編輯的告示牌 71 | right_click_air_to_apply_action_hint=\\ {italic}對空氣點擊右鍵以執行操作 72 | right_click_air_to_open_sign_editor={primary}請將視線移離告示牌,然後點擊右鍵,開啟告示牌編輯器 73 | sign_editor_item_name=告示牌編輯器 74 | must_look_at_sign_to_interact={error}您必須看著一個告示牌才能編輯它! 75 | lines_copied_section={primary}{strong}已複製的行: 76 | lines_cut_section={primary}{strong}已剪下的行: 77 | cancelled_pending_action={primary}等待中的編輯操作已取消。 78 | no_pending_action_to_cancel={error}沒有可以取消的編輯操作! 79 | wax_removed={primary}已給告示牌除蠟 80 | wax_applied={primary}已給告示牌上蠟 81 | # StatusSignSubcommand 82 | no_pending_action=無 83 | empty_clipboard={primaryDark}空白 84 | ## 0 - One of the "Interactions" 85 | pending_action_section={primary}{strong}等待中的操作:{reset}{0} 86 | ## 0 - {history_have} 87 | history_section={primary}{strong}編輯歷史:{reset}{0} 88 | ## 0 - Count of items in the undo stack 89 | ## 1 - Count of items in the redo stack 90 | history_have={primary}有 {primaryLight}{0,number}{primary} 個撤銷和 {primaryLight}{1,number}{primary} 個重做 91 | ## 0 - {empty_clipboard}, shown only if the clipboard is empty 92 | clipboard_contents_section={primary}{strong}剪貼簿內容:{reset}{0} 93 | # Interactions 94 | copy_sign_text=複製告示牌的文字 95 | cut_sign_text=剪下告示牌的文字 96 | paste_lines_from_clipboard=貼上剪貼簿裡的文字 97 | change_sign_text=更改告示牌的文字 98 | open_sign_editor=開啟告示牌編輯器 99 | unwax_sign=刮除告示牌上的蜂蠟 100 | wax_sign=為告示牌塗上蜂蠟 101 | # VersionSignSubcommand 102 | ## 0 - Version of the plugin 103 | version={primary}版本 {secondary}{0} 104 | # Errors 105 | forbidden_sign_edit={error}有其他插件或策略阻止了告示牌的編輯 106 | forbidden_waxed_sign_edit={error}您不能編輯已上蠟的告示牌。 107 | bypass_wax_cannot_rewax={error}無法重新上蠟:您沒有給告示牌重新上蠟的權限! 108 | missing_line_selection_exception={error}必須至少選擇一個行。 109 | ## 0 - Invalid input that the player tried to pass as a line selection 110 | number_parse_line_selection_exception={error}"{0}" 不是一個正確的行號。 111 | ## 0 - The index of the first line of a sign 112 | ## 1 - The index of the last line of a sign 113 | ## 2 - The invalid line index that the player inputted 114 | out_of_bounds_line_selection_exception={error}行號必須在 {0,number} 至 {1,number} 之間,卻提供了 {2,number}。 115 | ## 0 - The invalid lower bound that the player inputted for a line range selection 116 | ## 1 - The invalid upper bound that the player inputted for a line range selection 117 | ## 2 - The invalid line range selection that the player inputted 118 | range_order_line_selection_exception={error}在選擇 {2} 中,下限 {0,number} 不能高於上限 {1,number}。 119 | ## 0 - The invalid line range selection that the player inputted 120 | ## 1 - The full invalid line selection that the player inputted 121 | range_parse_line_selection_exception={error}在選擇 {1} 中,"{0}" 並非有效的範圍。 122 | nothing_to_undo={error}沒有可以撤銷的操作 123 | nothing_to_redo={error}沒有可以重做的操作 124 | block_state_not_placed_exception={error}指令執行失敗: 告示牌已不再存在! 125 | null_clipboard_exception={error}剪貼簿是空的! 126 | ## 0 - The string representation of a Java Exception 127 | uncaught_error={error}不明的錯誤:{0} 128 | cannot_open_sign_editor={error}{strong}無法開啟告示牌編輯器! 129 | ## 0 - The guessed cause of a sign edit failure, probably {minecraft_server_api_changed} 130 | likely_cause={primaryDark}可能的原因:{reset}{0} 131 | minecraft_server_api_changed=Minecraft 伺服器 API 改變了 132 | ## 0 - A message for the player to forward to the server admin 133 | to_server_admin={primaryDark}伺服器管理員:{reset}{0} 134 | check_for_updates_to_this_plugin=檢查這個插件的更新 135 | ## 0 - The string representation of a Java Exception 136 | error_code={primaryDark}錯誤代碼:{reset}{0} 137 | hint_more_details_with_server_admin={error}(伺服器的控制台有更多訊息) 138 | # Warnings 139 | modified_by_another_plugin=已被其他插件編輯 140 | -------------------------------------------------------------------------------- /src/resources/README.Comms.txt: -------------------------------------------------------------------------------- 1 | This plugin's text (colors and languages) displayed to players can be 2 | customized. 3 | 4 | To customize the text, create your own locale files in the ./overrides/ folder. 5 | 6 | Note that as the locale files are standard Java ResourceBundles, any script, 7 | region, and variants in the language tag should be delimited by underscores 8 | ("_") instead of the standard hyphen delimiter ("-"). For example, "zh-TW" 9 | should be in a file called "Comms_zh_TW.properties". 10 | 11 | The ./originals/ folder contains a read-only copy of the locale files used by 12 | this plugin. You should copy the locale files you want to customize from 13 | ./originals/ into ./overrides/ to get a full working copy of the text. Then, 14 | you can edit your copy or copies in ./overrides/ to set your own colors and 15 | translations. 16 | 17 | This file and everything in ./originals/ is managed by ${pluginName}. Any 18 | changes will be overwritten when the plugin is reloaded, so only make your 19 | changes in the ./overrides/ folder. 20 | -------------------------------------------------------------------------------- /src/resources/config.yml.j2: -------------------------------------------------------------------------------- 1 | # ${pluginName} ${pluginVersion} configuration file 2 | # 3 | # This file is managed by ${pluginName}. The configuration option values can be changed by hand, but any comment or 4 | # spacing changes will be overwritten automatically by ${pluginName}. 5 | # 6 | # Manual changes are applied the next time ${pluginName} is loaded. 7 | --- 8 | # Sign line starts at 9 | # Possible values: 10 | # 1 - (Default) Line number 1 corresponds to the top-most line of sign blocks. 11 | # 0 - Line number 0 corresponds to the top-most line of sign blocks. 12 | line-starts-at: {{ line_starts_at }} 13 | 14 | # Sets whether a right-mouse click is required to edit a sign 15 | # Possible values: 16 | # "auto" - (Default) Behave like "false" when the player is looking at a sign. 17 | # Behave like "true" when the player is not looking at a sign. 18 | # "false" - Edit signs by looking at them and then typing a /sign command, which instantly edits the targeted sign. 19 | # "true" - Edit signs by typing a /sign command and then right-mouse clicking a sign. 20 | clicking: "{{ clicking }}" 21 | 22 | # Language presented to the player if the player's language is not supported 23 | # Can be any IETF BCP 47 language tag. Details: https://tools.ietf.org/html/bcp47 24 | # Supported values: See https://git.io/SignEdit-README#supported-locales 25 | locale: "{{ locale }}" 26 | # Overrides the player's language choice 27 | # Possible values: 28 | # false - (Default) Uses the player's language if supported; otherwise uses the language specified in the "locale" key 29 | # true - Forces all players to see the language specified in the "locale" key 30 | force-locale: {{ force_locale }} 31 | 32 | # Workarounds for bugs in Minecraft or other Bukkit plugins 33 | compatibility: 34 | # Choose whether to use the alternative sign editor graphical user interface 35 | # Possible values: 36 | # "Auto" - (Default) Only show the alternative sign editor on Minecraft 1.16.1 37 | # "EditableBook" - Always show the alternative sign editor regardless of Minecraft version 38 | # "Native" - Always show the native sign editor regardless of Minecraft version 39 | sign-ui: "{{ compatibility__sign_ui }}" 40 | 41 | # Events to send to permission validation plugins when a player edits a sign 42 | # Possible values: 43 | # "Standard" - (Default) Only send SignChangeEvent to other plugins for validation 44 | # "Extra" - Send BlockBreakEvent, BlockPlaceEvent, and SignChangeEvent to other plugins for validation 45 | # "None" - (Not recommended) Disable permission validation by not sending any Events 46 | edit-validation: "{{ compatibility__edit_validation }}" -------------------------------------------------------------------------------- /src/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | main: ${mainClass.get()} 2 | name: ${pluginName} 3 | author: Deltik 4 | website: https://github.com/Deltik/SignEdit 5 | version: "${pluginVersion}" 6 | api-version: "1.13" 7 | description: Edit existing signs 8 | commands: 9 | sign: 10 | aliases: [signedit, editsign, se] 11 | description: Edit a targeted sign 12 | usage: | 13 | See the online documentation for additional help: 14 | https://git.io/SignEdit-README 15 | 16 | / help [] 17 | Show usage syntax for every available / subcommand. 18 | Specify a number to view a specific page. 19 | 20 | / ui 21 | Open the native Minecraft sign editor on the targeted sign. 22 | 23 | / [set] [] 24 | Change each of the lines of the targeted sign to . 25 | Formatting codes are parsed with `&` in place of `§`. 26 | If is blank, erase the lines . 27 | "set" can be omitted. 28 | 29 | / clear [] 30 | Erase the text on the targeted sign. 31 | If is specified, only those lines are blanked out. 32 | 33 | / cancel 34 | Abort your pending sign edit action. 35 | 36 | / status 37 | Show the pending action, what is in the copy buffer, and an overview of the undo/redo history stack. 38 | 39 | / copy [] 40 | Copy the targeted sign's text. 41 | If is specified, only those lines are copied. 42 | 43 | / cut [] 44 | Copy the targeted sign's text and remove it from the sign. 45 | If is specified, only those lines are cut. 46 | 47 | / paste 48 | Paste the lines buffered by the previous /sign copy or /sign cut command onto the targeted sign. 49 | 50 | / undo 51 | Revert the previous sign text change. Does not affect non-text changes like waxing and dyeing. 52 | 53 | / redo 54 | Restore the most recent sign text change that was undone by /sign undo. 55 | 56 | / unwax 57 | Remove wax from the targeted sign, 58 | allowing the sign to be edited by the Minecraft 1.20+ right-click action. 59 | 60 | / wax 61 | Apply wax to the targeted sign, 62 | preventing the sign from being edited by right-clicking the sign in Minecraft 1.20+. 63 | 64 | / version 65 | Show the version of this plugin. 66 | 67 | permissions: 68 | signedit.use: 69 | description: Legacy permission granting access to all /sign subcommands 70 | signedit.sign.*: 71 | description: Allow access to all /sign subcommands 72 | children: 73 | signedit.sign.help: true 74 | signedit.sign.ui: true 75 | signedit.sign.set: true 76 | signedit.sign.clear: true 77 | signedit.sign.cancel: true 78 | signedit.sign.status: true 79 | signedit.sign.copy: true 80 | signedit.sign.cut: true 81 | signedit.sign.paste: true 82 | signedit.sign.undo: true 83 | signedit.sign.redo: true 84 | signedit.sign.unwax: true 85 | signedit.sign.wax: true 86 | signedit.sign.version: true 87 | signedit.sign.help: 88 | description: Allow /sign help 89 | signedit.sign.ui: 90 | description: Allow /sign ui 91 | signedit.sign.set: 92 | description: Allow /sign set 93 | signedit.sign.clear: 94 | description: Allow /sign clear 95 | signedit.sign.cancel: 96 | description: Allow /sign cancel 97 | signedit.sign.status: 98 | description: Allow /sign status 99 | signedit.sign.copy: 100 | description: Allow /sign copy 101 | signedit.sign.cut: 102 | description: Allow /sign cut 103 | signedit.sign.paste: 104 | description: Allow /sign paste 105 | signedit.sign.undo: 106 | description: Allow /sign undo 107 | signedit.sign.redo: 108 | description: Allow /sign redo 109 | signedit.sign.unwax: 110 | description: Allow /sign unwax; access to this command implicitly removes wax when editing with another command 111 | signedit.sign.wax: 112 | description: Allow /sign wax; access to this command implicitly reapplies wax if the sign was de-waxed for editing 113 | signedit.sign.version: 114 | description: Allow /sign version -------------------------------------------------------------------------------- /test/net/deltik/mc/signedit/ChatCommsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.Locale; 25 | import java.util.ResourceBundle; 26 | 27 | import static org.junit.jupiter.api.Assertions.assertEquals; 28 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 29 | 30 | public class ChatCommsTest { 31 | @Test 32 | public void resourceBundleControlFallbackLocale() { 33 | Locale fallbackLocale = new Locale.Builder().setLanguageTag("el-GR").build(); 34 | Locale badLocale = new Locale.Builder().setLanguageTag("hu-HU").build(); 35 | ResourceBundle.Control control = new ChatComms.UTF8ResourceBundleControl(fallbackLocale); 36 | 37 | assertEquals( 38 | fallbackLocale, 39 | control.getFallbackLocale("Comms", badLocale) 40 | ); 41 | } 42 | 43 | @Test 44 | public void resourceBundleControlFallbackLocaleNoInfiniteLoop() { 45 | Locale fallbackLocale = new Locale.Builder().setLanguageTag("en-US").build(); 46 | Locale badLocale = new Locale.Builder().setLanguageTag("en-US").build(); 47 | ResourceBundle.Control control = new ChatComms.UTF8ResourceBundleControl(fallbackLocale); 48 | 49 | assertNotEquals( 50 | fallbackLocale, 51 | control.getFallbackLocale("Comms", badLocale) 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/net/deltik/mc/signedit/UserCommsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.io.TempDir; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.nio.file.Path; 28 | import java.nio.file.Paths; 29 | import java.util.Collection; 30 | import java.util.Scanner; 31 | import java.util.regex.Pattern; 32 | 33 | import static org.junit.jupiter.api.Assertions.*; 34 | 35 | public class UserCommsTest { 36 | @TempDir 37 | Path pluginConfigFolder; 38 | 39 | @Test 40 | public void deployDirectoryStructure() throws IOException { 41 | UserComms userComms = new UserComms(pluginConfigFolder.toAbsolutePath().toString()); 42 | 43 | userComms.deploy(); 44 | 45 | assertTrue(Paths.get( 46 | pluginConfigFolder.toAbsolutePath().toString(), "locales", "originals" 47 | ).toFile().isDirectory()); 48 | assertTrue(Paths.get( 49 | pluginConfigFolder.toAbsolutePath().toString(), "locales", "overrides" 50 | ).toFile().isDirectory()); 51 | assertTrue(Paths.get( 52 | pluginConfigFolder.toAbsolutePath().toString(), "locales", "README.txt" 53 | ).toFile().length() > 0); 54 | } 55 | 56 | @Test 57 | public void deployCopiesOriginals() throws IOException { 58 | UserComms userComms = new UserComms(pluginConfigFolder.toAbsolutePath().toString()); 59 | 60 | userComms.deploy(); 61 | 62 | Pattern pattern; 63 | pattern = Pattern.compile(".*/Comms[_.].*"); 64 | final Collection list = ResourceList.getResources(pattern); 65 | for (final String name : list) { 66 | String fileName = Paths.get(name).getFileName().toString(); 67 | File file = Paths.get( 68 | pluginConfigFolder.toAbsolutePath().toString(), "locales", "originals", fileName 69 | ).toFile(); 70 | assertTrue(file.isFile()); 71 | 72 | Scanner scanner = new Scanner(file); 73 | Pattern contentCheckRegex = Pattern.compile("^# .* locale file$"); 74 | boolean contentCheckPassed = false; 75 | while (scanner.hasNextLine()) { 76 | String line = scanner.nextLine(); 77 | if (contentCheckRegex.matcher(line).matches()) { 78 | contentCheckPassed = true; 79 | break; 80 | } 81 | } 82 | if (!contentCheckPassed) { 83 | fail(file.getAbsolutePath() + " does not contain regex match: " + contentCheckRegex); 84 | } 85 | } 86 | } 87 | 88 | @SuppressWarnings("ResultOfMethodCallIgnored") 89 | @Test 90 | public void deployRemovesUnexpectedFilesFromOriginalsCopy() throws IOException { 91 | UserComms userComms = new UserComms(pluginConfigFolder.toAbsolutePath().toString()); 92 | File originalsDir = Paths.get( 93 | pluginConfigFolder.toAbsolutePath().toString(), "locales", "originals" 94 | ).toFile(); 95 | originalsDir.mkdirs(); 96 | File garbageDir = Paths.get(originalsDir.getAbsolutePath(), "FOLDER-NONSENSE", "NEST-ME").toFile(); 97 | garbageDir.mkdirs(); 98 | File garbageFile = Paths.get(garbageDir.getAbsolutePath(), "REMOVE-ME").toFile(); 99 | garbageFile.createNewFile(); 100 | File garbageFile2 = Paths.get(originalsDir.getAbsolutePath(), "REMOVE-ME").toFile(); 101 | garbageFile2.createNewFile(); 102 | 103 | assertTrue(garbageFile.exists()); 104 | assertTrue(garbageFile2.exists()); 105 | 106 | userComms.deploy(); 107 | 108 | assertFalse(garbageFile.exists()); 109 | assertFalse(garbageFile2.exists()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/net/deltik/mc/signedit/subcommands/VersionSignSubcommandTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017-2025 Deltik 3 | * 4 | * This file is part of SignEdit for Bukkit. 5 | * 6 | * SignEdit for Bukkit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * SignEdit for Bukkit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with SignEdit for Bukkit. If not, see . 18 | */ 19 | 20 | package net.deltik.mc.signedit.subcommands; 21 | 22 | import net.deltik.mc.signedit.ChatComms; 23 | import net.deltik.mc.signedit.ChatCommsModule; 24 | import net.deltik.mc.signedit.Configuration; 25 | import net.deltik.mc.signedit.SignEditPlugin; 26 | import net.deltik.mc.signedit.interactions.InteractionCommand; 27 | import org.bukkit.command.CommandSender; 28 | import org.bukkit.entity.Player; 29 | import org.bukkit.plugin.Plugin; 30 | import org.bukkit.plugin.PluginDescriptionFile; 31 | import org.junit.jupiter.api.Test; 32 | 33 | import java.util.Locale; 34 | 35 | import static org.mockito.ArgumentMatchers.contains; 36 | import static org.mockito.Mockito.*; 37 | 38 | public class VersionSignSubcommandTest { 39 | @Test 40 | public void signVersionShowsVersion() { 41 | String expected = "1.3.1"; 42 | 43 | Player player = mock(Player.class); 44 | Configuration config = mock(Configuration.class); 45 | when(config.getLocale()).thenReturn(new Locale("en")); 46 | PluginDescriptionFile pluginDescriptionFile = mock(PluginDescriptionFile.class); 47 | Plugin plugin = mock(SignEditPlugin.class); 48 | when(plugin.getDescription()).thenReturn(pluginDescriptionFile); 49 | when(pluginDescriptionFile.getVersion()).thenReturn(expected); 50 | InteractionCommand subcommand = new VersionSignSubcommand( 51 | plugin, 52 | new ChatCommsModule.ChatCommsComponent.Builder() { 53 | ChatComms comms; 54 | 55 | @Override 56 | public ChatCommsModule.ChatCommsComponent build() { 57 | return () -> comms; 58 | } 59 | 60 | @Override 61 | public ChatCommsModule.ChatCommsComponent.Builder commandSender(CommandSender commandSender) { 62 | this.comms = new ChatComms(player, config); 63 | return this; 64 | } 65 | }, 66 | player 67 | ); 68 | subcommand.execute(); 69 | 70 | verify(player).sendMessage(contains(expected)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline 2 | --------------------------------------------------------------------------------