增加一些动作/结构并且补注释:( 呜呜呜补注释好累头号疼

This commit is contained in:
Starrysky
2026-02-17 00:07:42 +08:00
parent 65f516a11f
commit 67e286981e
26 changed files with 572 additions and 34 deletions

View File

@@ -4,27 +4,48 @@ import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import top.sunsetlab.event.EventListener;
import top.sunsetlab.utils.ActionManager;
import top.sunsetlab.utils.LangUtils;
import top.sunsetlab.utils.StructureManager;
import java.util.logging.Logger;
/**
* 插件主类
*/
public class PluginMain extends JavaPlugin implements Listener {
/// 插件版本
public static final String VERSION = "0.0.1-alpha";
/// 插件语言
public static final String LANG = "zh_CN";
/// 调试模式
public static final boolean DEBUG = true;
/// 日志类 - 仅在初始化后调用
public static Logger LOGGER;
/// 结构管理器,用于加载/匹配预设的结构
public static StructureManager structureManager;
/// 动作管理器,用于加载/调用预设的动作
public static ActionManager actionManager;
/// 插件实例
public static JavaPlugin plugin;
/// 语言管理,本地化用
public static LangUtils langUtils;
/**
* 插件启用时调用
*/
@Override
public void onEnable() {
// 初始化
plugin = this;
LOGGER = getLogger();
langUtils = new LangUtils();
langUtils.init(LANG);
structureManager = new StructureManager();
structureManager.load();
actionManager = new ActionManager();
actionManager.load();
getLogger().info("Plugin ver" + VERSION + "loaded." + (DEBUG?"(DEBUG)":""));
getLogger().info("Plugin ver" + VERSION + " loaded." + (DEBUG?"(DEBUG)":""));
// 注册时间监听器
getServer().getPluginManager().registerEvents(new EventListener(), this);
}
}

View File

@@ -3,7 +3,14 @@ package top.sunsetlab.actions;
import org.bukkit.Location;
import org.bukkit.entity.Player;
/// 打雷动作
public class ActionLightning implements IActionBase{
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
@Override
public boolean call(Location location, Player caller) {
location.getWorld().strikeLightning(location);

View File

@@ -0,0 +1,35 @@
package top.sunsetlab.actions;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.entity.Player;
/// 施放延迟
public class CastDelay implements IActionBase{
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
@Override
public boolean call(Location location, Player caller) {
int count = 0;
while (count < 50) {
try {
Thread.sleep(100);
}catch (InterruptedException ignored){}
location.getWorld().spawnParticle(
Particle.DUST,
location,
20,
1.0,
0.0,
1.0,
new Particle.DustOptions(Color.RED, 1.0f));
count++;
}
return true;
}
}

View File

@@ -0,0 +1,32 @@
package top.sunsetlab.actions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import top.sunsetlab.PluginMain;
/// 门控动作,检查莹石存量
public class CheckGlowDust implements IActionBase {
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
@Override
public boolean call(Location location, Player caller) {
if (PluginMain.DEBUG) {
PluginMain.LOGGER.info("Checking amount of glow dust");
}
if (caller.getInventory().getItemInMainHand().getType() == Material.GLOWSTONE_DUST
&& caller.getInventory().getItemInMainHand().getAmount() >= 20) {
int amount = caller.getInventory().getItemInMainHand().getAmount();
caller.getInventory().getItemInMainHand().setAmount(amount - 20);
return true;
}
caller.sendMessage(PluginMain.langUtils.translateC("starlight.message.glowdust_lack"));
return false;
}
}

View File

@@ -3,6 +3,13 @@ package top.sunsetlab.actions;
import org.bukkit.Location;
import org.bukkit.entity.Player;
/// 动作基类
public interface IActionBase {
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
boolean call(Location location, Player caller);
}

View File

@@ -0,0 +1,34 @@
package top.sunsetlab.actions;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Player;
/// 放置火焰
public class PlaceFire implements IActionBase {
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
@Override
public boolean call(Location location, Player caller) {
placeFire(location.clone().add(1,0,0));
placeFire(location.clone().add(-1,0,0));
placeFire(location.clone().add(0,0,1));
placeFire(location.clone().add(0,0,-1));
placeFire(location.clone().add(0,0,0));
return true;
}
/**
* 在指定位置放置火焰
* @param location 位置
*/
private void placeFire(Location location) {
World world = location.getWorld();
world.setBlockData(location, Material.FIRE.createBlockData());
}
}

View File

@@ -0,0 +1,20 @@
package top.sunsetlab.actions;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
/// 向调用者播放铁砧的声音
public class PlayAnvilSound implements IActionBase {
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
@Override
public boolean call(Location location, Player caller) {
caller.playSound(location, Sound.BLOCK_ANVIL_LAND, 1, 1);
return true;
}
}

View File

@@ -0,0 +1,33 @@
package top.sunsetlab.actions;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
/// 嬗变!(神奇炼金术)
public class Transformation implements IActionBase {
/**
* 调用动作
* @param location 动作执行位置
* @param caller 动作执行者
* @return 是否成功
*/
@Override
public boolean call(Location location, Player caller) {
location.getWorld().setBlockData(
location.getBlockX(),
location.getBlockY(),
location.getBlockZ(),
Material.NETHERITE_BLOCK.createBlockData()
);
location.getWorld().setBlockData(
location.clone().subtract(1,0,0),
Material.AIR.createBlockData()
);
location.getWorld().setBlockData(
location.clone().add(1,0,0),
Material.AIR.createBlockData()
);
return true;
}
}

View File

@@ -1,7 +1,9 @@
package top.sunsetlab.event;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
@@ -11,31 +13,47 @@ import top.sunsetlab.utils.JsonStructure;
import java.util.ArrayList;
/// 事件监听器
public class EventListener implements Listener {
/**
* 当玩家与世界交互时触发
* @param event 事件实例
*/
@EventHandler
public void clickEvent(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
if (event.getPlayer().getInventory().getItemInMainHand().getType() != Material.GLOWSTONE_DUST) {
Player player = event.getPlayer();
if (player.getInventory().getItemInMainHand().getType() != Material.GLOWSTONE_DUST) {
return;
}
if (event.getClickedBlock() != null && event.getClickedBlock().getType() != Material.REDSTONE_WIRE) {
return;
}
Location location = event.getClickedBlock().getLocation();
String structid = PluginMain.structureManager.match(location);
if (structid == null) {
return;
}
JsonStructure structure = PluginMain.structureManager.getStructure(structid);
if (structure == null) {
return;
}
if (structure.getPreConditionTask() != null
&& !PluginMain.actionManager.call(structure.getPreConditionTask(), location, player)) {
return;
}
structure.breakStructure(location);
// 异步执行动作
Bukkit.getScheduler().runTaskAsynchronously(PluginMain.plugin, () -> {
String structid = PluginMain.structureManager.match(event.getClickedBlock().getLocation());
if (structid == null) {
return;
}
JsonStructure structure = PluginMain.structureManager.getStructure(structid);
if (structure == null) {
return;
}
ArrayList<String> actions = structure.getActions();
for (String action : actions) {
PluginMain.actionManager.call(action, event.getClickedBlock().getLocation(), event.getPlayer());
PluginMain.actionManager.call(action, location, player);
}
});
event.setCancelled(true);

View File

@@ -16,20 +16,29 @@ class ActionList {
ArrayList<String> locations;
}
/**
* 动作管理器
*/
public class ActionManager {
private final HashMap<String, JsonAction> actions;
public ActionManager() {
actions = new HashMap<>();
}
/**
* 初始化
*/
public void load() {
// 从本地文件读取
InputStream is = ActionManager.class.getResourceAsStream("/actions.json");
if (is == null) {
throw new RuntimeException("actions.json file not found!");
}
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
// 解析json
Gson gson = new Gson();
ActionList actionList = gson.fromJson(reader, ActionList.class);
// 加载单个动作
for (String location : actionList.locations) {
JsonAction action = new JsonAction(location);
actions.put(action.getId(), action);
@@ -37,7 +46,15 @@ public class ActionManager {
PluginMain.LOGGER.info("Loaded " + actions.size() + " actions.");
}
/**
* 调用动作
* @param actionId 动作id
* @param location 执行位置
* @param caller 执行者
* @return 动作是否执行成功sync动作始终返回true
*/
public boolean call(String actionId, Location location, Player caller) {
// 获取动作
JsonAction action = actions.get(actionId);
if (action == null) {
throw new RuntimeException("Invalid action id " + actionId);

View File

@@ -1,8 +1,10 @@
package top.sunsetlab.utils;
import com.google.gson.Gson;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import top.sunsetlab.PluginMain;
import top.sunsetlab.actions.IActionBase;
import java.io.BufferedReader;
@@ -14,11 +16,19 @@ import java.lang.reflect.InvocationTargetException;
class ActionData {
String id;
String classname;
boolean sync;
}
/**
* Json定义的动作
*/
public class JsonAction {
private final ActionData data;
/**
* 构造器
* @param location 资源文件(json)的位置
*/
public JsonAction(String location) {
InputStream is = JsonAction.class.getResourceAsStream("/" + location);
if (is == null) {
@@ -29,15 +39,32 @@ public class JsonAction {
data = gson.fromJson(br, ActionData.class);
}
/**
* 调用动作
* @param location 调用位置
* @param caller 执行者
* @return 动作是否成功运行sync动作始终为true
*/
public boolean run(Location location, Player caller) {
try {
// 通过反射加载动作类
Class<?> clazz = Class.forName(data.classname);
if (!IActionBase.class.isAssignableFrom(clazz)) {
throw new RuntimeException(data.classname + " is not a Action");
}
// 获取构造器
Constructor<?> constructor = clazz.getConstructor();
IActionBase action = (IActionBase) constructor.newInstance();
return action.call(location, caller);
if (data.sync) {
// 在Minecraft线程内执行一般用于世界交互
Bukkit.getScheduler().runTask(PluginMain.plugin, () -> {
action.call(location, caller);
});
return true;
}else {
// 异步执行
return action.call(location, caller);
}
}catch (ClassNotFoundException
| NoSuchMethodException
| SecurityException
@@ -51,6 +78,10 @@ public class JsonAction {
}
}
/**
* 获取动作id
* @return 动作id
*/
public String getId() {
return data.id;
}

View File

@@ -1,11 +1,9 @@
package top.sunsetlab.utils;
import com.google.gson.Gson;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.block.BlockType;
import org.jspecify.annotations.NonNull;
import top.sunsetlab.PluginMain;
import java.io.BufferedReader;
@@ -22,12 +20,22 @@ class JsonStructureData {
int[] center;
String id;
ArrayList<String> actions;
String precondition_task;
ArrayList<String> noclear;
}
/// Json定义的结构
public class JsonStructure {
/// 结构数据
private final JsonStructureData data;
/// 结构资源文件位置
private final String structure_loc;
/**
* 构造函数
* @param location 资源文件的位置
* @throws RuntimeException 在无法读取资源文件时抛出
*/
public JsonStructure(String location) throws RuntimeException {
InputStream file = JsonStructure.class.getResourceAsStream("/" + location);
if (file == null) {
@@ -40,7 +48,13 @@ public class JsonStructure {
structure_loc = location;
}
/**
* 尝试匹配结构
* @param pos 结构中心点位置
* @return 结构是否匹配?
*/
public boolean match(Location pos) {
// 确定起始点,终点
World world = pos.getWorld();
Location currentpos = new Location(
world,
@@ -59,7 +73,14 @@ public class JsonStructure {
PluginMain.LOGGER.log(Level.INFO, "Matching structure " + structure_loc
+ "; From " + currentpos + " To " + endpos);
}
// 记录方块种类 - 仅在需要时生成
ArrayList<BlockType> types = null;
// 循环匹配每个方块
// TODO 有没有更好的方法
while (!currentpos.equals(endpos)) {
// 相对西北角的位置
int rx = currentpos.getBlockX() - startpos.getBlockX();
int rz = currentpos.getBlockZ() - startpos.getBlockZ();
// if (PluginMain.DEBUG) {
@@ -67,28 +88,50 @@ public class JsonStructure {
// + currentpos
// + "(relative (" + rx + "," + rz + "))");
// }
// 应该存在的方块
String mark = String.valueOf(data.structure.get(rz).charAt(rx));
String type = data.marks.get(mark);
if (type == null) {
throw new RuntimeException("In Structure "+ structure_loc +"; Undefined mark: " + mark);
}
NamespacedKey key = NamespacedKey.fromString(type);
if (key == null) {
throw new RuntimeException("In Structure "+ structure_loc +"; Invalid key: " + type);
}
BlockType blockType = Registry.BLOCK.get(key);
if (blockType == null) {
throw new RuntimeException("In Structure "+ structure_loc +"; Invalid block type: " + type);
}
if (!Objects.equals(world.getBlockAt(currentpos).getType().asBlockType(), blockType)) {
if (PluginMain.DEBUG) {
if (!type.equals("any")) {
// 匹配特定方块
BlockType blockType = getBlockType(type);
if (!Objects.equals(world.getBlockAt(currentpos).getType().asBlockType(), blockType)) {
if (PluginMain.DEBUG) {
PluginMain.LOGGER.log(Level.INFO, "Block Mismatch at " + currentpos
+ "(relative (" + rx + "," + rz + "))"
+ "; Expected " + blockType
+ ", but got " + world.getBlockAt(currentpos).getType());
}
return false;
}
}else {
// 匹配任意方块
if (types == null) {
types = new ArrayList<>();
for (String s : data.marks.values()) {
if (s.equals("any")) {
continue;
}
types.add(getBlockType(s));
}
}
// 是否为空气
boolean isAir = Objects.equals(world.getBlockAt(currentpos).getType().asBlockType(), Material.AIR.asBlockType());
// 是否在列表内 - 为了防止玩家摆个正方形导致怪异的匹配
boolean isInList = types.contains(world.getBlockAt(currentpos).getType().asBlockType());
// 当非空气且在列表内时终止
if (!isAir && isInList ) {
PluginMain.LOGGER.log(Level.INFO, "Block Mismatch at " + currentpos
+ "(relative (" + rx + "," + rz + "))"
+ "; Expected " + blockType
+ ", but got " + world.getBlockAt(currentpos).getType());
+ " Tried any, but got " + world.getBlockAt(currentpos).getType());
return false;
}
return false;
}
// 递增当前坐标
currentpos.add(1,0,0);
if (currentpos.getBlockX() == endpos.getBlockX() + 1) {
currentpos.setX(startpos.getBlockX());
@@ -101,11 +144,96 @@ public class JsonStructure {
return true;
}
/**
* 通过id获取方块类型
* @param type 方块id
* @return 方块类型
*/
private @NonNull BlockType getBlockType(String type) {
NamespacedKey key = NamespacedKey.fromString(type);
if (key == null) {
throw new RuntimeException("In Structure " + structure_loc + "; Invalid key: " + type);
}
BlockType blockType = Registry.BLOCK.get(key);
if (blockType == null) {
throw new RuntimeException("In Structure " + structure_loc + "; Invalid block type: " + type);
}
return blockType;
}
/**
* 获取结构id
* @return 结构id
*/
public String getId() {
return data.id;
}
/**
* 获取要执行的动作列表
* @return 动作列表
*/
public ArrayList<String> getActions() {
return data.actions;
}
/**
* 破坏整个结构
* @param pos 结构中心位置
*/
public void breakStructure(Location pos) {
// 清理赦免:D (学生物学的)
// FIXME 这东西用不了
ArrayList<BlockType> noclears = new ArrayList<>();
if (data.noclear != null) {
// 已知这里正常
for (String s : data.noclear) {
if (PluginMain.DEBUG) {
PluginMain.LOGGER.log(Level.INFO, "Will not clear " + s);
}
noclears.add(getBlockType(s));
}
}
// 确认起始点和终点
World world = pos.getWorld();
Location beginPos = new Location(
pos.getWorld(),
pos.getBlockX() - data.center[0],
pos.getBlockY(),
pos.getBlockZ() - data.center[1]
);
Location currentPos = beginPos.clone();
Location endPos = new Location(
pos.getWorld(),
pos.getBlockX() + data.center[0],
pos.getBlockY(),
pos.getBlockZ() + data.center[1]
);
// 遍历每一个方块
// TODO 有没有更好的方法
while (!beginPos.equals(endPos)) {
// 这里的判断貌似有问题
if (!noclears.contains(world.getBlockAt(beginPos).getType().asBlockType())) {
world.getBlockAt(currentPos).setType(Material.AIR);
}
currentPos.add(1,0,0);
if (currentPos.getBlockX() == endPos.getBlockX() + 1) {
currentPos.setX(beginPos.getBlockX());
currentPos.setZ(currentPos.getBlockZ() + 1);
if (currentPos.getBlockZ() == endPos.getBlockZ() + 1) {
break;
}
}
}
}
/**
* 获取门控任务(仅当该任务成功时执行任务列表)
* @return 门控任务id
*/
public String getPreConditionTask() {
return data.precondition_task;
}
}

View File

@@ -0,0 +1,51 @@
package top.sunsetlab.utils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import net.kyori.adventure.text.Component;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
/// 本地化工具
public class LangUtils {
/// 一个很好用的键值对
private HashMap<String,String> map;
/**
* 初始化
* @param lang 使用的语言
*/
public void init(String lang){
InputStream is = LangUtils.class.getResourceAsStream("/assets/lang/" + lang + ".json");
if (is == null) {
throw new RuntimeException("Unable to load language file: " + lang + ".json");
}
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
Gson gson = new Gson();
map = gson.fromJson(reader, new TypeToken<HashMap<String, String>>(){}.getType());
}
/**
* 获取本地化文本
* @param key 本地化键
* @return 本地化文本
*/
public String translate(String key) {
if (map.containsKey(key)) {
return map.get(key);
}
return key;
}
/**
* 获取本地化文本组件
* @param key 本地化键
* @return 本地化文本组件
*/
public Component translateC(String key) {
return Component.text(translate(key));
}
}

View File

@@ -16,14 +16,25 @@ class StructureList {
ArrayList<String> locations = new ArrayList<>();
}
// 呜呜呜补文档补的快哭了
/// 结构管理器
public class StructureManager {
/// 又是一个好用的键值对
private final HashMap<String,JsonStructure> structures;
/**
* 构造函数
*/
public StructureManager() {
structures = new HashMap<>();
}
/**
* 初始化函数
*/
public void load() {
InputStream is = StructureList.class.getResourceAsStream("/structures.json");
InputStream is = StructureManager.class.getResourceAsStream("/structures.json");
if (is == null) {
throw new RuntimeException("structures.json not found");
}
@@ -37,6 +48,11 @@ public class StructureManager {
PluginMain.LOGGER.log(Level.INFO, "Loaded " + structures.size() + " structures");
}
/**
* 在所有结构中尝试匹配
* @param location 结构的中心位置
* @return 匹配到的id没有匹配到则为null
*/
public @Nullable String match(Location location) {
for (JsonStructure structure : structures.values()) {
if (structure.match(location)) {
@@ -46,7 +62,25 @@ public class StructureManager {
return null;
}
/**
* 根据id获取结构
* @param id 结构id
* @return 结构对象
*/
public @Nullable JsonStructure getStructure(String id) {
return structures.get(id);
}
/**
* 根据id破坏结构
* @param id 结构id
* @param location 结构的中心位置
*/
public void breakStructure(String id, Location location) {
JsonStructure structure = structures.get(id);
if (structure == null) {
throw new RuntimeException("structure with id " + id + " not found");
}
structure.breakStructure(location);
}
}

View File

@@ -1,5 +1,10 @@
{
"locations": [
"actions/lightning.json"
"actions/lightning.json",
"actions/castdelay.json",
"actions/checkglowdust.json",
"actions/transformation.json",
"actions/placefire.json",
"actions/playanvilsound.json"
]
}

View File

@@ -0,0 +1,5 @@
{
"id": "castdelay",
"classname": "top.sunsetlab.actions.CastDelay",
"sync": false
}

View File

@@ -0,0 +1,5 @@
{
"id": "checkglowdust",
"classname": "top.sunsetlab.actions.CheckGlowDust",
"sync": false
}

View File

@@ -1,4 +1,5 @@
{
"id": "lightning",
"classname": "top.sunsetlab.actions.ActionLightning"
"classname": "top.sunsetlab.actions.ActionLightning",
"sync": true
}

View File

@@ -0,0 +1,5 @@
{
"id": "placefire",
"classname": "top.sunsetlab.actions.PlaceFire",
"sync": true
}

View File

@@ -0,0 +1,5 @@
{
"id": "playanvilsound",
"classname": "top.sunsetlab.actions.PlayAnvilSound",
"sync": false
}

View File

@@ -0,0 +1,5 @@
{
"id": "transformation",
"classname": "top.sunsetlab.actions.Transformation",
"sync": true
}

View File

@@ -0,0 +1,3 @@
{
"starlight.message.glowdust_lack": "Seems i cannot afford this spell"
}

View File

@@ -0,0 +1,3 @@
{
"starlight.message.glowdust_lack": "貌似我无法承受这个法阵的消耗"
}

View File

@@ -1,5 +1,6 @@
{
"locations": [
"structures/01.json"
"structures/01.json",
"structures/transformation.json"
]
}

View File

@@ -16,11 +16,12 @@
" xxx "
],
"marks": {
" ": "minecraft:air",
" ": "any",
"x": "minecraft:redstone_wire"
},
"center": [6,6],
"actions": [
"castdelay",
"lightning"
]
}

View File

@@ -0,0 +1,31 @@
{
"id": "transformation",
"structure": [
" x ",
" xxxxx ",
" x p x ",
"xxoxoxx",
" x p x ",
" xxxxx ",
" x "
],
"marks": {
"x": "minecraft:redstone_wire",
"o": "minecraft:diamond_block",
"p": "minecraft:redstone_block",
" ": "any"
},
"center": [3,3],
"actions": [
"placefire",
"playanvilsound",
"castdelay",
"playanvilsound",
"transformation",
"lightning"
],
"precondition_task": "checkglowdust",
"noclear": [
"minecraft:diamond_block"
]
}