Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
fc048a450b | |||
e5e2d065a4 | |||
710d2679a6 | |||
a6a710f671 | |||
dfb821ca23 | |||
9c462690dc | |||
7744442575 | |||
78bb6c9447 | |||
5896a30928 | |||
67b39b6ae2 | |||
b8a3dbddd5 | |||
71bf3ed675 | |||
ecabf4a8bc | |||
f89765f024 | |||
23e723c236 | |||
20af309966 | |||
ddc47c4a8f | |||
d7d989eb6b | |||
1a88653e61 | |||
3095f6670a | |||
db054d9111 | |||
e6f2b7191e | |||
53f6de8355 | |||
3bed47ae3a | |||
025da753a2 | |||
01e2c11bbe | |||
8a23f668b3 | |||
f0450e754d |
@ -9,34 +9,35 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: "Pull & Checkout"
|
- name: "Pull & Checkout"
|
||||||
uses: https://ssl.lunadeer.cn:14446/actions/checkout@v3
|
uses: https://ssl.lunadeer.cn:14446/actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: "Set up Maven"
|
- name: "Set up Maven"
|
||||||
uses: https://ssl.lunadeer.cn:14446/actions/setup-maven@v4
|
uses: https://ssl.lunadeer.cn:14446/actions/setup-maven@v4
|
||||||
- name: "Set up JDK 17"
|
- name: "Set up JDK 17"
|
||||||
uses: https://ssl.lunadeer.cn:14446/actions/setup-java@v3
|
uses: https://ssl.lunadeer.cn:14446/actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
cache: maven
|
cache: maven
|
||||||
- name: "Build with Maven"
|
- name: "Build with Maven"
|
||||||
run: mvn -B package --file pom.xml
|
run: mvn -B package --file pom.xml
|
||||||
- name: "Copy jar to staging"
|
- name: "Copy jar to staging"
|
||||||
run: mkdir staging && cp target/*.jar staging
|
run: mkdir staging && cp target/*.jar staging
|
||||||
- name: "Build & test"
|
- name: "Build & test"
|
||||||
run: |
|
run: |
|
||||||
echo "done!"
|
echo "done!"
|
||||||
- name: "setup go for release script"
|
- name: "setup go for release script"
|
||||||
run: |
|
run: |
|
||||||
wget --no-check-certificate https://go.dev/dl/go1.21.6.linux-amd64.tar.gz
|
wget --no-check-certificate https://golang.google.cn/dl/go1.21.6.linux-amd64.tar.gz
|
||||||
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.6.linux-amd64.tar.gz
|
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.6.linux-amd64.tar.gz
|
||||||
ln -s /usr/local/go/bin/go /usr/bin/go
|
ln -s /usr/local/go/bin/go /usr/bin/go
|
||||||
go version
|
go version
|
||||||
- name: "Release"
|
- name: "Release"
|
||||||
uses: https://ssl.lunadeer.cn:14446/actions/release-action@main
|
uses: https://ssl.lunadeer.cn:14446/zhangyuheng/release-action@main
|
||||||
with:
|
with:
|
||||||
files: |-
|
note: "带 `original-` 前缀的文件无法用于运行,请下载不带此前缀的版本。"
|
||||||
staging/*.jar
|
files: |-
|
||||||
api_key: '${{secrets.RELEASE_TOKEN}}'
|
staging/*.jar
|
||||||
|
api_key: '${{secrets.RELEASE_TOKEN}}'
|
72
README.md
72
README.md
@ -1,22 +1,31 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
# ColorfulMap
|
# ColorfulMap
|
||||||
|
|
||||||
开源地址:[Gitea](https://ssl.lunadeer.cn:14446/zhangyuheng/ColorfulMap)
|
<img src="https://ssl.lunadeer.cn:14437/i/2024/02/21/65d5e0d10b7d5.png" alt="" width="70%">
|
||||||
|
|
||||||
|
### [开源地址](https://ssl.lunadeer.cn:14446/zhangyuheng/ColorfulMap) | [文档地址](https://ssl.lunadeer.cn:14448/doc/26/)
|
||||||
|
|
||||||
|
### [下载页面](https://ssl.lunadeer.cn:14446/zhangyuheng/ColorfulMap/releases)
|
||||||
|
|
||||||
|
### [统计页面](https://bstats.org/plugin/bukkit/ColorfulMap/21443) | [Hangar](https://hangar.papermc.io/zhangyuheng/ColorfulMap)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
![](https://ssl.lunadeer.cn:14437/i/2024/02/21/65d5e0d10b7d5.png)
|
|
||||||
|
|
||||||
用于将图片转换为地图画,悬挂在展示框上增添装饰。
|
用于将图片转换为地图画,悬挂在展示框上增添装饰。
|
||||||
|
|
||||||
## 说明
|
## 说明
|
||||||
|
|
||||||
本插件大约相当于 [ImageFrame](https://github.com/LOOHP/ImageFrame) 的简版以及 [ImageMaps](https://github.com/SydMontague/ImageMaps) 的高版本重制版。前者功能丰富,不过可能由于项目体量较大,对于新版本的兼容较慢,后者在 1.18 开始就停止了更新,切不支持 Folia 核心。
|
本插件大约相当于 [ImageFrame](https://github.com/LOOHP/ImageFrame)的简版以及 [ImageMaps](https://github.com/SydMontague/ImageMaps) 的高版本重制版。前者功能丰富,不过可能由于项目体量较大,对于新版本的兼容较慢,后者在1.18 开始就停止了更新,且不支持 Folia 核心。
|
||||||
|
|
||||||
## 功能介绍
|
## 功能介绍
|
||||||
|
|
||||||
- 将图片转换为地图画;
|
- 将图片转换为地图画;
|
||||||
- 图片缩放;
|
- 图片缩放;
|
||||||
- 自动放置;
|
- 自动放置;
|
||||||
|
- 支持消耗金钱生成地图画(需要 Vault 前置);
|
||||||
|
|
||||||
## 支持版本
|
## 支持版本
|
||||||
|
|
||||||
@ -31,41 +40,66 @@
|
|||||||
|
|
||||||
## 玩家使用方法
|
## 玩家使用方法
|
||||||
|
|
||||||
1. 首先需要将你想要转换的图片上传到 [图床](https://ssl.lunadeer.cn:14437/) ,便于本插件从网络读取图片内容。上传完成后会得到一个图片的网络地址,复制此地址。
|
1. 首先需要将你想要转换的图片上传到 [图床](https://ssl.lunadeer.cn:14437/) ,便于本插件从网络读取图片内容。上传完成后会得到一个图片的网络地址,复制此地址。
|
||||||
|
|
||||||
2. 在游戏中输入指令:`/tomap <图片地址>` 即可获得一张地图。地图的左上角会显示这张地图画所需的展示框阵列的尺寸,如下图所示 5x4 代表你需要在墙上准备一组长5格,宽4格的展示框阵列才能摆的下这张地图画。
|
2. 在游戏中输入指令:`/tomap <图片地址>` 即可获得一张地图
|
||||||
|
|
||||||
![](https://ssl.lunadeer.cn:14437/i/2024/02/21/65d5ef7e8d676.png)
|
![](https://ssl.lunadeer.cn:14437/i/2024/07/30/66a89345ca899.png)
|
||||||
|
|
||||||
|
地图的lore中记录了此地图画的大小,下图中的 8x10 表明此地图画需要长8格、高10格的展示框阵列才能放置。
|
||||||
|
|
||||||
|
![](https://ssl.lunadeer.cn:14437/i/2024/07/30/66a893ba56d7d.png)
|
||||||
|
|
||||||
3. 对着展示框阵列的**左下角展示框**摆放此地图,则会自动在墙上的剩余展示框内放置对应的地图。
|
3. 对着展示框阵列的**左下角展示框**摆放此地图,则会自动在墙上的剩余展示框内放置对应的地图。
|
||||||
|
|
||||||
4. 如果图片太大或太小可以尝试在指令后加入缩放倍率,例如 `/tomap <图片地址> 0.5`表示将以原图的50%大小渲染。如果你希望将图片填满所有地图边缘处没有留白,那么你需要保证你的图片的长宽分辨率均为**128的倍数**,因为在MC中一张地图的分辨率为128x128。
|
4. 如果图片太大或太小可以尝试在指令后加入缩放倍率,例如 `/tomap <图片地址> 0.5` 表示将以原图的50%大小渲染。如果你希望将图片填满所有地图边缘处没有留白,那么你需要保证你的图片的长宽分辨率均为**128的倍数**,因为在MC中一张地图的分辨率为128x128。
|
||||||
|
|
||||||
## 管理员指南
|
## 管理员指南
|
||||||
|
|
||||||
## 指令
|
### 指令 & 权限节点
|
||||||
|
|
||||||
> 以下指令中尖括号 `<>` 表示必填,方括号 `[]` 表示选填。
|
> 以下指令中尖括号 `<>` 表示必填,方括号 `[]` 表示选填。
|
||||||
|
|
||||||
### 玩家指令
|
| 功能 | 指令 | 权限节点 | 默认 |
|
||||||
|
|-------|------------------------|--------------------|------|
|
||||||
|
| 生成地图画 | `/tomap <图片地址> [缩放倍率]` | colorfulmap.tomap | true |
|
||||||
|
| 重载配置 | `/reloadColorfulMap` | colorfulmap.reload | op |
|
||||||
|
|
||||||
|
### 如何查图
|
||||||
|
|
||||||
| 功能 | 指令 |
|
2.0版本开始地图画会保存一份玩家获取图片的原图,路径为 `plugins/ColorfulMap/maps/{图片uuid}/raw.png` ,方便服主快速查阅图片内容。
|
||||||
| --- | --- |
|
|
||||||
| 生成地图画 | `/tomap <图片地址> [缩放倍率]` |
|
|
||||||
|
|
||||||
|
当玩家放置地图后还会同时保存一份元数据文件,`plugins/ColorfulMap/maps/{图片uuid}/meta.txt`,此文件记录了放置地图的玩家、放置时间、地图画的位置等信息。
|
||||||
|
|
||||||
|
### 配置文件参考
|
||||||
### 管理员指令
|
|
||||||
|
|
||||||
## 配置文件参考
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# 地图画阵列的最大尺寸 宽
|
||||||
MaxFrameX: 32
|
MaxFrameX: 32
|
||||||
|
|
||||||
|
# 地图画阵列的最大尺寸 高
|
||||||
MaxFrameY: 18
|
MaxFrameY: 18
|
||||||
|
|
||||||
|
# 是否启用经济系统 消耗玩家金钱生成地图画
|
||||||
|
Economy:
|
||||||
|
Enable: false
|
||||||
|
CostPerMap: 100.0 # 每张地图画的单价 - 3*3的地图画需要9张地图画 也就是 9*100 = 900
|
||||||
|
|
||||||
|
# 图床地址白名单,不在白名单中的图床将无法使用,留空表示不启用地址白名单
|
||||||
|
AddressWhiteList: []
|
||||||
|
|
||||||
|
CheckUpdate: true
|
||||||
|
|
||||||
Debug: false
|
Debug: false
|
||||||
```
|
```
|
||||||
|
|
||||||
## TODO
|
## 建议与反馈
|
||||||
|
|
||||||
|
Mail: [zhangyuheng@lunadeer.cn](mailto:zhangyuheng@lunadeer.cn)
|
||||||
|
|
||||||
|
QQ群:309428300
|
||||||
|
|
||||||
|
## 统计
|
||||||
|
|
||||||
|
![bstats](https://bstats.org/signatures/bukkit/ColorfulMap.svg)
|
||||||
|
|
||||||
|
27
pom.xml
27
pom.xml
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>cn.lunadeer</groupId>
|
<groupId>cn.lunadeer</groupId>
|
||||||
<artifactId>ColorfulMap</artifactId>
|
<artifactId>ColorfulMap</artifactId>
|
||||||
<version>1.0</version>
|
<version>2.6</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>ColorfulMap</name>
|
<name>ColorfulMap</name>
|
||||||
@ -62,6 +62,14 @@
|
|||||||
<id>papermc</id>
|
<id>papermc</id>
|
||||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>codemc-repo</id>
|
||||||
|
<url>https://repo.codemc.org/repository/maven-public</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -71,5 +79,22 @@
|
|||||||
<version>1.20.1-R0.1-SNAPSHOT</version>
|
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-platform-bukkit</artifactId>
|
||||||
|
<version>4.3.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.MilkBowl</groupId>
|
||||||
|
<artifactId>VaultAPI</artifactId>
|
||||||
|
<version>1.7</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.milkbowl.vault</groupId>
|
||||||
|
<artifactId>VaultUnlockedAPI</artifactId>
|
||||||
|
<version>2.2</version><!-- Validate this is the most recent version from the CI -->
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package cn.lunadeer.colorfulmap;
|
package cn.lunadeer.colorfulmap;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.commands.Purge;
|
||||||
|
import cn.lunadeer.colorfulmap.commands.Reload;
|
||||||
import cn.lunadeer.colorfulmap.commands.ToMap;
|
import cn.lunadeer.colorfulmap.commands.ToMap;
|
||||||
import cn.lunadeer.colorfulmap.utils.XLogger;
|
import cn.lunadeer.colorfulmap.utils.*;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
@ -13,15 +15,30 @@ public final class ColorfulMap extends JavaPlugin {
|
|||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Plugin startup logic
|
// Plugin startup logic
|
||||||
instance = this;
|
instance = this;
|
||||||
|
new XLogger(this);
|
||||||
config = new Configuration(this);
|
config = new Configuration(this);
|
||||||
|
XLogger.setDebug(config.isDebug());
|
||||||
|
new Notification(this);
|
||||||
|
new Scheduler(this);
|
||||||
|
|
||||||
Objects.requireNonNull(Bukkit.getPluginCommand("tomap")).setExecutor(new ToMap());
|
Objects.requireNonNull(Bukkit.getPluginCommand("tomap")).setExecutor(new ToMap());
|
||||||
|
Objects.requireNonNull(Bukkit.getPluginCommand("reloadColorfulMap")).setExecutor(new Reload());
|
||||||
|
Objects.requireNonNull(Bukkit.getPluginCommand("purgeColorfulMap")).setExecutor(new Purge());
|
||||||
|
|
||||||
Bukkit.getPluginManager().registerEvents(new Events(), this);
|
Bukkit.getPluginManager().registerEvents(new Events(), this);
|
||||||
|
|
||||||
new MapManager().init();
|
new MapManager().init();
|
||||||
|
|
||||||
|
bStatsMetrics metrics = new bStatsMetrics(this, 21443);
|
||||||
|
if (config.isCheckUpdate()) {
|
||||||
|
giteaReleaseCheck = new GiteaReleaseCheck(this,
|
||||||
|
"https://ssl.lunadeer.cn:14446",
|
||||||
|
"zhangyuheng",
|
||||||
|
"ColorfulMap");
|
||||||
|
}
|
||||||
|
|
||||||
XLogger.info("ColorfulMap 已加载");
|
XLogger.info("ColorfulMap 已加载");
|
||||||
XLogger.info("版本: " + getPluginMeta().getVersion());
|
XLogger.info("版本: " + this.getDescription().getVersion());
|
||||||
// https://patorjk.com/software/taag/#p=display&f=Big&t=ColorfulMap
|
// https://patorjk.com/software/taag/#p=display&f=Big&t=ColorfulMap
|
||||||
XLogger.info(" _____ _ __ _ __ __");
|
XLogger.info(" _____ _ __ _ __ __");
|
||||||
XLogger.info(" / ____| | | / _| | | \\/ |");
|
XLogger.info(" / ____| | | / _| | | \\/ |");
|
||||||
@ -41,4 +58,5 @@ public final class ColorfulMap extends JavaPlugin {
|
|||||||
|
|
||||||
public static ColorfulMap instance;
|
public static ColorfulMap instance;
|
||||||
public static Configuration config;
|
public static Configuration config;
|
||||||
|
private GiteaReleaseCheck giteaReleaseCheck;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package cn.lunadeer.colorfulmap;
|
package cn.lunadeer.colorfulmap;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.utils.VaultConnect.VaultConnect;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class Configuration {
|
public class Configuration {
|
||||||
|
|
||||||
public Configuration(ColorfulMap plugin) {
|
public Configuration(ColorfulMap plugin) {
|
||||||
@ -17,6 +21,26 @@ public class Configuration {
|
|||||||
_debug = _file.getBoolean("Debug", false);
|
_debug = _file.getBoolean("Debug", false);
|
||||||
_max_frame_x = _file.getInt("MaxFrameX", 32);
|
_max_frame_x = _file.getInt("MaxFrameX", 32);
|
||||||
_max_frame_y = _file.getInt("MaxFrameY", 18);
|
_max_frame_y = _file.getInt("MaxFrameY", 18);
|
||||||
|
_check_update = _file.getBoolean("CheckUpdate", true);
|
||||||
|
_economy_enable = _file.getBoolean("Economy.Enable", false);
|
||||||
|
_price = (float) _file.getDouble("Economy.CostPerMap", 100.0);
|
||||||
|
_address_white_list = _file.getStringList("AddressWhiteList");
|
||||||
|
if (_economy_enable) {
|
||||||
|
XLogger.info("已启用经济系统");
|
||||||
|
new VaultConnect(_plugin);
|
||||||
|
}
|
||||||
|
saveAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveAll() {
|
||||||
|
_file.set("Debug", _debug);
|
||||||
|
_file.set("MaxFrameX", _max_frame_x);
|
||||||
|
_file.set("MaxFrameY", _max_frame_y);
|
||||||
|
_file.set("CheckUpdate", _check_update);
|
||||||
|
_file.set("Economy.Enable", _economy_enable);
|
||||||
|
_file.set("Economy.CostPerMap", _price);
|
||||||
|
_file.set("AddressWhiteList", _address_white_list);
|
||||||
|
_plugin.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isDebug() {
|
public Boolean isDebug() {
|
||||||
@ -49,9 +73,53 @@ public class Configuration {
|
|||||||
_plugin.saveConfig();
|
_plugin.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isCheckUpdate() {
|
||||||
|
return _check_update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckUpdate(Boolean check_update) {
|
||||||
|
_check_update = check_update;
|
||||||
|
_file.set("CheckUpdate", check_update);
|
||||||
|
_plugin.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isEconomyEnable() {
|
||||||
|
return _economy_enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEconomyEnable(Boolean economy_enable) {
|
||||||
|
_economy_enable = economy_enable;
|
||||||
|
_file.set("Economy.Enable", economy_enable);
|
||||||
|
_plugin.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float getPrice() {
|
||||||
|
return _price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(Float price) {
|
||||||
|
_price = price;
|
||||||
|
_file.set("Economy.CostPerMap", price);
|
||||||
|
_plugin.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAddressWhiteList() {
|
||||||
|
return _address_white_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddressWhiteList(List<String> address_white_list) {
|
||||||
|
_address_white_list = address_white_list;
|
||||||
|
_file.set("AddressWhiteList", address_white_list);
|
||||||
|
_plugin.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
private final ColorfulMap _plugin;
|
private final ColorfulMap _plugin;
|
||||||
private FileConfiguration _file;
|
private FileConfiguration _file;
|
||||||
private Boolean _debug;
|
private Boolean _debug;
|
||||||
private Integer _max_frame_x;
|
private Integer _max_frame_x;
|
||||||
private Integer _max_frame_y;
|
private Integer _max_frame_y;
|
||||||
|
private Boolean _check_update;
|
||||||
|
private Boolean _economy_enable;
|
||||||
|
private Float _price;
|
||||||
|
private List<String > _address_white_list;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package cn.lunadeer.colorfulmap;
|
|||||||
|
|
||||||
import cn.lunadeer.colorfulmap.generator.ImageRenderer;
|
import cn.lunadeer.colorfulmap.generator.ImageRenderer;
|
||||||
import cn.lunadeer.colorfulmap.utils.Notification;
|
import cn.lunadeer.colorfulmap.utils.Notification;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.VaultConnect.VaultConnect;
|
||||||
import cn.lunadeer.colorfulmap.utils.XLogger;
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
@ -16,11 +17,15 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static cn.lunadeer.colorfulmap.Apis.getItemFrameMatrix;
|
import static cn.lunadeer.colorfulmap.Apis.getItemFrameMatrix;
|
||||||
|
import static cn.lunadeer.colorfulmap.StorageMaps.getImageTilePath;
|
||||||
|
import static cn.lunadeer.colorfulmap.StorageMaps.writeMeta;
|
||||||
|
|
||||||
public class Events implements Listener {
|
public class Events implements Listener {
|
||||||
@EventHandler(priority = EventPriority.HIGHEST)
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
@ -47,13 +52,23 @@ public class Events implements Listener {
|
|||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
List<Component> lore = item.getItemMeta().lore();
|
List<Component> lore = item.getItemMeta().lore();
|
||||||
XLogger.debug("PlayerInteractEntityEvent");
|
XLogger.debug("PlayerInteractEntityEvent");
|
||||||
if (lore == null || lore.size() != 3) {
|
if (lore == null || lore.size() != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
XLogger.debug("putImageMapsOnItemFrame");
|
XLogger.debug("putImageMapsOnItemFrame");
|
||||||
UUID uuid = UUID.fromString(((TextComponent) lore.get(0)).content());
|
/*
|
||||||
int count_x = Integer.parseInt(((TextComponent) lore.get(1)).content());
|
MapMeta meta = (MapMeta) mapItem.getItemMeta();
|
||||||
int count_y = Integer.parseInt(((TextComponent) lore.get(2)).content());
|
List<Component> lore = new ArrayList<>();
|
||||||
|
String size_info = "size: " + x_count + "x" + y_count;
|
||||||
|
lore.add(Component.text(size_info).hoverEvent(Component.text(map_images_uuid.toString())));
|
||||||
|
meta.lore(lore);
|
||||||
|
mapItem.setItemMeta(meta);
|
||||||
|
*/
|
||||||
|
TextComponent textComponent = (TextComponent) lore.get(0);
|
||||||
|
UUID uuid = UUID.fromString(((TextComponent) Objects.requireNonNull(textComponent.hoverEvent()).value()).content());
|
||||||
|
String[] size = textComponent.content().split(": ")[1].split("x");
|
||||||
|
int count_x = Integer.parseInt(size[0]);
|
||||||
|
int count_y = Integer.parseInt(size[1]);
|
||||||
XLogger.debug("uuid: " + uuid);
|
XLogger.debug("uuid: " + uuid);
|
||||||
XLogger.debug("count_x: " + count_x);
|
XLogger.debug("count_x: " + count_x);
|
||||||
XLogger.debug("count_y: " + count_y);
|
XLogger.debug("count_y: " + count_y);
|
||||||
@ -67,10 +82,25 @@ public class Events implements Listener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ColorfulMap.config.isEconomyEnable()) {
|
||||||
|
float cost = ColorfulMap.config.getPrice() * count_x * count_y;
|
||||||
|
if (!VaultConnect.instance.economyAvailable()) {
|
||||||
|
Notification.error(player, "无法放置地图画: 无法连接到经济插件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (VaultConnect.instance.getBalance(player) < cost) {
|
||||||
|
Notification.error(player, "无法放置地图画: 余额不足");
|
||||||
|
Notification.error(player, "此图片尺寸为 %d x %d 个展示框,单价 %f,总价 %f,你的余额为 %f",
|
||||||
|
count_x, count_y, ColorfulMap.config.getPrice(), cost, VaultConnect.instance.getBalance(player));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VaultConnect.instance.withdrawPlayer(player, cost);
|
||||||
|
}
|
||||||
|
|
||||||
List<ItemStack> maps = new ArrayList<>();
|
List<ItemStack> maps = new ArrayList<>();
|
||||||
for (int y = 0; y < count_y; y++) {
|
for (int y = 0; y < count_y; y++) {
|
||||||
for (int x = 0; x < count_x; x++) {
|
for (int x = 0; x < count_x; x++) {
|
||||||
String path = "maps/" + uuid + "/" + x + "_" + y + ".png";
|
String path = getImageTilePath(uuid, x, y);
|
||||||
ItemStack map = ImageRenderer.getMapItemFromImageTile(player, path);
|
ItemStack map = ImageRenderer.getMapItemFromImageTile(player, path);
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
Notification.error(player, "无法加载地图,原始路径丢失:" + path);
|
Notification.error(player, "无法加载地图,原始路径丢失:" + path);
|
||||||
@ -80,6 +110,15 @@ public class Events implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录操作信息
|
||||||
|
List<String> imageMeta = new ArrayList<>();
|
||||||
|
imageMeta.add("Player: " + player.getName() + "(" + player.getUniqueId() + ")");
|
||||||
|
imageMeta.add("Time: " + LocalTime.now());
|
||||||
|
imageMeta.add("Location: " + item_frame.getLocation());
|
||||||
|
imageMeta.add("Size: " + count_x + "x" + count_y);
|
||||||
|
imageMeta.add("ImageUUID: " + uuid);
|
||||||
|
writeMeta(uuid, imageMeta);
|
||||||
|
|
||||||
XLogger.debug("maps size: " + maps.size());
|
XLogger.debug("maps size: " + maps.size());
|
||||||
XLogger.debug("item_frames size: " + item_frames.size());
|
XLogger.debug("item_frames size: " + item_frames.size());
|
||||||
for (int i = 0; i < item_frames.size(); i++) {
|
for (int i = 0; i < item_frames.size(); i++) {
|
||||||
|
@ -16,9 +16,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class MapManager implements Listener {
|
public class MapManager implements Listener {
|
||||||
|
|
||||||
@ -40,7 +38,7 @@ public class MapManager implements Listener {
|
|||||||
public void init() {
|
public void init() {
|
||||||
MapManager.instance = this;
|
MapManager.instance = this;
|
||||||
Bukkit.getPluginManager().registerEvents(this, ColorfulMap.instance);
|
Bukkit.getPluginManager().registerEvents(this, ColorfulMap.instance);
|
||||||
loadImages();
|
reloadImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -86,8 +84,9 @@ public class MapManager implements Listener {
|
|||||||
/***
|
/***
|
||||||
* Loads images from data file to HashMap.
|
* Loads images from data file to HashMap.
|
||||||
*/
|
*/
|
||||||
private void loadImages() {
|
public void reloadImages() {
|
||||||
FileConfiguration config = dataFile.getConfig();
|
FileConfiguration config = dataFile.getConfig();
|
||||||
|
List<String> recordToDelete = new ArrayList<>();
|
||||||
for (String world : config.getKeys(false)) {
|
for (String world : config.getKeys(false)) {
|
||||||
for (String id : Objects.requireNonNull(config.getConfigurationSection(world)).getKeys(false)) {
|
for (String id : Objects.requireNonNull(config.getConfigurationSection(world)).getKeys(false)) {
|
||||||
String path = config.getString(world + "." + id);
|
String path = config.getString(world + "." + id);
|
||||||
@ -96,7 +95,8 @@ public class MapManager implements Listener {
|
|||||||
}
|
}
|
||||||
BufferedImage image = StorageMaps.load(path);
|
BufferedImage image = StorageMaps.load(path);
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
XLogger.err("无法加载图片: " + path);
|
XLogger.err("无法加载图片: %s 已移除记录", path);
|
||||||
|
recordToDelete.add(world + "." + id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!savedImages.containsKey(world)) {
|
if (!savedImages.containsKey(world)) {
|
||||||
@ -105,6 +105,11 @@ public class MapManager implements Listener {
|
|||||||
savedImages.get(world).put(Integer.parseInt(id), image);
|
savedImages.get(world).put(Integer.parseInt(id), image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 删除无效记录
|
||||||
|
for (String record : recordToDelete) {
|
||||||
|
config.set(record, null);
|
||||||
|
}
|
||||||
|
dataFile.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -117,6 +122,27 @@ public class MapManager implements Listener {
|
|||||||
return savedImages.get(world_name.getName()).get(id);
|
return savedImages.get(world_name.getName()).get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Integer> getMapIds(World world) {
|
||||||
|
if (!savedImages.containsKey(world.getName())) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return new ArrayList<>(savedImages.get(world.getName()).keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMap(World world, Integer id) {
|
||||||
|
if (!savedImages.containsKey(world.getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FileConfiguration config = dataFile.getConfig();
|
||||||
|
String path = config.getString(world.getName() + "." + id);
|
||||||
|
if (path == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new File(path).delete();
|
||||||
|
config.set(world.getName() + "." + id, null);
|
||||||
|
dataFile.saveConfig();
|
||||||
|
savedImages.get(world.getName()).remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
static class MapsFile {
|
static class MapsFile {
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package cn.lunadeer.colorfulmap;
|
package cn.lunadeer.colorfulmap;
|
||||||
|
|
||||||
import cn.lunadeer.colorfulmap.utils.Notification;
|
import cn.lunadeer.colorfulmap.utils.Notification;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@ -16,26 +17,36 @@ public class StorageMaps {
|
|||||||
/**
|
/**
|
||||||
* save images to plugins/ColorfulMap/maps/{map_uuid}/x_y.png
|
* save images to plugins/ColorfulMap/maps/{map_uuid}/x_y.png
|
||||||
*
|
*
|
||||||
* @param player player
|
* @param player player
|
||||||
* @param images images
|
* @param images images
|
||||||
* @param x_count x_count
|
* @param x_count x_count
|
||||||
* @param y_count y_count
|
* @param y_count y_count
|
||||||
*/
|
*/
|
||||||
public static UUID save(Player player, List<BufferedImage> images, int x_count, int y_count) {
|
public static UUID save(Player player, BufferedImage raw, BufferedImage thumb,
|
||||||
|
List<BufferedImage> images, int x_count, int y_count) {
|
||||||
if (images.size() != x_count * y_count) {
|
if (images.size() != x_count * y_count) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
UUID map_uuid = UUID.randomUUID();
|
UUID map_uuid = UUID.randomUUID();
|
||||||
|
File map_folder = new File(data_folder, "maps/" + map_uuid);
|
||||||
|
if (!map_folder.exists()) {
|
||||||
|
if (!map_folder.mkdirs()) {
|
||||||
|
Notification.error(player, "Failed to save map: failed to create map folder");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File raw_file = new File(map_folder, "raw.png");
|
||||||
|
File thumb_file = new File(map_folder, "thumb.png");
|
||||||
|
try {
|
||||||
|
ImageIO.write(raw, "png", raw_file);
|
||||||
|
ImageIO.write(thumb, "png", thumb_file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Notification.error(player, "Failed to save map: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
for (int y = 0; y < y_count; y++) {
|
for (int y = 0; y < y_count; y++) {
|
||||||
for (int x = 0; x < x_count; x++) {
|
for (int x = 0; x < x_count; x++) {
|
||||||
BufferedImage image = images.get(y * x_count + x);
|
BufferedImage image = images.get(y * x_count + x);
|
||||||
File map_folder = new File(data_folder, "maps/" + map_uuid);
|
|
||||||
if (!map_folder.exists()) {
|
|
||||||
if (!map_folder.mkdirs()) {
|
|
||||||
Notification.error(player, "Failed to save map: failed to create map folder");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File image_file = new File(map_folder, x + "_" + y + ".png");
|
File image_file = new File(map_folder, x + "_" + y + ".png");
|
||||||
try {
|
try {
|
||||||
ImageIO.write(image, "png", image_file);
|
ImageIO.write(image, "png", image_file);
|
||||||
@ -48,13 +59,30 @@ public class StorageMaps {
|
|||||||
return map_uuid;
|
return map_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getThumbnailPath(UUID map_uuid) {
|
||||||
|
return "maps/" + map_uuid + "/thumb.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getImageTilePath(UUID map_uuid, int x, int y) {
|
||||||
|
return "maps/" + map_uuid + "/" + x + "_" + y + ".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeMeta(UUID map_uuid, List<String> info) {
|
||||||
|
File meta_file = new File(data_folder, "maps/" + map_uuid + "/meta.txt");
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.write(meta_file.toPath(), info);
|
||||||
|
} catch (Exception e) {
|
||||||
|
XLogger.err("写入地图画操作记录失败:%s", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load images from plugins/ColorfulMap/maps/{map_uuid}/x_y.png
|
* load images from plugins/ColorfulMap/maps/{map_uuid}/x_y.png
|
||||||
*
|
*
|
||||||
* @param player player
|
* @param player player
|
||||||
* @param map_uuid map_uuid
|
* @param map_uuid map_uuid
|
||||||
* @param x_count x_count
|
* @param x_count x_count
|
||||||
* @param y_count y_count
|
* @param y_count y_count
|
||||||
* @return images
|
* @return images
|
||||||
*/
|
*/
|
||||||
public static List<BufferedImage> load(Player player, UUID map_uuid, int x_count, int y_count) {
|
public static List<BufferedImage> load(Player player, UUID map_uuid, int x_count, int y_count) {
|
||||||
@ -82,4 +110,34 @@ public class StorageMaps {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void purgeStorageFolder() {
|
||||||
|
File[] map_folders = new File(data_folder, "maps").listFiles();
|
||||||
|
if (map_folders == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (File map_folder : map_folders) {
|
||||||
|
if (!map_folder.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
File[] files = map_folder.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> filenames = new ArrayList<>();
|
||||||
|
for (File file : files) {
|
||||||
|
filenames.add(file.getName());
|
||||||
|
}
|
||||||
|
if (filenames.isEmpty()) {
|
||||||
|
map_folder.delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!filenames.contains("meta.txt")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (files.length == 3 && filenames.contains("raw.png") && filenames.contains("thumb.png") && filenames.contains("meta.txt")) {
|
||||||
|
map_folder.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
49
src/main/java/cn/lunadeer/colorfulmap/commands/Purge.java
Normal file
49
src/main/java/cn/lunadeer/colorfulmap/commands/Purge.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.commands;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.ColorfulMap;
|
||||||
|
import cn.lunadeer.colorfulmap.MapManager;
|
||||||
|
import cn.lunadeer.colorfulmap.StorageMaps;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Item;
|
||||||
|
import org.bukkit.entity.ItemFrame;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.map.MapView;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Purge implements CommandExecutor {
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
ColorfulMap.instance.getServer().getWorlds().forEach(world -> {
|
||||||
|
List<Integer> worldMapIds = new ArrayList<>();
|
||||||
|
world.getEntities().forEach(entity -> {
|
||||||
|
if (!(entity instanceof org.bukkit.entity.ItemFrame)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ItemFrame itemFrame = (ItemFrame) entity;
|
||||||
|
if (!itemFrame.getItem().getType().equals(org.bukkit.Material.FILLED_MAP)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ItemStack item = itemFrame.getItem();
|
||||||
|
MapView mapView = ((org.bukkit.inventory.meta.MapMeta) item.getItemMeta()).getMapView();
|
||||||
|
if (mapView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worldMapIds.add(mapView.getId());
|
||||||
|
});
|
||||||
|
List<Integer> currentIds = MapManager.instance.getMapIds(world);
|
||||||
|
for (Integer id : currentIds) {
|
||||||
|
if (!worldMapIds.contains(id)) {
|
||||||
|
MapManager.instance.removeMap(world, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
StorageMaps.purgeStorageFolder();
|
||||||
|
MapManager.instance.reloadImages();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
19
src/main/java/cn/lunadeer/colorfulmap/commands/Reload.java
Normal file
19
src/main/java/cn/lunadeer/colorfulmap/commands/Reload.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.commands;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.ColorfulMap;
|
||||||
|
import cn.lunadeer.colorfulmap.MapManager;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.Notification;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandExecutor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class Reload implements CommandExecutor {
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
ColorfulMap.config.reload();
|
||||||
|
MapManager.instance.reloadImages();
|
||||||
|
Notification.info(sender, "重载成功");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package cn.lunadeer.colorfulmap.commands;
|
package cn.lunadeer.colorfulmap.commands;
|
||||||
|
|
||||||
import cn.lunadeer.colorfulmap.ColorfulMap;
|
|
||||||
import cn.lunadeer.colorfulmap.generator.Multi;
|
import cn.lunadeer.colorfulmap.generator.Multi;
|
||||||
import cn.lunadeer.colorfulmap.utils.Notification;
|
import cn.lunadeer.colorfulmap.utils.Notification;
|
||||||
import cn.lunadeer.colorfulmap.utils.Time;
|
import cn.lunadeer.colorfulmap.utils.Scheduler;
|
||||||
import cn.lunadeer.colorfulmap.utils.XLogger;
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
@ -46,14 +45,8 @@ public class ToMap implements CommandExecutor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ItemStack mapImage = Multi.generate(player, url, scale);
|
|
||||||
// if (mapImage == null){
|
|
||||||
// Notification.error(player, "生成地图失败");
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// player.getInventory().addItem(mapImage);
|
|
||||||
float finalScale = scale;
|
float finalScale = scale;
|
||||||
Time.runAsync(ColorfulMap.instance, () -> {
|
Scheduler.runTaskAsync(() -> {
|
||||||
ItemStack mapImage = Multi.generate(player, url, finalScale);
|
ItemStack mapImage = Multi.generate(player, url, finalScale);
|
||||||
if (mapImage == null) {
|
if (mapImage == null) {
|
||||||
Notification.error(player, "生成地图失败");
|
Notification.error(player, "生成地图失败");
|
||||||
|
@ -2,7 +2,10 @@ package cn.lunadeer.colorfulmap.generator;
|
|||||||
|
|
||||||
import cn.lunadeer.colorfulmap.ColorfulMap;
|
import cn.lunadeer.colorfulmap.ColorfulMap;
|
||||||
import cn.lunadeer.colorfulmap.StorageMaps;
|
import cn.lunadeer.colorfulmap.StorageMaps;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.ImageTool;
|
||||||
import cn.lunadeer.colorfulmap.utils.Notification;
|
import cn.lunadeer.colorfulmap.utils.Notification;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.UrlTools;
|
||||||
|
import cn.lunadeer.colorfulmap.utils.VaultConnect.VaultConnect;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
@ -15,25 +18,31 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static cn.lunadeer.colorfulmap.generator.TextRenderer.applyTextToMap;
|
import static cn.lunadeer.colorfulmap.StorageMaps.getThumbnailPath;
|
||||||
|
import static cn.lunadeer.colorfulmap.generator.ImageRenderer.getMapItemFromImageTile;
|
||||||
|
|
||||||
public class Multi {
|
public class Multi {
|
||||||
|
|
||||||
public static ItemStack generate(Player player, String url, Float scale) {
|
public static ItemStack generate(Player player, String url, Float scale) {
|
||||||
try {
|
try {
|
||||||
URL _url = new URL(url);
|
if (!UrlTools.isInWhiteList(url)) {
|
||||||
BufferedImage image = ImageIO.read(_url);
|
Notification.error(player, "无法生成地图画: 此图床地址不被允许使用");
|
||||||
if (scale != 1.0) {
|
return null;
|
||||||
int width = image.getWidth();
|
|
||||||
int height = image.getHeight();
|
|
||||||
int new_width = (int) (width * scale);
|
|
||||||
int new_height = (int) (height * scale);
|
|
||||||
BufferedImage newImage = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
newImage.getGraphics().drawImage(image, 0, 0, new_width, new_height, null);
|
|
||||||
image = newImage;
|
|
||||||
}
|
}
|
||||||
int image_width = image.getWidth();
|
URL _url = new URL(url);
|
||||||
int image_height = image.getHeight();
|
BufferedImage raw_image = ImageIO.read(_url);
|
||||||
|
if (raw_image == null) {
|
||||||
|
Notification.error(player, "无法读取有效图片,请检查图片地址是否正确或者更换图床");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BufferedImage resized_image;
|
||||||
|
if (scale != 1.0) {
|
||||||
|
resized_image = ImageTool.resize(raw_image, scale);
|
||||||
|
} else {
|
||||||
|
resized_image = raw_image;
|
||||||
|
}
|
||||||
|
int image_width = resized_image.getWidth();
|
||||||
|
int image_height = resized_image.getHeight();
|
||||||
int x_count = (int) Math.ceil(image_width / 128.0);
|
int x_count = (int) Math.ceil(image_width / 128.0);
|
||||||
int y_count = (int) Math.ceil(image_height / 128.0);
|
int y_count = (int) Math.ceil(image_height / 128.0);
|
||||||
if (x_count > ColorfulMap.config.getMaxFrameX() || y_count > ColorfulMap.config.getMaxFrameY()) {
|
if (x_count > ColorfulMap.config.getMaxFrameX() || y_count > ColorfulMap.config.getMaxFrameY()) {
|
||||||
@ -42,17 +51,15 @@ public class Multi {
|
|||||||
}
|
}
|
||||||
int new_width = x_count * 128;
|
int new_width = x_count * 128;
|
||||||
int new_height = y_count * 128;
|
int new_height = y_count * 128;
|
||||||
BufferedImage newImage = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_ARGB);
|
resized_image = ImageTool.center(resized_image, new_width, new_height);
|
||||||
newImage.getGraphics().drawImage(image, (new_width - image_width) / 2, (new_height - image_height) / 2, null);
|
image_width = resized_image.getWidth();
|
||||||
image = newImage;
|
image_height = resized_image.getHeight();
|
||||||
image_width = image.getWidth();
|
|
||||||
image_height = image.getHeight();
|
|
||||||
List<BufferedImage> split_images = new ArrayList<>();
|
List<BufferedImage> split_images = new ArrayList<>();
|
||||||
for (int y = 0; y < y_count; y++) {
|
for (int y = 0; y < y_count; y++) {
|
||||||
for (int x = 0; x < x_count; x++) {
|
for (int x = 0; x < x_count; x++) {
|
||||||
int width = Math.min(128, image_width - x * 128);
|
int width = Math.min(128, image_width - x * 128);
|
||||||
int height = Math.min(128, image_height - y * 128);
|
int height = Math.min(128, image_height - y * 128);
|
||||||
BufferedImage sub_image = image.getSubimage(x * 128, y * 128, width, height);
|
BufferedImage sub_image = resized_image.getSubimage(x * 128, y * 128, width, height);
|
||||||
split_images.add(sub_image);
|
split_images.add(sub_image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,20 +67,21 @@ public class Multi {
|
|||||||
Notification.error(player, "无法生成地图画: 图片为空");
|
Notification.error(player, "无法生成地图画: 图片为空");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
UUID map_images_uuid = StorageMaps.save(player, split_images, x_count, y_count);
|
|
||||||
|
UUID map_images_uuid = StorageMaps.save(player, raw_image, ImageTool.thumb(raw_image), split_images, x_count, y_count);
|
||||||
if (map_images_uuid == null) {
|
if (map_images_uuid == null) {
|
||||||
Notification.error(player, "无法生成地图画: 无法保存图片");
|
Notification.error(player, "无法生成地图画: 无法保存图片");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<String> map_info = new ArrayList<>();
|
ItemStack mapItem = getMapItemFromImageTile(player, getThumbnailPath(map_images_uuid));
|
||||||
map_info.add("size: " + x_count + "x" + y_count);
|
if (mapItem == null) {
|
||||||
ItemStack mapItem = applyTextToMap(player, map_info);
|
Notification.error(player, "无法生成地图画: 无法生成地图");
|
||||||
// add lore to map item (uuid, x_count, y_count)
|
return null;
|
||||||
|
}
|
||||||
MapMeta meta = (MapMeta) mapItem.getItemMeta();
|
MapMeta meta = (MapMeta) mapItem.getItemMeta();
|
||||||
List<Component> lore = new ArrayList<>();
|
List<Component> lore = new ArrayList<>();
|
||||||
lore.add(Component.text(map_images_uuid.toString()));
|
String size_info = "size: " + x_count + "x" + y_count;
|
||||||
lore.add(Component.text(String.valueOf(x_count)));
|
lore.add(Component.text(size_info).hoverEvent(Component.text(map_images_uuid.toString())));
|
||||||
lore.add(Component.text(String.valueOf(y_count)));
|
|
||||||
meta.lore(lore);
|
meta.lore(lore);
|
||||||
mapItem.setItemMeta(meta);
|
mapItem.setItemMeta(meta);
|
||||||
return mapItem;
|
return mapItem;
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
package cn.lunadeer.colorfulmap.generator;
|
|
||||||
|
|
||||||
import cn.lunadeer.colorfulmap.utils.Notification;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.bukkit.inventory.meta.MapMeta;
|
|
||||||
import org.bukkit.map.MapCanvas;
|
|
||||||
import org.bukkit.map.MapRenderer;
|
|
||||||
import org.bukkit.map.MapView;
|
|
||||||
import org.bukkit.map.MinecraftFont;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TextRenderer extends MapRenderer {
|
|
||||||
|
|
||||||
public TextRenderer(List<String> text) {
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<String> text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render to the given map.
|
|
||||||
*
|
|
||||||
* @param map The MapView being rendered to.
|
|
||||||
* @param canvas The canvas to use for rendering.
|
|
||||||
* @param player The player who triggered the rendering.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player) {
|
|
||||||
try {
|
|
||||||
int y = 0;
|
|
||||||
for (String line : text) {
|
|
||||||
canvas.drawText(0, y, MinecraftFont.Font, line);
|
|
||||||
y += 10;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Notification.error(player, "Failed to render text: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ItemStack applyTextToMap(Player player, List<String> text){
|
|
||||||
ItemStack mapItem = new ItemStack(Material.FILLED_MAP, 1);
|
|
||||||
MapMeta meta = (MapMeta) mapItem.getItemMeta();
|
|
||||||
MapView mapView = Bukkit.createMap(player.getWorld());
|
|
||||||
TextRenderer renderer = new TextRenderer(text);
|
|
||||||
mapView.addRenderer(renderer);
|
|
||||||
meta.setMapView(mapView);
|
|
||||||
mapItem.setItemMeta(meta);
|
|
||||||
return mapItem;
|
|
||||||
}
|
|
||||||
}
|
|
14
src/main/java/cn/lunadeer/colorfulmap/utils/Common.java
Normal file
14
src/main/java/cn/lunadeer/colorfulmap/utils/Common.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
public class Common {
|
||||||
|
|
||||||
|
public static boolean isPaper() {
|
||||||
|
try {
|
||||||
|
Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask");
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,222 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.event.ClickEvent;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.json.simple.JSONArray;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
import org.json.simple.parser.JSONParser;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class GiteaReleaseCheck implements Listener {
|
||||||
|
private static class GiteaRelease {
|
||||||
|
public String tag_name;
|
||||||
|
public String message;
|
||||||
|
public String html_url;
|
||||||
|
public String download_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GiteaReleaseCheck instance = null;
|
||||||
|
|
||||||
|
public GiteaReleaseCheck(JavaPlugin plugin, String giteaServer, String owner, String repo) {
|
||||||
|
instance = this;
|
||||||
|
this.gitea_server = giteaServer;
|
||||||
|
this.owner = owner;
|
||||||
|
this.repo = repo;
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.current_version = plugin.getDescription().getVersion();
|
||||||
|
this.plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
// 异步每12小时检查一次更新
|
||||||
|
Scheduler.runTaskRepeatAsync(() -> {
|
||||||
|
getLatestRelease();
|
||||||
|
if (auto_update) {
|
||||||
|
downloadUpdate();
|
||||||
|
}
|
||||||
|
}, (10 + new Random().nextInt(10)) * 20, (60 * 60 * 12 + new Random().nextInt(60)) * 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableAutoUpdate() {
|
||||||
|
auto_update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String repoReleases() {
|
||||||
|
return gitea_server + "/api/v1/repos/" + owner + "/" + repo + "/releases";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tag(String tagName) {
|
||||||
|
return gitea_server + "/api/v1/repos/" + owner + "/" + repo + "/tags/" + tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getLatestRelease() {
|
||||||
|
XLogger.info("================================");
|
||||||
|
XLogger.info("正在检查更新...");
|
||||||
|
// send get request to repoReleases()
|
||||||
|
try {
|
||||||
|
// 发送 GET 请求
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) new URL(repoReleases()).openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
connection.connect();
|
||||||
|
// 获取响应
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
try (BufferedReader bufferedReader =
|
||||||
|
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = bufferedReader.readLine()) != null) {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSONArray releaseList = (JSONArray) new JSONParser().parse(builder.toString());
|
||||||
|
JSONObject latestRelease = (JSONObject) releaseList.get(0);
|
||||||
|
GiteaRelease release = new GiteaRelease();
|
||||||
|
release.tag_name = (String) latestRelease.get("tag_name");
|
||||||
|
release.message = (String) latestRelease.get("body");
|
||||||
|
release.html_url = (String) latestRelease.get("html_url");
|
||||||
|
JSONArray assets = (JSONArray) latestRelease.get("assets");
|
||||||
|
if (assets.size() > 0) {
|
||||||
|
JSONObject asset = (JSONObject) assets.get(0);
|
||||||
|
release.download_url = (String) asset.get("browser_download_url");
|
||||||
|
}
|
||||||
|
latest_release = release;
|
||||||
|
XLogger.debug("Latest release: " + latest_release.tag_name);
|
||||||
|
XLogger.debug("Message: " + latest_release.message);
|
||||||
|
XLogger.debug("Download URL: " + latest_release.download_url);
|
||||||
|
XLogger.debug("HTML URL: " + latest_release.html_url);
|
||||||
|
if (isNewVersion(current_version, latest_release.tag_name)) {
|
||||||
|
XLogger.info("发现新版本:" + latest_release.tag_name);
|
||||||
|
XLogger.info("版本信息:");
|
||||||
|
String[] message = latest_release.message.split("\n");
|
||||||
|
for (String line : message) {
|
||||||
|
XLogger.info("\t" + line);
|
||||||
|
}
|
||||||
|
XLogger.info("下载页面:" + latest_release.html_url);
|
||||||
|
} else {
|
||||||
|
XLogger.info("当前已是最新版本:" + current_version);
|
||||||
|
}
|
||||||
|
XLogger.info("================================");
|
||||||
|
} catch (Exception e) {
|
||||||
|
XLogger.err("Failed to get latest release: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTagMessage(String tagName) {
|
||||||
|
try {
|
||||||
|
// 发送 GET 请求
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) new URL(tag(tagName)).openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
connection.connect();
|
||||||
|
// 获取响应
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
try (BufferedReader bufferedReader =
|
||||||
|
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = bufferedReader.readLine()) != null) {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSONObject tag = (JSONObject) new JSONParser().parse(builder.toString());
|
||||||
|
return (String) tag.get("message");
|
||||||
|
} catch (Exception e) {
|
||||||
|
XLogger.debug("Failed to get tag message: " + e.getMessage());
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadUpdate() {
|
||||||
|
if (latest_release == null) {
|
||||||
|
getLatestRelease();
|
||||||
|
if (latest_release == null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isNewVersion(current_version, latest_release.tag_name)) {
|
||||||
|
XLogger.info("当前已是最新版本");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (latest_release.download_url == null) {
|
||||||
|
XLogger.err("下载地址不可用");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
XLogger.info("================================");
|
||||||
|
XLogger.info("正在下载更新...");
|
||||||
|
File pluginsFolder = plugin.getDataFolder().getParentFile();
|
||||||
|
File newJarFile = new File(pluginsFolder, latest_release.download_url.substring(latest_release.download_url.lastIndexOf("/") + 1));
|
||||||
|
// send get request to download_url
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) new URL(latest_release.download_url).openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
connection.connect();
|
||||||
|
// 获取响应写入文件到 newJarFile
|
||||||
|
try (DataOutputStream outputStream = new DataOutputStream(Files.newOutputStream(newJarFile.toPath()))) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = connection.getInputStream().read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XLogger.info("更新下载完成");
|
||||||
|
XLogger.info("新版本:" + latest_release.tag_name);
|
||||||
|
XLogger.info("请删除旧版本插件,然后重启服务器。");
|
||||||
|
XLogger.info("================================");
|
||||||
|
} catch (Exception e) {
|
||||||
|
XLogger.err("Failed to auto update: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
private void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
if (!event.getPlayer().isOp()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
if (latest_release != null && isNewVersion(current_version, latest_release.tag_name)) {
|
||||||
|
Notification.info(player, "发现新版本:" + latest_release.tag_name + " 详细内容请查看控制台或点击下方链接");
|
||||||
|
Component download = Component.text(latest_release.html_url).clickEvent(ClickEvent.openUrl(latest_release.html_url)).hoverEvent(Component.text("点击打开"));
|
||||||
|
Notification.info(player,download);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String gitea_server;
|
||||||
|
private final String owner;
|
||||||
|
private final String repo;
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private final String current_version;
|
||||||
|
private GiteaRelease latest_release = null;
|
||||||
|
private boolean auto_update = false;
|
||||||
|
|
||||||
|
private boolean isNewVersion(String current, String in_coming) {
|
||||||
|
// 只保留数字和点号
|
||||||
|
current = current.replaceAll("[^0-9.]", "");
|
||||||
|
in_coming = in_coming.replaceAll("[^0-9.]", "");
|
||||||
|
XLogger.debug("Current version: " + current);
|
||||||
|
XLogger.debug("In-coming version: " + in_coming);
|
||||||
|
String[] current_version = current.split("\\.");
|
||||||
|
String[] in_coming_version = in_coming.split("\\.");
|
||||||
|
for (int i = 0; i < Math.min(current_version.length, in_coming_version.length); i++) {
|
||||||
|
int current_v = Integer.parseInt(current_version[i]);
|
||||||
|
int in_coming_v = Integer.parseInt(in_coming_version[i]);
|
||||||
|
if (current_v < in_coming_v) {
|
||||||
|
return true;
|
||||||
|
} else if (current_v > in_coming_v) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current_version.length < in_coming_version.length;
|
||||||
|
}
|
||||||
|
}
|
33
src/main/java/cn/lunadeer/colorfulmap/utils/ImageTool.java
Normal file
33
src/main/java/cn/lunadeer/colorfulmap/utils/ImageTool.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
public class ImageTool {
|
||||||
|
|
||||||
|
public static BufferedImage resize(BufferedImage image, float scale) {
|
||||||
|
int new_width = (int) (image.getWidth() * scale);
|
||||||
|
int new_height = (int) (image.getHeight() * scale);
|
||||||
|
BufferedImage newImage = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
newImage.getGraphics().drawImage(image, 0, 0, new_width, new_height, null);
|
||||||
|
return newImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedImage center(BufferedImage image, int width, int height) {
|
||||||
|
int image_width = image.getWidth();
|
||||||
|
int image_height = image.getHeight();
|
||||||
|
BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
newImage.getGraphics().drawImage(image, (width - image_width) / 2, (height - image_height) / 2, null);
|
||||||
|
return newImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedImage thumb(BufferedImage img) {
|
||||||
|
double scale;
|
||||||
|
if (img.getWidth() > img.getHeight()) {
|
||||||
|
scale = 128.0 / img.getWidth();
|
||||||
|
} else {
|
||||||
|
scale = 128.0 / img.getHeight();
|
||||||
|
}
|
||||||
|
return center(resize(img, (float) scale), 128, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,59 +5,125 @@ import net.kyori.adventure.text.format.Style;
|
|||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
public class Notification {
|
public class Notification {
|
||||||
|
public static Notification instance;
|
||||||
|
|
||||||
|
public final SendMessageAbstract sender;
|
||||||
|
|
||||||
|
public Notification(JavaPlugin plugin) {
|
||||||
|
instance = this;
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.prefix = "[" + plugin.getName() + "]";
|
||||||
|
this.sender = new SendMessageAbstract(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
private static final Style i_style = Style.style(TextColor.color(139, 255, 123));
|
private static final Style i_style = Style.style(TextColor.color(139, 255, 123));
|
||||||
private static final Style w_style = Style.style(TextColor.color(255, 185, 69));
|
private static final Style w_style = Style.style(TextColor.color(255, 185, 69));
|
||||||
private static final Style e_style = Style.style(TextColor.color(255, 96, 72));
|
private static final Style e_style = Style.style(TextColor.color(255, 96, 72));
|
||||||
|
private static final Style d_style = Style.style(TextColor.color(0, 255, 255));
|
||||||
|
|
||||||
private static final String prefix = "[ColorfulMap] ";
|
private String prefix;
|
||||||
|
private JavaPlugin plugin;
|
||||||
|
|
||||||
|
public void setPrefix(String prefix) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
public static void info(Player player, String msg) {
|
public static void info(Player player, String msg) {
|
||||||
player.sendMessage(Component.text(prefix + msg, i_style));
|
instance.sender.sendMessage(player, Component.text(instance.prefix + " " + msg, i_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(Player player, String msg, Object... args) {
|
||||||
|
instance.sender.sendMessage(player, Component.text(instance.prefix + " " + String.format(msg, args), i_style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warn(Player player, String msg) {
|
public static void warn(Player player, String msg) {
|
||||||
player.sendMessage(Component.text(prefix + msg, w_style));
|
instance.sender.sendMessage(player, Component.text(instance.prefix + " " + msg, w_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void warn(Player player, String msg, Object... args) {
|
||||||
|
instance.sender.sendMessage(player, Component.text(instance.prefix + " " + String.format(msg, args), w_style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error(Player player, String msg) {
|
public static void error(Player player, String msg) {
|
||||||
player.sendMessage(Component.text(prefix + msg, e_style));
|
instance.sender.sendMessage(player, Component.text(instance.prefix + " " + msg, e_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void error(Player player, String msg, Object... args) {
|
||||||
|
instance.sender.sendMessage(player, Component.text(instance.prefix + " " + String.format(msg, args), e_style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void info(CommandSender sender, String msg) {
|
public static void info(CommandSender sender, String msg) {
|
||||||
sender.sendMessage(Component.text(prefix + msg, i_style));
|
instance.sender.sendMessage(sender, Component.text(instance.prefix + " " + msg, i_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(CommandSender sender, String msg, Object... args) {
|
||||||
|
instance.sender.sendMessage(sender, Component.text(instance.prefix + " " + String.format(msg, args), i_style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warn(CommandSender sender, String msg) {
|
public static void warn(CommandSender sender, String msg) {
|
||||||
sender.sendMessage(Component.text(prefix + msg, w_style));
|
instance.sender.sendMessage(sender, Component.text(instance.prefix + " " + msg, w_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void warn(CommandSender sender, String msg, Object... args) {
|
||||||
|
instance.sender.sendMessage(sender, Component.text(instance.prefix + " " + String.format(msg, args), w_style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error(CommandSender sender, String msg) {
|
public static void error(CommandSender sender, String msg) {
|
||||||
sender.sendMessage(Component.text(prefix + msg, e_style));
|
instance.sender.sendMessage(sender, Component.text(instance.prefix + " " + msg, e_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void error(CommandSender sender, String msg, Object... args) {
|
||||||
|
instance.sender.sendMessage(sender, Component.text(instance.prefix + " " + String.format(msg, args), e_style));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void info(Player player, Component msg) {
|
public static void info(Player player, Component msg) {
|
||||||
player.sendMessage(Component.text(prefix, i_style).append(msg));
|
instance.sender.sendMessage(player, Component.text(instance.prefix, i_style).append(Component.text(" ")).append(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warn(Player player, Component msg) {
|
public static void warn(Player player, Component msg) {
|
||||||
player.sendMessage(Component.text(prefix, w_style).append(msg));
|
instance.sender.sendMessage(player, Component.text(instance.prefix, w_style).append(Component.text(" ")).append(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error(Player player, Component msg) {
|
public static void error(Player player, Component msg) {
|
||||||
player.sendMessage(Component.text(prefix, e_style).append(msg));
|
instance.sender.sendMessage(player, Component.text(instance.prefix, e_style).append(Component.text(" ")).append(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void info(CommandSender player, Component msg) {
|
public static void info(CommandSender player, Component msg) {
|
||||||
player.sendMessage(Component.text(prefix, i_style).append(msg));
|
instance.sender.sendMessage(player, Component.text(instance.prefix, i_style).append(Component.text(" ")).append(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warn(CommandSender player, Component msg) {
|
public static void warn(CommandSender player, Component msg) {
|
||||||
player.sendMessage(Component.text(prefix, w_style).append(msg));
|
instance.sender.sendMessage(player, Component.text(instance.prefix, w_style).append(Component.text(" ")).append(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error(CommandSender player, Component msg) {
|
public static void error(CommandSender player, Component msg) {
|
||||||
player.sendMessage(Component.text(prefix, e_style).append(msg));
|
instance.sender.sendMessage(player, Component.text(instance.prefix, e_style).append(Component.text(" ")).append(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void all(String msg) {
|
||||||
|
instance.sender.broadcast(Component.text(instance.prefix + " " + msg, i_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void all(Component msg) {
|
||||||
|
instance.sender.broadcast(Component.text(instance.prefix, i_style).append(Component.text(" ")).append(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void all(String msg, Object... args) {
|
||||||
|
instance.sender.broadcast(Component.text(instance.prefix + " " + String.format(msg, args), i_style));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void actionBar(Player player, String msg) {
|
||||||
|
instance.sender.sendActionBar(player, Component.text(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void actionBar(Player player, String msg, Object... args) {
|
||||||
|
instance.sender.sendActionBar(player, Component.text(String.format(msg, args)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void actionBar(Player player, Component msg) {
|
||||||
|
instance.sender.sendActionBar(player, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
58
src/main/java/cn/lunadeer/colorfulmap/utils/Scheduler.java
Normal file
58
src/main/java/cn/lunadeer/colorfulmap/utils/Scheduler.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static cn.lunadeer.colorfulmap.utils.Common.isPaper;
|
||||||
|
|
||||||
|
public class Scheduler {
|
||||||
|
public static Scheduler instance;
|
||||||
|
private final JavaPlugin plugin;
|
||||||
|
private boolean isPaper = false;
|
||||||
|
|
||||||
|
public Scheduler(JavaPlugin plugin) {
|
||||||
|
instance = this;
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.isPaper = isPaper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void cancelAll() {
|
||||||
|
if (instance.isPaper) {
|
||||||
|
instance.plugin.getServer().getGlobalRegionScheduler().cancelTasks(instance.plugin);
|
||||||
|
instance.plugin.getServer().getGlobalRegionScheduler().cancelTasks(instance.plugin);
|
||||||
|
} else {
|
||||||
|
instance.plugin.getServer().getScheduler().cancelTasks(instance.plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task asynchronously
|
||||||
|
*
|
||||||
|
* @param task The task to run
|
||||||
|
* @return The scheduled task
|
||||||
|
*/
|
||||||
|
public static void runTaskAsync(Runnable task) {
|
||||||
|
if (instance.isPaper) {
|
||||||
|
instance.plugin.getServer().getAsyncScheduler().runNow(instance.plugin, (plugin) -> task.run());
|
||||||
|
} else {
|
||||||
|
instance.plugin.getServer().getScheduler().runTask(instance.plugin, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task repeatedly asynchronously
|
||||||
|
*
|
||||||
|
* @param task The task to run
|
||||||
|
* @param delay The delay in milliseconds
|
||||||
|
* @param period The period in milliseconds
|
||||||
|
* @return The scheduled task
|
||||||
|
*/
|
||||||
|
public static void runTaskRepeatAsync(Runnable task, long delay, long period) {
|
||||||
|
if (instance.isPaper) {
|
||||||
|
instance.plugin.getServer().getAsyncScheduler().runAtFixedRate(instance.plugin, (plugin) -> task.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS);
|
||||||
|
} else {
|
||||||
|
instance.plugin.getServer().getScheduler().runTaskTimerAsynchronously(instance.plugin, task, delay, period);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class SendMessageAbstract {
|
||||||
|
|
||||||
|
private BukkitAudiences adventure = null;
|
||||||
|
private JavaPlugin plugin;
|
||||||
|
|
||||||
|
public SendMessageAbstract(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
if (!Common.isPaper()) {
|
||||||
|
this.adventure = BukkitAudiences.create(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(Player player, Component msg) {
|
||||||
|
if (this.adventure == null) {
|
||||||
|
player.sendMessage(msg);
|
||||||
|
} else {
|
||||||
|
this.adventure.player(player).sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendMessage(CommandSender sender, Component msg) {
|
||||||
|
if (this.adventure == null) {
|
||||||
|
sender.sendMessage(msg);
|
||||||
|
} else {
|
||||||
|
this.adventure.sender(sender).sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcast(Component msg) {
|
||||||
|
if (this.adventure == null) {
|
||||||
|
plugin.getServer().broadcast(msg);
|
||||||
|
} else {
|
||||||
|
adventure.all().sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendActionBar(Player player, Component msg) {
|
||||||
|
if (this.adventure == null) {
|
||||||
|
player.sendActionBar(msg);
|
||||||
|
} else {
|
||||||
|
this.adventure.player(player).sendActionBar(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,67 +0,0 @@
|
|||||||
package cn.lunadeer.colorfulmap.utils;
|
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class Time {
|
|
||||||
|
|
||||||
public static String nowStr() {
|
|
||||||
// yyyy-MM-dd HH:mm:ss
|
|
||||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 尝试获取folia的调度器
|
|
||||||
*
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
private static boolean tryFolia() {
|
|
||||||
try {
|
|
||||||
Bukkit.getAsyncScheduler();
|
|
||||||
return true;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Boolean IS_FOLIA = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断是否是folia核心
|
|
||||||
*
|
|
||||||
* @return 是否是folia核心
|
|
||||||
*/
|
|
||||||
public static Boolean isFolia() {
|
|
||||||
if (IS_FOLIA == null) IS_FOLIA = tryFolia();
|
|
||||||
return IS_FOLIA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定时异步任务
|
|
||||||
*
|
|
||||||
* @param plugin 插件
|
|
||||||
* @param runnable 任务
|
|
||||||
* @param ticks 间隔
|
|
||||||
*/
|
|
||||||
public static void runAtFixedRateAsync(Plugin plugin, Runnable runnable, int ticks) {
|
|
||||||
if (isFolia())
|
|
||||||
Bukkit.getAsyncScheduler().runAtFixedRate(plugin, (task) -> runnable.run(), ticks / 20, ticks / 20, TimeUnit.SECONDS);
|
|
||||||
else Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, ticks, ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void runLater(Plugin plugin, Runnable runnable, int ticks) {
|
|
||||||
if (isFolia())
|
|
||||||
Bukkit.getAsyncScheduler().runDelayed(plugin, (task) -> runnable.run(), ticks / 20, TimeUnit.SECONDS);
|
|
||||||
else Bukkit.getScheduler().runTaskLater(plugin, runnable, ticks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void runAsync(Plugin plugin, Runnable runnable) {
|
|
||||||
if (isFolia())
|
|
||||||
Bukkit.getAsyncScheduler().runNow(plugin, (task) -> runnable.run());
|
|
||||||
else Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable);
|
|
||||||
}
|
|
||||||
}
|
|
25
src/main/java/cn/lunadeer/colorfulmap/utils/UrlTools.java
Normal file
25
src/main/java/cn/lunadeer/colorfulmap/utils/UrlTools.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.ColorfulMap;
|
||||||
|
|
||||||
|
public class UrlTools {
|
||||||
|
|
||||||
|
public static boolean isInWhiteList(String url) {
|
||||||
|
if (ColorfulMap.config.getAddressWhiteList().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (String whiteUrl : ColorfulMap.config.getAddressWhiteList()) {
|
||||||
|
if (url.startsWith(whiteUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (url.startsWith("http://" + whiteUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (url.startsWith("https://" + whiteUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils.VaultConnect;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
|
import net.milkbowl.vault.economy.Economy;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class Vault implements VaultInterface {
|
||||||
|
|
||||||
|
private Economy econ = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean init(JavaPlugin plugin) {
|
||||||
|
RegisteredServiceProvider<Economy> rsp = plugin.getServer().getServicesManager().getRegistration(Economy.class);
|
||||||
|
if (rsp != null) {
|
||||||
|
econ = rsp.getProvider();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
XLogger.err("Vault 不可用");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String currencyNamePlural() {
|
||||||
|
return econ.currencyNamePlural();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String currencyNameSingular() {
|
||||||
|
return econ.currencyNameSingular();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void withdrawPlayer(Player player, double amount) {
|
||||||
|
econ.withdrawPlayer(player, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void depositPlayer(Player player, double amount) {
|
||||||
|
econ.depositPlayer(player, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getBalance(Player player) {
|
||||||
|
return econ.getBalance(player);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils.VaultConnect;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
|
import net.milkbowl.vault2.economy.Economy;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public class Vault2 implements VaultInterface {
|
||||||
|
|
||||||
|
private Economy econ = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean init(JavaPlugin plugin) {
|
||||||
|
RegisteredServiceProvider<Economy> rsp = plugin.getServer().getServicesManager().getRegistration(Economy.class);
|
||||||
|
if (rsp != null) {
|
||||||
|
econ = rsp.getProvider();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
XLogger.err("VaultUnlocked 不可用");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String currencyNamePlural() {
|
||||||
|
return econ.defaultCurrencyNamePlural();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String currencyNameSingular() {
|
||||||
|
return econ.defaultCurrencyNameSingular();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void withdrawPlayer(Player player, double amount) {
|
||||||
|
econ.withdraw("MPU", player.getUniqueId(), BigDecimal.valueOf(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void depositPlayer(Player player, double amount) {
|
||||||
|
econ.deposit("MPU", player.getUniqueId(), BigDecimal.valueOf(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getBalance(Player player) {
|
||||||
|
return econ.getBalance("MPU", player.getUniqueId()).doubleValue();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils.VaultConnect;
|
||||||
|
|
||||||
|
import cn.lunadeer.colorfulmap.utils.XLogger;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.server.ServiceRegisterEvent;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public class VaultConnect implements Listener {
|
||||||
|
|
||||||
|
public static VaultConnect instance;
|
||||||
|
private VaultInterface vaultInstance = null;
|
||||||
|
private JavaPlugin plugin;
|
||||||
|
|
||||||
|
public VaultConnect(JavaPlugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
instance = this;
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGHEST)
|
||||||
|
public void onEnable(ServiceRegisterEvent event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean economyAvailable() {
|
||||||
|
if (vaultInstance == null) {
|
||||||
|
if (foundClass("net.milkbowl.vault.economy.Economy")) {
|
||||||
|
vaultInstance = new Vault();
|
||||||
|
} else if (foundClass("net.milkbowl.vault2.economy.Economy")) {
|
||||||
|
vaultInstance = new Vault2();
|
||||||
|
} else {
|
||||||
|
XLogger.err("没有可用的经济插件");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!vaultInstance.init(plugin)) {
|
||||||
|
vaultInstance = null;
|
||||||
|
XLogger.err("没有可用的经济插件");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String currencyNamePlural() {
|
||||||
|
if (economyAvailable()) {
|
||||||
|
return vaultInstance.currencyNamePlural();
|
||||||
|
}
|
||||||
|
XLogger.warn("没有可用的经济插件");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String currencyNameSingular() {
|
||||||
|
if (economyAvailable()) {
|
||||||
|
return vaultInstance.currencyNameSingular();
|
||||||
|
}
|
||||||
|
XLogger.warn("没有可用的经济插件");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void withdrawPlayer(Player player, double amount) {
|
||||||
|
if (economyAvailable()) {
|
||||||
|
vaultInstance.withdrawPlayer(player, amount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XLogger.warn("没有可用的经济插件");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void depositPlayer(Player player, double amount) {
|
||||||
|
if (economyAvailable()) {
|
||||||
|
vaultInstance.depositPlayer(player, amount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XLogger.warn("没有可用的经济插件");
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getBalance(Player player) {
|
||||||
|
if (economyAvailable()) {
|
||||||
|
return vaultInstance.getBalance(player);
|
||||||
|
}
|
||||||
|
XLogger.warn("没有可用的经济插件");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean foundClass(String className) {
|
||||||
|
try {
|
||||||
|
Class.forName(className);
|
||||||
|
return true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils.VaultConnect;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
public interface VaultInterface {
|
||||||
|
|
||||||
|
public boolean init(JavaPlugin plugin);
|
||||||
|
|
||||||
|
public String currencyNamePlural();
|
||||||
|
|
||||||
|
|
||||||
|
public String currencyNameSingular();
|
||||||
|
|
||||||
|
public void withdrawPlayer(Player player, double amount);
|
||||||
|
|
||||||
|
public void depositPlayer(Player player, double amount);
|
||||||
|
|
||||||
|
public double getBalance(Player player);
|
||||||
|
|
||||||
|
}
|
@ -1,56 +1,62 @@
|
|||||||
package cn.lunadeer.colorfulmap.utils;
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
import cn.lunadeer.colorfulmap.ColorfulMap;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.bukkit.entity.Player;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class XLogger {
|
public class XLogger {
|
||||||
private static final ColorfulMap _plugin = ColorfulMap.instance;
|
public static XLogger instance;
|
||||||
private static final Logger _logger = _plugin.getLogger();
|
|
||||||
|
|
||||||
private static final String prefix = "[ColorfulMap] ";
|
public XLogger() {
|
||||||
|
instance = this;
|
||||||
public static void info(Player player, String message) {
|
this._logger = Logger.getLogger("Lunadeer");
|
||||||
Notification.info(player, prefix + "I | " + message);
|
|
||||||
if (ColorfulMap.config.isDebug())
|
|
||||||
debug("来自玩家[ " + player.getName() + " ] 的信息 | " + message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public XLogger(@Nullable JavaPlugin plugin) {
|
||||||
|
instance = this;
|
||||||
|
this._logger = plugin != null ? plugin.getLogger() : Logger.getLogger("Lunadeer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XLogger setDebug(boolean debug) {
|
||||||
|
instance._debug = debug;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Logger _logger;
|
||||||
|
private boolean _debug = false;
|
||||||
|
|
||||||
public static void info(String message) {
|
public static void info(String message) {
|
||||||
_logger.info(" I | " + message);
|
instance._logger.info(" I | " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warn(Player player, String message) {
|
public static void info(String message, Object... args) {
|
||||||
Notification.warn(player, prefix + "W | " + message);
|
instance._logger.info(" I | " + String.format(message, args));
|
||||||
if (ColorfulMap.config.isDebug())
|
|
||||||
debug("来自玩家[ " + player.getName() + " ] 的警告 | " + message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void warn(String message) {
|
public static void warn(String message) {
|
||||||
_logger.info(" W | " + message);
|
instance._logger.warning(" W | " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void err(Player player, String message) {
|
public static void warn(String message, Object... args) {
|
||||||
Notification.error(player, prefix + "E | " + message);
|
instance._logger.warning(" W | " + String.format(message, args));
|
||||||
if (ColorfulMap.config.isDebug())
|
|
||||||
debug("来自玩家[ " + player.getName() + " ] 的报错 | " + message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void err(String message) {
|
public static void err(String message) {
|
||||||
_logger.info(" E | " + message);
|
instance._logger.severe(" E | " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(Player player, String message) {
|
public static void err(String message, Object... args) {
|
||||||
if (!ColorfulMap.config.isDebug()) return;
|
instance._logger.severe(" E | " + String.format(message, args));
|
||||||
if (player.isOp())
|
|
||||||
Notification.info(player, prefix + "D | " + message);
|
|
||||||
else
|
|
||||||
debug("来自玩家[ " + player.getName() + " ] 的调试 | " + message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(String message) {
|
public static void debug(String message) {
|
||||||
if (!ColorfulMap.config.isDebug()) return;
|
if (!instance._debug) return;
|
||||||
_logger.info(" D | " + message);
|
instance._logger.info(" D | " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void debug(String message, Object... args) {
|
||||||
|
if (!instance._debug) return;
|
||||||
|
instance._logger.info(" D | " + String.format(message, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
863
src/main/java/cn/lunadeer/colorfulmap/utils/bStatsMetrics.java
Normal file
863
src/main/java/cn/lunadeer/colorfulmap/utils/bStatsMetrics.java
Normal file
@ -0,0 +1,863 @@
|
|||||||
|
package cn.lunadeer.colorfulmap.utils;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
|
public class bStatsMetrics {
|
||||||
|
private final Plugin plugin;
|
||||||
|
|
||||||
|
private final MetricsBase metricsBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Metrics instance.
|
||||||
|
*
|
||||||
|
* @param plugin Your plugin instance.
|
||||||
|
* @param serviceId The id of the service. It can be found at <a
|
||||||
|
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
|
||||||
|
*/
|
||||||
|
public bStatsMetrics(JavaPlugin plugin, int serviceId) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
// Get the config file
|
||||||
|
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||||
|
File configFile = new File(bStatsFolder, "config.yml");
|
||||||
|
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
|
||||||
|
if (!config.isSet("serverUuid")) {
|
||||||
|
config.addDefault("enabled", true);
|
||||||
|
config.addDefault("serverUuid", UUID.randomUUID().toString());
|
||||||
|
config.addDefault("logFailedRequests", false);
|
||||||
|
config.addDefault("logSentData", false);
|
||||||
|
config.addDefault("logResponseStatusText", false);
|
||||||
|
// Inform the server owners about bStats
|
||||||
|
config
|
||||||
|
.options()
|
||||||
|
.header(
|
||||||
|
"bStats (https://bStats.org) collects some basic information for plugin authors, like how\n"
|
||||||
|
+ "many people use their plugin and their total player count. It's recommended to keep bStats\n"
|
||||||
|
+ "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n"
|
||||||
|
+ "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n"
|
||||||
|
+ "anonymous.")
|
||||||
|
.copyDefaults(true);
|
||||||
|
try {
|
||||||
|
config.save(configFile);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load the data
|
||||||
|
boolean enabled = config.getBoolean("enabled", true);
|
||||||
|
String serverUUID = config.getString("serverUuid");
|
||||||
|
boolean logErrors = config.getBoolean("logFailedRequests", false);
|
||||||
|
boolean logSentData = config.getBoolean("logSentData", false);
|
||||||
|
boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false);
|
||||||
|
metricsBase =
|
||||||
|
new MetricsBase(
|
||||||
|
"bukkit",
|
||||||
|
serverUUID,
|
||||||
|
serviceId,
|
||||||
|
enabled,
|
||||||
|
this::appendPlatformData,
|
||||||
|
this::appendServiceData,
|
||||||
|
null,
|
||||||
|
//submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
|
||||||
|
plugin::isEnabled,
|
||||||
|
(message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
|
||||||
|
(message) -> this.plugin.getLogger().log(Level.INFO, message),
|
||||||
|
logErrors,
|
||||||
|
logSentData,
|
||||||
|
logResponseStatusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuts down the underlying scheduler service.
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
metricsBase.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom chart.
|
||||||
|
*
|
||||||
|
* @param chart The chart to add.
|
||||||
|
*/
|
||||||
|
public void addCustomChart(CustomChart chart) {
|
||||||
|
metricsBase.addCustomChart(chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendPlatformData(JsonObjectBuilder builder) {
|
||||||
|
builder.appendField("playerAmount", getPlayerAmount());
|
||||||
|
builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0);
|
||||||
|
builder.appendField("bukkitVersion", Bukkit.getVersion());
|
||||||
|
builder.appendField("bukkitName", Bukkit.getName());
|
||||||
|
builder.appendField("javaVersion", System.getProperty("java.version"));
|
||||||
|
builder.appendField("osName", System.getProperty("os.name"));
|
||||||
|
builder.appendField("osArch", System.getProperty("os.arch"));
|
||||||
|
builder.appendField("osVersion", System.getProperty("os.version"));
|
||||||
|
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendServiceData(JsonObjectBuilder builder) {
|
||||||
|
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPlayerAmount() {
|
||||||
|
try {
|
||||||
|
// Around MC 1.8 the return type was changed from an array to a collection,
|
||||||
|
// This fixes java.lang.NoSuchMethodError:
|
||||||
|
// org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
|
||||||
|
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
|
||||||
|
return onlinePlayersMethod.getReturnType().equals(Collection.class)
|
||||||
|
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
|
||||||
|
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Just use the new method if the reflection failed
|
||||||
|
return Bukkit.getOnlinePlayers().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MetricsBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version of the Metrics class.
|
||||||
|
*/
|
||||||
|
public static final String METRICS_VERSION = "3.0.2";
|
||||||
|
|
||||||
|
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
|
private final String platform;
|
||||||
|
|
||||||
|
private final String serverUuid;
|
||||||
|
|
||||||
|
private final int serviceId;
|
||||||
|
|
||||||
|
private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer;
|
||||||
|
|
||||||
|
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
|
||||||
|
|
||||||
|
private final Consumer<Runnable> submitTaskConsumer;
|
||||||
|
|
||||||
|
private final Supplier<Boolean> checkServiceEnabledSupplier;
|
||||||
|
|
||||||
|
private final BiConsumer<String, Throwable> errorLogger;
|
||||||
|
|
||||||
|
private final Consumer<String> infoLogger;
|
||||||
|
|
||||||
|
private final boolean logErrors;
|
||||||
|
|
||||||
|
private final boolean logSentData;
|
||||||
|
|
||||||
|
private final boolean logResponseStatusText;
|
||||||
|
|
||||||
|
private final Set<CustomChart> customCharts = new HashSet<>();
|
||||||
|
|
||||||
|
private final boolean enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MetricsBase class instance.
|
||||||
|
*
|
||||||
|
* @param platform The platform of the service.
|
||||||
|
* @param serviceId The id of the service.
|
||||||
|
* @param serverUuid The server uuid.
|
||||||
|
* @param enabled Whether or not data sending is enabled.
|
||||||
|
* @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
|
||||||
|
* appends all platform-specific data.
|
||||||
|
* @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
|
||||||
|
* appends all service-specific data.
|
||||||
|
* @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be
|
||||||
|
* used to delegate the data collection to a another thread to prevent errors caused by
|
||||||
|
* concurrency. Can be {@code null}.
|
||||||
|
* @param checkServiceEnabledSupplier A supplier to check if the service is still enabled.
|
||||||
|
* @param errorLogger A consumer that accepts log message and an error.
|
||||||
|
* @param infoLogger A consumer that accepts info log messages.
|
||||||
|
* @param logErrors Whether or not errors should be logged.
|
||||||
|
* @param logSentData Whether or not the sent data should be logged.
|
||||||
|
* @param logResponseStatusText Whether or not the response status text should be logged.
|
||||||
|
*/
|
||||||
|
public MetricsBase(
|
||||||
|
String platform,
|
||||||
|
String serverUuid,
|
||||||
|
int serviceId,
|
||||||
|
boolean enabled,
|
||||||
|
Consumer<JsonObjectBuilder> appendPlatformDataConsumer,
|
||||||
|
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
|
||||||
|
Consumer<Runnable> submitTaskConsumer,
|
||||||
|
Supplier<Boolean> checkServiceEnabledSupplier,
|
||||||
|
BiConsumer<String, Throwable> errorLogger,
|
||||||
|
Consumer<String> infoLogger,
|
||||||
|
boolean logErrors,
|
||||||
|
boolean logSentData,
|
||||||
|
boolean logResponseStatusText) {
|
||||||
|
ScheduledThreadPoolExecutor scheduler =
|
||||||
|
new ScheduledThreadPoolExecutor(1, task -> new Thread(task, "bStats-Metrics"));
|
||||||
|
// We want delayed tasks (non-periodic) that will execute in the future to be
|
||||||
|
// cancelled when the scheduler is shutdown.
|
||||||
|
// Otherwise, we risk preventing the server from shutting down even when
|
||||||
|
// MetricsBase#shutdown() is called
|
||||||
|
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.platform = platform;
|
||||||
|
this.serverUuid = serverUuid;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.appendPlatformDataConsumer = appendPlatformDataConsumer;
|
||||||
|
this.appendServiceDataConsumer = appendServiceDataConsumer;
|
||||||
|
this.submitTaskConsumer = submitTaskConsumer;
|
||||||
|
this.checkServiceEnabledSupplier = checkServiceEnabledSupplier;
|
||||||
|
this.errorLogger = errorLogger;
|
||||||
|
this.infoLogger = infoLogger;
|
||||||
|
this.logErrors = logErrors;
|
||||||
|
this.logSentData = logSentData;
|
||||||
|
this.logResponseStatusText = logResponseStatusText;
|
||||||
|
checkRelocation();
|
||||||
|
if (enabled) {
|
||||||
|
// WARNING: Removing the option to opt-out will get your plugin banned from
|
||||||
|
// bStats
|
||||||
|
startSubmitting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCustomChart(CustomChart chart) {
|
||||||
|
this.customCharts.add(chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
scheduler.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startSubmitting() {
|
||||||
|
final Runnable submitTask =
|
||||||
|
() -> {
|
||||||
|
if (!enabled || !checkServiceEnabledSupplier.get()) {
|
||||||
|
// Submitting data or service is disabled
|
||||||
|
scheduler.shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (submitTaskConsumer != null) {
|
||||||
|
submitTaskConsumer.accept(this::submitData);
|
||||||
|
} else {
|
||||||
|
this.submitData();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven
|
||||||
|
// distribution of requests on the
|
||||||
|
// bStats backend. To circumvent this problem, we introduce some randomness into
|
||||||
|
// the initial and second delay.
|
||||||
|
// WARNING: You must not modify and part of this Metrics class, including the
|
||||||
|
// submit delay or frequency!
|
||||||
|
// WARNING: Modifying this code will get your plugin banned on bStats. Just
|
||||||
|
// don't do it!
|
||||||
|
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
|
||||||
|
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
|
||||||
|
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
|
||||||
|
scheduler.scheduleAtFixedRate(
|
||||||
|
submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitData() {
|
||||||
|
final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder();
|
||||||
|
appendPlatformDataConsumer.accept(baseJsonBuilder);
|
||||||
|
final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder();
|
||||||
|
appendServiceDataConsumer.accept(serviceJsonBuilder);
|
||||||
|
JsonObjectBuilder.JsonObject[] chartData =
|
||||||
|
customCharts.stream()
|
||||||
|
.map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toArray(JsonObjectBuilder.JsonObject[]::new);
|
||||||
|
serviceJsonBuilder.appendField("id", serviceId);
|
||||||
|
serviceJsonBuilder.appendField("customCharts", chartData);
|
||||||
|
baseJsonBuilder.appendField("service", serviceJsonBuilder.build());
|
||||||
|
baseJsonBuilder.appendField("serverUUID", serverUuid);
|
||||||
|
baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION);
|
||||||
|
JsonObjectBuilder.JsonObject data = baseJsonBuilder.build();
|
||||||
|
scheduler.execute(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
// Send the data
|
||||||
|
sendData(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Something went wrong! :(
|
||||||
|
if (logErrors) {
|
||||||
|
errorLogger.accept("Could not submit bStats metrics data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendData(JsonObjectBuilder.JsonObject data) throws Exception {
|
||||||
|
if (logSentData) {
|
||||||
|
infoLogger.accept("Sent bStats metrics data: " + data.toString());
|
||||||
|
}
|
||||||
|
String url = String.format(REPORT_URL, platform);
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
|
||||||
|
// Compress the data to save bandwidth
|
||||||
|
byte[] compressedData = compress(data.toString());
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.addRequestProperty("Accept", "application/json");
|
||||||
|
connection.addRequestProperty("Connection", "close");
|
||||||
|
connection.addRequestProperty("Content-Encoding", "gzip");
|
||||||
|
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
|
connection.setRequestProperty("User-Agent", "Metrics-Service/1");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
|
||||||
|
outputStream.write(compressedData);
|
||||||
|
}
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
try (BufferedReader bufferedReader =
|
||||||
|
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||||
|
String line;
|
||||||
|
while ((line = bufferedReader.readLine()) != null) {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (logResponseStatusText) {
|
||||||
|
infoLogger.accept("Sent data to bStats and received response: " + builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the class was properly relocated.
|
||||||
|
*/
|
||||||
|
private void checkRelocation() {
|
||||||
|
// You can use the property to disable the check in your test environment
|
||||||
|
if (System.getProperty("bstats.relocatecheck") == null
|
||||||
|
|| !System.getProperty("bstats.relocatecheck").equals("false")) {
|
||||||
|
// Maven's Relocate is clever and changes strings, too. So we have to use this
|
||||||
|
// little "trick" ... :D
|
||||||
|
final String defaultPackage =
|
||||||
|
new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'});
|
||||||
|
final String examplePackage =
|
||||||
|
new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
|
||||||
|
// We want to make sure no one just copy & pastes the example and uses the wrong
|
||||||
|
// package names
|
||||||
|
if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage)
|
||||||
|
|| MetricsBase.class.getPackage().getName().startsWith(examplePackage)) {
|
||||||
|
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gzips the given string.
|
||||||
|
*
|
||||||
|
* @param str The string to gzip.
|
||||||
|
* @return The gzipped string.
|
||||||
|
*/
|
||||||
|
private static byte[] compress(final String str) throws IOException {
|
||||||
|
if (str == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
|
||||||
|
gzip.write(str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SimplePie extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<String> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public SimplePie(String chartId, Callable<String> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
String value = callable.call();
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("value", value).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MultiLineChart extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<Map<String, Integer>> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||||
|
Map<String, Integer> map = callable.call();
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean allSkipped = true;
|
||||||
|
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||||
|
if (entry.getValue() == 0) {
|
||||||
|
// Skip this invalid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allSkipped = false;
|
||||||
|
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
if (allSkipped) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AdvancedPie extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<Map<String, Integer>> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||||
|
Map<String, Integer> map = callable.call();
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean allSkipped = true;
|
||||||
|
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||||
|
if (entry.getValue() == 0) {
|
||||||
|
// Skip this invalid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allSkipped = false;
|
||||||
|
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
if (allSkipped) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SimpleBarChart extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<Map<String, Integer>> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||||
|
Map<String, Integer> map = callable.call();
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||||
|
valuesBuilder.appendField(entry.getKey(), new int[]{entry.getValue()});
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AdvancedBarChart extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<Map<String, int[]>> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||||
|
Map<String, int[]> map = callable.call();
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean allSkipped = true;
|
||||||
|
for (Map.Entry<String, int[]> entry : map.entrySet()) {
|
||||||
|
if (entry.getValue().length == 0) {
|
||||||
|
// Skip this invalid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allSkipped = false;
|
||||||
|
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
if (allSkipped) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DrilldownPie extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<Map<String, Map<String, Integer>>> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||||
|
Map<String, Map<String, Integer>> map = callable.call();
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean reallyAllSkipped = true;
|
||||||
|
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
|
||||||
|
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
|
||||||
|
boolean allSkipped = true;
|
||||||
|
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
|
||||||
|
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
|
||||||
|
allSkipped = false;
|
||||||
|
}
|
||||||
|
if (!allSkipped) {
|
||||||
|
reallyAllSkipped = false;
|
||||||
|
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reallyAllSkipped) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class CustomChart {
|
||||||
|
|
||||||
|
private final String chartId;
|
||||||
|
|
||||||
|
protected CustomChart(String chartId) {
|
||||||
|
if (chartId == null) {
|
||||||
|
throw new IllegalArgumentException("chartId must not be null");
|
||||||
|
}
|
||||||
|
this.chartId = chartId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObjectBuilder.JsonObject getRequestJsonObject(
|
||||||
|
BiConsumer<String, Throwable> errorLogger, boolean logErrors) {
|
||||||
|
JsonObjectBuilder builder = new JsonObjectBuilder();
|
||||||
|
builder.appendField("chartId", chartId);
|
||||||
|
try {
|
||||||
|
JsonObjectBuilder.JsonObject data = getChartData();
|
||||||
|
if (data == null) {
|
||||||
|
// If the data is null we don't send the chart.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
builder.appendField("data", data);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (logErrors) {
|
||||||
|
errorLogger.accept("Failed to get data for custom chart with id " + chartId, t);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SingleLineChart extends CustomChart {
|
||||||
|
|
||||||
|
private final Callable<Integer> callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*
|
||||||
|
* @param chartId The id of the chart.
|
||||||
|
* @param callable The callable which is used to request the chart data.
|
||||||
|
*/
|
||||||
|
public SingleLineChart(String chartId, Callable<Integer> callable) {
|
||||||
|
super(chartId);
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||||
|
int value = callable.call();
|
||||||
|
if (value == 0) {
|
||||||
|
// Null = skip the chart
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new JsonObjectBuilder().appendField("value", value).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extremely simple JSON builder.
|
||||||
|
*
|
||||||
|
* <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough
|
||||||
|
* for its use-case.
|
||||||
|
*/
|
||||||
|
public static class JsonObjectBuilder {
|
||||||
|
|
||||||
|
private StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
private boolean hasAtLeastOneField = false;
|
||||||
|
|
||||||
|
public JsonObjectBuilder() {
|
||||||
|
builder.append("{");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a null field to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendNull(String key) {
|
||||||
|
appendFieldUnescaped(key, "null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a string field to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param value The value of the field.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendField(String key, String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("JSON value must not be null");
|
||||||
|
}
|
||||||
|
appendFieldUnescaped(key, "\"" + escape(value) + "\"");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an integer field to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param value The value of the field.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendField(String key, int value) {
|
||||||
|
appendFieldUnescaped(key, String.valueOf(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an object to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param object The object.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendField(String key, JsonObject object) {
|
||||||
|
if (object == null) {
|
||||||
|
throw new IllegalArgumentException("JSON object must not be null");
|
||||||
|
}
|
||||||
|
appendFieldUnescaped(key, object.toString());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a string array to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param values The string array.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendField(String key, String[] values) {
|
||||||
|
if (values == null) {
|
||||||
|
throw new IllegalArgumentException("JSON values must not be null");
|
||||||
|
}
|
||||||
|
String escapedValues =
|
||||||
|
Arrays.stream(values)
|
||||||
|
.map(value -> "\"" + escape(value) + "\"")
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
appendFieldUnescaped(key, "[" + escapedValues + "]");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an integer array to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param values The integer array.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendField(String key, int[] values) {
|
||||||
|
if (values == null) {
|
||||||
|
throw new IllegalArgumentException("JSON values must not be null");
|
||||||
|
}
|
||||||
|
String escapedValues =
|
||||||
|
Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
|
||||||
|
appendFieldUnescaped(key, "[" + escapedValues + "]");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an object array to the JSON.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param values The integer array.
|
||||||
|
* @return A reference to this object.
|
||||||
|
*/
|
||||||
|
public JsonObjectBuilder appendField(String key, JsonObject[] values) {
|
||||||
|
if (values == null) {
|
||||||
|
throw new IllegalArgumentException("JSON values must not be null");
|
||||||
|
}
|
||||||
|
String escapedValues =
|
||||||
|
Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
|
||||||
|
appendFieldUnescaped(key, "[" + escapedValues + "]");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a field to the object.
|
||||||
|
*
|
||||||
|
* @param key The key of the field.
|
||||||
|
* @param escapedValue The escaped value of the field.
|
||||||
|
*/
|
||||||
|
private void appendFieldUnescaped(String key, String escapedValue) {
|
||||||
|
if (builder == null) {
|
||||||
|
throw new IllegalStateException("JSON has already been built");
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
throw new IllegalArgumentException("JSON key must not be null");
|
||||||
|
}
|
||||||
|
if (hasAtLeastOneField) {
|
||||||
|
builder.append(",");
|
||||||
|
}
|
||||||
|
builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
|
||||||
|
hasAtLeastOneField = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the JSON string and invalidates this builder.
|
||||||
|
*
|
||||||
|
* @return The built JSON string.
|
||||||
|
*/
|
||||||
|
public JsonObject build() {
|
||||||
|
if (builder == null) {
|
||||||
|
throw new IllegalStateException("JSON has already been built");
|
||||||
|
}
|
||||||
|
JsonObject object = new JsonObject(builder.append("}").toString());
|
||||||
|
builder = null;
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
|
||||||
|
*
|
||||||
|
* <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
|
||||||
|
* Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
|
||||||
|
*
|
||||||
|
* @param value The value to escape.
|
||||||
|
* @return The escaped value.
|
||||||
|
*/
|
||||||
|
private static String escape(String value) {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
char c = value.charAt(i);
|
||||||
|
if (c == '"') {
|
||||||
|
builder.append("\\\"");
|
||||||
|
} else if (c == '\\') {
|
||||||
|
builder.append("\\\\");
|
||||||
|
} else if (c <= '\u000F') {
|
||||||
|
builder.append("\\u000").append(Integer.toHexString(c));
|
||||||
|
} else if (c <= '\u001F') {
|
||||||
|
builder.append("\\u00").append(Integer.toHexString(c));
|
||||||
|
} else {
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A super simple representation of a JSON object.
|
||||||
|
*
|
||||||
|
* <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
|
||||||
|
* allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
|
||||||
|
* JsonObject)}.
|
||||||
|
*/
|
||||||
|
public static class JsonObject {
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
private JsonObject(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,4 +2,12 @@ MaxFrameX: 32
|
|||||||
|
|
||||||
MaxFrameY: 18
|
MaxFrameY: 18
|
||||||
|
|
||||||
|
Economy:
|
||||||
|
Enable: false
|
||||||
|
CostPerMap: 100.0
|
||||||
|
|
||||||
|
AddressWhiteList: []
|
||||||
|
|
||||||
|
CheckUpdate: true
|
||||||
|
|
||||||
Debug: false
|
Debug: false
|
@ -8,3 +8,22 @@ commands:
|
|||||||
tomap:
|
tomap:
|
||||||
description: 将图片转换为地图画
|
description: 将图片转换为地图画
|
||||||
usage: /tomap <图片url> [缩放比例(默认1)]
|
usage: /tomap <图片url> [缩放比例(默认1)]
|
||||||
|
permission: colorfulmap.tomap
|
||||||
|
reloadColorfulMap:
|
||||||
|
description: 重载ColorfulMap配置
|
||||||
|
usage: /reloadColorfulMap
|
||||||
|
permission: colorfulmap.reload
|
||||||
|
purgeColorfulMap:
|
||||||
|
description: 清除无效的地图画
|
||||||
|
usage: /purgeColorfulMap
|
||||||
|
permission: colorfulmap.purge
|
||||||
|
permissions:
|
||||||
|
colorfulmap.tomap:
|
||||||
|
description: 允许使用/tomap命令
|
||||||
|
default: true
|
||||||
|
colorfulmap.reload:
|
||||||
|
description: 允许使用/reloadColorfulMap命令
|
||||||
|
default: op
|
||||||
|
colorfulmap.purge:
|
||||||
|
description: 允许使用/purgeColorfulMap命令
|
||||||
|
default: op
|
||||||
|
Loading…
Reference in New Issue
Block a user