├── .github ├── ISSUE_TEMPLATE │ ├── -------------others.md │ ├── --------feature-request.md │ └── ---bug---report-a-bug.md └── workflows │ ├── auto-build.yml │ └── codacy-analysis.yml ├── .gitignore ├── LICENSE ├── README.MD ├── README.ZH ├── build.gradle ├── ci ├── README.md ├── bukkit.yml ├── server.properties └── world │ ├── advancements │ └── 276e8e63-1a19-3670-9f6c-4884a95c1800.json │ ├── data │ └── raids.dat │ ├── datapacks │ └── bukkit │ │ └── pack.mcmeta │ ├── level.dat │ ├── level.dat_old │ ├── region │ ├── r.-1.-1.mca │ ├── r.-1.0.mca │ ├── r.-1.1.mca │ ├── r.0.-1.mca │ ├── r.0.0.mca │ ├── r.0.1.mca │ ├── r.1.-1.mca │ ├── r.1.0.mca │ └── r.1.1.mca │ ├── session.lock │ └── uid.dat ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── io │ │ └── ib67 │ │ └── manhunt │ │ ├── Logging.java │ │ ├── ManHunt.java │ │ ├── Metrics.java │ │ ├── event │ │ ├── HuntEndEvent.java │ │ └── HuntStartedEvent.java │ │ ├── game │ │ ├── Game.java │ │ ├── GamePhase.java │ │ ├── GamePlayer.java │ │ ├── GameResult.java │ │ └── stat │ │ │ ├── AdvancementRecord.java │ │ │ ├── GameStat.java │ │ │ └── PlayerStat.java │ │ ├── gui │ │ └── Vote.java │ │ ├── listener │ │ ├── AdvancementAndPhase.java │ │ ├── Chat.java │ │ ├── Craft.java │ │ ├── Death.java │ │ ├── Interact.java │ │ ├── JoinAndLeave.java │ │ ├── Move.java │ │ ├── PlayerPvP.java │ │ └── Respawn.java │ │ ├── placeholder │ │ └── MHPlaceholder.java │ │ ├── radar │ │ ├── Radar.java │ │ └── SimpleRadar.java │ │ ├── setting │ │ ├── AdditionConfig.java │ │ ├── I18n.java │ │ └── MainConfig.java │ │ └── util │ │ ├── AdvancementTranslator.java │ │ ├── LodestoneCompass.java │ │ └── SimpleConfig.java └── resources │ └── plugin.yml └── test └── java └── LangFileGen.java /.github/ISSUE_TEMPLATE/-------------others.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 我要说的不是上面那些 / Others 3 | about: 那么你要说什么? 4 | title: "[OTHER] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--------feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 添加新特性 / Feature Request 3 | about: 给项目提意见 4 | title: "[Feature]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **请问您的特性和什么问题有关吗?** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **描述一下您的建议...** 14 | A clear and concise description of what you want to happen. 15 | 16 | **您考虑过的其他的想法...** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **附加内容...** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug---report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交 BUG / Report a Bug 3 | about: 提交一个BUG,让我们做得更好 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **描述 BUG** 11 | A clear and concise description of what the bug is. 12 | 13 | **如何复现** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **预期行为** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **截图/录屏** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **系统信息等** 27 | - 系统/OS: 28 | - Java版本/JavaVer: 29 | - ManiHunt版本/ManHunt Version: 30 | 31 | **附加内容** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/workflows/auto-build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Auto Build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 11 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 11 21 | - name: Grant execute permission for gradlew 22 | run: chmod +x gradlew 23 | - name: Build with Gradle 24 | run: ./gradlew ci 25 | - name: Upload artifact 26 | uses: actions/upload-artifact@v1.0.0 27 | with: 28 | name: "Artifact" 29 | # Directory containing files to upload 30 | path: build/libs -------------------------------------------------------------------------------- /.github/workflows/codacy-analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow checks out code, performs a Codacy security scan 2 | # and integrates the results with the 3 | # GitHub Advanced Security code scanning feature. For more information on 4 | # the Codacy security scan action usage and parameters, see 5 | # https://github.com/codacy/codacy-analysis-cli-action. 6 | # For more information on Codacy Analysis CLI in general, see 7 | # https://github.com/codacy/codacy-analysis-cli. 8 | 9 | name: Codacy Security Scan 10 | 11 | on: 12 | push: 13 | branches: [ master,dev ] 14 | pull_request: 15 | branches: [ master,dev ] 16 | 17 | jobs: 18 | codacy-security-scan: 19 | name: Codacy Security Scan 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Checkout the repository to the GitHub Actions runner 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | 26 | # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis 27 | - name: Run Codacy Analysis CLI 28 | uses: codacy/codacy-analysis-cli-action@1.1.0 29 | with: 30 | # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository 31 | # You can also omit the token and run the tools that support default configurations 32 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 33 | verbose: true 34 | output: results.sarif 35 | format: sarif 36 | # Adjust severity of non-security issues 37 | gh-code-scanning-compat: true 38 | # Force 0 exit code to allow SARIF file generation 39 | # This will handover control about PR rejection to the GitHub side 40 | max-allowed-issues: 2147483647 41 | 42 | # Upload the SARIF file generated in the previous step 43 | - name: Upload SARIF results file 44 | uses: github/codeql-action/upload-sarif@v1 45 | with: 46 | sarif_file: results.sarif 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Server CT & SaltedFish Club 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Project Archived. 2 | Sorry,but for some reason,this project was ARCHIVED. 3 | However, you can play this game if you want, but we may suppress any bug feedbacks or feature requests. 4 | PRs welcome but maintainers will not continue contributing to this project. 5 | See you next game. <3 6 | Contact us via issue if you want to maintain it. We're working on [*a new minigame framework*](https://github.com/saltedfishclub/Oyster) and preparing for a fully-rewritten. 7 | 8 | # ManiHunt 9 | > You should read it as `ManyHunt` but write it as `ManHunt1551`. 10 | 11 | Idea from Minecraft Youtuber `Dream`. 12 | [中文版本](README.ZH) 13 | # How 2 use 14 | 15 | Download the latest build at `Github Actions` 16 | Or download the stable versions at `Github Releases` 17 | 18 | # How 2 play 19 | 20 | A game will start automatically when online players count to reach the value you set. 21 | The `runner` selected by voting and other players will be hunters. 22 | When the ender dragon died, the runner wins. 23 | But if the Runner died, the hunters win. So, the hunters' goal is to find the runner and kill him. we gave some tools to 24 | hunters to achieve their goal, `A compass which tracks runner` 25 | The runner has their radars, too. When a hunter attempted to approach him, he will receive a warning message. 26 | 27 | # Video 28 | 29 | > Maybe yours? 30 | 31 | # ATTENTION 32 | 33 | Although MIT License didn't define it, you're highly supposed to... 34 | 35 | - Announce that your project(s) used them in the introduction if you used the ideas from ManiHunt Team 36 | 37 | [I have some problems](mailto://icebear67@sfclub.cc) 38 | -------------------------------------------------------------------------------- /README.ZH: -------------------------------------------------------------------------------- 1 | 6 | 7 | # ManiHunt 8 | 读作`ManyHunt`,写作`ManHunt1551` 9 | 源自Minecraft Youtuber `Dream` 的创意。 10 | 11 | # How 2 use 12 | 在`Actions`下载最新开发版构建 13 | 或在`Releases`下载正式发布版本 14 | 15 | # How 2 play 16 | 17 | 当人数满足配置文件内设置的人数后,游戏将自动开始。 18 | 玩家通过投票选举人员作为 `Runner` ,其他玩家作为`Hunter` 19 | `Runner`处于极限模式下,并且需要击杀末影龙才能胜利。 20 | `Hunter`负责追杀`Runner`,在第一个`Hunter`合成出指南针后,所有`Hunter`自动解锁无限指南针。(右键定位`Runner`) 21 | 但`Runner`也有自己的雷达,当`Hunter`靠近时,将会发出警报.. 22 | 23 | # Video 24 | 25 | > Maybe yours? 26 | 27 | # ATTENTION 28 | 29 | 虽然 MIT 只规定了 `在软件和软件的所有副本中都必须包含版权声明和许可声明。` 30 | 但`ManiHunt`项目组强烈建议您做到: 31 | 32 | - 对于**借用了本项目的创意(除了Dream视频之外自主创新的想法)的项目**,请在项目信息/宣传贴/about信息中标明:**本插件/项目使用了来自 ManiHunt 的源码/创意** 33 | 34 | [我对此条款有疑问,点击此处激情对线](mailto://icebear67@sfclub.cc) 35 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.ReplaceTokens 2 | 3 | import java.nio.file.Files 4 | import java.util.concurrent.TimeUnit 5 | 6 | plugins { 7 | id "java" 8 | id 'com.github.johnrengelman.shadow' version "6.0.0" 9 | } 10 | group = 'io.ib67' 11 | version = '1.2' 12 | 13 | sourceCompatibility = '1.8' 14 | targetCompatibility = '1.8' 15 | 16 | repositories { 17 | mavenCentral() 18 | maven { 19 | name = 'spigotmc-repo' 20 | url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' 21 | } 22 | maven { 23 | name = 'sonatype' 24 | url = 'https://oss.sonatype.org/content/groups/public/' 25 | } 26 | maven { 27 | name = 'jitpack' 28 | url = "https://jitpack.io" 29 | } 30 | } 31 | 32 | dependencies { 33 | // https://mvnrepository.com/artifact/junit/junit 34 | testCompile group: 'junit', name: 'junit', version: '4.13' 35 | // https://mvnrepository.com/artifact/com.google.code.gson/gson 36 | testCompile group: 'com.google.code.gson', name: 'gson', version: '2.8.6' 37 | compileOnly 'org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT' 38 | testCompile 'org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT' 39 | compileOnly 'com.github.PlaceholderAPI:PlaceholderAPI:2.10.9' 40 | // https://mvnrepository.com/artifact/org.projectlombok/lombok 41 | compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.12' 42 | annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.12' 43 | } 44 | processResources { 45 | from(sourceSets.main.resources.srcDirs) { 46 | filter ReplaceTokens, tokens: [version: version] 47 | } 48 | } 49 | tasks.shadowJar.finalizedBy(tasks.test) 50 | 51 | task ci {} 52 | tasks.ci.dependsOn tasks.shadowJar 53 | tasks.ci.doLast { 54 | if(!System.getProperty("user.name").equals("runner")){ 55 | System.out.println("Not CI User,Skip!"); 56 | return; 57 | } 58 | File SPIGOT_ROOT = new File("./spigot"); 59 | SPIGOT_ROOT.mkdir(); 60 | 61 | System.out.println("Downloading Spigot..."); 62 | File SPIGOT_JAR = new File("./spigot/spigot.jar"); 63 | new URL("https://serverjars.com/api/fetchJar/spigot/1.16").withInputStream{ i -> SPIGOT_JAR.withOutputStream{ it << i }} 64 | System.out.println("Setting up..."); 65 | new File("./spigot/eula.txt").createNewFile(); 66 | Files.write(new File("./spigot/eula.txt").toPath(),("#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\n" + 67 | "#Sat May 15 14:07:13 CST 2021\n" + 68 | "eula=true\n").getBytes()); 69 | File PLUGIN_DIR=new File("./spigot/plugins"); 70 | File OUR_JAR = new File("./build/libs/ManHunt-1.2-all.jar"); 71 | PLUGIN_DIR.mkdir(); 72 | Files.copy(OUR_JAR.toPath(),new File(PLUGIN_DIR,"ManiHunt.jar").toPath()); 73 | File PAPI = new File("./spigot/plugins/PAPI.jar"); 74 | new URL("https://github.com/PlaceholderAPI/PlaceholderAPI/releases/download/2.10.9/PlaceholderAPI-2.10.9.jar").withInputStream{ i -> PAPI.withOutputStream{ it << i }} 75 | //HttpRequest.get("https://github.com/PlaceholderAPI/PlaceholderAPI/releases/download/2.10.9/PlaceholderAPI-2.10.9.jar").receive(PAPI); 76 | Process b = new ProcessBuilder().directory(SPIGOT_ROOT) 77 | .command("java","-jar","spigot.jar", 78 | "-W","/home/runner/work/ManiHunt/ManiHunt/ci", 79 | "--bukkit-settings","/home/runner/work/ManiHunt/ManiHunt/ci/bukkit.yml", 80 | "--config","/home/runner/work/ManiHunt/ManiHunt/ci/server.properties" 81 | ).start(); 82 | b.waitFor(12, TimeUnit.SECONDS) 83 | 84 | //boolean a=Files.readAllLines(new File(SPIGOT_ROOT,"logs/latest.log").toPath()).stream().anyMatch(e->e.contains("ManHunt Started! We're waiting for more players."))?false:true; 85 | boolean a = new File("./spigot/mh_run_success").exists(); 86 | System.out.println(new String(Files.readAllBytes(new File(SPIGOT_ROOT,"logs/latest.log").toPath()))) 87 | if(!a){ 88 | throw new RuntimeException("ManiHunt didn't load successfully."); 89 | } 90 | } -------------------------------------------------------------------------------- /ci/README.md: -------------------------------------------------------------------------------- 1 | this folder is designed to speed-up CI auto test. -------------------------------------------------------------------------------- /ci/bukkit.yml: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for Bukkit. 2 | # As you can see, there's actually not that much to configure without any plugins. 3 | # For a reference for any variable inside this file, check out the Bukkit Wiki at 4 | # https://www.spigotmc.org/go/bukkit-yml 5 | # 6 | # If you need help on this file, feel free to join us on irc or leave a message 7 | # on the forums asking for advice. 8 | # 9 | # IRC: #spigot @ irc.spi.gt 10 | # (If this means nothing to you, just go to https://www.spigotmc.org/go/irc ) 11 | # Forums: https://www.spigotmc.org/ 12 | # Bug tracker: https://www.spigotmc.org/go/bugs 13 | 14 | 15 | settings: 16 | allow-end: false 17 | warn-on-overload: true 18 | permissions-file: permissions.yml 19 | update-folder: update 20 | plugin-profiling: false 21 | connection-throttle: 4000 22 | query-plugins: true 23 | deprecated-verbose: default 24 | shutdown-message: Server closed 25 | minimum-api: none 26 | spawn-limits: 27 | monsters: 70 28 | animals: 10 29 | water-animals: 15 30 | water-ambient: 20 31 | ambient: 15 32 | chunk-gc: 33 | period-in-ticks: 600 34 | ticks-per: 35 | animal-spawns: 400 36 | monster-spawns: 1 37 | water-spawns: 1 38 | water-ambient-spawns: 1 39 | ambient-spawns: 1 40 | autosave: 6000 41 | aliases: now-in-commands.yml 42 | -------------------------------------------------------------------------------- /ci/server.properties: -------------------------------------------------------------------------------- 1 | #Minecraft server properties 2 | #Fri Apr 23 18:13:08 CST 2021 3 | enable-jmx-monitoring=false 4 | rcon.port=25575 5 | level-seed= 6 | gamemode=survival 7 | enable-command-block=false 8 | enable-query=false 9 | generator-settings= 10 | level-name=world 11 | motd=A Minecraft Server 12 | query.port=25565 13 | pvp=true 14 | generate-structures=true 15 | difficulty=easy 16 | network-compression-threshold=256 17 | max-tick-time=60000 18 | max-players=20 19 | use-native-transport=true 20 | online-mode=false 21 | enable-status=true 22 | allow-flight=false 23 | broadcast-rcon-to-ops=true 24 | view-distance=4 25 | max-build-height=256 26 | server-ip= 27 | allow-nether=false 28 | server-port=25565 29 | enable-rcon=false 30 | sync-chunk-writes=true 31 | op-permission-level=4 32 | prevent-proxy-connections=false 33 | resource-pack= 34 | entity-broadcast-range-percentage=100 35 | rcon.password= 36 | player-idle-timeout=0 37 | debug=false 38 | force-gamemode=false 39 | rate-limit=0 40 | hardcore=false 41 | white-list=false 42 | broadcast-console-to-ops=true 43 | spawn-npcs=false 44 | spawn-animals=false 45 | snooper-enabled=false 46 | function-permission-level=2 47 | level-type=flat 48 | text-filtering-config= 49 | spawn-monsters=false 50 | enforce-whitelist=false 51 | resource-pack-sha1= 52 | spawn-protection=16 53 | max-world-size=29999984 54 | -------------------------------------------------------------------------------- /ci/world/advancements/276e8e63-1a19-3670-9f6c-4884a95c1800.json: -------------------------------------------------------------------------------- 1 | { 2 | "minecraft:story/lava_bucket": { 3 | "criteria": { 4 | "lava_bucket": "2021-04-23 16:23:26 +0800" 5 | }, 6 | "done": true 7 | }, 8 | "minecraft:recipes/building_blocks/diamond_block": { 9 | "criteria": { 10 | "has_diamond": "2021-04-23 16:40:40 +0800" 11 | }, 12 | "done": true 13 | }, 14 | "minecraft:recipes/transportation/dark_oak_boat": { 15 | "criteria": { 16 | "in_water": "2021-04-23 16:24:58 +0800" 17 | }, 18 | "done": true 19 | }, 20 | "minecraft:recipes/tools/diamond_pickaxe": { 21 | "criteria": { 22 | "has_diamond": "2021-04-23 16:40:40 +0800" 23 | }, 24 | "done": true 25 | }, 26 | "minecraft:recipes/tools/diamond_hoe": { 27 | "criteria": { 28 | "has_diamond": "2021-04-23 16:40:40 +0800" 29 | }, 30 | "done": true 31 | }, 32 | "minecraft:recipes/transportation/birch_boat": { 33 | "criteria": { 34 | "in_water": "2021-04-23 16:24:58 +0800" 35 | }, 36 | "done": true 37 | }, 38 | "minecraft:recipes/decorations/chest": { 39 | "criteria": { 40 | "has_lots_of_items": "2021-04-23 16:36:54 +0800" 41 | }, 42 | "done": true 43 | }, 44 | "minecraft:recipes/misc/diamond_from_blasting": { 45 | "criteria": { 46 | "has_diamond_ore": "2021-04-23 16:37:13 +0800" 47 | }, 48 | "done": true 49 | }, 50 | "minecraft:recipes/combat/diamond_boots": { 51 | "criteria": { 52 | "has_diamond": "2021-04-23 16:40:40 +0800" 53 | }, 54 | "done": true 55 | }, 56 | "minecraft:story/mine_diamond": { 57 | "criteria": { 58 | "diamond": "2021-04-23 16:40:40 +0800" 59 | }, 60 | "done": true 61 | }, 62 | "minecraft:recipes/tools/diamond_shovel": { 63 | "criteria": { 64 | "has_diamond": "2021-04-23 16:40:40 +0800" 65 | }, 66 | "done": true 67 | }, 68 | "minecraft:recipes/combat/diamond_helmet": { 69 | "criteria": { 70 | "has_diamond": "2021-04-23 16:40:40 +0800" 71 | }, 72 | "done": true 73 | }, 74 | "minecraft:recipes/transportation/acacia_boat": { 75 | "criteria": { 76 | "in_water": "2021-04-23 16:24:58 +0800" 77 | }, 78 | "done": true 79 | }, 80 | "minecraft:recipes/transportation/oak_boat": { 81 | "criteria": { 82 | "in_water": "2021-04-23 16:24:58 +0800" 83 | }, 84 | "done": true 85 | }, 86 | "minecraft:adventure/totem_of_undying": { 87 | "criteria": { 88 | "used_totem": "2021-04-23 16:56:19 +0800" 89 | }, 90 | "done": true 91 | }, 92 | "minecraft:recipes/combat/diamond_sword": { 93 | "criteria": { 94 | "has_diamond": "2021-04-23 16:40:40 +0800" 95 | }, 96 | "done": true 97 | }, 98 | "minecraft:recipes/combat/diamond_chestplate": { 99 | "criteria": { 100 | "has_diamond": "2021-04-23 16:40:40 +0800" 101 | }, 102 | "done": true 103 | }, 104 | "minecraft:recipes/tools/diamond_axe": { 105 | "criteria": { 106 | "has_diamond": "2021-04-23 16:40:40 +0800" 107 | }, 108 | "done": true 109 | }, 110 | "minecraft:recipes/transportation/jungle_boat": { 111 | "criteria": { 112 | "in_water": "2021-04-23 16:24:58 +0800" 113 | }, 114 | "done": true 115 | }, 116 | "minecraft:recipes/transportation/spruce_boat": { 117 | "criteria": { 118 | "in_water": "2021-04-23 16:24:58 +0800" 119 | }, 120 | "done": true 121 | }, 122 | "minecraft:recipes/misc/diamond_from_smelting": { 123 | "criteria": { 124 | "has_diamond_ore": "2021-04-23 16:37:13 +0800" 125 | }, 126 | "done": true 127 | }, 128 | "minecraft:recipes/decorations/jukebox": { 129 | "criteria": { 130 | "has_diamond": "2021-04-23 16:40:40 +0800" 131 | }, 132 | "done": true 133 | }, 134 | "minecraft:adventure/adventuring_time": { 135 | "criteria": { 136 | "minecraft:jungle": "2021-04-23 16:19:01 +0800", 137 | "minecraft:jungle_hills": "2021-04-23 18:15:40 +0800", 138 | "minecraft:river": "2021-04-23 18:15:12 +0800" 139 | }, 140 | "done": false 141 | }, 142 | "minecraft:recipes/combat/diamond_leggings": { 143 | "criteria": { 144 | "has_diamond": "2021-04-23 16:40:40 +0800" 145 | }, 146 | "done": true 147 | }, 148 | "DataVersion": 2586 149 | } -------------------------------------------------------------------------------- /ci/world/data/raids.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/data/raids.dat -------------------------------------------------------------------------------- /ci/world/datapacks/bukkit/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "Data pack for resources provided by Bukkit plugins", 4 | "pack_format": 6 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ci/world/level.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/level.dat -------------------------------------------------------------------------------- /ci/world/level.dat_old: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/level.dat_old -------------------------------------------------------------------------------- /ci/world/region/r.-1.-1.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.-1.-1.mca -------------------------------------------------------------------------------- /ci/world/region/r.-1.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.-1.0.mca -------------------------------------------------------------------------------- /ci/world/region/r.-1.1.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.-1.1.mca -------------------------------------------------------------------------------- /ci/world/region/r.0.-1.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.0.-1.mca -------------------------------------------------------------------------------- /ci/world/region/r.0.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.0.0.mca -------------------------------------------------------------------------------- /ci/world/region/r.0.1.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.0.1.mca -------------------------------------------------------------------------------- /ci/world/region/r.1.-1.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.1.-1.mca -------------------------------------------------------------------------------- /ci/world/region/r.1.0.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.1.0.mca -------------------------------------------------------------------------------- /ci/world/region/r.1.1.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/region/r.1.1.mca -------------------------------------------------------------------------------- /ci/world/session.lock: -------------------------------------------------------------------------------- 1 | ☃ -------------------------------------------------------------------------------- /ci/world/uid.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/ci/world/uid.dat -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/gradle.properties -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Server-CT/ManiHunt/47d552ede7d9ff14e259be1cabeca2a07c4dbe66/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-6.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /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 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ManHunt' 2 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/Logging.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt; 2 | 3 | 4 | import net.md_5.bungee.api.ChatColor; 5 | import org.bukkit.Bukkit; 6 | 7 | public class Logging { 8 | private static final String FORMAT = ChatColor.AQUA.toString() + ChatColor.BOLD + "Man" + ChatColor.RED + "Hunt " + ChatColor.RESET + ChatColor.WHITE + ">> %"; 9 | 10 | public static void info(String mesg) { 11 | Bukkit.getServer().getConsoleSender().sendMessage(FORMAT.replaceAll("%", mesg)); 12 | } 13 | 14 | public static void warn(String mesg) { 15 | Bukkit.getServer().getConsoleSender().sendMessage(FORMAT.replaceAll("%", ChatColor.RED + mesg)); 16 | 17 | } 18 | 19 | public static void debug(String mesg) { 20 | if (!ManHunt.debug) return; 21 | Bukkit.getServer().getConsoleSender().sendMessage(FORMAT.replaceAll("%", "[D] " + ChatColor.UNDERLINE + mesg.replaceAll(org.bukkit.ChatColor.COLOR_CHAR + "", "&"))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/ManHunt.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonParser; 5 | import io.ib67.manhunt.event.HuntEndEvent; 6 | import io.ib67.manhunt.event.HuntStartedEvent; 7 | import io.ib67.manhunt.game.Game; 8 | import io.ib67.manhunt.game.stat.PlayerStat; 9 | import io.ib67.manhunt.gui.Vote; 10 | import io.ib67.manhunt.listener.*; 11 | import io.ib67.manhunt.placeholder.MHPlaceholder; 12 | import io.ib67.manhunt.setting.I18n; 13 | import io.ib67.manhunt.setting.MainConfig; 14 | import io.ib67.manhunt.util.SimpleConfig; 15 | import lombok.Getter; 16 | import net.md_5.bungee.chat.TranslationRegistry; 17 | import org.bukkit.Bukkit; 18 | import org.bukkit.Material; 19 | import org.bukkit.command.Command; 20 | import org.bukkit.command.CommandSender; 21 | import org.bukkit.entity.Player; 22 | import org.bukkit.plugin.Plugin; 23 | import org.bukkit.plugin.java.JavaPlugin; 24 | 25 | import java.io.File; 26 | import java.io.FileReader; 27 | import java.io.InputStream; 28 | import java.io.InputStreamReader; 29 | import java.net.URL; 30 | import java.nio.file.Files; 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | import java.util.function.Consumer; 34 | import java.util.stream.StreamSupport; 35 | 36 | public final class ManHunt extends JavaPlugin { 37 | private static ManHunt instance; 38 | public static boolean debug = false; 39 | private final SimpleConfig mainConfig = new SimpleConfig<>(getDataFolder(), MainConfig.class); 40 | private final SimpleConfig language = new SimpleConfig<>(getDataFolder(), I18n.class); 41 | @Getter 42 | private final Map mojangLocales = new HashMap<>(); 43 | @Getter 44 | private Game game; 45 | private final String serverVersion = Bukkit.getVersion(); 46 | private static final JsonParser jsonParser = new JsonParser(); 47 | private Metrics metrics; 48 | 49 | public static ManHunt getInstance() { 50 | return instance; 51 | } 52 | 53 | public MainConfig getMainConfig() { 54 | return mainConfig.get(); 55 | } 56 | 57 | public I18n getLanguage() { 58 | return language.get(); 59 | } 60 | 61 | @Override 62 | @SuppressWarnings("all") 63 | public void onEnable() { 64 | if (!System.getProperty("user.name").equals("runner")) metrics = new Metrics(this,11759); // prevent fake stats 65 | Logging.info("Loading..."); 66 | Logging.info("Server Version: " + serverVersion); 67 | if (Material.valueOf("LODESTONE") == null) { 68 | Logging.warn("WE CANT FIND LODESTONE IN THIS VERSION!! THAT MEANS YOU'RE RUNNING MANHUNT IN LEGACY VERSION! (MC < 1.16)"); 69 | Logging.warn("PROBLEMS MAY BE IGNORED."); 70 | } 71 | getDataFolder().mkdirs(); 72 | new File(getDataFolder(), "stats").mkdirs(); 73 | instance = this; 74 | mainConfig.saveDefault(); 75 | mainConfig.reloadConfig(); 76 | Logging.info("Loading Language: " + mainConfig.get().serverLanguage); 77 | loadLanguages(); 78 | debug = mainConfig.get().verbose; 79 | if (debug) { 80 | Logging.warn("VERBOSE MODE ON.IF YOU DON'T KNOW WHAT IS IT,PLEASE SHUT IT DOWN IN YOUR CONFIG."); 81 | } 82 | if (getLanguage().version != I18n.VERSION) { 83 | Logging.warn("Language file OUTDATED! If you're using translated locale file,please update it."); 84 | Logging.warn("Now using default settings."); 85 | language.set(new I18n()); 86 | } 87 | loadMojangLocale(); 88 | game = new Game(mainConfig.get().maxPlayers, 89 | g -> Bukkit.getPluginManager().callEvent(new HuntStartedEvent(g)), 90 | g -> Bukkit.getPluginManager().callEvent(new HuntEndEvent(g))); 91 | Plugin pluginPlaceholderAPI = Bukkit.getPluginManager().getPlugin("PlaceholderAPI"); 92 | if(pluginPlaceholderAPI != null){ 93 | Logging.info("Hooking into PlaceholderAPI"); 94 | new MHPlaceholder(this).register(); 95 | } 96 | loadAdditions(); 97 | loadListeners(); 98 | Logging.info("ManHunt Started! We're waiting for more players."); 99 | if (System.getProperty("user.name").equals("runner")) { 100 | try { 101 | new File("mh_run_success").createNewFile(); 102 | }catch(Throwable t){ 103 | t.printStackTrace(); 104 | } 105 | } 106 | } 107 | 108 | private void loadLanguages() { 109 | language.setConfigFileName("locale/" + mainConfig.get().serverLanguage + ".json"); 110 | language.saveDefault(); 111 | language.reloadConfig(); 112 | 113 | } 114 | 115 | private void loadListeners() { 116 | Bukkit.getPluginManager().registerEvents(new AdvancementAndPhase(), this); 117 | Bukkit.getPluginManager().registerEvents(new PlayerPvP(), this); 118 | Bukkit.getPluginManager().registerEvents(new Chat(), this); 119 | Bukkit.getPluginManager().registerEvents(new Craft(), this); 120 | Bukkit.getPluginManager().registerEvents(new JoinAndLeave(), this); 121 | Bukkit.getPluginManager().registerEvents(new Death(), this); 122 | Bukkit.getPluginManager().registerEvents(new Interact(), this); 123 | Bukkit.getPluginManager().registerEvents(new Respawn(), this); 124 | Bukkit.getPluginManager().registerEvents(new Move(), this); 125 | } 126 | 127 | private void loadAdditions() { 128 | //todo 129 | } 130 | 131 | //@SneakyThrows 132 | private void loadMojangLocale() { 133 | if (!(getDataFolder().isDirectory() || getDataFolder().mkdirs())) 134 | throw new RuntimeException("Invalid data folder."); 135 | 136 | File lang = new File(getDataFolder(), getMainConfig().serverLanguage.toLowerCase() + ".cache"); 137 | if (lang.exists() && lang.length() > 0) { 138 | try { 139 | JsonElement jo = jsonParser.parse(new FileReader(lang)); 140 | 141 | Consumer func = e -> { 142 | String name = jo.getAsJsonObject().get("advancements." + e.replaceAll("/", "\\.") + ".title").getAsString(); 143 | mojangLocales.put(e, name); 144 | Logging.debug(e + " -> " + name); 145 | }; 146 | PlayerStat.mentionedNormal.forEach(func); 147 | PlayerStat.mentionedSpecial.forEach(func); 148 | Logging.info("MojangLocales loaded."); 149 | } catch (Exception e) { 150 | e.printStackTrace(); 151 | Logging.warn("Failed to load cache,using spigot-locals."); 152 | loadMojangLocaleLocal(); 153 | } 154 | } else { 155 | try { 156 | final String defaultBase = "https://launchermeta.mojang.com/"; 157 | final String baseURL = getMainConfig().mojangServers.launchmetaBaseUrl; 158 | final String hash = jsonParser.parse(new InputStreamReader(new URL(jsonParser.parse(new InputStreamReader( 159 | new URL(StreamSupport.stream(jsonParser.parse(new InputStreamReader(new URL(baseURL + 160 | "mc/game/version_manifest.json") 161 | .openStream())) 162 | .getAsJsonObject() 163 | .getAsJsonArray("versions") 164 | .spliterator(), false) 165 | .map(JsonElement::getAsJsonObject) 166 | .filter(jo -> serverVersion.contains(jo.get("id").getAsString())) 167 | .findAny() 168 | .orElseThrow(() -> new AssertionError("Impossible null")) 169 | .get("url") 170 | .getAsString() 171 | .replaceFirst(defaultBase, baseURL)).openStream())) 172 | .getAsJsonObject() 173 | .getAsJsonObject("assetIndex") 174 | .get("url") 175 | .getAsString() 176 | .replaceFirst(defaultBase, 177 | baseURL)).openStream())) 178 | .getAsJsonObject() 179 | .getAsJsonObject("objects") 180 | .getAsJsonObject("minecraft/lang/" + getMainConfig().serverLanguage.toLowerCase() + ".json") 181 | .get("hash") 182 | .getAsString(); 183 | try (InputStream input = new URL(getMainConfig().mojangServers.resourceDownloadBaseUrl + 184 | hash.charAt(0) + 185 | hash.charAt(1) + 186 | "/" + 187 | hash).openStream()) { 188 | Files.copy(input, lang.toPath()); 189 | } 190 | loadMojangLocale(); 191 | } catch (Exception e) { 192 | e.printStackTrace(); 193 | Logging.warn("Failed to download cache.Using spigot-local"); 194 | loadMojangLocaleLocal(); 195 | } 196 | } 197 | } 198 | 199 | private void loadMojangLocaleLocal() { 200 | //use spigot local. 201 | PlayerStat.mentionedNormal.forEach(e -> { 202 | mojangLocales.put(e, 203 | TranslationRegistry.INSTANCE.translate("advancements." + 204 | e.replaceAll("\\/", "\\.") + 205 | ".title")); 206 | Logging.debug(e + 207 | " -> " + 208 | TranslationRegistry.INSTANCE.translate("advancements." + 209 | e.replaceAll("\\/", "\\.") + 210 | ".title")); 211 | }); 212 | PlayerStat.mentionedSpecial.forEach(e -> { 213 | mojangLocales.put(e, 214 | TranslationRegistry.INSTANCE.translate("advancements." + 215 | e.replaceAll("\\/", "\\.") + 216 | ".title")); 217 | Logging.debug(e + 218 | " -> " + 219 | TranslationRegistry.INSTANCE.translate("advancements." + 220 | e.replaceAll("\\/", "\\.") + 221 | ".title")); 222 | }); 223 | } 224 | 225 | @Override 226 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 227 | if (label.equalsIgnoreCase("vote")) { 228 | if (!(sender instanceof Player)) { 229 | sender.sendMessage("Player-only command."); 230 | return true; 231 | } 232 | if (game.isStarted()) { 233 | sender.sendMessage(getLanguage().GAMING.VOTE.GAME_ALREADY_STARTED); 234 | return true; 235 | } 236 | Player player = (Player) sender; 237 | Vote vote = ManHunt.getInstance().getGame().vote; 238 | if (vote != null && vote.getShouldVote().contains(player.getUniqueId())) { 239 | Bukkit.getScheduler().runTaskLater(ManHunt.getInstance(), 240 | () -> player.openInventory(vote.getVoteInv()), 241 | 10); 242 | } 243 | } 244 | return true; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/Metrics.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.DataOutputStream; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.lang.reflect.Method; 10 | import java.net.URL; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.Arrays; 13 | import java.util.Collection; 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | import java.util.Objects; 17 | import java.util.Set; 18 | import java.util.UUID; 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.ScheduledExecutorService; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.function.BiConsumer; 24 | import java.util.function.Consumer; 25 | import java.util.function.Supplier; 26 | import java.util.logging.Level; 27 | import java.util.stream.Collectors; 28 | import java.util.zip.GZIPOutputStream; 29 | import javax.net.ssl.HttpsURLConnection; 30 | import org.bukkit.Bukkit; 31 | import org.bukkit.configuration.file.YamlConfiguration; 32 | import org.bukkit.entity.Player; 33 | import org.bukkit.plugin.Plugin; 34 | import org.bukkit.plugin.java.JavaPlugin; 35 | 36 | public class Metrics { 37 | 38 | private final Plugin plugin; 39 | 40 | private final MetricsBase metricsBase; 41 | 42 | /** 43 | * Creates a new Metrics instance. 44 | * 45 | * @param plugin Your plugin instance. 46 | * @param serviceId The id of the service. It can be found at What is my plugin id? 48 | */ 49 | public Metrics(JavaPlugin plugin, int serviceId) { 50 | this.plugin = plugin; 51 | // Get the config file 52 | File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); 53 | File configFile = new File(bStatsFolder, "config.yml"); 54 | YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); 55 | if (!config.isSet("serverUuid")) { 56 | config.addDefault("enabled", true); 57 | config.addDefault("serverUuid", UUID.randomUUID().toString()); 58 | config.addDefault("logFailedRequests", false); 59 | config.addDefault("logSentData", false); 60 | config.addDefault("logResponseStatusText", false); 61 | // Inform the server owners about bStats 62 | config 63 | .options() 64 | .header( 65 | "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" 66 | + "many people use their plugin and their total player count. It's recommended to keep bStats\n" 67 | + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" 68 | + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" 69 | + "anonymous.") 70 | .copyDefaults(true); 71 | try { 72 | config.save(configFile); 73 | } catch (IOException ignored) { 74 | } 75 | } 76 | // Load the data 77 | boolean enabled = config.getBoolean("enabled", true); 78 | String serverUUID = config.getString("serverUuid"); 79 | boolean logErrors = config.getBoolean("logFailedRequests", false); 80 | boolean logSentData = config.getBoolean("logSentData", false); 81 | boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); 82 | metricsBase = 83 | new MetricsBase( 84 | "bukkit", 85 | serverUUID, 86 | serviceId, 87 | enabled, 88 | this::appendPlatformData, 89 | this::appendServiceData, 90 | submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), 91 | plugin::isEnabled, 92 | (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), 93 | (message) -> this.plugin.getLogger().log(Level.INFO, message), 94 | logErrors, 95 | logSentData, 96 | logResponseStatusText); 97 | } 98 | 99 | /** 100 | * Adds a custom chart. 101 | * 102 | * @param chart The chart to add. 103 | */ 104 | public void addCustomChart(CustomChart chart) { 105 | metricsBase.addCustomChart(chart); 106 | } 107 | 108 | private void appendPlatformData(JsonObjectBuilder builder) { 109 | builder.appendField("playerAmount", getPlayerAmount()); 110 | builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); 111 | builder.appendField("bukkitVersion", Bukkit.getVersion()); 112 | builder.appendField("bukkitName", Bukkit.getName()); 113 | builder.appendField("javaVersion", System.getProperty("java.version")); 114 | builder.appendField("osName", System.getProperty("os.name")); 115 | builder.appendField("osArch", System.getProperty("os.arch")); 116 | builder.appendField("osVersion", System.getProperty("os.version")); 117 | builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); 118 | } 119 | 120 | private void appendServiceData(JsonObjectBuilder builder) { 121 | builder.appendField("pluginVersion", plugin.getDescription().getVersion()); 122 | } 123 | 124 | private int getPlayerAmount() { 125 | try { 126 | // Around MC 1.8 the return type was changed from an array to a collection, 127 | // This fixes java.lang.NoSuchMethodError: 128 | // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; 129 | Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); 130 | return onlinePlayersMethod.getReturnType().equals(Collection.class) 131 | ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() 132 | : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; 133 | } catch (Exception e) { 134 | // Just use the new method if the reflection failed 135 | return Bukkit.getOnlinePlayers().size(); 136 | } 137 | } 138 | 139 | public static class MetricsBase { 140 | 141 | /** The version of the Metrics class. */ 142 | public static final String METRICS_VERSION = "2.2.1"; 143 | 144 | private static final ScheduledExecutorService scheduler = 145 | Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); 146 | 147 | private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; 148 | 149 | private final String platform; 150 | 151 | private final String serverUuid; 152 | 153 | private final int serviceId; 154 | 155 | private final Consumer appendPlatformDataConsumer; 156 | 157 | private final Consumer appendServiceDataConsumer; 158 | 159 | private final Consumer submitTaskConsumer; 160 | 161 | private final Supplier checkServiceEnabledSupplier; 162 | 163 | private final BiConsumer errorLogger; 164 | 165 | private final Consumer infoLogger; 166 | 167 | private final boolean logErrors; 168 | 169 | private final boolean logSentData; 170 | 171 | private final boolean logResponseStatusText; 172 | 173 | private final Set customCharts = new HashSet<>(); 174 | 175 | private final boolean enabled; 176 | 177 | /** 178 | * Creates a new MetricsBase class instance. 179 | * 180 | * @param platform The platform of the service. 181 | * @param serviceId The id of the service. 182 | * @param serverUuid The server uuid. 183 | * @param enabled Whether or not data sending is enabled. 184 | * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 185 | * appends all platform-specific data. 186 | * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and 187 | * appends all service-specific data. 188 | * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be 189 | * used to delegate the data collection to a another thread to prevent errors caused by 190 | * concurrency. Can be {@code null}. 191 | * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. 192 | * @param errorLogger A consumer that accepts log message and an error. 193 | * @param infoLogger A consumer that accepts info log messages. 194 | * @param logErrors Whether or not errors should be logged. 195 | * @param logSentData Whether or not the sent data should be logged. 196 | * @param logResponseStatusText Whether or not the response status text should be logged. 197 | */ 198 | public MetricsBase( 199 | String platform, 200 | String serverUuid, 201 | int serviceId, 202 | boolean enabled, 203 | Consumer appendPlatformDataConsumer, 204 | Consumer appendServiceDataConsumer, 205 | Consumer submitTaskConsumer, 206 | Supplier checkServiceEnabledSupplier, 207 | BiConsumer errorLogger, 208 | Consumer infoLogger, 209 | boolean logErrors, 210 | boolean logSentData, 211 | boolean logResponseStatusText) { 212 | this.platform = platform; 213 | this.serverUuid = serverUuid; 214 | this.serviceId = serviceId; 215 | this.enabled = enabled; 216 | this.appendPlatformDataConsumer = appendPlatformDataConsumer; 217 | this.appendServiceDataConsumer = appendServiceDataConsumer; 218 | this.submitTaskConsumer = submitTaskConsumer; 219 | this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; 220 | this.errorLogger = errorLogger; 221 | this.infoLogger = infoLogger; 222 | this.logErrors = logErrors; 223 | this.logSentData = logSentData; 224 | this.logResponseStatusText = logResponseStatusText; 225 | checkRelocation(); 226 | if (enabled) { 227 | startSubmitting(); 228 | } 229 | } 230 | 231 | public void addCustomChart(CustomChart chart) { 232 | this.customCharts.add(chart); 233 | } 234 | 235 | private void startSubmitting() { 236 | final Runnable submitTask = 237 | () -> { 238 | if (!enabled || !checkServiceEnabledSupplier.get()) { 239 | // Submitting data or service is disabled 240 | scheduler.shutdown(); 241 | return; 242 | } 243 | if (submitTaskConsumer != null) { 244 | submitTaskConsumer.accept(this::submitData); 245 | } else { 246 | this.submitData(); 247 | } 248 | }; 249 | // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution 250 | // of requests on the 251 | // bStats backend. To circumvent this problem, we introduce some randomness into the initial 252 | // and second delay. 253 | // WARNING: You must not modify and part of this Metrics class, including the submit delay or 254 | // frequency! 255 | // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! 256 | long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); 257 | long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); 258 | scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); 259 | scheduler.scheduleAtFixedRate( 260 | submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); 261 | } 262 | 263 | private void submitData() { 264 | final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); 265 | appendPlatformDataConsumer.accept(baseJsonBuilder); 266 | final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); 267 | appendServiceDataConsumer.accept(serviceJsonBuilder); 268 | JsonObjectBuilder.JsonObject[] chartData = 269 | customCharts.stream() 270 | .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) 271 | .filter(Objects::nonNull) 272 | .toArray(JsonObjectBuilder.JsonObject[]::new); 273 | serviceJsonBuilder.appendField("id", serviceId); 274 | serviceJsonBuilder.appendField("customCharts", chartData); 275 | baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); 276 | baseJsonBuilder.appendField("serverUUID", serverUuid); 277 | baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); 278 | JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); 279 | scheduler.execute( 280 | () -> { 281 | try { 282 | // Send the data 283 | sendData(data); 284 | } catch (Exception e) { 285 | // Something went wrong! :( 286 | if (logErrors) { 287 | errorLogger.accept("Could not submit bStats metrics data", e); 288 | } 289 | } 290 | }); 291 | } 292 | 293 | private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { 294 | if (logSentData) { 295 | infoLogger.accept("Sent bStats metrics data: " + data.toString()); 296 | } 297 | String url = String.format(REPORT_URL, platform); 298 | HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); 299 | // Compress the data to save bandwidth 300 | byte[] compressedData = compress(data.toString()); 301 | connection.setRequestMethod("POST"); 302 | connection.addRequestProperty("Accept", "application/json"); 303 | connection.addRequestProperty("Connection", "close"); 304 | connection.addRequestProperty("Content-Encoding", "gzip"); 305 | connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); 306 | connection.setRequestProperty("Content-Type", "application/json"); 307 | connection.setRequestProperty("User-Agent", "Metrics-Service/1"); 308 | connection.setDoOutput(true); 309 | try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { 310 | outputStream.write(compressedData); 311 | } 312 | StringBuilder builder = new StringBuilder(); 313 | try (BufferedReader bufferedReader = 314 | new BufferedReader(new InputStreamReader(connection.getInputStream()))) { 315 | String line; 316 | while ((line = bufferedReader.readLine()) != null) { 317 | builder.append(line); 318 | } 319 | } 320 | if (logResponseStatusText) { 321 | infoLogger.accept("Sent data to bStats and received response: " + builder); 322 | } 323 | } 324 | 325 | /** Checks that the class was properly relocated. */ 326 | private void checkRelocation() { 327 | // You can use the property to disable the check in your test environment 328 | if (System.getProperty("bstats.relocatecheck") == null 329 | || !System.getProperty("bstats.relocatecheck").equals("false")) { 330 | // Maven's Relocate is clever and changes strings, too. So we have to use this little 331 | // "trick" ... :D 332 | final String defaultPackage = 333 | new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); 334 | final String examplePackage = 335 | new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); 336 | // We want to make sure no one just copy & pastes the example and uses the wrong package 337 | // names 338 | if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) 339 | || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { 340 | throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); 341 | } 342 | } 343 | } 344 | 345 | /** 346 | * Gzips the given string. 347 | * 348 | * @param str The string to gzip. 349 | * @return The gzipped string. 350 | */ 351 | private static byte[] compress(final String str) throws IOException { 352 | if (str == null) { 353 | return null; 354 | } 355 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 356 | try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { 357 | gzip.write(str.getBytes(StandardCharsets.UTF_8)); 358 | } 359 | return outputStream.toByteArray(); 360 | } 361 | } 362 | 363 | public static class AdvancedBarChart extends CustomChart { 364 | 365 | private final Callable> callable; 366 | 367 | /** 368 | * Class constructor. 369 | * 370 | * @param chartId The id of the chart. 371 | * @param callable The callable which is used to request the chart data. 372 | */ 373 | public AdvancedBarChart(String chartId, Callable> callable) { 374 | super(chartId); 375 | this.callable = callable; 376 | } 377 | 378 | @Override 379 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 380 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 381 | Map map = callable.call(); 382 | if (map == null || map.isEmpty()) { 383 | // Null = skip the chart 384 | return null; 385 | } 386 | boolean allSkipped = true; 387 | for (Map.Entry entry : map.entrySet()) { 388 | if (entry.getValue().length == 0) { 389 | // Skip this invalid 390 | continue; 391 | } 392 | allSkipped = false; 393 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 394 | } 395 | if (allSkipped) { 396 | // Null = skip the chart 397 | return null; 398 | } 399 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 400 | } 401 | } 402 | 403 | public static class SimpleBarChart extends CustomChart { 404 | 405 | private final Callable> callable; 406 | 407 | /** 408 | * Class constructor. 409 | * 410 | * @param chartId The id of the chart. 411 | * @param callable The callable which is used to request the chart data. 412 | */ 413 | public SimpleBarChart(String chartId, Callable> callable) { 414 | super(chartId); 415 | this.callable = callable; 416 | } 417 | 418 | @Override 419 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 420 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 421 | Map map = callable.call(); 422 | if (map == null || map.isEmpty()) { 423 | // Null = skip the chart 424 | return null; 425 | } 426 | for (Map.Entry entry : map.entrySet()) { 427 | valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); 428 | } 429 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 430 | } 431 | } 432 | 433 | public static class MultiLineChart extends CustomChart { 434 | 435 | private final Callable> callable; 436 | 437 | /** 438 | * Class constructor. 439 | * 440 | * @param chartId The id of the chart. 441 | * @param callable The callable which is used to request the chart data. 442 | */ 443 | public MultiLineChart(String chartId, Callable> callable) { 444 | super(chartId); 445 | this.callable = callable; 446 | } 447 | 448 | @Override 449 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 450 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 451 | Map map = callable.call(); 452 | if (map == null || map.isEmpty()) { 453 | // Null = skip the chart 454 | return null; 455 | } 456 | boolean allSkipped = true; 457 | for (Map.Entry entry : map.entrySet()) { 458 | if (entry.getValue() == 0) { 459 | // Skip this invalid 460 | continue; 461 | } 462 | allSkipped = false; 463 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 464 | } 465 | if (allSkipped) { 466 | // Null = skip the chart 467 | return null; 468 | } 469 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 470 | } 471 | } 472 | 473 | public static class AdvancedPie extends CustomChart { 474 | 475 | private final Callable> callable; 476 | 477 | /** 478 | * Class constructor. 479 | * 480 | * @param chartId The id of the chart. 481 | * @param callable The callable which is used to request the chart data. 482 | */ 483 | public AdvancedPie(String chartId, Callable> callable) { 484 | super(chartId); 485 | this.callable = callable; 486 | } 487 | 488 | @Override 489 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 490 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 491 | Map map = callable.call(); 492 | if (map == null || map.isEmpty()) { 493 | // Null = skip the chart 494 | return null; 495 | } 496 | boolean allSkipped = true; 497 | for (Map.Entry entry : map.entrySet()) { 498 | if (entry.getValue() == 0) { 499 | // Skip this invalid 500 | continue; 501 | } 502 | allSkipped = false; 503 | valuesBuilder.appendField(entry.getKey(), entry.getValue()); 504 | } 505 | if (allSkipped) { 506 | // Null = skip the chart 507 | return null; 508 | } 509 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 510 | } 511 | } 512 | 513 | public abstract static class CustomChart { 514 | 515 | private final String chartId; 516 | 517 | protected CustomChart(String chartId) { 518 | if (chartId == null) { 519 | throw new IllegalArgumentException("chartId must not be null"); 520 | } 521 | this.chartId = chartId; 522 | } 523 | 524 | public JsonObjectBuilder.JsonObject getRequestJsonObject( 525 | BiConsumer errorLogger, boolean logErrors) { 526 | JsonObjectBuilder builder = new JsonObjectBuilder(); 527 | builder.appendField("chartId", chartId); 528 | try { 529 | JsonObjectBuilder.JsonObject data = getChartData(); 530 | if (data == null) { 531 | // If the data is null we don't send the chart. 532 | return null; 533 | } 534 | builder.appendField("data", data); 535 | } catch (Throwable t) { 536 | if (logErrors) { 537 | errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); 538 | } 539 | return null; 540 | } 541 | return builder.build(); 542 | } 543 | 544 | protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; 545 | } 546 | 547 | public static class SingleLineChart extends CustomChart { 548 | 549 | private final Callable callable; 550 | 551 | /** 552 | * Class constructor. 553 | * 554 | * @param chartId The id of the chart. 555 | * @param callable The callable which is used to request the chart data. 556 | */ 557 | public SingleLineChart(String chartId, Callable callable) { 558 | super(chartId); 559 | this.callable = callable; 560 | } 561 | 562 | @Override 563 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 564 | int value = callable.call(); 565 | if (value == 0) { 566 | // Null = skip the chart 567 | return null; 568 | } 569 | return new JsonObjectBuilder().appendField("value", value).build(); 570 | } 571 | } 572 | 573 | public static class SimplePie extends CustomChart { 574 | 575 | private final Callable callable; 576 | 577 | /** 578 | * Class constructor. 579 | * 580 | * @param chartId The id of the chart. 581 | * @param callable The callable which is used to request the chart data. 582 | */ 583 | public SimplePie(String chartId, Callable callable) { 584 | super(chartId); 585 | this.callable = callable; 586 | } 587 | 588 | @Override 589 | protected JsonObjectBuilder.JsonObject getChartData() throws Exception { 590 | String value = callable.call(); 591 | if (value == null || value.isEmpty()) { 592 | // Null = skip the chart 593 | return null; 594 | } 595 | return new JsonObjectBuilder().appendField("value", value).build(); 596 | } 597 | } 598 | 599 | public static class DrilldownPie extends CustomChart { 600 | 601 | private final Callable>> callable; 602 | 603 | /** 604 | * Class constructor. 605 | * 606 | * @param chartId The id of the chart. 607 | * @param callable The callable which is used to request the chart data. 608 | */ 609 | public DrilldownPie(String chartId, Callable>> callable) { 610 | super(chartId); 611 | this.callable = callable; 612 | } 613 | 614 | @Override 615 | public JsonObjectBuilder.JsonObject getChartData() throws Exception { 616 | JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); 617 | Map> map = callable.call(); 618 | if (map == null || map.isEmpty()) { 619 | // Null = skip the chart 620 | return null; 621 | } 622 | boolean reallyAllSkipped = true; 623 | for (Map.Entry> entryValues : map.entrySet()) { 624 | JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); 625 | boolean allSkipped = true; 626 | for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { 627 | valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); 628 | allSkipped = false; 629 | } 630 | if (!allSkipped) { 631 | reallyAllSkipped = false; 632 | valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); 633 | } 634 | } 635 | if (reallyAllSkipped) { 636 | // Null = skip the chart 637 | return null; 638 | } 639 | return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); 640 | } 641 | } 642 | 643 | /** 644 | * An extremely simple JSON builder. 645 | * 646 | *

While this class is neither feature-rich nor the most performant one, it's sufficient enough 647 | * for its use-case. 648 | */ 649 | public static class JsonObjectBuilder { 650 | 651 | private StringBuilder builder = new StringBuilder(); 652 | 653 | private boolean hasAtLeastOneField = false; 654 | 655 | public JsonObjectBuilder() { 656 | builder.append("{"); 657 | } 658 | 659 | /** 660 | * Appends a null field to the JSON. 661 | * 662 | * @param key The key of the field. 663 | * @return A reference to this object. 664 | */ 665 | public JsonObjectBuilder appendNull(String key) { 666 | appendFieldUnescaped(key, "null"); 667 | return this; 668 | } 669 | 670 | /** 671 | * Appends a string field to the JSON. 672 | * 673 | * @param key The key of the field. 674 | * @param value The value of the field. 675 | * @return A reference to this object. 676 | */ 677 | public JsonObjectBuilder appendField(String key, String value) { 678 | if (value == null) { 679 | throw new IllegalArgumentException("JSON value must not be null"); 680 | } 681 | appendFieldUnescaped(key, "\"" + escape(value) + "\""); 682 | return this; 683 | } 684 | 685 | /** 686 | * Appends an integer field to the JSON. 687 | * 688 | * @param key The key of the field. 689 | * @param value The value of the field. 690 | * @return A reference to this object. 691 | */ 692 | public JsonObjectBuilder appendField(String key, int value) { 693 | appendFieldUnescaped(key, String.valueOf(value)); 694 | return this; 695 | } 696 | 697 | /** 698 | * Appends an object to the JSON. 699 | * 700 | * @param key The key of the field. 701 | * @param object The object. 702 | * @return A reference to this object. 703 | */ 704 | public JsonObjectBuilder appendField(String key, JsonObject object) { 705 | if (object == null) { 706 | throw new IllegalArgumentException("JSON object must not be null"); 707 | } 708 | appendFieldUnescaped(key, object.toString()); 709 | return this; 710 | } 711 | 712 | /** 713 | * Appends a string array to the JSON. 714 | * 715 | * @param key The key of the field. 716 | * @param values The string array. 717 | * @return A reference to this object. 718 | */ 719 | public JsonObjectBuilder appendField(String key, String[] values) { 720 | if (values == null) { 721 | throw new IllegalArgumentException("JSON values must not be null"); 722 | } 723 | String escapedValues = 724 | Arrays.stream(values) 725 | .map(value -> "\"" + escape(value) + "\"") 726 | .collect(Collectors.joining(",")); 727 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 728 | return this; 729 | } 730 | 731 | /** 732 | * Appends an integer array to the JSON. 733 | * 734 | * @param key The key of the field. 735 | * @param values The integer array. 736 | * @return A reference to this object. 737 | */ 738 | public JsonObjectBuilder appendField(String key, int[] values) { 739 | if (values == null) { 740 | throw new IllegalArgumentException("JSON values must not be null"); 741 | } 742 | String escapedValues = 743 | Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); 744 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 745 | return this; 746 | } 747 | 748 | /** 749 | * Appends an object array to the JSON. 750 | * 751 | * @param key The key of the field. 752 | * @param values The integer array. 753 | * @return A reference to this object. 754 | */ 755 | public JsonObjectBuilder appendField(String key, JsonObject[] values) { 756 | if (values == null) { 757 | throw new IllegalArgumentException("JSON values must not be null"); 758 | } 759 | String escapedValues = 760 | Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); 761 | appendFieldUnescaped(key, "[" + escapedValues + "]"); 762 | return this; 763 | } 764 | 765 | /** 766 | * Appends a field to the object. 767 | * 768 | * @param key The key of the field. 769 | * @param escapedValue The escaped value of the field. 770 | */ 771 | private void appendFieldUnescaped(String key, String escapedValue) { 772 | if (builder == null) { 773 | throw new IllegalStateException("JSON has already been built"); 774 | } 775 | if (key == null) { 776 | throw new IllegalArgumentException("JSON key must not be null"); 777 | } 778 | if (hasAtLeastOneField) { 779 | builder.append(","); 780 | } 781 | builder.append("\"").append(escape(key)).append("\":").append(escapedValue); 782 | hasAtLeastOneField = true; 783 | } 784 | 785 | /** 786 | * Builds the JSON string and invalidates this builder. 787 | * 788 | * @return The built JSON string. 789 | */ 790 | public JsonObject build() { 791 | if (builder == null) { 792 | throw new IllegalStateException("JSON has already been built"); 793 | } 794 | JsonObject object = new JsonObject(builder.append("}").toString()); 795 | builder = null; 796 | return object; 797 | } 798 | 799 | /** 800 | * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. 801 | * 802 | *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. 803 | * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). 804 | * 805 | * @param value The value to escape. 806 | * @return The escaped value. 807 | */ 808 | private static String escape(String value) { 809 | final StringBuilder builder = new StringBuilder(); 810 | for (int i = 0; i < value.length(); i++) { 811 | char c = value.charAt(i); 812 | if (c == '"') { 813 | builder.append("\\\""); 814 | } else if (c == '\\') { 815 | builder.append("\\\\"); 816 | } else if (c <= '\u000F') { 817 | builder.append("\\u000").append(Integer.toHexString(c)); 818 | } else if (c <= '\u001F') { 819 | builder.append("\\u00").append(Integer.toHexString(c)); 820 | } else { 821 | builder.append(c); 822 | } 823 | } 824 | return builder.toString(); 825 | } 826 | 827 | /** 828 | * A super simple representation of a JSON object. 829 | * 830 | *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not 831 | * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, 832 | * JsonObject)}. 833 | */ 834 | public static class JsonObject { 835 | 836 | private final String value; 837 | 838 | private JsonObject(String value) { 839 | this.value = value; 840 | } 841 | 842 | @Override 843 | public String toString() { 844 | return value; 845 | } 846 | } 847 | } 848 | } 849 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/event/HuntEndEvent.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.event; 2 | 3 | import io.ib67.manhunt.game.Game; 4 | import lombok.Getter; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | 8 | public class HuntEndEvent extends Event { 9 | @Getter 10 | private final Game runningGame; 11 | 12 | public HuntEndEvent(Game g) { 13 | this.runningGame = g; 14 | } 15 | 16 | private final HandlerList handlerList = new HandlerList(); 17 | 18 | @Override 19 | public HandlerList getHandlers() { 20 | return handlerList; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/event/HuntStartedEvent.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.event; 2 | 3 | import io.ib67.manhunt.game.Game; 4 | import lombok.Getter; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | 8 | public class HuntStartedEvent extends Event { 9 | @Getter 10 | private final Game runningGame; 11 | 12 | public HuntStartedEvent(Game g) { 13 | this.runningGame = g; 14 | } 15 | 16 | private final HandlerList handlerList = new HandlerList(); 17 | 18 | @Override 19 | public HandlerList getHandlers() { 20 | return handlerList; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/Game.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game; 2 | 3 | import com.google.common.io.Files; 4 | import com.google.gson.Gson; 5 | import io.ib67.manhunt.ManHunt; 6 | import io.ib67.manhunt.game.stat.GameStat; 7 | import io.ib67.manhunt.gui.Vote; 8 | import io.ib67.manhunt.radar.Radar; 9 | import io.ib67.manhunt.radar.SimpleRadar; 10 | import io.ib67.manhunt.setting.I18n; 11 | import io.ib67.manhunt.util.LodestoneCompass; 12 | import lombok.Getter; 13 | import lombok.SneakyThrows; 14 | import org.bukkit.*; 15 | import org.bukkit.entity.Player; 16 | 17 | import java.io.File; 18 | import java.util.*; 19 | import java.util.function.Consumer; 20 | 21 | public class Game { 22 | protected List inGamePlayers = new LinkedList<>(); 23 | private final int playersToStart; 24 | @Getter 25 | private GameResult result = GameResult.NOT_PRODUCED; 26 | private final Consumer gameEnd; 27 | private final Consumer gameStart; 28 | @Getter 29 | private Player runner; 30 | private long startTime; 31 | @Getter 32 | private GamePhase phase = GamePhase.WAITING_FOR_PLAYER; 33 | @Getter 34 | private final GameStat gameStat = new GameStat(); 35 | @Getter 36 | private boolean compassEnabled = false; 37 | public boolean runnerNether = false; 38 | public boolean runnerEnd = false; 39 | @Getter 40 | private Radar radar; 41 | public Vote vote; 42 | 43 | public Game(int playersToStart, Consumer gameStart, Consumer gameEnd) { 44 | this.gameStart = gameStart; 45 | this.gameEnd = gameEnd; 46 | this.playersToStart = playersToStart; 47 | Bukkit.getWorld("world").setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); 48 | Bukkit.getWorld("world").setDifficulty(Difficulty.PEACEFUL); 49 | } 50 | 51 | public void setCompassEnabled(boolean status) { 52 | this.compassEnabled = status; 53 | if (status) { 54 | Bukkit.broadcastMessage(ManHunt.getInstance().getLanguage().GAMING.HUNTER.UNLIMITED_COMPASS_UNLOCKED); 55 | inGamePlayers.stream() 56 | .filter(e -> e.getRole() == GamePlayer.Role.HUNTER && !e.getPlayer().getInventory().contains(Material.COMPASS)) 57 | .forEach(e -> { 58 | e.getPlayer().getInventory().addItem(LodestoneCompass.allocate(e.getPlayer(), runner.getLocation())); 59 | e.getPlayer().sendMessage(ManHunt.getInstance().getLanguage().GAMING.HUNTER.COMPASS_ARRIVED); 60 | e.getPlayer().sendMessage(ManHunt.getInstance().getLanguage().GAMING.HUNTER.UNLIMITED_COMPASS_USAGE); 61 | }); 62 | } else { 63 | inGamePlayers.stream() 64 | .filter(e -> e.getRole() == GamePlayer.Role.HUNTER) 65 | .forEach(e -> e.getPlayer().getInventory().remove(Material.COMPASS)); 66 | Bukkit.broadcastMessage(ManHunt.getInstance().getLanguage().GAMING.HUNTER.UNLIMITED_COMPASS_LOCKED); 67 | } 68 | } 69 | public GamePlayer.Role roleOf(Player player){ 70 | Optional gp = isInGame(player); 71 | return gp.isPresent()?gp.get().getRole(): GamePlayer.Role.SPECTATOR; 72 | } 73 | public void start(Player runner) { 74 | vote = null; 75 | if (runner == null) { 76 | Bukkit.broadcastMessage("An exception was occurred! Please feedback to server admin :: ERROR: RUNNER_IS_NULL"); 77 | Bukkit.broadcastMessage("GAME INTERRUPTED."); 78 | return; 79 | } 80 | Bukkit.getWorld("world").setDifficulty(Difficulty.valueOf(ManHunt.getInstance().getMainConfig().difficulty)); 81 | Bukkit.getWorld("world").setGameRule(GameRule.DO_DAYLIGHT_CYCLE, true); 82 | phase = GamePhase.STARTING; 83 | startTime = System.currentTimeMillis(); 84 | this.runner = runner; 85 | I18n i18n = ManHunt.getInstance().getLanguage(); 86 | inGamePlayers.forEach(e -> { 87 | gameStat.addPlayer(e); 88 | e.getPlayer().setGameMode(GameMode.SURVIVAL); 89 | e.getPlayer().sendMessage(i18n.GAMING.GAME_INTRODUCTION); 90 | if (e.getPlayer().getUniqueId().equals(runner.getUniqueId())) { 91 | e.setRole(GamePlayer.Role.RUNNER); 92 | e.getPlayer().sendTitle(i18n.GAMING.RUNNER.TITLE_MAIN, 93 | i18n.GAMING.RUNNER.TITLE_SUB, 94 | 20, 95 | 2 * 20, 96 | 20); 97 | if(ManHunt.getInstance().getMainConfig().enableAirDrop){ 98 | airDrop(runner); 99 | } 100 | } else { 101 | e.setRole(GamePlayer.Role.HUNTER); 102 | e.getPlayer().sendTitle(i18n.GAMING.HUNTER.TITLE_MAIN, 103 | i18n.GAMING.HUNTER.TITLE_SUB, 104 | 20, 105 | 2 * 20, 106 | 20); 107 | } 108 | }); 109 | Bukkit.broadcastMessage(ChatColor.GREEN + "Runner: " + runner.getDisplayName() + " !"); 110 | initRador(); 111 | phase = GamePhase.STARTED; 112 | gameStart.accept(this); 113 | } 114 | 115 | private void airDrop(Player runner) { 116 | int counter = 0 ; 117 | Location loc = runner.getLocation(); 118 | while(true) { 119 | loc = new Location(loc.getWorld(), loc.getBlockX(), 0, loc.getBlockZ()); 120 | Random random = new Random(); 121 | loc.add(random.nextInt(200) + 100, 0, random.nextInt(200) + 100); 122 | loc = loc.getWorld().getHighestBlockAt(loc).getLocation(); 123 | loc.getBlock().setType(Material.GLASS); 124 | loc.setY(loc.getY() + 1); 125 | if(ManHunt.getInstance().getMainConfig().tryToAvoidOcean){ 126 | if(loc.getWorld().getBiome(loc.getBlockX(),loc.getBlockY(),loc.getBlockZ()).name().endsWith("OCEAN")){ 127 | counter++; 128 | if(counter>5){ 129 | ManHunt.getInstance().getLogger().warning("Failed to locate a place where is not ocean."); 130 | break; 131 | } 132 | }else{ 133 | break; 134 | } 135 | }else{ 136 | break; 137 | } 138 | } 139 | runner.teleport(loc); 140 | } 141 | 142 | private void initRador() { 143 | radar = new SimpleRadar(runner, ManHunt.getInstance().getMainConfig().radorWarnDistance); 144 | radar.start(); 145 | } 146 | 147 | @SneakyThrows 148 | public void stop(GameResult result) { 149 | gameStat.setTotalTime(System.currentTimeMillis() - startTime); 150 | this.result = result; 151 | phase = GamePhase.END; 152 | radar.stop(); 153 | String title = result == GameResult.HUNTER_WIN ? 154 | ManHunt.getInstance().getLanguage().GAMING.HUNTER.WON : 155 | ManHunt.getInstance().getLanguage().GAMING.RUNNER.WON; 156 | inGamePlayers.stream().map(GamePlayer::getPlayer).forEach(p -> { 157 | if (p == null) return; 158 | p.setGameMode(GameMode.SPECTATOR); 159 | p.teleport(Bukkit.getWorlds().get(0).getSpawnLocation()); 160 | p.sendTitle(title, "", 20, 2 * 20, 20); 161 | }); 162 | gameStat.readySerialization(); 163 | String report = new Gson().toJson(gameStat); 164 | String statId; 165 | if (ManHunt.getInstance().getMainConfig().uploadStats) { 166 | statId = uploadReport(report); 167 | } else { 168 | statId = UUID.randomUUID().toString() + "-LOCAL"; 169 | } 170 | Files.write(report.getBytes(), new File(ManHunt.getInstance().getDataFolder(), "stats/" + statId + ".json")); 171 | gameEnd.accept(this); 172 | Bukkit.broadcastMessage(ManHunt.getInstance().getLanguage().GAMING.SHUTDOWN); 173 | Bukkit.getScheduler().runTaskLater(ManHunt.getInstance(), Bukkit::shutdown, 30 * 20L); 174 | } 175 | 176 | private String uploadReport(String report) { 177 | //TODO 178 | return UUID.randomUUID().toString() + "-LOCAL"; 179 | } 180 | 181 | public boolean isStarted() { 182 | return phase != GamePhase.WAITING_FOR_PLAYER && phase != GamePhase.STARTING; 183 | } 184 | 185 | public boolean joinPlayer(Player player) { 186 | if (isStarted()) { 187 | if (!isInGame(player).isPresent()) { 188 | player.setGameMode(GameMode.SPECTATOR); 189 | player.sendMessage(ManHunt.getInstance().getLanguage().GAMING.SPECTATOR_RULE); 190 | return false; 191 | } else { 192 | return true; 193 | } 194 | } 195 | player.setGameMode(GameMode.ADVENTURE); 196 | inGamePlayers.add(GamePlayer.builder().player(player.getName()).build()); 197 | Bukkit.getOnlinePlayers().forEach(e -> e.sendTitle(String.format(ManHunt.getInstance().getLanguage().GAMING.WAITING_FOR_PLAYERS_MAINTITLE, 198 | inGamePlayers.size(), 199 | playersToStart), ManHunt.getInstance().getLanguage().GAMING.WAITING_FOR_PLAYERS_SUBTITLE, 0, 600 * 20, 0)); 200 | if (inGamePlayers.size() >= playersToStart) { 201 | Bukkit.broadcastMessage(ManHunt.getInstance().getLanguage().GAMING.VOTE.VOTE_START); 202 | Bukkit.getOnlinePlayers().forEach(e -> e.sendTitle("", "", 0, 0, 0));//Clear 203 | vote = new Vote(inGamePlayers.stream().map(GamePlayer::getPlayer).map(Player::getUniqueId), 204 | v -> start(v.getResult())); 205 | Bukkit.getScheduler().runTaskLater(ManHunt.getInstance(), () -> vote.startVote(), 10); 206 | } 207 | return true; 208 | } 209 | 210 | public void kickPlayer(String player) { 211 | inGamePlayers.stream() 212 | .filter(e -> e.getPlayer().getName().equals(player)) 213 | .findFirst() 214 | .ifPresent(inGamePlayers::remove); 215 | } 216 | 217 | 218 | public Optional isInGame(Player player) { 219 | return inGamePlayers.stream().filter(s -> { 220 | Player playe = s.getPlayer(); 221 | if (playe == null) return false; 222 | return playe.getName().equals(player.getName()); 223 | }).findFirst(); 224 | } 225 | 226 | public List getInGamePlayers() { 227 | return inGamePlayers; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/GamePhase.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game; 2 | 3 | public enum GamePhase { 4 | STARTING, STARTED, END, WAITING_FOR_PLAYER 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/GamePlayer.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game; 2 | 3 | 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.Objects; 12 | 13 | @Builder 14 | public class GamePlayer { 15 | private final String player; 16 | @Getter 17 | @Setter(AccessLevel.PACKAGE) 18 | private Role role; 19 | public Player getPlayer() { 20 | return Bukkit.getPlayer(player); 21 | } 22 | 23 | public enum Role { 24 | RUNNER, HUNTER, SPECTATOR 25 | } 26 | 27 | @Override 28 | public boolean equals(Object obj) { 29 | return this == obj || (obj instanceof GamePlayer && Objects.equals(this.player, ((GamePlayer) obj).player)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/GameResult.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game; 2 | 3 | 4 | public enum GameResult { 5 | RUNNER_WIN, HUNTER_WIN, NOT_PRODUCED 6 | } -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/stat/AdvancementRecord.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game.stat; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class AdvancementRecord { 9 | private final String advancement; 10 | private final long usedTime; 11 | @Builder.Default 12 | private final long time = System.currentTimeMillis(); 13 | private final int score; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/stat/GameStat.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game.stat; 2 | 3 | import io.ib67.manhunt.game.GamePlayer; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.bukkit.Sound; 7 | import org.bukkit.advancement.Advancement; 8 | import org.bukkit.entity.Player; 9 | 10 | import java.util.LinkedHashMap; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | 14 | public class GameStat { 15 | private final Map playerStats = new LinkedHashMap<>(); 16 | @Getter 17 | @Setter 18 | private Phase gamePhase = Phase.GETTING_STARTED; 19 | @Getter 20 | @Setter 21 | private long totalTime; 22 | 23 | /** 24 | * @param player 25 | * @param advancement 26 | * @return -1 when player not found. 27 | */ 28 | public int addAdvancement(Player player, Advancement advancement) { 29 | if (playerStats.containsKey(player.getUniqueId())) { 30 | int score = playerStats.get(player.getUniqueId()).achieve(advancement); 31 | if (score > 0) { 32 | player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 2.0F, 1.0F); 33 | } 34 | return score; 35 | } else { 36 | return -1; //Not Found 37 | } 38 | } 39 | 40 | public void addPlayer(GamePlayer player) { 41 | playerStats.put(player.getPlayer().getUniqueId(), new PlayerStat(player)); 42 | } 43 | 44 | public void addAdvancement(Player player, String advancement, int score) { 45 | if (playerStats.containsKey(player.getUniqueId())) { 46 | player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 2.0F, 1.0F); 47 | playerStats.get(player.getUniqueId()).achieve(advancement, score); 48 | } 49 | } 50 | 51 | public void readySerialization() { 52 | playerStats.values().forEach(PlayerStat::calculate); 53 | } 54 | 55 | public enum Phase { 56 | GETTING_STARTED, IRON_ARMOR, IN_NETHER, BLAZE_ROD_GOT, FIND_STRONGHOLD, IN_END, KILLED_THE_DRAGON 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/game/stat/PlayerStat.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.game.stat; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.GamePlayer; 5 | import io.ib67.manhunt.setting.MainConfig; 6 | import io.ib67.manhunt.util.AdvancementTranslator; 7 | import org.bukkit.Statistic; 8 | import org.bukkit.advancement.Advancement; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.Arrays; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | @SuppressWarnings("unused") 16 | public class PlayerStat { 17 | private final GamePlayer player; 18 | public final static List mentionedNormal = Arrays.asList("story/upgrade_tools", "story/smelt_iron", "story/lava_bucket", 19 | "story/enter_the_nether", "nether/obtain_blaze_rod", "story/follow_ender_eye", "story/enter_the_end"); 20 | public final static List mentionedSpecial = Arrays.asList("story/mine_diamond", "story/enchant_item" 21 | , "story/form_obsidian", "nether/return_to_sender", "nether/brew_potion", "nether/distract_piglin", "adventure/trade"); 22 | 23 | public PlayerStat(GamePlayer p) { 24 | player = p; 25 | } 26 | 27 | public transient int normalScore; 28 | public transient int specialScore; 29 | 30 | { 31 | MainConfig cfg = ManHunt.getInstance().getMainConfig(); 32 | normalScore = cfg.playerScores.advancementNormal; 33 | specialScore = cfg.playerScores.advancementSpecial; 34 | } 35 | 36 | /** 37 | * Minute. 38 | */ 39 | private int playedTime; 40 | private int takenDamages; 41 | private int causedDamages; 42 | private int walkDistance; 43 | private int deathCounts; 44 | private final List advancements = new LinkedList<>(); 45 | private int finalScore; 46 | 47 | private transient long lastAdvancementTime = System.currentTimeMillis(); 48 | 49 | public void calculate() { 50 | Player p = player.getPlayer(); 51 | playedTime = p.getStatistic(Statistic.PLAY_ONE_MINUTE) / 20 / 60; 52 | takenDamages = p.getStatistic(Statistic.DAMAGE_TAKEN) - p.getStatistic(Statistic.DAMAGE_BLOCKED_BY_SHIELD); 53 | causedDamages = p.getStatistic(Statistic.DAMAGE_DEALT); 54 | walkDistance = p.getStatistic(Statistic.BOAT_ONE_CM) 55 | + p.getStatistic(Statistic.AVIATE_ONE_CM) 56 | + p.getStatistic(Statistic.HORSE_ONE_CM) 57 | + p.getStatistic(Statistic.MINECART_ONE_CM) 58 | + p.getStatistic(Statistic.PIG_ONE_CM) 59 | + p.getStatistic(Statistic.STRIDER_ONE_CM) 60 | + p.getStatistic(Statistic.CLIMB_ONE_CM) 61 | + p.getStatistic(Statistic.CROUCH_ONE_CM) 62 | + p.getStatistic(Statistic.FLY_ONE_CM) 63 | + p.getStatistic(Statistic.SPRINT_ONE_CM) 64 | + p.getStatistic(Statistic.SWIM_ONE_CM) 65 | + p.getStatistic(Statistic.WALK_ONE_CM) 66 | + p.getStatistic(Statistic.WALK_ON_WATER_ONE_CM) 67 | + p.getStatistic(Statistic.WALK_UNDER_WATER_ONE_CM); 68 | deathCounts = p.getStatistic(Statistic.DEATHS); 69 | int scoresOfAdvs = 0; 70 | for (AdvancementRecord advancement : advancements) { 71 | scoresOfAdvs = scoresOfAdvs + advancement.getScore(); 72 | } 73 | int scoreOfStats = 0; 74 | if (takenDamages != 0) { 75 | scoreOfStats = (int) (scoreOfStats + Math.ceil(causedDamages) / Math.ceil(takenDamages)); 76 | } else { 77 | scoreOfStats = scoreOfStats + causedDamages; 78 | } 79 | scoreOfStats = scoreOfStats - deathCounts * 100; 80 | finalScore = scoreOfStats + scoresOfAdvs; 81 | } 82 | 83 | /** 84 | * Add to statistics 85 | * 86 | * @param adv 87 | * @return score (0 == nothing happened) 88 | */ 89 | public int achieve(Advancement adv) { 90 | long distance = System.currentTimeMillis() - lastAdvancementTime; 91 | if (mentionedNormal.contains(adv.getKey().getKey())) { 92 | advancements.add( 93 | AdvancementRecord.builder() 94 | .score(Math.round(normalScore / distance + 1)) 95 | .usedTime(distance) 96 | .advancement(AdvancementTranslator.translate(adv.getKey().getKey())) 97 | .build()); 98 | lastAdvancementTime = System.currentTimeMillis(); 99 | return Math.round(normalScore / (distance + 1)); //ScoreAddition by time. 100 | } else if (mentionedSpecial.contains(adv.getKey().getKey())) { 101 | int score = Math.round(specialScore / (distance + 1)); 102 | advancements.add( 103 | AdvancementRecord.builder() 104 | .score(score) 105 | .usedTime(distance) 106 | .advancement(AdvancementTranslator.translate(adv.getKey().getKey())) 107 | .build()); 108 | lastAdvancementTime = System.currentTimeMillis(); 109 | return score; 110 | } 111 | return 0; 112 | } 113 | 114 | public void achieve(String advancement, int score) { 115 | advancements.add(AdvancementRecord.builder() 116 | .advancement(advancement) 117 | .score(score) 118 | .usedTime(System.currentTimeMillis() - lastAdvancementTime) 119 | .build()); 120 | lastAdvancementTime = System.currentTimeMillis(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/gui/Vote.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.gui; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.setting.I18n; 5 | import lombok.Getter; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.Material; 8 | import org.bukkit.OfflinePlayer; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.EventHandler; 11 | import org.bukkit.event.Listener; 12 | import org.bukkit.event.inventory.InventoryClickEvent; 13 | import org.bukkit.inventory.Inventory; 14 | import org.bukkit.inventory.InventoryHolder; 15 | import org.bukkit.inventory.ItemStack; 16 | import org.bukkit.inventory.meta.SkullMeta; 17 | 18 | import java.util.*; 19 | import java.util.function.Consumer; 20 | import java.util.stream.Collectors; 21 | import java.util.stream.Stream; 22 | 23 | public class Vote implements Listener, InventoryHolder { 24 | @Getter 25 | private final LinkedList shouldVote; 26 | private final LinkedList voted = new LinkedList<>(); 27 | private final TreeMap voteMap = new TreeMap<>(); 28 | @Getter 29 | private final Inventory voteInv; 30 | private final Consumer callback; 31 | 32 | public Vote(List shouldVote, Consumer callback) { 33 | this.shouldVote = new LinkedList<>(shouldVote); 34 | this.callback = callback; 35 | this.voteInv = Bukkit.createInventory(this, 36 | (this.shouldVote.size() / 9 + (this.shouldVote.size() % 9 == 0 ? 0 : 1)) * 37 | 9, 38 | ManHunt.getInstance().getLanguage().GAMING.VOTE.VOTE_TITLE); 39 | initInventory(); 40 | } 41 | 42 | public Vote(Stream shouldVote, Consumer callback) { 43 | this.shouldVote = shouldVote.collect(Collectors.toCollection(LinkedList::new)); 44 | this.callback = callback; 45 | this.voteInv = Bukkit.createInventory(this, 46 | (this.shouldVote.size() / 9 + (this.shouldVote.size() % 9 == 0 ? 0 : 1)) * 47 | 9, 48 | ManHunt.getInstance().getLanguage().GAMING.VOTE.VOTE_TITLE); 49 | initInventory(); 50 | } 51 | 52 | @SuppressWarnings("deprecated") 53 | private void initInventory() { 54 | ItemStack head = new ItemStack(Material.PLAYER_HEAD); 55 | SkullMeta meta = (SkullMeta) Objects.requireNonNull(head.getItemMeta()); 56 | for (UUID uuid : shouldVote) { 57 | meta.setOwningPlayer(Bukkit.getOfflinePlayer(uuid)); 58 | meta.setDisplayName(String.format(ManHunt.getInstance().getLanguage().GAMING.VOTE.VOTE_ITEM_FORMAT, meta.getOwner())); 59 | head.setItemMeta(meta); 60 | voteInv.addItem(head.clone()); 61 | } 62 | } 63 | 64 | public Inventory getInventory() { 65 | return voteInv; 66 | } 67 | 68 | public void startVote() { 69 | Bukkit.getPluginManager().registerEvents(this, ManHunt.getInstance()); 70 | shouldVote.stream().map(Bukkit::getPlayer).filter(Objects::nonNull).forEach(p -> p.openInventory(voteInv)); 71 | } 72 | 73 | public void endVote() { 74 | InventoryClickEvent.getHandlerList().unregister(this); 75 | } 76 | 77 | public void vote(Player from, OfflinePlayer to) { 78 | UUID fromUUID = from.getUniqueId(); 79 | I18n i18n = ManHunt.getInstance().getLanguage(); 80 | if (!shouldVote.contains(fromUUID)) { 81 | from.sendMessage(i18n.GAMING.VOTE.SHOULD_NOT_VOTE); 82 | return; 83 | } 84 | if (voted.contains(fromUUID)) { 85 | from.sendMessage(i18n.GAMING.VOTE.ALREADY_VOTED); 86 | return; 87 | } 88 | 89 | voteMap.put(to.getUniqueId(), voteMap.containsKey(to.getUniqueId()) ? voteMap.get(to.getUniqueId()) + 1 : 1); 90 | 91 | voted.add(fromUUID); 92 | from.sendMessage(String.format(i18n.GAMING.VOTE.VOTE_SUCCEED, Objects.requireNonNull(to).getName())); 93 | 94 | Bukkit.broadcastMessage(String.format(i18n.GAMING.VOTE.VOTING, voted.size(), shouldVote.size())); 95 | if (allVoted()) { 96 | callback.accept(this); 97 | endVote(); 98 | } 99 | } 100 | 101 | private boolean allVoted() { 102 | return voted.size() == shouldVote.size(); 103 | } 104 | 105 | public Player getResult() { 106 | return Bukkit.getPlayer(voteMap.entrySet() 107 | .stream() 108 | .max(Map.Entry.comparingByValue()) 109 | .orElseThrow(() -> new IllegalStateException("Impossible null")) 110 | .getKey()); 111 | } 112 | @SuppressWarnings("deprecated") 113 | @EventHandler 114 | public void onClick(InventoryClickEvent event) { 115 | if (!(event.getWhoClicked() instanceof Player && 116 | event.getCurrentItem() != null && 117 | event.getInventory().getHolder() instanceof Vote)) 118 | return; 119 | Player p = (Player) event.getWhoClicked(); 120 | ItemStack itemClicked = event.getCurrentItem(); 121 | event.setCancelled(true); 122 | if (itemClicked.getType() != Material.PLAYER_HEAD) 123 | return; 124 | 125 | SkullMeta meta = (SkullMeta) itemClicked.getItemMeta(); 126 | vote(p, Bukkit.getPlayer(Objects.requireNonNull(Objects.requireNonNull(meta).getOwner()))); 127 | 128 | p.closeInventory(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/AdvancementAndPhase.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.Game; 5 | import io.ib67.manhunt.game.GamePlayer; 6 | import io.ib67.manhunt.game.stat.GameStat; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.Material; 9 | import org.bukkit.World; 10 | import org.bukkit.entity.EntityType; 11 | import org.bukkit.event.EventHandler; 12 | import org.bukkit.event.Listener; 13 | import org.bukkit.event.entity.EntityDeathEvent; 14 | import org.bukkit.event.inventory.CraftItemEvent; 15 | import org.bukkit.event.player.PlayerAdvancementDoneEvent; 16 | import org.bukkit.event.player.PlayerInteractEvent; 17 | import org.bukkit.event.player.PlayerPortalEvent; 18 | 19 | public class AdvancementAndPhase implements Listener { 20 | @EventHandler 21 | public void onAdv(PlayerAdvancementDoneEvent adv) { 22 | int score = ManHunt.getInstance().getGame().getGameStat().addAdvancement(adv.getPlayer(), adv.getAdvancement()); 23 | if (score > 0) { 24 | adv.getPlayer().sendMessage(String.format(ManHunt.getInstance().getLanguage().GAMING.ARCHIVE_TARGET, 25 | score)); 26 | } 27 | } 28 | 29 | @EventHandler 30 | public void onCraft(CraftItemEvent event) { 31 | String name = event.getRecipe().getResult().getType().toString(); 32 | if (name.contains("IRON") && name.contains("CHESTPLATE") || 33 | name.contains("BOOT") || 34 | name.contains("HELMET") || 35 | name.contains("LEGGING")) { 36 | if (ManHunt.getInstance().getGame().getGameStat().getGamePhase().ordinal() < 37 | GameStat.Phase.IRON_ARMOR.ordinal()) { 38 | ManHunt.getInstance().getGame().getGameStat().setGamePhase(GameStat.Phase.IRON_ARMOR); 39 | }//IN_NETHER, BLAZE_ROD_GOT, FIND_STRONGHOLD, IN_END, KILLED_THE_DRAGON; 40 | } 41 | } 42 | 43 | @EventHandler 44 | public void onUseItem(PlayerInteractEvent a) { 45 | if (a.getItem() == null) return; 46 | if (a.getItem().getType() == Material.ENDER_EYE) { 47 | if (ManHunt.getInstance().getGame().getGameStat().getGamePhase().ordinal() < 48 | GameStat.Phase.FIND_STRONGHOLD.ordinal()) { 49 | ManHunt.getInstance().getGame().getGameStat().setGamePhase(GameStat.Phase.FIND_STRONGHOLD); 50 | } 51 | } 52 | } 53 | 54 | @EventHandler 55 | public void onKillDragon(EntityDeathEvent e) { 56 | if (e.getEntity().getType() == EntityType.ENDER_DRAGON) { 57 | if (ManHunt.getInstance().getGame().getGameStat().getGamePhase().ordinal() < 58 | GameStat.Phase.KILLED_THE_DRAGON.ordinal()) { 59 | ManHunt.getInstance().getGame().getGameStat().setGamePhase(GameStat.Phase.KILLED_THE_DRAGON); 60 | } 61 | } 62 | } 63 | 64 | @EventHandler 65 | public void onIntoNether(PlayerPortalEvent e) { 66 | Game game = ManHunt.getInstance().getGame(); 67 | if (e.getTo().getWorld().getEnvironment() == World.Environment.NETHER) { 68 | if (game.isInGame(e.getPlayer()).filter(g -> g.getRole() == GamePlayer.Role.RUNNER).isPresent() && 69 | !game.runnerNether) { 70 | game.runnerNether = true; 71 | Bukkit.broadcastMessage(ManHunt.getInstance().getLanguage().GAMING.RUNNER.ARRIVE_NETHER); 72 | } 73 | if (game.getGameStat().getGamePhase().ordinal() < GameStat.Phase.IN_NETHER.ordinal()) { 74 | game.getGameStat().setGamePhase(GameStat.Phase.IN_NETHER); 75 | } 76 | } else if (e.getTo().getWorld().getEnvironment() == World.Environment.THE_END) { 77 | if (game.isInGame(e.getPlayer()).filter(g -> g.getRole() == GamePlayer.Role.RUNNER).isPresent() && 78 | !game.runnerEnd) { 79 | game.runnerEnd = true; 80 | Bukkit.broadcastMessage(ManHunt.getInstance().getLanguage().GAMING.RUNNER.ARRIVE_END); 81 | } 82 | if (game.getGameStat().getGamePhase().ordinal() < GameStat.Phase.IN_END.ordinal()) { 83 | game.getGameStat().setGamePhase(GameStat.Phase.IN_END); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/Chat.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.Game; 5 | import io.ib67.manhunt.game.GamePhase; 6 | import io.ib67.manhunt.game.GamePlayer; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.ChatColor; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.EventHandler; 11 | import org.bukkit.event.Listener; 12 | import org.bukkit.event.player.AsyncPlayerChatEvent; 13 | 14 | import java.util.Optional; 15 | 16 | public class Chat implements Listener { 17 | @EventHandler 18 | public void onChat(AsyncPlayerChatEvent event) { 19 | Game game = ManHunt.getInstance().getGame(); 20 | 21 | if (game.getPhase() != GamePhase.STARTED) { 22 | event.setFormat(ChatColor.GRAY + event.getPlayer().getDisplayName() + ": " + event.getMessage()); 23 | return; 24 | } 25 | 26 | Player player = event.getPlayer(); 27 | Optional og = game.isInGame(player); 28 | if (og.isPresent()) { 29 | GamePlayer.Role role = og.orElse(null).getRole(); 30 | event.setFormat((role == GamePlayer.Role.HUNTER ? 31 | ChatColor.RED + "[HUNTER] " : 32 | ChatColor.GREEN + "[RUNNER] ") + ChatColor.RESET + event.getFormat()); 33 | if (role == GamePlayer.Role.HUNTER && event.getMessage().startsWith("#")) { 34 | event.setCancelled(true); 35 | event.setFormat(ChatColor.WHITE + "[TEAM]" + event.getPlayer().getName() + ": " + event.getMessage()); 36 | String send = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); 37 | Bukkit.getScheduler().runTask(ManHunt.getInstance(), 38 | () -> game.getInGamePlayers() 39 | .stream() 40 | .filter(g -> g.getRole() == 41 | GamePlayer.Role.HUNTER) 42 | .map(GamePlayer::getPlayer) 43 | .forEach(p -> p.sendMessage(send))); 44 | } 45 | } else { 46 | event.setFormat(ChatColor.GRAY + "[SPECTATOR] " + ChatColor.RESET + event.getFormat()); 47 | if (ManHunt.getInstance().getMainConfig().muteSpectatorInGlobalChannel) { 48 | event.setCancelled(true); 49 | String send = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); 50 | Bukkit.getScheduler().runTask(ManHunt.getInstance(), 51 | () -> Bukkit.getOnlinePlayers() 52 | .stream() 53 | .filter(p -> !game.isInGame(p).isPresent()) 54 | .forEach(p -> p.sendMessage(send))); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/Craft.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.Game; 5 | import io.ib67.manhunt.game.GamePhase; 6 | import io.ib67.manhunt.game.GamePlayer; 7 | import org.bukkit.Material; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.event.inventory.CraftItemEvent; 12 | import org.bukkit.inventory.ItemStack; 13 | 14 | public class Craft implements Listener { 15 | @EventHandler 16 | public void onCraft(CraftItemEvent event) { 17 | Game game = ManHunt.getInstance().getGame(); 18 | 19 | if (game.getPhase() != GamePhase.STARTED || 20 | game.isCompassEnabled() || 21 | !(event.getWhoClicked() instanceof Player)) 22 | return; 23 | 24 | if (event.getRecipe().getResult().getType() == Material.COMPASS) { 25 | event.setCurrentItem(new ItemStack(Material.GRASS)); 26 | game.setCompassEnabled(game.isInGame((Player) event.getWhoClicked()) 27 | .filter(g -> g.getRole() == GamePlayer.Role.HUNTER) 28 | .isPresent()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/Death.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.Game; 5 | import io.ib67.manhunt.game.GamePhase; 6 | import io.ib67.manhunt.game.GamePlayer; 7 | import io.ib67.manhunt.game.GameResult; 8 | import org.bukkit.Material; 9 | import org.bukkit.entity.EntityType; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.event.EventHandler; 12 | import org.bukkit.event.Listener; 13 | import org.bukkit.event.entity.EntityDeathEvent; 14 | 15 | public class Death implements Listener { 16 | @EventHandler 17 | public void onEntityDeath(EntityDeathEvent e) { 18 | Game game = ManHunt.getInstance().getGame(); 19 | 20 | if (game.getPhase() != GamePhase.STARTED) 21 | return; 22 | 23 | if (e.getEntityType() == EntityType.PLAYER) { 24 | //Player Died Event 25 | Player player = (Player) e.getEntity(); 26 | game.isInGame(player).ifPresent(p -> { 27 | if (p.getRole() == GamePlayer.Role.RUNNER) 28 | game.stop(GameResult.HUNTER_WIN); 29 | else if (p.getRole() == GamePlayer.Role.HUNTER) 30 | e.getDrops().removeIf(i -> i.getType() == Material.COMPASS); 31 | }); 32 | } else if (e.getEntityType() == EntityType.ENDER_DRAGON) 33 | game.stop(GameResult.RUNNER_WIN); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/Interact.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.Game; 5 | import io.ib67.manhunt.game.GamePhase; 6 | import io.ib67.manhunt.game.GamePlayer; 7 | import io.ib67.manhunt.util.LodestoneCompass; 8 | import net.md_5.bungee.api.ChatMessageType; 9 | import net.md_5.bungee.api.chat.TextComponent; 10 | import org.bukkit.Location; 11 | import org.bukkit.Material; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.event.EventHandler; 14 | import org.bukkit.event.Listener; 15 | import org.bukkit.event.block.Action; 16 | import org.bukkit.event.player.PlayerInteractEvent; 17 | import org.bukkit.event.player.PlayerPortalEvent; 18 | import org.bukkit.inventory.EquipmentSlot; 19 | import org.bukkit.inventory.ItemStack; 20 | import org.bukkit.inventory.PlayerInventory; 21 | 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Objects; 25 | import java.util.function.BiConsumer; 26 | 27 | public class Interact implements Listener { 28 | public Map lastLoc = new HashMap<>(); 29 | @EventHandler 30 | public void onInteract(PlayerInteractEvent event) { 31 | Game game = ManHunt.getInstance().getGame(); 32 | 33 | if (game.getPhase() != GamePhase.STARTED) 34 | return; 35 | 36 | 37 | if ((event.getAction() == Action.RIGHT_CLICK_AIR || 38 | event.getAction() == Action.RIGHT_CLICK_BLOCK) && 39 | event.hasItem() && 40 | Objects.requireNonNull(event.getItem()).getType() == Material.COMPASS) { 41 | event.setCancelled(true); 42 | final BiConsumer setItem = event.getHand() == EquipmentSlot.HAND ? 43 | PlayerInventory::setItemInMainHand : 44 | PlayerInventory::setItemInOffHand; 45 | game.isInGame(event.getPlayer()) 46 | .filter(g -> g.getRole() == GamePlayer.Role.HUNTER) 47 | .map(GamePlayer::getPlayer) 48 | .map(Player::getInventory) 49 | .ifPresent(i -> { 50 | Player runner = game.getRunner(); 51 | TextComponent actBarMsg = new TextComponent(String.format(ManHunt.getInstance().getLanguage().GAMING.HUNTER.ACTION_BAR_RADOR, runner.getDisplayName())); 52 | if (event.getPlayer().getWorld() == runner.getLocation().getWorld()) { 53 | if (runner.getLocation().distance(event.getPlayer().getLocation()) >= ManHunt.getInstance().getMainConfig().distanceFar) { 54 | actBarMsg.addExtra(" "+String.format(ManHunt.getInstance().getLanguage().GAMING.HUNTER.ACTION_BAR_RADOR_PART_FAR, ManHunt.getInstance().getMainConfig().distanceFar)); 55 | } 56 | setItem.accept(i, LodestoneCompass.allocate(event.getPlayer(), runner.getLocation())); 57 | event.getPlayer().spigot().sendMessage(ChatMessageType.ACTION_BAR, actBarMsg); 58 | } else { 59 | if (ManHunt.getInstance().getMainConfig().blockCompassWhenDifferentWorld) { 60 | event.getPlayer().spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(ManHunt.getInstance().getLanguage().GAMING.HUNTER.FAILED_TO_TRACK)); 61 | setItem.accept(i, new ItemStack(Material.COMPASS)); 62 | } else { 63 | if (lastLoc.containsKey(event.getPlayer().getWorld().getName())) { 64 | Location loc = lastLoc.get(event.getPlayer().getWorld().getName()); 65 | setItem.accept(i, LodestoneCompass.allocate(event.getPlayer(), loc)); 66 | if (loc.distance(event.getPlayer().getLocation()) >= ManHunt.getInstance().getMainConfig().distanceFar) { 67 | actBarMsg.addExtra(String.format(ManHunt.getInstance().getLanguage().GAMING.HUNTER.ACTION_BAR_RADOR_PART_FAR, ManHunt.getInstance().getMainConfig().distanceFar)); 68 | } 69 | event.getPlayer().spigot().sendMessage(ChatMessageType.ACTION_BAR, actBarMsg); 70 | } else { 71 | event.getPlayer().spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(ManHunt.getInstance().getLanguage().GAMING.HUNTER.WARN_RUNNER_NOT_ENTERED)); 72 | } 73 | } 74 | } 75 | 76 | 77 | }); 78 | } 79 | } 80 | 81 | @EventHandler 82 | public void onMove(PlayerPortalEvent event) { 83 | Game game = ManHunt.getInstance().getGame(); 84 | game.isInGame(event.getPlayer()) 85 | .filter(g -> g.getRole() == GamePlayer.Role.RUNNER) 86 | .ifPresent(g -> { 87 | lastLoc.put(event.getFrom().getWorld().getName(), event.getFrom()); 88 | }); 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/JoinAndLeave.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.GamePhase; 5 | import io.ib67.manhunt.gui.Vote; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.event.player.PlayerJoinEvent; 10 | import org.bukkit.event.player.PlayerQuitEvent; 11 | 12 | public class JoinAndLeave implements Listener { 13 | @EventHandler 14 | public void onJoin(PlayerJoinEvent event) { 15 | Bukkit.getScheduler().runTaskLater(ManHunt.getInstance(), () -> { 16 | ManHunt.getInstance().getGame().joinPlayer(event.getPlayer()); 17 | Vote vote = ManHunt.getInstance().getGame().vote; 18 | if (vote != null && vote.getShouldVote().contains(event.getPlayer().getUniqueId())) { 19 | Bukkit.getScheduler().runTaskLater(ManHunt.getInstance(), 20 | () -> event.getPlayer().openInventory(vote.getVoteInv()), 21 | 10); 22 | } 23 | }, 10); 24 | } 25 | 26 | @EventHandler 27 | public void onLeave(PlayerQuitEvent event) { 28 | if (ManHunt.getInstance().getGame().getPhase() == GamePhase.WAITING_FOR_PLAYER) 29 | ManHunt.getInstance().getGame().kickPlayer(event.getPlayer().getName()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/Move.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.Listener; 6 | import org.bukkit.event.player.PlayerMoveEvent; 7 | 8 | public class Move implements Listener { 9 | @EventHandler 10 | public void onMove(PlayerMoveEvent e) { 11 | if (!ManHunt.getInstance().getGame().isStarted()) { 12 | if (e.getPlayer().getLocation().distance(e.getPlayer().getWorld().getSpawnLocation()) > 30) { 13 | e.getPlayer().teleport(e.getPlayer().getWorld().getSpawnLocation()); 14 | e.getPlayer().sendMessage(ManHunt.getInstance().getLanguage().GAMING.DONT_RUN_AWAY); 15 | } 16 | return; 17 | } 18 | ManHunt.getInstance().getGame().getRadar().onMove(e.getPlayer()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/PlayerPvP.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.stat.GameStat; 5 | import io.ib67.manhunt.setting.I18n; 6 | import org.bukkit.entity.EntityType; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.Listener; 10 | import org.bukkit.event.entity.EntityDamageByEntityEvent; 11 | 12 | public class PlayerPvP implements Listener { 13 | @EventHandler 14 | public void onHit(EntityDamageByEntityEvent e) { 15 | if (!ManHunt.getInstance().getGame().isStarted()) { 16 | e.setCancelled(true); 17 | } 18 | if (e.getDamager().getType() == EntityType.PLAYER && e.getEntity().getType() == EntityType.PLAYER) { 19 | Player runner = ManHunt.getInstance().getGame().getRunner(); 20 | Player damager = (Player) e.getDamager(); 21 | Player defender = (Player) e.getEntity(); 22 | if (damager != runner && defender != runner && ManHunt.getInstance().getMainConfig().disableTeamMateDamage) { 23 | e.setCancelled(true); 24 | return; 25 | } 26 | GameStat gameStat = ManHunt.getInstance().getGame().getGameStat(); 27 | I18n i18N = ManHunt.getInstance().getLanguage(); 28 | int score = ManHunt.getInstance().getMainConfig().playerScores.critical; 29 | if (defender.equals(runner)) { 30 | if (e.getFinalDamage() > 7) { 31 | gameStat.addAdvancement(damager, "CRITICAL_TO_RUNNER", score); 32 | damager.sendMessage(String.format(i18N.GAMING.CRITICAL_TARGET, score)); 33 | } 34 | } else if (damager.equals(runner)) { 35 | if (e.getFinalDamage() > 7) { 36 | gameStat.addAdvancement(damager, "CRITICAL_TO_HUNTER", score); 37 | damager.sendMessage(String.format(i18N.GAMING.CRITICAL_TARGET, score)); 38 | } 39 | } 40 | if (defender.getHealth() - e.getFinalDamage() <= 0) { 41 | score = ManHunt.getInstance().getMainConfig().playerScores.kill; 42 | gameStat.addAdvancement(damager, "KILL_ENEMY", score); 43 | damager.sendMessage(String.format(i18N.GAMING.KILL_TARGET, score)); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/listener/Respawn.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.listener; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.Game; 5 | import io.ib67.manhunt.util.LodestoneCompass; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.player.PlayerRespawnEvent; 9 | 10 | public class Respawn implements Listener { 11 | @EventHandler 12 | public void onRespawn(PlayerRespawnEvent event) { 13 | Game game = ManHunt.getInstance().getGame(); 14 | 15 | if (!game.isStarted() || !game.isCompassEnabled()) 16 | return; 17 | 18 | event.getPlayer().getInventory().addItem(LodestoneCompass.allocate(event.getPlayer(), game.getRunner().getLocation())); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/placeholder/MHPlaceholder.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.placeholder; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.game.GamePlayer; 5 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 6 | import org.bukkit.ChatColor; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.util.Arrays; 10 | import java.util.Optional; 11 | 12 | public class MHPlaceholder extends PlaceholderExpansion { 13 | private final ManHunt plugin; 14 | public MHPlaceholder(ManHunt plugin) { 15 | this.plugin = plugin; 16 | } 17 | @Override 18 | public boolean persist() { 19 | return true; 20 | } 21 | @Override 22 | public boolean canRegister() { 23 | return true; 24 | } 25 | @Override 26 | public String getAuthor() { 27 | return Arrays.toString(plugin.getDescription().getAuthors().toArray()); 28 | } 29 | @Override 30 | public String getIdentifier() { 31 | return "ManiHunt"; 32 | } 33 | @Override 34 | public String getVersion() { 35 | return plugin.getDescription().getVersion(); 36 | } 37 | 38 | public String onPlaceholderRequest(Player player, String identifier) { 39 | Optional role = ManHunt.getInstance().getGame().isInGame(player); 40 | if(identifier.equals("rule")){ 41 | if(role.isPresent()){ 42 | switch (role.get().getRole()){ 43 | case HUNTER: 44 | return (ChatColor.RED + "HUNTER"); 45 | case RUNNER: 46 | return (ChatColor.GREEN + "RUNNER"); 47 | } 48 | }else{ 49 | return ChatColor.GRAY + "SPECTATOR"; // todo I18N required 50 | } 51 | } 52 | return null; 53 | } 54 | } 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/radar/Radar.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.radar; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.util.Set; 6 | 7 | public interface Radar { 8 | void start(); 9 | 10 | void stop(); 11 | 12 | void onMove(Player p); 13 | 14 | Set getNearbyList(); 15 | 16 | void setWarnDistance(int i); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/radar/SimpleRadar.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.radar; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | import io.ib67.manhunt.setting.I18n; 5 | import net.md_5.bungee.api.ChatMessageType; 6 | import net.md_5.bungee.api.chat.TextComponent; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.scheduler.BukkitTask; 10 | 11 | import java.util.Arrays; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | import java.util.StringJoiner; 15 | 16 | public class SimpleRadar implements Radar { 17 | private final Player player; 18 | private final Set nearbyPlayers = new HashSet<>(); 19 | private int warnDistance; 20 | private BukkitTask keeper; 21 | 22 | public SimpleRadar(Player player, int warnDistance) { 23 | this.player = player; 24 | this.warnDistance = warnDistance; 25 | } 26 | 27 | @Override 28 | public void start() { 29 | stop(); 30 | keeper = Bukkit.getScheduler().runTaskTimer(ManHunt.getInstance(), () -> { 31 | I18n i18n = ManHunt.getInstance().getLanguage(); 32 | if (nearbyPlayers.size() != 0) { 33 | TextComponent textComponent = new TextComponent(String.format(i18n.GAMING.SIMPLE_RADOR.NEAR, 34 | setAsStr(), 35 | warnDistance)); 36 | player.spigot().sendMessage(ChatMessageType.ACTION_BAR,textComponent); 37 | } else { 38 | TextComponent textComponent = new TextComponent(String.format(i18n.GAMING.SIMPLE_RADOR.SAFE, 39 | warnDistance)); 40 | player.spigot().sendMessage(ChatMessageType.ACTION_BAR,textComponent); 41 | } 42 | 43 | }, 0L, 10L); //0.5s 44 | } 45 | 46 | @Override 47 | public void stop() { 48 | if (keeper != null && !keeper.isCancelled()) keeper.cancel(); 49 | } 50 | 51 | private String setAsStr() { 52 | CharSequence separator; 53 | StringJoiner sb = new StringJoiner(", "); 54 | nearbyPlayers.forEach(e -> sb.add(e.getDisplayName())); 55 | return sb.toString(); 56 | } 57 | 58 | @Override 59 | public void onMove(Player p) { 60 | if (p == player) { 61 | return; 62 | } 63 | I18n i18n = ManHunt.getInstance().getLanguage(); 64 | if (p.getLocation().getWorld() != player.getLocation().getWorld()) { 65 | return; 66 | } 67 | double distance = player.getLocation().distance(p.getLocation()); 68 | if (distance <= warnDistance) { 69 | if (!nearbyPlayers.contains(p)) { 70 | nearbyPlayers.add(p); 71 | player.sendMessage(String.format(i18n.GAMING.SIMPLE_RADOR.HINT_CHAT_COMING, p.getName())); 72 | if (Arrays.stream(p.getInventory().getContents()).anyMatch(e -> e.getType().name().endsWith("BED")) 73 | && ManHunt.getInstance().getMainConfig().enableBedHint) { 74 | player.sendMessage(i18n.GAMING.SIMPLE_RADOR.HUNTER_HAS_BED); 75 | } 76 | } 77 | } else { 78 | if (nearbyPlayers.contains(p)) { 79 | nearbyPlayers.remove(p); 80 | player.sendMessage(String.format(i18n.GAMING.SIMPLE_RADOR.HINT_CHAT_LEAVE, p.getName())); 81 | } 82 | } 83 | } 84 | 85 | @Override 86 | public Set getNearbyList() { 87 | return nearbyPlayers; 88 | } 89 | 90 | @Override 91 | public void setWarnDistance(int i) { 92 | this.warnDistance = i; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/setting/AdditionConfig.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.setting; 2 | 3 | public abstract class AdditionConfig { 4 | public boolean enable; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/setting/I18n.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.setting; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | public class I18n { 6 | public static final int VERSION = 4; 7 | public Gaming GAMING = new Gaming(); 8 | /** 9 | * 配置文件版本号 10 | */ 11 | public int version = VERSION; 12 | 13 | public static class Gaming { 14 | public String WAITING_FOR_PLAYERS_SUBTITLE = ChatColor.GOLD + "正在等待更多玩家进入游戏!"; 15 | public String WAITING_FOR_PLAYERS_MAINTITLE = " %d / %d"; 16 | public Hunter HUNTER = new Hunter(); 17 | public Runner RUNNER = new Runner(); 18 | public Vote VOTE = new Vote(); 19 | public String SPECTATOR_RULE = ChatColor.GREEN + "游戏已开始,请保持安静。"; 20 | public String[] GAME_INTRODUCTION = new String[]{ 21 | ChatColor.AQUA + "欢迎来到 " + ChatColor.RED + "ManHunt!", 22 | ChatColor.WHITE + "在本游戏中,将有多名玩家扮演" + ChatColor.RED + "猎人" + ChatColor.WHITE + ",1 名玩家扮演" + ChatColor.RED + "逃亡者" + ChatColor.WHITE + "。", 23 | ChatColor.WHITE + "游戏规则:", 24 | ChatColor.WHITE + "- " + ChatColor.GREEN + "猎人杀死逃亡者时,猎人胜利。", 25 | ChatColor.WHITE + "- " + ChatColor.GREEN + "逃亡者杀死末影龙时,逃亡者胜利", 26 | ChatColor.WHITE + "当猎人第一次造出指南针后," + ChatColor.RED + "将会开启无限指南针,并通过右键交互定位逃亡者位置", 27 | ChatColor.WHITE + "并且每个人每完成一个成就后," + ChatColor.GREEN + "将有机率拿到一样加成物品。 " + ChatColor.GRAY 28 | + "前提:服务器开启加成模式", 29 | ChatColor.WHITE + "游戏通过 投票 选举逃亡者。" 30 | }; 31 | public String ARCHIVE_TARGET = ChatColor.GOLD + "达成成就: +%dXP"; 32 | public String KILL_TARGET = ChatColor.GOLD + "杀死对手: +%dXP"; 33 | public String CRITICAL_TARGET = ChatColor.GOLD + "重拳出击!: +%dXP"; 34 | public String DONT_RUN_AWAY = ChatColor.RED + "请不要跑出出生点。"; 35 | public String SHUTDOWN = "30S 后将会自动重启。"; 36 | public SimpleRador SIMPLE_RADOR = new SimpleRador(); 37 | 38 | public static class SimpleRador { 39 | public String NEAR = ChatColor.RED + "%s 正在靠近! (<=%dM)"; 40 | public String SAFE = ChatColor.GREEN + "半径 %dM 内无猎人出现。"; 41 | public String HINT_CHAT_COMING = "猎人 %s 正在接近。"; 42 | public String HINT_CHAT_LEAVE = "猎人 %s 离开雷达范围之外。"; 43 | public String HUNTER_HAS_BED = ChatColor.WHITE + "%s 带了床,他可能会把你炸飞天."; 44 | } 45 | 46 | public static class Hunter { 47 | public String WON = ChatColor.RED + "游戏结束!猎人 胜利"; 48 | public String TITLE_MAIN = ChatColor.RED.toString() + ChatColor.MAGIC + "%%% " + ChatColor.RESET + ChatColor.RED + "游戏开始 %%%"; 49 | public String TITLE_SUB = "找到逃亡者并杀死他"; 50 | public String UNLIMITED_COMPASS_UNLOCKED = ChatColor.RED + "猎人已解锁无限指南针"; 51 | public String UNLIMITED_COMPASS_USAGE = ChatColor.GREEN + "请持续 " + ChatColor.GOLD + "[右键] " + ChatColor.GREEN + "指南针以刷新指向。"; 52 | public String UNLIMITED_COMPASS_LOCKED = ChatColor.RED + "无限指南针已被销毁!猎人需要重新制作指南针"; 53 | public String FAILED_TO_TRACK = ChatColor.RED + "无法追踪逃亡者!"; 54 | public String WARN_RUNNER_NOT_ENTERED = ChatColor.RED + "逃亡者尚未到达这个世界"; 55 | public String ACTION_BAR_RADOR = ChatColor.AQUA + "TRACKING: %s"; 56 | public String ACTION_BAR_RADOR_PART_FAR = ChatColor.RED + "DISTANCE >> %d"; 57 | public String COMPASS_ARRIVED = ChatColor.LIGHT_PURPLE + "指南针已经到达!请查收背包"; 58 | } 59 | 60 | public static class Runner { 61 | public String WON = ChatColor.GREEN + "游戏结束!逃亡者 胜利"; 62 | public String TITLE_MAIN = ChatColor.RED.toString() + ChatColor.MAGIC + "%%% " + ChatColor.RESET + ChatColor.RED + "游戏开始 %%%"; 63 | public String TITLE_SUB = "杀死末影龙,同时躲避猎人!"; 64 | public String ARRIVE_NETHER = ChatColor.BOLD + "逃亡者已到达 地狱"; 65 | public String ARRIVE_END = ChatColor.BOLD + "逃亡者已到达 末地"; 66 | } 67 | 68 | public static class Vote { 69 | public String VOTE_START = ChatColor.GREEN + "人数满足 正在进行投票!" + ChatColor.GRAY + "如果不小心关闭界面,请使用 /vote 再次打开。"; 70 | public String ALREADY_VOTED = ChatColor.RED + "您已经投票过了"; 71 | public String SHOULD_NOT_VOTE = ChatColor.RED + "您没有投票的权利"; 72 | public String VOTE_SUCCEED = ChatColor.GREEN + "您成功投给了 %s"; 73 | public String VOTING = ChatColor.GOLD + "投票中: %d / %d 已投票"; 74 | public String GAME_ALREADY_STARTED = ChatColor.RED + "投票已结束。"; 75 | public String VOTE_TITLE = "谁 是 幸 运 嘉 宾 ?"; 76 | public String VOTE_ITEM_FORMAT = "那当然是 %s"; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/setting/MainConfig.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.setting; 2 | 3 | public class MainConfig { 4 | public int maxPlayers = 3; 5 | public boolean verbose = false; 6 | public boolean muteSpectatorInGlobalChannel = false; 7 | public String serverLanguage = "zh_CN"; 8 | public String difficulty = "NORMAL"; 9 | public int radorWarnDistance = 30; 10 | public Scores playerScores = new Scores(); 11 | public boolean blockCompassWhenDifferentWorld = false; 12 | public boolean disableTeamMateDamage = false; 13 | public int distanceFar = 2000; 14 | public boolean enableBedHint = true; 15 | public boolean uploadStats = true; 16 | public boolean enableAirDrop = true; 17 | public boolean tryToAvoidOcean = true; 18 | public Servers mojangServers = new Servers(); 19 | 20 | public static class Servers { 21 | public String launchmetaBaseUrl = "https://launchermeta.mojang.com/"; 22 | public String resourceDownloadBaseUrl = "http://resources.download.minecraft.net/"; 23 | } 24 | 25 | public static class Scores { 26 | public int critical = 400; 27 | public int kill = 800; 28 | public int advancementNormal = 500; 29 | public int advancementSpecial = 1000; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/util/AdvancementTranslator.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.util; 2 | 3 | import io.ib67.manhunt.ManHunt; 4 | 5 | public class AdvancementTranslator { 6 | public static String translate(String key) { 7 | return ManHunt.getInstance().getMojangLocales().get(key); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/util/LodestoneCompass.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.util; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.Material; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.bukkit.inventory.meta.CompassMeta; 8 | 9 | public class LodestoneCompass { 10 | @SuppressWarnings("all") 11 | public static ItemStack allocate(Player p, Location loc) { 12 | if (Material.valueOf("LODESTONE") == null) { 13 | //Old version. 14 | p.setCompassTarget(loc); 15 | return new ItemStack(Material.COMPASS); 16 | } 17 | ItemStack compass = new ItemStack(Material.COMPASS); 18 | CompassMeta meta = (CompassMeta) compass.getItemMeta(); 19 | meta.setLodestoneTracked(false); 20 | meta.setLodestone(loc); 21 | compass.setItemMeta(meta); 22 | return compass; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/ib67/manhunt/util/SimpleConfig.java: -------------------------------------------------------------------------------- 1 | package io.ib67.manhunt.util; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.SneakyThrows; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.FileReader; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | 15 | /** 16 | * Apache License 17 | * Version 2.0, January 2004 18 | * http://www.apache.org/licenses/ 19 | *

20 | * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 21 | *

22 | * 1. Definitions. 23 | *

24 | * "License" shall mean the terms and conditions for use, reproduction, 25 | * and distribution as defined by Sections 1 through 9 of this document. 26 | *

27 | * "Licensor" shall mean the copyright owner or entity authorized by 28 | * the copyright owner that is granting the License. 29 | *

30 | * "Legal Entity" shall mean the union of the acting entity and all 31 | * other entities that control, are controlled by, or are under common 32 | * control with that entity. For the purposes of this definition, 33 | * "control" means (i) the power, direct or indirect, to cause the 34 | * direction or management of such entity, whether by contract or 35 | * otherwise, or (ii) ownership of fifty percent (50%) or more of the 36 | * outstanding shares, or (iii) beneficial ownership of such entity. 37 | *

38 | * "You" (or "Your") shall mean an individual or Legal Entity 39 | * exercising permissions granted by this License. 40 | *

41 | * "Source" form shall mean the preferred form for making modifications, 42 | * including but not limited to software source code, documentation 43 | * source, and configuration files. 44 | *

45 | * "Object" form shall mean any form resulting from mechanical 46 | * transformation or translation of a Source form, including but 47 | * not limited to compiled object code, generated documentation, 48 | * and conversions to other media types. 49 | *

50 | * "Work" shall mean the work of authorship, whether in Source or 51 | * Object form, made available under the License, as indicated by a 52 | * copyright notice that is included in or attached to the work 53 | * (an example is provided in the Appendix below). 54 | *

55 | * "Derivative Works" shall mean any work, whether in Source or Object 56 | * form, that is based on (or derived from) the Work and for which the 57 | * editorial revisions, annotations, elaborations, or other modifications 58 | * represent, as a whole, an original work of authorship. For the purposes 59 | * of this License, Derivative Works shall not include works that remain 60 | * separable from, or merely link (or bind by name) to the interfaces of, 61 | * the Work and Derivative Works thereof. 62 | *

63 | * "Contribution" shall mean any work of authorship, including 64 | * the original version of the Work and any modifications or additions 65 | * to that Work or Derivative Works thereof, that is intentionally 66 | * submitted to Licensor for inclusion in the Work by the copyright owner 67 | * or by an individual or Legal Entity authorized to submit on behalf of 68 | * the copyright owner. For the purposes of this definition, "submitted" 69 | * means any form of electronic, verbal, or written communication sent 70 | * to the Licensor or its representatives, including but not limited to 71 | * communication on electronic mailing lists, source code control systems, 72 | * and issue tracking systems that are managed by, or on behalf of, the 73 | * Licensor for the purpose of discussing and improving the Work, but 74 | * excluding communication that is conspicuously marked or otherwise 75 | * designated in writing by the copyright owner as "Not a Contribution." 76 | *

77 | * "Contributor" shall mean Licensor and any individual or Legal Entity 78 | * on behalf of whom a Contribution has been received by Licensor and 79 | * subsequently incorporated within the Work. 80 | *

81 | * 2. Grant of Copyright License. Subject to the terms and conditions of 82 | * this License, each Contributor hereby grants to You a perpetual, 83 | * worldwide, non-exclusive, no-charge, royalty-free, irrevocable 84 | * copyright license to reproduce, prepare Derivative Works of, 85 | * publicly display, publicly perform, sublicense, and distribute the 86 | * Work and such Derivative Works in Source or Object form. 87 | *

88 | * 3. Grant of Patent License. Subject to the terms and conditions of 89 | * this License, each Contributor hereby grants to You a perpetual, 90 | * worldwide, non-exclusive, no-charge, royalty-free, irrevocable 91 | * (except as stated in this section) patent license to make, have made, 92 | * use, offer to sell, sell, import, and otherwise transfer the Work, 93 | * where such license applies only to those patent claims licensable 94 | * by such Contributor that are necessarily infringed by their 95 | * Contribution(s) alone or by combination of their Contribution(s) 96 | * with the Work to which such Contribution(s) was submitted. If You 97 | * institute patent litigation against any entity (including a 98 | * cross-claim or counterclaim in a lawsuit) alleging that the Work 99 | * or a Contribution incorporated within the Work constitutes direct 100 | * or contributory patent infringement, then any patent licenses 101 | * granted to You under this License for that Work shall terminate 102 | * as of the date such litigation is filed. 103 | *

104 | * 4. Redistribution. You may reproduce and distribute copies of the 105 | * Work or Derivative Works thereof in any medium, with or without 106 | * modifications, and in Source or Object form, provided that You 107 | * meet the following conditions: 108 | *

109 | * (a) You must give any other recipients of the Work or 110 | * Derivative Works a copy of this License; and 111 | *

112 | * (b) You must cause any modified files to carry prominent notices 113 | * stating that You changed the files; and 114 | *

115 | * (c) You must retain, in the Source form of any Derivative Works 116 | * that You distribute, all copyright, patent, trademark, and 117 | * attribution notices from the Source form of the Work, 118 | * excluding those notices that do not pertain to any part of 119 | * the Derivative Works; and 120 | *

121 | * (d) If the Work includes a "NOTICE" text file as part of its 122 | * distribution, then any Derivative Works that You distribute must 123 | * include a readable copy of the attribution notices contained 124 | * within such NOTICE file, excluding those notices that do not 125 | * pertain to any part of the Derivative Works, in at least one 126 | * of the following places: within a NOTICE text file distributed 127 | * as part of the Derivative Works; within the Source form or 128 | * documentation, if provided along with the Derivative Works; or, 129 | * within a display generated by the Derivative Works, if and 130 | * wherever such third-party notices normally appear. The contents 131 | * of the NOTICE file are for informational purposes only and 132 | * do not modify the License. You may add Your own attribution 133 | * notices within Derivative Works that You distribute, alongside 134 | * or as an addendum to the NOTICE text from the Work, provided 135 | * that such additional attribution notices cannot be construed 136 | * as modifying the License. 137 | *

138 | * You may add Your own copyright statement to Your modifications and 139 | * may provide additional or different license terms and conditions 140 | * for use, reproduction, or distribution of Your modifications, or 141 | * for any such Derivative Works as a whole, provided Your use, 142 | * reproduction, and distribution of the Work otherwise complies with 143 | * the conditions stated in this License. 144 | *

145 | * 5. Submission of Contributions. Unless You explicitly state otherwise, 146 | * any Contribution intentionally submitted for inclusion in the Work 147 | * by You to the Licensor shall be under the terms and conditions of 148 | * this License, without any additional terms or conditions. 149 | * Notwithstanding the above, nothing herein shall supersede or modify 150 | * the terms of any separate license agreement you may have executed 151 | * with Licensor regarding such Contributions. 152 | *

153 | * 6. Trademarks. This License does not grant permission to use the trade 154 | * names, trademarks, service marks, or product names of the Licensor, 155 | * except as required for reasonable and customary use in describing the 156 | * origin of the Work and reproducing the content of the NOTICE file. 157 | *

158 | * 7. Disclaimer of Warranty. Unless required by applicable law or 159 | * agreed to in writing, Licensor provides the Work (and each 160 | * Contributor provides its Contributions) on an "AS IS" BASIS, 161 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 162 | * implied, including, without limitation, any warranties or conditions 163 | * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 164 | * PARTICULAR PURPOSE. You are solely responsible for determining the 165 | * appropriateness of using or redistributing the Work and assume any 166 | * risks associated with Your exercise of permissions under this License. 167 | *

168 | * 8. Limitation of Liability. In no event and under no legal theory, 169 | * whether in tort (including negligence), contract, or otherwise, 170 | * unless required by applicable law (such as deliberate and grossly 171 | * negligent acts) or agreed to in writing, shall any Contributor be 172 | * liable to You for damages, including any direct, indirect, special, 173 | * incidental, or consequential damages of any character arising as a 174 | * result of this License or out of the use or inability to use the 175 | * Work (including but not limited to damages for loss of goodwill, 176 | * work stoppage, computer failure or malfunction, or any and all 177 | * other commercial damages or losses), even if such Contributor 178 | * has been advised of the possibility of such damages. 179 | *

180 | * 9. Accepting Warranty or Additional Liability. While redistributing 181 | * the Work or Derivative Works thereof, You may choose to offer, 182 | * and charge a fee for, acceptance of support, warranty, indemnity, 183 | * or other liability obligations and/or rights consistent with this 184 | * License. However, in accepting such obligations, You may act only 185 | * on Your own behalf and on Your sole responsibility, not on behalf 186 | * of any other Contributor, and only if You agree to indemnify, 187 | * defend, and hold each Contributor harmless for any liability 188 | * incurred by, or claims asserted against, such Contributor by reason 189 | * of your accepting any such warranty or additional liability. 190 | *

191 | * END OF TERMS AND CONDITIONS 192 | *

193 | * APPENDIX: How to apply the Apache License to your work. 194 | *

195 | * To apply the Apache License to your work, attach the following 196 | * boilerplate notice, with the fields enclosed by brackets "[]" 197 | * replaced with your own identifying information. (Don't include 198 | * the brackets!) The text should be enclosed in the appropriate 199 | * comment syntax for the file format. We also recommend that a 200 | * file or class name and description of purpose be included on the 201 | * same "printed page" as the copyright notice for easier 202 | * identification within third-party archives. 203 | *

204 | * Copyright [2020] [SaltedFish Club] 205 | *

206 | * Licensed under the Apache License, Version 2.0 (the "License"); 207 | * you may not use this file except in compliance with the License. 208 | * You may obtain a copy of the License at 209 | *

210 | * http://www.apache.org/licenses/LICENSE-2.0 211 | *

212 | * Unless required by applicable law or agreed to in writing, software 213 | * distributed under the License is distributed on an "AS IS" BASIS, 214 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 215 | * See the License for the specific language governing permissions and 216 | * limitations under the License. 217 | * 配置包装,Edited for ManHunt 218 | * 219 | * @param 数据类 220 | */ 221 | public class SimpleConfig { 222 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 223 | private final String root; 224 | private C configObj; 225 | @Setter 226 | @Getter 227 | private String configFileName = "config.json"; 228 | private final Class clazz; 229 | 230 | @SneakyThrows 231 | public SimpleConfig(File rootDir, Class configClass) { 232 | rootDir.mkdirs(); 233 | this.root = rootDir.getAbsolutePath(); 234 | this.clazz = configClass; 235 | this.configObj = configClass.getDeclaredConstructor().newInstance(); 236 | saveDefault(); 237 | reloadConfig(); 238 | } 239 | 240 | /** 241 | * Save Config to config.json 242 | */ 243 | @SneakyThrows 244 | public void saveConfig() { 245 | Files.write(new File(root + "/" + getConfigFileName()).toPath(), gson.toJson(configObj).getBytes()); 246 | } 247 | 248 | /** 249 | * 若文件不存在则保存默认 250 | */ 251 | @SneakyThrows 252 | public void saveDefault() { 253 | Path a = new File(root + "/" + getConfigFileName()).toPath(); 254 | a.getParent().toFile().mkdirs(); 255 | if (!Files.exists(a)) { 256 | Files.write(a, gson.toJson(configObj).getBytes()); 257 | } 258 | } 259 | 260 | /** 261 | * 获取包装对象 262 | * 263 | * @return 264 | */ 265 | public C get() { 266 | return configObj; 267 | } 268 | 269 | public void set(C c) { 270 | this.configObj = c; 271 | } 272 | 273 | /** 274 | * Reload MainConfig 275 | */ 276 | @SneakyThrows 277 | public void reloadConfig() { 278 | configObj = gson.fromJson(new BufferedReader(new FileReader(root + "/" + getConfigFileName())), clazz); 279 | } 280 | } -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ManHunt 2 | version: @version@ 3 | authors: [ "iceBear67","czm" ] 4 | main: io.ib67.manhunt.ManHunt 5 | softdepend: ["PlaceholderAPI"] 6 | api-version: 1.16 7 | commands: 8 | vote: 9 | description: vote 10 | usage: /vote 11 | default: all -------------------------------------------------------------------------------- /src/test/java/LangFileGen.java: -------------------------------------------------------------------------------- 1 | import com.google.gson.Gson; 2 | import com.google.gson.GsonBuilder; 3 | import io.ib67.manhunt.setting.I18n; 4 | import org.junit.Test; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | 10 | public class LangFileGen { 11 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 12 | 13 | @Test 14 | public void gen() throws Exception { 15 | I18n i18n = new I18n(); 16 | String textToWrite = gson.toJson(i18n); 17 | Files.write(Paths.get("./build/libs/zh_CN.json"), textToWrite.getBytes(StandardCharsets.UTF_8)); 18 | } 19 | } 20 | --------------------------------------------------------------------------------