9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 03:19:14 +00:00

Merge pull request #84 from Xiao-MoMi/dev

0.0.43
This commit is contained in:
XiaoMoMi
2025-04-05 03:08:50 +08:00
committed by GitHub
137 changed files with 3470 additions and 2577 deletions

View File

@@ -93,6 +93,7 @@ tasks {
relocate("net.objecthunter.exp4j", "net.momirealms.craftengine.libraries.exp4j")
relocate("net.bytebuddy", "net.momirealms.craftengine.libraries.bytebuddy")
relocate("org.yaml.snakeyaml", "net.momirealms.craftengine.libraries.snakeyaml")
relocate("org.ahocorasick", "net.momirealms.craftengine.libraries.ahocorasick")
}
}

View File

@@ -72,5 +72,6 @@ tasks {
relocate("net.objecthunter.exp4j", "net.momirealms.craftengine.libraries.exp4j")
relocate("net.bytebuddy", "net.momirealms.craftengine.libraries.bytebuddy")
relocate("org.yaml.snakeyaml", "net.momirealms.craftengine.libraries.snakeyaml")
relocate("org.ahocorasick", "net.momirealms.craftengine.libraries.ahocorasick")
}
}

View File

@@ -12,16 +12,16 @@ public class BukkitBootstrap extends JavaPlugin {
@Override
public void onLoad() {
this.plugin.load();
this.plugin.onPluginLoad();
}
@Override
public void onEnable() {
this.plugin.enable();
this.plugin.onPluginEnable();
}
@Override
public void onDisable() {
this.plugin.disable();
this.plugin.onPluginDisable();
}
}

View File

@@ -69,6 +69,13 @@ search_recipe_admin:
- /craftengine item search-recipe
- /ce item search-recipe
totem_animation:
enable: true
permission: ce.command.admin.totem_animation
usage:
- /craftengine feature totem-animation
- /ce feature totem-animation
# Debug commands
debug_set_block:
enable: true

View File

@@ -152,7 +152,14 @@ image:
sign: true
recipe:
# Enable the plugin's recipe system
enable: true
# Disable vanilla recipes
disable-vanilla-recipes:
# Disable all vanilla recipes
all: false
# Disable the recipes in list
list: []
gui:
browser:

View File

@@ -25,4 +25,5 @@ snake-yaml=${snake_yaml_version}
adventure-text-minimessage=${adventure_bundle_version}
adventure-text-serializer-gson=${adventure_bundle_version}
adventure-text-serializer-json=${adventure_bundle_version}
netty-codec-http=${netty_version}
netty-codec-http=${netty_version}
ahocorasick=${ahocorasick_version}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -471,15 +471,15 @@ templates#settings#sounds:
arguments:
block_type: bamboo
default:sound/vine:
template: default:block
template: default:sound/block_template
arguments:
block_type: vine
default:sound/lantern:
template: default:block
template: default:sound/block_template
arguments:
block_type: lantern
default:sound/amethyst_block:
template: default:block
template: default:sound/block_template
arguments:
block_type: amethyst_block

View File

@@ -38,11 +38,11 @@ argument.parse.failure.aggregate.missing: "<red>Missing component '<arg:0>'</red
argument.parse.failure.aggregate.failure: "<red>Invalid component '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>Could not resolve <arg:1> or <arg:2> from '<arg:0>'</red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' is not a named text color</red>"
command.reload.config.success: "<white>Configs reloaded in <green><arg:0></green> ms.</white>"
command.reload.config.success: "<white>Configs reloaded in <green><arg:0></green> ms.</white> <gray>(Async: <arg:1>ms | Sync: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>Config reload failed. Check console logs.</red>"
command.reload.pack.success: "<white>Resource pack reloaded in <green><arg:0></green> ms.</white>"
command.reload.pack.failure: "<red>Resource pack reload failed. Check console logs.</red>"
command.reload.all.success: "<white>Reload completed in <green><arg:0></green> ms.</white>"
command.reload.all.success: "<white>Reload completed in <green><arg:0></green> ms.</white> <gray>(Async: <arg:1>ms | Sync: <arg:2>ms | Pack: <arg:3>ms)</gray>"
command.reload.all.failure: "<red>Reload failed. Check console logs.</red>"
command.item.get.success: "<white>Got <arg:0> <arg:1></white>"
command.item.get.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>"
@@ -52,4 +52,54 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
command.search_recipe.not_found: "<red>No recipe found for this item</red>"
command.search_usage.not_found: "<red>No usage found for this item</red>"
command.search_recipe.no_item: "<red>Please hold an item before running this command</red>"
command.search_usage.no_item: "<red>Please hold an item before running this command</red>"
command.search_usage.no_item: "<red>Please hold an item before running this command</red>"
command.totem_animation.failure.not_totem: "<red>Item '<arg:0>' is not minecraft:totem_of_undying</red>"
warning.config.image.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated image '<arg:1>'.</yellow>"
warning.config.image.lack_height: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'height' argument.</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' violates the bitmap image rule: 'height' should be no lower than 'ascent'.</yellow>"
warning.config.image.no_file: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'file' argument.</yellow>"
warning.config.image.invalid_resource_location: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' has a 'file' argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.image.invalid_font_name: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' has a 'font' argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.image.lack_char: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'char' argument.</yellow>"
warning.config.image.codepoint_in_use: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is using a character[<arg:3>(<arg:4>)] in font <arg:2> that has been used by another image '<arg:5>'.</yellow>"
warning.config.image.invalid_codepoint_grid: "<yellow>Issue found in file <arg:0> - Image '<arg:1>' has an invalid 'chars' codepoint grind.</yellow>"
warning.config.image.file_not_exist: "<yellow>Issue found in file <arg:0> - PNG file '<arg:2>' not found for image '<arg:1>'.</yellow>"
warning.config.recipe.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated recipe '<arg:1>'.</yellow>"
warning.config.i18n.unknown_locale: "<yellow>Issue found in file <arg:0> - Unknown locale '<arg:1>'.</yellow>"
warning.config.template.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated template '<arg:1>'.</yellow>"
warning.config.vanilla_loot.type_not_exist: "<yellow>Issue found in file <arg:0> - 'type' not set for vanilla loot '<arg:1>'.</yellow>"
warning.config.vanilla_loot.block.invalid_target: "<yellow>Issue found in file <arg:0> - Invalid block target [<arg:2>] in vanilla loot '<arg:1>'.</yellow>"
warning.config.sound.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated sound '<arg:1>'.</yellow>"
warning.config.jukebox_song.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated jukebox song '<arg:1>'.</yellow>"
warning.config.furniture.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated furniture '<arg:1>'.</yellow>"
warning.config.furniture.lack_placement: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'placement' argument.</yellow>"
warning.config.furniture.element.lack_item: "<yellow>Issue found in file <arg:0> - The furniture '<arg:1>' is missing the required 'item' argument for one of its elements.</yellow>"
warning.config.item.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated item '<arg:1>'.</yellow>"
warning.config.item.lack_material: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'material' argument.</yellow>"
warning.config.item.invalid_material: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is using an invalid material type '<arg:2>'.</yellow>"
warning.config.item.bad_custom_model_data_value: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is using a custom model data [<arg:2>] that is too large. It's recommended to use a value lower than 16,777,216.</yellow>"
warning.config.item.custom_model_data_conflict: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is using a custom model data [<arg:2>] that has been occupied by item '<arg:3>'</yellow>"
warning.config.item.lack_model_id: "<yellow>Issue found in file <arg:0> - The item '<arg:1>' is missing the required 'custom-model-data' or 'item-model' argument.</yellow>"
warning.config.block.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated block '<arg:1>'.</yellow>"
warning.config.block.lack_state: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'state' argument.</yellow>"
warning.config.block.state.lack_real_id: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'id' argument for 'state'.</yellow>"
warning.config.block.state.lack_state: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'state' argument for 'state'.</yellow>"
warning.config.block.state.lack_properties: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'properties' section for 'states'.</yellow>"
warning.config.block.state.lack_appearances: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'appearances' section for 'states'.</yellow>"
warning.config.block.state.lack_variants: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'variants' section for 'states'.</yellow>"
warning.config.block.state.variant.lack_appearance: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'appearance' argument for variant '<arg:2>'.</yellow>"
warning.config.block.state.variant.invalid_appearance: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' has an error that the variant '<arg:2>' is using a non-existing appearance '<arg:3>'.</yellow>"
warning.config.block.state.invalid_state: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an invalid vanilla block state '<arg:2>'."
warning.config.block.state.unavailable_state: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an unavailable vanilla block state '<arg:2>'."
warning.config.block.state.invalid_vanilla_state_id: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using a vanilla block state '<arg:2>' that exceeds the available slot range '0~<arg:3>'."
warning.config.block.state.conflict: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using a vanilla block state '<arg:2>' that has been occupied by '<arg:3>'."
warning.config.block.state.bind_real_state: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' failed to bind real block state for '<arg:2>' as the state has been occupied by '<arg:3>'.</yellow>"
warning.config.block.state.invalid_property_structure: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' has an invalid property structure '<arg:2>'."
warning.config.block.state.invalid_property: "<yellow>Issue found in file <arg:0> - Failed to create property '<arg:2>' for block '<arg:1>': <arg:3>."
warning.config.block.state.no_model_set: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'model' or 'models' argument.</yellow>"
warning.config.block.state.invalid_real_state_id: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using a real block state '<arg:2>' that exceeds the available slot range '0~<arg:3>'. Consider adding more real states in 'additional-real-blocks.yml' if the slots are used up.</yellow>"
warning.config.block.state.model.lack_path: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'path' option for 'model'.</yellow>"
warning.config.block.state.model.invalid_resource_location: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' has a 'path' argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.model.generation.conflict: "<yellow>Issue found in file <arg:0> - Failed to generate model for '<arg:1>' as two or more configurations attempt to generate different json models with the same path: '<arg:2>'</yellow>"
warning.config.model.generation.texture.invalid_resource_location: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' has a '<arg:2>' texture argument [<arg:3>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"
warning.config.model.generation.parent.invalid_resource_location: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' has a parent argument [<arg:2>] that contains legal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters</yellow>"

View File

@@ -38,11 +38,11 @@ argument.parse.failure.aggregate.missing: "<red>Componente Faltante '<arg:0>'</r
argument.parse.failure.aggregate.failure: "<red>Componente Invalido '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>No se ha podido resolver <arg:1> o <arg:2> de '<arg:0>'</red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' no es un color de texto con nombre</red>"
command.reload.config.success: "<white>Recargado. Tomó <green><arg:0></green> ms.</white>"
command.reload.config.success: "<white>Recargado. Tomó <green><arg:0></green> ms.</white> <gray>(Async: <arg:1>ms | Sync: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>Error al recargar la configuración. Por favor, revisa el registro de la consola.</red>"
command.reload.pack.success: "<white>Paquete de recursos recargado. Tomó <green><arg:0></green> ms.</white>"
command.reload.pack.failure: "<red>Error al recargar el paquete de recursos. Por favor, revisa el registro de la consola.</red>"
command.reload.all.success: "<white>Todo recargado. Tomó <green><arg:0></green> ms.</white>"
command.reload.all.success: "<white>Todo recargado. Tomó <green><arg:0></green> ms.</white> <gray>(Async: <arg:1>ms | Sync: <arg:2>ms | Pack: <arg:3>ms)</gray>"
command.reload.all.failure: "<red>Error al recargar. Por favor, revisa el registro de la consola.</red>"
command.item.get.success: "<white>Obtener <arg:0> <arg:1></white>"
command.item.get.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>"
@@ -52,4 +52,5 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
command.search_recipe.not_found: "<red>No se encontró ninguna receta para este objeto</red>"
command.search_usage.not_found: "<red>No se encontró ningún uso para este objeto</red>"
command.search_recipe.no_item: "<red>Por favor, sostén un objeto antes de ejecutar este comando</red>"
command.search_usage.no_item: "<red>Por favor, sostén un objeto antes de ejecutar este comando</red>"
command.search_usage.no_item: "<red>Por favor, sostén un objeto antes de ejecutar este comando</red>"
command.totem_animation.failure.not_totem: "<red>'<arg:0>' no es del tipo totem_of_undying</red>"

View File

@@ -38,12 +38,12 @@ argument.parse.failure.aggregate.missing: "<red>缺少组件 '<arg:0>'</red>"
argument.parse.failure.aggregate.failure: "<red>无效的组件 '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>无法从 '<arg:0>' 解析 <arg:1> 或 <arg:2></red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' 不是颜色代码</red>"
command.reload.config.success: "<white>重新加载配置完成. 耗时 <green><arg:0></green> 毫秒</white>"
command.reload.config.failure: "<red>重新加载配置失败,请检查控制台日志</red>"
command.reload.config.success: "<white>重新加载配置完成. 耗时 <green><arg:0></green> 毫秒</white> <gray>(异步: <arg:1>ms | 同步: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>重新加载配置失败,请检查控制台日志</red>"
command.reload.pack.success: "<white>资源包重新加载完成. 耗时 <green><arg:0></green> 毫秒</white>"
command.reload.pack.failure: "<red>重新加载资源包失败,请检查控制台日志</red>"
command.reload.all.success: "<white>全部重新加载完成. 耗时 <green><arg:0></green> 毫秒</white>"
command.reload.all.failure: "<red>重新加载失败,请检查控制台日志</red>"
command.reload.pack.failure: "<red>重新加载资源包失败,请检查控制台日志</red>"
command.reload.all.success: "<white>全部重新加载完成. 耗时 <green><arg:0></green> 毫秒</white> <gray>(异步: <arg:1>ms | 同步: <arg:2>ms | 资源包: <arg:3>ms)</gray>"
command.reload.all.failure: "<red>重新加载失败,请检查控制台日志</red>"
command.item.get.success: "<white>获得<arg:0>个<arg:1></white>"
command.item.get.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>"
command.item.give.success.single: "<lang:commands.give.success.single:'<arg:0>':'<arg:1>':'<arg:2>'>"
@@ -52,4 +52,54 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
command.search_recipe.not_found: "<red>找不到此物品的配方</red>"
command.search_usage.not_found: "<red>找不到此物品的用途</red>"
command.search_recipe.no_item: "<red>请手持物品后再执行此命令</red>"
command.search_usage.no_item: "<red>请手持物品后再执行此命令</red>"
command.search_usage.no_item: "<red>请手持物品后再执行此命令</red>"
command.totem_animation.failure.not_totem: "<red>'<arg:0>' 不是 totem_of_undying 类型</red>"
warning.config.image.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 重复定义</yellow>"
warning.config.image.lack_height: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 缺少必要的 'height' 高度参数</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度</yellow>"
warning.config.image.no_file: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 缺少必要的 'file' 文件参数</yellow>"
warning.config.image.invalid_resource_location: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 的 'file' 参数 [<arg:2>] 包含非法字符请参考资源路径规范https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"
warning.config.image.invalid_font_name: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 的 'font' 字体参数 [<arg:2>] 包含非法字符请参考资源路径规范https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"
warning.config.image.lack_char: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 缺少必要的 'char' 字符参数</yellow>"
warning.config.image.codepoint_in_use: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 使用的字体 <arg:2> 字符 [<arg:3>(<arg:4>)] 已被其他图片 '<arg:5>' 占用</yellow>"
warning.config.image.invalid_codepoint_grid: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 的 'chars' 码位网格配置无效</yellow>"
warning.config.image.file_not_exist: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 对应的PNG文件 <arg:2> 不存在</yellow>"
warning.config.recipe.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 配方 '<arg:1>' 重复定义</yellow>"
warning.config.i18n.unknown_locale: "<yellow>在文件 <arg:0> 中发现问题 - 未知的语言区域 '<arg:1>'</yellow>"
warning.config.template.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 模板 '<arg:1>' 重复定义</yellow>"
warning.config.vanilla_loot.type_not_exist: "<yellow>在文件 <arg:0> 中发现问题 - 原版战利品 '<arg:1>' 未设置 'type' 类型参数</yellow>"
warning.config.vanilla_loot.block.invalid_target: "<yellow>在文件 <arg:0> 中发现问题 - 原版战利品 '<arg:1>' 中的方块目标 [<arg:2>] 无效</yellow>"
warning.config.sound.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 音效 '<arg:1>' 重复定义</yellow>"
warning.config.jukebox_song.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 唱片机歌曲 '<arg:1>' 重复定义</yellow>"
warning.config.furniture.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 家具 '<arg:1>' 重复定义</yellow>"
warning.config.furniture.lack_placement: "<yellow>在文件 <arg:0> 中发现问题 - 家具 '<arg:1>' 缺少必要的 'placement' 放置参数</yellow>"
warning.config.furniture.element.lack_item: "<yellow>在文件 <arg:0> 中发现问题 - 家具 '<arg:1>' 的某个元素缺少必要的 'item' 物品参数</yellow>"
warning.config.item.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 物品 '<arg:1>' 重复定义</yellow>"
warning.config.item.lack_material: "<yellow>在文件 <arg:0> 中发现问题 - 物品 '<arg:1>' 缺少必要的 'material' 材料参数</yellow>"
warning.config.item.invalid_material: "<yellow>在文件 <arg:0> 中发现问题 - 物品 '<arg:1>' 使用了无效的材料类型 '<arg:2>'</yellow>"
warning.config.item.bad_custom_model_data_value: "<yellow>在文件 <arg:0> 中发现问题 - 物品 '<arg:1>' 的自定义模型数据值 [<arg:2>] 过大建议使用低于16,777,216的值</yellow>"
warning.config.item.custom_model_data_conflict: "<yellow>在文件 <arg:0> 中发现问题 - 物品 '<arg:1>' 的自定义模型数据 [<arg:2>] 与物品 '<arg:3>' 发生冲突</yellow>"
warning.config.item.lack_model_id: "<yellow>在文件 <arg:0> 中发现问题 - 物品 '<arg:1>' 缺少必要的 'custom-model-data' 或 'item-model' 模型标识参数</yellow>"
warning.config.block.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 重复定义</yellow>"
warning.config.block.lack_state: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 缺少必要的 'state' 状态参数</yellow>"
warning.config.block.state.lack_real_id: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'state' 配置缺少必要的 'id' 标识参数</yellow>"
warning.config.block.state.lack_state: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'state' 配置缺少必要的 'state' 状态参数</yellow>"
warning.config.block.state.lack_properties: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'states' 配置缺少必要的 'properties' 属性配置</yellow>"
warning.config.block.state.lack_appearances: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'states' 配置缺少必要的 'appearances' 外观配置</yellow>"
warning.config.block.state.lack_variants: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'states' 配置缺少必要的 'variants' 变体配置</yellow>"
warning.config.block.state.variant.lack_appearance: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 '<arg:2>' 变体配置缺少必要的 'appearance' 外观参数</yellow>"
warning.config.block.state.variant.invalid_appearance: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 '<arg:2>' 变体引用了不存在的外观配置 '<arg:3>'</yellow>"
warning.config.block.state.invalid_state: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 使用了无效的原版方块状态 '<arg:2>'</yellow>"
warning.config.block.state.unavailable_state: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 使用了不可用的原版方块状态 '<arg:2>'</yellow>"
warning.config.block.state.invalid_vanilla_state_id: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 使用的原版方块状态 '<arg:2>' 超出可用槽位范围 '0~<arg:3>'</yellow>"
warning.config.block.state.conflict: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 使用的原版方块状态 '<arg:2>' 已被 '<arg:3>' 占用</yellow>"
warning.config.block.state.bind_real_state: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 未能绑定真实方块状态 '<arg:2>',该状态已被 '<arg:3>' 占用</yellow>"
warning.config.block.state.invalid_property_structure: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的属性结构 '<arg:2>' 配置无效</yellow>"
warning.config.block.state.invalid_property: "<yellow>在文件 <arg:0> 中发现问题 - 无法为方块 '<arg:1>' 创建属性 '<arg:2>'<arg:3></yellow>"
warning.config.block.state.no_model_set: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 缺少必要的 'model' 或 'models' 模型参数</yellow>"
warning.config.block.state.invalid_real_state_id: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 使用的真实方块状态 '<arg:2>' 超出可用槽位范围 '0~<arg:3>'。若槽位已用尽,请考虑在 additional-real-blocks.yml 中添加更多真实状态</yellow>"
warning.config.block.state.model.lack_path: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'model' 配置缺少必要的 'path' 路径参数</yellow>"
warning.config.block.state.model.invalid_resource_location: "<yellow>在文件 <arg:0> 中发现问题 - 方块 '<arg:1>' 的 'path' 路径参数 [<arg:2>] 包含非法字符请参考资源路径规范https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"
warning.config.model.generation.conflict: "<yellow>在文件 <arg:0> 中发现问题 - 无法为 '<arg:1>' 生成模型,多个配置尝试用相同路径 '<arg:2>' 生成不同的JSON模型</yellow>"
warning.config.model.generation.texture.invalid_resource_location: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 的 '<arg:2>' 纹理参数 [<arg:3>] 包含非法字符请参考资源路径规范https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"
warning.config.model.generation.parent.invalid_resource_location: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 的父模型参数 [<arg:2>] 包含非法字符请参考资源路径规范https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"

View File

@@ -38,11 +38,11 @@ argument.parse.failure.aggregate.missing: "<red>缺少元件 '<arg:0>'</red>"
argument.parse.failure.aggregate.failure: "<red>無效的元件 '<arg:0>': <arg:1></red>"
argument.parse.failure.either: "<red>無法從 '<arg:0>' 解析 <arg:1> 或 <arg:2></red>"
argument.parse.failure.namedtextcolor: "<red>'<arg:0>' 不是顏色代碼</red>"
command.reload.config.success: "<white>重新加載配置完成. 耗時 <green><arg:0></green> 毫秒</white>"
command.reload.config.success: "<white>重新加載配置完成. 耗時 <green><arg:0></green> 毫秒</white> <gray>(非同步: <arg:1>ms | 同步: <arg:2>ms)</gray>"
command.reload.config.failure: "<red>重新加載配置失敗,請檢查控制台日誌。</red>"
command.reload.pack.success: "<white>資源包重新加載完成. 耗時 <green><arg:0></green> 毫秒</white>"
command.reload.pack.failure: "<red>重新加載資源包失敗,請檢查控制台日誌。</red>"
command.reload.all.success: "<white>全部重新加載完成. 耗時 <green><arg:0></green> 毫秒</white>"
command.reload.all.success: "<white>全部重新加載完成. 耗時 <green><arg:0></green> 毫秒</white> <gray>(非同步: <arg:1>ms | 同步: <arg:2>ms | 資源包: <arg:3>ms)</gray>"
command.reload.all.failure: "<red>重新加載失敗,請檢查控制台日誌。</red>"
command.item.get.success: "<white>獲得<arg:0>個<arg:1></white>"
command.item.get.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:0>'></red>"
@@ -52,4 +52,5 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
command.search_recipe.not_found: "<red>找不到此物品的配方</red>"
command.search_usage.not_found: "<red>找不到此物品的用途</red>"
command.search_recipe.no_item: "<red>執行此命令前請手持物品</red>"
command.search_usage.no_item: "<red>執行此命令前請手持物品</red>"
command.search_usage.no_item: "<red>執行此命令前請手持物品</red>"
command.totem_animation.failure.not_totem: "<red>'<arg:0>' 不是 totem_of_undying 類型</red>"

View File

@@ -6,7 +6,6 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
@@ -14,7 +13,6 @@ import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.Vec3d;
@@ -39,7 +37,7 @@ public final class CraftEngineBlocks {
*/
@Nullable
public static CustomBlock byId(@NotNull Key id) {
return BukkitBlockManager.instance().getBlock(id).orElse(null);
return BukkitBlockManager.instance().blockById(id).orElse(null);
}
/**

View File

@@ -34,7 +34,7 @@ public class CraftEngineFurniture {
* @return the custom furniture
*/
public static CustomFurniture byId(@NotNull Key id) {
return BukkitFurnitureManager.instance().getFurniture(id).orElse(null);
return BukkitFurnitureManager.instance().furnitureById(id).orElse(null);
}
/**
@@ -139,7 +139,7 @@ public class CraftEngineFurniture {
*/
@Nullable
public static LoadedFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) {
return BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(baseEntity.getEntityId());
return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntity.getEntityId());
}
/**
@@ -152,7 +152,7 @@ public class CraftEngineFurniture {
public static LoadedFurniture getLoadedFurnitureBySeat(@NotNull Entity seat) {
Integer baseEntityId = seat.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
if (baseEntityId == null) return null;
return BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(baseEntityId);
return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntityId);
}
/**
@@ -163,7 +163,7 @@ public class CraftEngineFurniture {
*/
public static boolean remove(@NotNull Entity furniture) {
if (!isFurniture(furniture)) return false;
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(furniture.getEntityId());
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(furniture.getEntityId());
if (loadedFurniture == null) return false;
loadedFurniture.destroy();
return true;
@@ -181,7 +181,7 @@ public class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(furniture)) return false;
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(furniture.getEntityId());
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(furniture.getEntityId());
if (loadedFurniture == null) return false;
remove(loadedFurniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound);
return true;
@@ -201,7 +201,7 @@ public class CraftEngineFurniture {
boolean dropLoot,
boolean playSound) {
if (!isFurniture(furniture)) return false;
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(furniture.getEntityId());
LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(furniture.getEntityId());
if (loadedFurniture == null) return false;
remove(loadedFurniture, player, dropLoot, playSound);
return true;

View File

@@ -8,14 +8,13 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.PushReaction;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
@@ -29,7 +28,10 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.world.GenericGameEvent;
import org.bukkit.inventory.ItemStack;
@@ -58,7 +60,7 @@ public class BlockEventListener implements Listener {
player.swingHand(event.getHand());
}
// send sound if the placed block's sounds are removed
if (ConfigManager.enableSoundSystem()) {
if (Config.enableSoundSystem()) {
Block block = event.getBlock();
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
@@ -169,7 +171,7 @@ public class BlockEventListener implements Listener {
});
}
// sound system
if (ConfigManager.enableSoundSystem()) {
if (Config.enableSoundSystem()) {
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
if (this.manager.isBlockSoundRemoved(ownerBlock)) {
try {
@@ -241,7 +243,7 @@ public class BlockEventListener implements Listener {
if (!BlockStateUtils.isVanillaBlock(stateId)) {
ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId);
player.playSound(playerLocation, state.sounds().stepSound().id().toString(), SoundCategory.BLOCKS, state.sounds().stepSound().volume(), state.sounds().stepSound().pitch());
} else if (ConfigManager.enableSoundSystem()) {
} else if (Config.enableSoundSystem()) {
Object ownerBlock = BlockStateUtils.getBlockOwner(blockState);
if (manager.isBlockSoundRemoved(ownerBlock)) {
try {

View File

@@ -19,10 +19,14 @@ import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.properties.Properties;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
@@ -33,7 +37,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
@@ -45,6 +48,7 @@ import java.util.*;
public class BukkitBlockManager extends AbstractBlockManager {
private static BukkitBlockManager instance;
private final BukkitCraftEngine plugin;
private final BlockParser blockParser;
// A temporary map used to detect whether the same block state corresponds to multiple models.
private final Map<Integer, Key> tempRegistryIdConflictMap = new HashMap<>();
@@ -55,11 +59,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
// The total amount of blocks registered
private int customBlockCount;
// CraftEngine objects
private final Map<Key, CustomBlock> id2CraftEngineBlocks = new HashMap<>();
private final ImmutableBlockState[] stateId2ImmutableBlockStates;
protected final ImmutableBlockState[] stateId2ImmutableBlockStates;
// Minecraft objects
// Cached new blocks $ holders
private ImmutableMap<Key, Integer> internalId2StateId;
@@ -83,25 +83,21 @@ public class BukkitBlockManager extends AbstractBlockManager {
private final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
// for mod, real block id -> state models
private final Map<Key, JsonElement> modBlockStates = new HashMap<>();
// Cached command suggestions
private final List<Suggestion> cachedSuggestions = new ArrayList<>();
// Cached Namespace
private final Set<String> namespacesInUse = new HashSet<>();
// Event listeners
private final BlockEventListener blockEventListener;
private final FallingBlockRemoveListener fallingBlockRemoveListener;
private WorldEditCommandHelper weCommandHelper;
public BukkitBlockManager(BukkitCraftEngine plugin) {
super(plugin);
this.plugin = plugin;
this.blockParser = new BlockParser();
this.initVanillaRegistry();
this.loadMappingsAndAdditionalBlocks();
if (plugin.hasMod() && plugin.requiresRestart()) {
blockEventListener = null;
fallingBlockRemoveListener = null;
stateId2ImmutableBlockStates = null;
stateId2ImmutableBlockStates = new ImmutableBlockState[]{};
return;
}
this.registerBlocks();
@@ -147,11 +143,9 @@ public class BukkitBlockManager extends AbstractBlockManager {
@Override
public void unload() {
super.clearModelsToGenerate();
super.unload();
this.clearCache();
this.appearanceToRealState.clear();
this.id2CraftEngineBlocks.clear();
this.cachedSuggestions.clear();
this.blockStateOverrides.clear();
this.modBlockStates.clear();
if (EmptyBlock.INSTANCE != null)
@@ -221,47 +215,16 @@ public class BukkitBlockManager extends AbstractBlockManager {
return Collections.unmodifiableMap(this.modBlockStates);
}
@Override
public ConfigSectionParser parser() {
return this.blockParser;
}
@Override
public Map<Key, Map<String, JsonElement>> blockOverrides() {
return Collections.unmodifiableMap(this.blockStateOverrides);
}
@Override
public Map<Key, CustomBlock> blocks() {
return Collections.unmodifiableMap(this.id2CraftEngineBlocks);
}
@Override
public Optional<CustomBlock> getBlock(Key key) {
return Optional.ofNullable(this.id2CraftEngineBlocks.get(key));
}
@Override
public Collection<Suggestion> cachedSuggestions() {
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
@Override
public void initSuggestions() {
this.cachedSuggestions.clear();
this.namespacesInUse.clear();
Set<String> states = new HashSet<>();
for (CustomBlock block : this.id2CraftEngineBlocks.values()) {
states.add(block.id().toString());
this.namespacesInUse.add(block.id().namespace());
for (ImmutableBlockState state : block.variantProvider().states()) {
states.add(state.toString());
}
}
for (String state : states) {
this.cachedSuggestions.add(Suggestion.suggestion(state));
}
}
public Set<String> namespacesInUse() {
return Collections.unmodifiableSet(namespacesInUse);
}
public ImmutableMap<Key, List<Integer>> blockAppearanceArranger() {
return blockAppearanceArranger;
}
@@ -368,162 +331,224 @@ public class BukkitBlockManager extends AbstractBlockManager {
}
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
// read block settings
BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false));
// read loot table
LootTable<ItemStack> lootTable = LootTable.fromMap(MiscUtils.castToMap(section.getOrDefault("loot", Map.of()), false));
// read states
Map<String, Property<?>> properties;
Map<String, Integer> appearances;
Map<String, VariantState> variants;
Map<String, Object> stateSection = MiscUtils.castToMap(section.get("state"), true);
if (stateSection != null) {
properties = Map.of();
int internalId = MiscUtils.getAsInt(stateSection.getOrDefault("id", -1));
if (PreConditions.runIfTrue(internalId < 0, () -> plugin.logger().warn(path, "No state id configured for block " + id))) return;
Pair<Key, Integer> pair = parseAppearanceSection(path, stateSection, id);
if (pair == null) return;
appearances = Map.of("default", pair.right());
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, pair.left().value() + "_" + internalId);
int internalBlockRegistryId = MiscUtils.getAsInt(this.internalId2StateId.getOrDefault(internalBlockId, -1));
if (internalBlockRegistryId == -1) {
plugin.logger().warn(path, "Failed to register " + id + " because id " + internalId + " is not a value between 0~" + (MiscUtils.getAsInt(this.registeredRealBlockSlots.get(pair.left()))-1) +
". Consider editing additional-real-blocks.yml if the number of real block IDs is insufficient while there are still available appearances");
return;
}
variants = Map.of("", new VariantState("default", settings, internalBlockRegistryId));
} else {
Map<String, Object> statesSection = MiscUtils.castToMap(section.get("states"), true);
if (statesSection == null) {
plugin.logger().warn(path, "No states configured for block " + id);
return;
}
Map<String, Object> propertySection = MiscUtils.castToMap(statesSection.get("properties"), true);
if (PreConditions.isNull(propertySection, () -> plugin.logger().warn(path, "No properties configured for block " + id))) return;
properties = parseProperties(path, propertySection);
Map<String, Object> appearancesSection = MiscUtils.castToMap(statesSection.get("appearances"), true);
if (PreConditions.isNull(appearancesSection, () -> plugin.logger().warn(path, "No appearances configured for block " + id))) return;
appearances = new HashMap<>();
Map<String, Key> tempTypeMap = new HashMap<>();
for (Map.Entry<String, Object> appearanceEntry : appearancesSection.entrySet()) {
if (appearanceEntry.getValue() instanceof Map<?, ?> appearanceSection) {
Pair<Key, Integer> pair = parseAppearanceSection(path, MiscUtils.castToMap(appearanceSection, false), id);
if (pair == null) return;
appearances.put(appearanceEntry.getKey(), pair.right());
tempTypeMap.put(appearanceEntry.getKey(), pair.left());
}
}
Map<String, Object> variantsSection = MiscUtils.castToMap(statesSection.get("variants"), true);
if (PreConditions.isNull(variantsSection, () -> plugin.logger().warn(path, "No variants configured for block " + id))) return;
variants = new HashMap<>();
for (Map.Entry<String, Object> variantEntry : variantsSection.entrySet()) {
if (variantEntry.getValue() instanceof Map<?, ?> variantSection0) {
Map<String, Object> variantSection = MiscUtils.castToMap(variantSection0, false);
String variantName = variantEntry.getKey();
String appearance = (String) variantSection.get("appearance");
if (appearance == null) {
plugin.logger().warn(path, "No appearance configured for variant " + variantName);
return;
}
if (!appearances.containsKey(appearance)) {
plugin.logger().warn(path, appearance + " is not a valid appearance for block " + id);
return;
}
int internalId = MiscUtils.getAsInt(variantSection.getOrDefault("id", -1));
Key baseBlock = tempTypeMap.get(appearance);
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, baseBlock.value() + "_" + internalId);
int internalBlockRegistryId = MiscUtils.getAsInt(this.internalId2StateId.getOrDefault(internalBlockId, -1));
if (internalBlockRegistryId == -1) {
plugin.logger().warn(path, "Failed to register " + id + " because id " + internalId + " is not a value between 0~" + (MiscUtils.getAsInt(this.registeredRealBlockSlots.getOrDefault(baseBlock, 1))-1) +
". Consider editing additional-real-blocks.yml if the number of real block IDs is insufficient while there are still available appearances");
return;
}
Map<String, Object> anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true);
variants.put(variantName, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId));
}
}
public class BlockParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"blocks", "block"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
// create or get block holder
Holder.Reference<CustomBlock> holder = BuiltInRegistries.BLOCK.get(id).orElseGet(() ->
((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), id)));
// create block
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false);
BukkitCustomBlock block = new BukkitCustomBlock(id, holder, properties, appearances, variants, settings, behaviorSection, lootTable);
@Override
public int loadingSequence() {
return LoadingSequence.BLOCK;
}
// bind appearance
bindAppearance(block);
this.id2CraftEngineBlocks.put(id, block);
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
// check duplicated config
if (byId.containsKey(id)) {
TranslationManager.instance().log("warning.config.block.duplicated", path.toString(), id.toString());
return;
}
// read block settings
BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false));
// read loot table
LootTable<ItemStack> lootTable = LootTable.fromMap(MiscUtils.castToMap(section.getOrDefault("loot", Map.of()), false));
// read states
Map<String, Property<?>> properties;
Map<String, Integer> appearances;
Map<String, VariantState> variants;
Map<String, Object> stateSection = MiscUtils.castToMap(section.get("state"), true);
if (stateSection != null) {
properties = Map.of();
int internalId = MiscUtils.getAsInt(stateSection.getOrDefault("id", -1));
if (internalId < 0) {
TranslationManager.instance().log("warning.config.block.state.lack_real_id", path.toString(), id.toString());
return;
}
// generate mod assets
if (ConfigManager.generateModAssets()) {
Pair<Key, Integer> pair = parseAppearanceSection(pack, path, id, stateSection);
if (pair == null) return;
appearances = Map.of("default", pair.right());
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, pair.left().value() + "_" + internalId);
int internalBlockRegistryId = MiscUtils.getAsInt(internalId2StateId.getOrDefault(internalBlockId, -1));
if (internalBlockRegistryId == -1) {
TranslationManager.instance().log("warning.config.block.state.invalid_real_state_id",
path.toString(),
id.toString(),
pair.left().value() + "_" + internalId,
String.valueOf(MiscUtils.getAsInt(registeredRealBlockSlots.get(pair.left()))-1)
);
return;
}
variants = Map.of("", new VariantState("default", settings, internalBlockRegistryId));
} else {
// states
Map<String, Object> statesSection = MiscUtils.castToMap(section.get("states"), true);
if (statesSection == null) {
TranslationManager.instance().log("warning.config.block.lack_state", path.toString(), id.toString());
return;
}
// properties
Map<String, Object> propertySection = MiscUtils.castToMap(statesSection.get("properties"), true);
if (propertySection == null) {
TranslationManager.instance().log("warning.config.block.state.lack_properties", path.toString(), id.toString());
return;
}
properties = parseProperties(path, id, propertySection);
// appearance
Map<String, Object> appearancesSection = MiscUtils.castToMap(statesSection.get("appearances"), true);
if (appearancesSection == null) {
TranslationManager.instance().log("warning.config.block.state.lack_appearances", path.toString(), id.toString());
return;
}
appearances = new HashMap<>();
Map<String, Key> tempTypeMap = new HashMap<>();
for (Map.Entry<String, Object> appearanceEntry : appearancesSection.entrySet()) {
if (appearanceEntry.getValue() instanceof Map<?, ?> appearanceSection) {
Pair<Key, Integer> pair = parseAppearanceSection(pack, path, id, MiscUtils.castToMap(appearanceSection, false));
if (pair == null) return;
appearances.put(appearanceEntry.getKey(), pair.right());
tempTypeMap.put(appearanceEntry.getKey(), pair.left());
}
}
// variants
Map<String, Object> variantsSection = MiscUtils.castToMap(statesSection.get("variants"), true);
if (variantsSection == null) {
TranslationManager.instance().log("warning.config.block.state.lack_variants", path.toString(), id.toString());
return;
}
variants = new HashMap<>();
for (Map.Entry<String, Object> variantEntry : variantsSection.entrySet()) {
if (variantEntry.getValue() instanceof Map<?, ?> variantSection0) {
Map<String, Object> variantSection = MiscUtils.castToMap(variantSection0, false);
String variantName = variantEntry.getKey();
String appearance = (String) variantSection.get("appearance");
if (appearance == null) {
TranslationManager.instance().log("warning.config.block.state.variant.lack_appearance", path.toString(), id.toString(), variantName);
return;
}
if (!appearances.containsKey(appearance)) {
TranslationManager.instance().log("warning.config.block.state.variant.invalid_appearance", path.toString(), id.toString(), variantName, appearance);
return;
}
int internalId = MiscUtils.getAsInt(variantSection.getOrDefault("id", -1));
Key baseBlock = tempTypeMap.get(appearance);
Key internalBlockId = Key.of(CraftEngine.NAMESPACE, baseBlock.value() + "_" + internalId);
int internalBlockRegistryId = MiscUtils.getAsInt(internalId2StateId.getOrDefault(internalBlockId, -1));
if (internalBlockRegistryId == -1) {
TranslationManager.instance().log("warning.config.block.state.invalid_real_state_id",
path.toString(),
id.toString(),
internalBlockId.toString(),
String.valueOf(MiscUtils.getAsInt(registeredRealBlockSlots.getOrDefault(baseBlock, 1)) - 1)
);
return;
}
Map<String, Object> anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true);
variants.put(variantName, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId));
}
}
}
// create or get block holder
Holder.Reference<CustomBlock> holder = BuiltInRegistries.BLOCK.get(id).orElseGet(() ->
((WritableRegistry<CustomBlock>) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), id)));
// create block
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false);
BukkitCustomBlock block = new BukkitCustomBlock(id, holder, properties, appearances, variants, settings, behaviorSection, lootTable);
// bind appearance and real state
for (ImmutableBlockState state : block.variantProvider().states()) {
Key realBlockId = BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState());
this.modBlockStates.put(realBlockId, this.tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId()));
ImmutableBlockState previous = stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
if (previous != null && !previous.isEmpty()) {
TranslationManager.instance().log("warning.config.block.state.bind_real_state", path.toString(), id.toString(), state.toString(), previous.toString());
continue;
}
stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state;
tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId());
appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId());
}
byId.put(id, block);
// generate mod assets
if (Config.generateModAssets()) {
for (ImmutableBlockState state : block.variantProvider().states()) {
Key realBlockId = BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState());
modBlockStates.put(realBlockId, tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId()));
}
}
}
}
private void bindAppearance(CustomBlock block) {
for (ImmutableBlockState state : block.variantProvider().states()) {
ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
if (previous != null && !previous.isEmpty()) {
this.plugin.logger().severe("[Fatal] Failed to bind real block state for " + state + ": the state is already occupied by " + previous);
continue;
}
this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state;
this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId());
this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId());
}
}
private Map<String, Property<?>> parseProperties(Path path, Map<String, Object> propertiesSection) {
private Map<String, Property<?>> parseProperties(Path path, Key id, Map<String, Object> propertiesSection) {
Map<String, Property<?>> properties = new HashMap<>();
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {
if (entry.getValue() instanceof Map<?, ?> params) {
Property<?> property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(params, false));
properties.put(entry.getKey(), property);
try {
Property<?> property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(params, false));
properties.put(entry.getKey(), property);
} catch (Exception e) {
TranslationManager.instance().log("warning.config.block.state.invalid_property", path.toString(), id.toString(), entry.getKey(), e.getMessage());
}
} else {
this.plugin.logger().warn(path, "Invalid property format: " + entry.getKey());
TranslationManager.instance().log("warning.config.block.state.invalid_property_structure", path.toString(), id.toString(), entry.getKey());
}
}
return properties;
}
@Nullable
private Pair<Key, Integer> parseAppearanceSection(Path path, Map<String, Object> section, Key id) {
private Pair<Key, Integer> parseAppearanceSection(Pack pack, Path path, Key id, Map<String, Object> section) {
// require state non null
String vanillaStateString = (String) section.get("state");
if (PreConditions.isNull(vanillaStateString,
() -> this.plugin.logger().warn(path, "No block state found for: " + id))) return null;
int vanillaStateRegistryId;
try {
vanillaStateRegistryId = parseVanillaStateRegistryId(vanillaStateString);
} catch (BlockStateArrangeException e) {
this.plugin.logger().warn(path, "Failed to load " + id + " - " + e.getMessage(), e);
if (vanillaStateString == null) {
TranslationManager.instance().log("warning.config.block.state.lack_state", path.toString(), id.toString());
return null;
}
// get its registry id
int vanillaStateRegistryId;
VanillaStateParseResult parseResult = parseVanillaStateRegistryId(vanillaStateString);
if (parseResult.success()) {
vanillaStateRegistryId = parseResult.result;
} else {
String[] args = new String[parseResult.args.length + 2];
args[0] = path.toString();
args[1] = id.toString();
System.arraycopy(parseResult.args, 0, args, 2, parseResult.args.length);
TranslationManager.instance().log(parseResult.reason(), args);
return null;
}
// check conflict
Key ifAny = this.tempRegistryIdConflictMap.get(vanillaStateRegistryId);
if (ifAny != null && !ifAny.equals(id)) {
this.plugin.logger().warn(path, "Can't use " + BlockStateUtils.idToBlockState(vanillaStateRegistryId) + " as the base block for " + id + " because the state has already been used by " + ifAny);
TranslationManager.instance().log("warning.config.block.state.conflict", path.toString(), id.toString(), BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(vanillaStateRegistryId)).getAsString(), ifAny.toString());
return null;
}
// require models not to be null
Object models = section.getOrDefault("models", section.get("model"));
if (models == null) {
TranslationManager.instance().log("warning.config.block.state.no_model_set", path.toString(), id.toString());
return null;
}
List<JsonObject> variants = new ArrayList<>();
if (models instanceof Map<?, ?> singleModelSection) {
loadVariantModel(variants, MiscUtils.castToMap(singleModelSection, false));
loadVariantModel(pack, path, id, variants, MiscUtils.castToMap(singleModelSection, false));
} else if (models instanceof List<?> modelList) {
for (Object model : modelList) {
if (model instanceof Map<?,?> singleModelMap) {
loadVariantModel(variants, MiscUtils.castToMap(singleModelMap, false));
loadVariantModel(pack, path, id, variants, MiscUtils.castToMap(singleModelMap, false));
}
}
} else {
this.plugin.logger().warn(path, "No model set for " + id);
return null;
}
if (variants.isEmpty()) return null;
this.tempRegistryIdConflictMap.put(vanillaStateRegistryId, id);
String blockState = BlockStateUtils.idToBlockState(vanillaStateRegistryId).toString();
Key block = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}')));
@@ -543,9 +568,17 @@ public class BukkitBlockManager extends AbstractBlockManager {
return Pair.of(block, vanillaStateRegistryId);
}
private void loadVariantModel(List<JsonObject> variants, Map<String, Object> singleModelMap) {
private void loadVariantModel(Pack pack, Path path, Key id, List<JsonObject> variants, Map<String, Object> singleModelMap) {
JsonObject json = new JsonObject();
String modelPath = (String) singleModelMap.get("path");
if (modelPath == null) {
TranslationManager.instance().log("warning.config.block.state.model.lack_path", path.toString(), id.toString());
return;
}
if (!ResourceLocation.isValid(modelPath)) {
TranslationManager.instance().log("warning.config.block.state.model.invalid_resource_location", path.toString(), id.toString(), modelPath);
return;
}
json.addProperty("model", modelPath);
if (singleModelMap.containsKey("x")) json.addProperty("x", MiscUtils.getAsInt(singleModelMap.get("x")));
if (singleModelMap.containsKey("y")) json.addProperty("y", MiscUtils.getAsInt(singleModelMap.get("y")));
@@ -553,57 +586,61 @@ public class BukkitBlockManager extends AbstractBlockManager {
if (singleModelMap.containsKey("weight")) json.addProperty("weight", MiscUtils.getAsInt(singleModelMap.get("weight")));
Map<String, Object> generationMap = MiscUtils.castToMap(singleModelMap.get("generation"), true);
if (generationMap != null) {
prepareModelGeneration(new ModelGeneration(Key.of(modelPath), generationMap));
prepareModelGeneration(path, id, new ModelGeneration(Key.of(modelPath), generationMap));
}
variants.add(json);
}
private int parseVanillaStateRegistryId(String blockState) throws BlockStateArrangeException {
private VanillaStateParseResult parseVanillaStateRegistryId(String blockState) {
String[] split = blockState.split(":", 3);
PreConditions.runIfTrue(split.length >= 4, () -> {
throw new BlockStateArrangeException("Invalid vanilla block state: " + blockState);
});
if (split.length >= 4) {
return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
}
int registryId;
// minecraft:xxx:0
// xxx:0
String stateOrId = split[split.length - 1];
boolean isId = !stateOrId.contains("[") && !stateOrId.contains("]");
if (isId) {
if (split.length == 1) {
throw new BlockStateArrangeException("Invalid vanilla block state: " + blockState);
}
if (split.length == 1) return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]);
try {
int id = split.length == 2 ? Integer.parseInt(split[1]) : Integer.parseInt(split[2]);
PreConditions.runIfTrue(id < 0, () -> {
throw new BlockStateArrangeException("Invalid block state: " + blockState);
});
if (id < 0) return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
List<Integer> arranger = this.blockAppearanceArranger.get(block);
if (arranger == null) {
throw new BlockStateArrangeException("No freed block state is available for block " + block);
}
if (id >= arranger.size()) {
throw new BlockStateArrangeException(blockState + " is not a valid block state because " + id + " is outside of the range (0~" + (arranger.size() - 1) + ")");
}
if (arranger == null) return VanillaStateParseResult.failure("warning.config.block.state.unavailable_state", new String[]{blockState});
if (id >= arranger.size()) return VanillaStateParseResult.failure("warning.config.block.state.invalid_vanilla_state_id", new String[]{blockState, String.valueOf(arranger.size() - 1)});
registryId = arranger.get(id);
} catch (NumberFormatException e) {
throw new BlockStateArrangeException("Invalid block state: " + blockState);
return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
}
} else {
try {
BlockData blockData = Bukkit.createBlockData(blockState);
registryId = BlockStateUtils.blockDataToId(blockData);
if (!this.blockAppearanceMapper.containsKey(registryId)) {
return VanillaStateParseResult.failure("warning.config.block.state.unavailable_state", new String[]{blockState});
}
} catch (IllegalArgumentException e) {
throw new BlockStateArrangeException("Invalid block state: " + blockState);
return VanillaStateParseResult.failure("warning.config.block.state.invalid_state", new String[]{blockState});
}
}
return registryId;
return VanillaStateParseResult.success(registryId);
}
public record VanillaStateParseResult(boolean success, int result, String reason, String[] args) {
public static VanillaStateParseResult success(int result) {
return new VanillaStateParseResult(true, result, null, null);
}
public static VanillaStateParseResult failure(String reason, String[] args) {
return new VanillaStateParseResult(false, -1, reason, args);
}
}
private void loadMappingsAndAdditionalBlocks() {
this.plugin.logger().info("Loading mappings.yml.");
File mappingFile = new File(plugin.dataFolderFile(), "mappings.yml");
YamlDocument mappings = ConfigManager.instance().loadOrCreateYamlData("mappings.yml");
YamlDocument mappings = Config.instance().loadOrCreateYamlData("mappings.yml");
Map<String, String> blockStateMappings = loadBlockStateMappings(mappings);
this.validateBlockStateMappings(mappingFile, blockStateMappings);
Map<Integer, String> stateMap = new HashMap<>();
@@ -616,7 +653,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
this.blockAppearanceMapper = ImmutableMap.copyOf(appearanceMapper);
this.blockAppearanceArranger = ImmutableMap.copyOf(appearanceArranger);
this.plugin.logger().info("Freed " + this.blockAppearanceMapper.size() + " block state appearances.");
YamlDocument additionalYaml = ConfigManager.instance().loadOrCreateYamlData("additional-real-blocks.yml");
YamlDocument additionalYaml = Config.instance().loadOrCreateYamlData("additional-real-blocks.yml");
this.registeredRealBlockSlots = this.buildRegisteredRealBlockSlots(blockTypeCounter, additionalYaml);
}

View File

@@ -48,7 +48,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
if (this.defaultBlockState != null) {
return this.defaultBlockState;
}
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(this.targetBlock);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().blockById(this.targetBlock);
if (optionalCustomBlock.isPresent()) {
CustomBlock customBlock = optionalCustomBlock.get();
this.defaultBlockState = customBlock.defaultState().customBlockState().handle();

View File

@@ -13,14 +13,13 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootContext;
import net.momirealms.craftengine.core.loot.number.NumberProvider;
import net.momirealms.craftengine.core.loot.number.NumberProviders;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.loot.number.NumberProvider;
import net.momirealms.craftengine.core.loot.number.NumberProviders;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.util.context.ContextKey;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.Vec3i;
import net.momirealms.craftengine.shared.block.BlockBehavior;
@@ -29,7 +28,6 @@ import org.bukkit.World;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;

View File

@@ -30,6 +30,7 @@ public class EntityDataValue {
public static final Object Serializers$OPTIONAL_BLOCK_POS;
public static final Object Serializers$DIRECTION;
public static final Object Serializers$OPTIONAL_UUID;
public static final Object Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE;
public static final Object Serializers$OPTIONAL_GLOBAL_POS;
public static final Object Serializers$COMPOUND_TAG;
public static final Object Serializers$VILLAGER_DATA;
@@ -64,7 +65,10 @@ public class EntityDataValue {
Serializers$BLOCK_POS = initSerializersByName("BLOCK_POS");
Serializers$OPTIONAL_BLOCK_POS = initSerializersByName("OPTIONAL_BLOCK_POS");
Serializers$DIRECTION = initSerializersByName("DIRECTION");
Serializers$OPTIONAL_UUID = initSerializersByName("OPTIONAL_UUID");
if (!VersionHelper.isVersionNewerThan1_21_5()) Serializers$OPTIONAL_UUID = initSerializersByName("OPTIONAL_UUID");
else Serializers$OPTIONAL_UUID = null;
if (VersionHelper.isVersionNewerThan1_21_5()) Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE = initSerializersByName("OPTIONAL_LIVING_ENTITY_REFERENCE");
else Serializers$OPTIONAL_LIVING_ENTITY_REFERENCE = null;
Serializers$OPTIONAL_GLOBAL_POS = initSerializersByName("OPTIONAL_GLOBAL_POS");
Serializers$COMPOUND_TAG = initSerializersByName("COMPOUND_TAG");
Serializers$VILLAGER_DATA = initSerializersByName("VILLAGER_DATA");

View File

@@ -10,19 +10,21 @@ import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import org.bukkit.*;
import org.bukkit.entity.*;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.persistence.PersistentDataType;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
@@ -31,35 +33,35 @@ import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class BukkitFurnitureManager implements FurnitureManager {
public class BukkitFurnitureManager extends AbstractFurnitureManager {
public static final NamespacedKey FURNITURE_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:furniture_id"));
public static final NamespacedKey FURNITURE_ANCHOR_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:anchor_type"));
public static final NamespacedKey FURNITURE_SEAT_BASE_ENTITY_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:seat_to_base_entity"));
public static final NamespacedKey FURNITURE_SEAT_VECTOR_3F_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:seat_vector"));
private static BukkitFurnitureManager instance;
private final BukkitCraftEngine plugin;
private final Map<Key, CustomFurniture> byId = new HashMap<>();
private final FurnitureParser furnitureParser;
private final Map<Integer, LoadedFurniture> furnitureByRealEntityId = new ConcurrentHashMap<>(256, 0.5f);
private final Map<Integer, LoadedFurniture> furnitureByEntityId = new ConcurrentHashMap<>(512, 0.5f);
// Event listeners
private final Listener dismountListener;
private final FurnitureEventListener furnitureEventListener;
// tick task
private SchedulerTask tickTask;
// Cached command suggestions
private final List<Suggestion> cachedSuggestions = new ArrayList<>();
public static BukkitFurnitureManager instance() {
return instance;
}
public BukkitFurnitureManager(BukkitCraftEngine plugin) {
instance = this;
this.plugin = plugin;
this.furnitureParser = new FurnitureParser();
this.furnitureEventListener = new FurnitureEventListener(this);
this.dismountListener = VersionHelper.isVersionNewerThan1_20_3() ? new DismountListener1_20_3(this) : new DismountListener1_20(this::handleDismount);
instance = this;
}
@Override
public Furniture place(CustomFurniture furniture, Vec3d vec3d, net.momirealms.craftengine.core.world.World world, AnchorType anchorType, boolean playSound) {
return this.place(furniture, new Location((World) world.platformWorld(), vec3d.x(), vec3d.y(), vec3d.z()), anchorType, playSound);
}
public LoadedFurniture place(CustomFurniture furniture, Location location, AnchorType anchorType, boolean playSound) {
@@ -77,151 +79,138 @@ public class BukkitFurnitureManager implements FurnitureManager {
SoundData data = furniture.settings().sounds().placeSound();
location.getWorld().playSound(location, data.id().toString(), SoundCategory.BLOCKS, data.volume(), data.pitch());
}
return getLoadedFurnitureByRealEntityId(furnitureEntity.getEntityId());
return loadedFurnitureByRealEntityId(furnitureEntity.getEntityId());
}
@Override
public void delayedLoad() {
this.initSuggestions();
public ConfigSectionParser parser() {
return this.furnitureParser;
}
@Override
public void initSuggestions() {
this.cachedSuggestions.clear();
for (Key key : this.byId.keySet()) {
this.cachedSuggestions.add(Suggestion.suggestion(key.toString()));
}
}
public class FurnitureParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" };
@Override
public Collection<Suggestion> cachedSuggestions() {
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
@SuppressWarnings("unchecked")
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
Map<String, Object> lootMap = MiscUtils.castToMap(section.get("loot"), true);
Map<String, Object> settingsMap = MiscUtils.castToMap(section.get("settings"), true);
Map<String, Object> placementMap = MiscUtils.castToMap(section.get("placement"), true);
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
if (placementMap == null) {
throw new IllegalArgumentException("Missing required parameter 'placement' for furniture " + id);
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
for (Map.Entry<String, Object> entry : placementMap.entrySet()) {
// anchor type
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), true);
@Override
public int loadingSequence() {
return LoadingSequence.FURNITURE;
}
// furniture display elements
List<FurnitureElement> elements = new ArrayList<>();
List<Map<String, Object>> elementConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("elements", List.of());
for (Map<String, Object> element : elementConfigs) {
String key = (String) element.get("item");
if (key == null) {
throw new IllegalArgumentException("Missing required parameter 'item' for furniture " + id);
@SuppressWarnings("unchecked")
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (byId.containsKey(id)) {
TranslationManager.instance().log("warning.config.furniture.duplicated", path.toString(), id.toString());
return;
}
Map<String, Object> lootMap = MiscUtils.castToMap(section.get("loot"), true);
Map<String, Object> settingsMap = MiscUtils.castToMap(section.get("settings"), true);
Map<String, Object> placementMap = MiscUtils.castToMap(section.get("placement"), true);
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
if (placementMap == null) {
TranslationManager.instance().log("warning.config.furniture.lack_placement", path.toString(), id.toString());
return;
}
for (Map.Entry<String, Object> entry : placementMap.entrySet()) {
// anchor type
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), true);
// furniture display elements
List<FurnitureElement> elements = new ArrayList<>();
List<Map<String, Object>> elementConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("elements", List.of());
for (Map<String, Object> element : elementConfigs) {
String key = (String) element.get("item");
if (key == null) {
TranslationManager.instance().log("warning.config.furniture.element.lack_item", path.toString(), id.toString());
return;
}
ItemDisplayContext transform = ItemDisplayContext.valueOf(element.getOrDefault("transform", "NONE").toString().toUpperCase(Locale.ENGLISH));
Billboard billboard = Billboard.valueOf(element.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH));
FurnitureElement furnitureElement = new BukkitFurnitureElement(Key.of(key), billboard, transform,
MiscUtils.getVector3f(element.getOrDefault("scale", "1")),
MiscUtils.getVector3f(element.getOrDefault("translation", "0")),
MiscUtils.getVector3f(element.getOrDefault("position", "0")),
MiscUtils.getQuaternionf(element.getOrDefault("rotation", "0"))
);
elements.add(furnitureElement);
}
// add colliders
List<Collider> colliders = new ArrayList<>();
// external model providers
Optional<ExternalModel> externalModel;
if (placementArguments.containsKey("model-engine")) {
externalModel = Optional.of(new ModelEngineModel(placementArguments.get("model-engine").toString()));
} else if (placementArguments.containsKey("better-model")) {
externalModel = Optional.of(new BetterModelModel(placementArguments.get("better-model").toString()));
} else {
externalModel = Optional.empty();
}
// add hitboxes
List<Map<String, Object>> hitboxConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("hitboxes", List.of());
List<HitBox> hitboxes = new ArrayList<>();
for (Map<String, Object> config : hitboxConfigs) {
HitBox hitBox = HitBoxTypes.fromMap(config);
hitboxes.add(hitBox);
hitBox.optionalCollider().ifPresent(colliders::add);
}
if (hitboxes.isEmpty() && externalModel.isEmpty()) {
hitboxes.add(InteractionHitBox.DEFAULT);
}
// rules
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
if (ruleSection != null) {
RotationRule rotationRule = Optional.ofNullable((String) ruleSection.get("rotation"))
.map(it -> RotationRule.valueOf(it.toUpperCase(Locale.ENGLISH)))
.orElse(RotationRule.ANY);
AlignmentRule alignmentRule = Optional.ofNullable((String) ruleSection.get("alignment"))
.map(it -> AlignmentRule.valueOf(it.toUpperCase(Locale.ENGLISH)))
.orElse(AlignmentRule.CENTER);
placements.put(anchorType, new CustomFurniture.Placement(
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBox[0]),
colliders.toArray(new Collider[0]),
rotationRule,
alignmentRule,
externalModel
));
} else {
placements.put(anchorType, new CustomFurniture.Placement(
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBox[0]),
colliders.toArray(new Collider[0]),
RotationRule.ANY,
AlignmentRule.CENTER,
externalModel
));
}
ItemDisplayContext transform = ItemDisplayContext.valueOf(element.getOrDefault("transform", "NONE").toString().toUpperCase(Locale.ENGLISH));
Billboard billboard = Billboard.valueOf(element.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH));
FurnitureElement furnitureElement = new BukkitFurnitureElement(Key.of(key), billboard, transform,
MiscUtils.getVector3f(element.getOrDefault("scale", "1")),
MiscUtils.getVector3f(element.getOrDefault("translation", "0")),
MiscUtils.getVector3f(element.getOrDefault("position", "0")),
MiscUtils.getQuaternionf(element.getOrDefault("rotation", "0"))
);
elements.add(furnitureElement);
}
// add colliders
List<Collider> colliders = new ArrayList<>();
// List<Map<String, Object>> colliderConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("colliders", List.of());
// for (Map<String, Object> config : colliderConfigs) {
// if (!config.containsKey("position")) {
// colliders.add(new Collider(
// (boolean) config.getOrDefault("can-be-hit-by-projectile", false),
// MiscUtils.getVector3d(config.getOrDefault("point-1", "0")),
// MiscUtils.getVector3d(config.getOrDefault("point-2", "0"))
// ));
// } else {
// colliders.add(new Collider(
// (boolean) config.getOrDefault("can-be-hit-by-projectile", false),
// MiscUtils.getVector3f(config.getOrDefault("position", "0")),
// MiscUtils.getAsFloat(config.getOrDefault("width", "1")),
// MiscUtils.getAsFloat(config.getOrDefault("height", "1"))
// ));
// }
// }
CustomFurniture furniture = new CustomFurniture(
id,
FurnitureSettings.fromMap(settingsMap),
placements,
lootMap == null ? null : LootTable.fromMap(lootMap)
);
// external model providers
Optional<ExternalModel> externalModel;
if (placementArguments.containsKey("model-engine")) {
externalModel = Optional.of(new ModelEngineModel(placementArguments.get("model-engine").toString()));
} else if (placementArguments.containsKey("better-model")) {
externalModel = Optional.of(new BetterModelModel(placementArguments.get("better-model").toString()));
} else {
externalModel = Optional.empty();
}
// add hitboxes
List<Map<String, Object>> hitboxConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("hitboxes", List.of());
List<HitBox> hitboxes = new ArrayList<>();
for (Map<String, Object> config : hitboxConfigs) {
HitBox hitBox = HitBoxTypes.fromMap(config);
hitboxes.add(hitBox);
hitBox.optionalCollider().ifPresent(colliders::add);
}
if (hitboxes.isEmpty() && externalModel.isEmpty()) {
hitboxes.add(InteractionHitBox.DEFAULT);
}
// rules
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
if (ruleSection != null) {
RotationRule rotationRule = Optional.ofNullable((String) ruleSection.get("rotation"))
.map(it -> RotationRule.valueOf(it.toUpperCase(Locale.ENGLISH)))
.orElse(RotationRule.ANY);
AlignmentRule alignmentRule = Optional.ofNullable((String) ruleSection.get("alignment"))
.map(it -> AlignmentRule.valueOf(it.toUpperCase(Locale.ENGLISH)))
.orElse(AlignmentRule.CENTER);
placements.put(anchorType, new CustomFurniture.Placement(
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBox[0]),
colliders.toArray(new Collider[0]),
rotationRule,
alignmentRule,
externalModel
));
} else {
placements.put(anchorType, new CustomFurniture.Placement(
elements.toArray(new FurnitureElement[0]),
hitboxes.toArray(new HitBox[0]),
colliders.toArray(new Collider[0]),
RotationRule.ANY,
AlignmentRule.CENTER,
externalModel
));
}
byId.put(id, furniture);
}
CustomFurniture furniture = new CustomFurniture(
id,
FurnitureSettings.fromMap(settingsMap),
placements,
lootMap == null ? null : LootTable.fromMap(lootMap)
);
this.byId.put(id, furniture);
}
public void tick() {
}
@Override
public void delayedInit() {
Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.bootstrap());
Bukkit.getPluginManager().registerEvents(this.furnitureEventListener, this.plugin.bootstrap());
this.tickTask = plugin.scheduler().sync().runRepeating(this::tick, 1, 1);
for (World world : Bukkit.getWorlds()) {
List<Entity> entities = world.getEntities();
for (Entity entity : entities) {
@@ -230,18 +219,10 @@ public class BukkitFurnitureManager implements FurnitureManager {
}
}
@Override
public void unload() {
this.byId.clear();
}
@Override
public void disable() {
HandlerList.unregisterAll(this.dismountListener);
HandlerList.unregisterAll(this.furnitureEventListener);
if (tickTask != null && !tickTask.cancelled()) {
tickTask.cancel();
}
unload();
for (Player player : Bukkit.getOnlinePlayers()) {
Entity vehicle = player.getVehicle();
@@ -251,23 +232,20 @@ public class BukkitFurnitureManager implements FurnitureManager {
}
}
@Override
public Optional<CustomFurniture> getFurniture(Key id) {
return Optional.ofNullable(this.byId.get(id));
}
@Override
public boolean isFurnitureRealEntity(int entityId) {
return this.furnitureByRealEntityId.containsKey(entityId);
}
@Nullable
public LoadedFurniture getLoadedFurnitureByRealEntityId(int entityId) {
@Override
public LoadedFurniture loadedFurnitureByRealEntityId(int entityId) {
return this.furnitureByRealEntityId.get(entityId);
}
@Override
@Nullable
public LoadedFurniture getLoadedFurnitureByEntityId(int entityId) {
public LoadedFurniture loadedFurnitureByEntityId(int entityId) {
return this.furnitureByEntityId.get(entityId);
}
@@ -296,7 +274,7 @@ public class BukkitFurnitureManager implements FurnitureManager {
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = getFurniture(key);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isEmpty()) return;
CustomFurniture customFurniture = optionalFurniture.get();
LoadedFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId());
@@ -318,7 +296,7 @@ public class BukkitFurnitureManager implements FurnitureManager {
String id = entity.getPersistentDataContainer().get(FURNITURE_KEY, PersistentDataType.STRING);
if (id == null) return;
Key key = Key.of(id);
Optional<CustomFurniture> optionalFurniture = getFurniture(key);
Optional<CustomFurniture> optionalFurniture = furnitureById(key);
if (optionalFurniture.isPresent()) {
CustomFurniture customFurniture = optionalFurniture.get();
LoadedFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId());
@@ -327,8 +305,8 @@ public class BukkitFurnitureManager implements FurnitureManager {
return;
}
// Remove the entity if it's not a valid furniture
if (ConfigManager.removeInvalidFurniture()) {
if (ConfigManager.furnitureToRemove().isEmpty() || ConfigManager.furnitureToRemove().contains(id)) {
if (Config.removeInvalidFurniture()) {
if (Config.furnitureToRemove().isEmpty() || Config.furnitureToRemove().contains(id)) {
entity.remove();
}
}
@@ -375,7 +353,7 @@ public class BukkitFurnitureManager implements FurnitureManager {
Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER);
if (baseFurniture == null) return;
vehicle.remove();
LoadedFurniture furniture = getLoadedFurnitureByRealEntityId(baseFurniture);
LoadedFurniture furniture = loadedFurnitureByRealEntityId(baseFurniture);
if (furniture == null) {
return;
}

View File

@@ -6,12 +6,15 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.EntityUtils;
import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.furniture.*;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.ArrayUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.QuaternionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Location;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.*;
@@ -24,7 +27,7 @@ import org.joml.Vector3f;
import java.lang.ref.WeakReference;
import java.util.*;
public class LoadedFurniture {
public class LoadedFurniture implements Furniture {
private final Key id;
private final CustomFurniture furniture;
private final AnchorType anchorType;
@@ -144,6 +147,7 @@ public class LoadedFurniture {
}
}
@Override
public void initializeColliders() {
Object world = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(this.location.getWorld());
for (CollisionEntity entity : this.collisionEntities) {
@@ -161,6 +165,16 @@ public class LoadedFurniture {
}
}
@Override
public Vec3d position() {
return new Vec3d(location.getX(), location.getY(), location.getZ());
}
@Override
public World world() {
return new BukkitWorld(this.location.getWorld());
}
@NotNull
public Location location() {
return this.location.clone();
@@ -175,10 +189,12 @@ public class LoadedFurniture {
return entity;
}
@Override
public boolean isValid() {
return baseEntity().isValid();
}
@Override
public void destroy() {
if (!isValid()) {
return;
@@ -197,6 +213,7 @@ public class LoadedFurniture {
this.seats.clear();
}
@Override
public void destroySeats() {
for (Entity entity : this.seats) {
entity.remove();
@@ -204,6 +221,7 @@ public class LoadedFurniture {
this.seats.clear();
}
@Override
public Optional<Seat> findFirstAvailableSeat(int targetEntityId) {
HitBox hitbox = hitBoxes.get(targetEntityId);
if (hitbox == null) return Optional.empty();
@@ -216,14 +234,12 @@ public class LoadedFurniture {
.findFirst();
}
@Override
public boolean removeOccupiedSeat(Vector3f seat) {
return this.occupiedSeats.remove(seat);
}
public boolean removeOccupiedSeat(Seat seat) {
return this.removeOccupiedSeat(seat.offset());
}
@Override
public boolean tryOccupySeat(Seat seat) {
if (this.occupiedSeats.contains(seat.offset())) {
return false;
@@ -232,10 +248,12 @@ public class LoadedFurniture {
return true;
}
@Override
public UUID uuid() {
return this.baseEntity().getUniqueId();
}
@Override
public int baseEntityId() {
return this.baseEntityId;
}
@@ -254,31 +272,29 @@ public class LoadedFurniture {
return this.collisionEntities;
}
@NotNull
public AnchorType anchorType() {
@Override
public @NotNull AnchorType anchorType() {
return this.anchorType;
}
@NotNull
public Key id() {
@Override
public @NotNull Key id() {
return this.id;
}
@NotNull
public CustomFurniture config() {
@Override
public @NotNull CustomFurniture config() {
return this.furniture;
}
@Override
public boolean hasExternalModel() {
return hasExternalModel;
}
public CustomFurniture.Placement placement() {
return this.placement;
}
public Map<Integer, HitBox> hitBoxes() {
return this.hitBoxes;
@Override
public void spawnSeatEntityForPlayer(net.momirealms.craftengine.core.entity.player.Player player, Seat seat) {
spawnSeatEntityForPlayer((Player) player.platformPlayer(), seat);
}
public void spawnSeatEntityForPlayer(org.bukkit.entity.Player player, Seat seat) {

View File

@@ -36,7 +36,7 @@ public class ShulkerHitBox extends AbstractHitBox {
ShulkerData.Peek.addEntityDataIfNotDefaultValue(peek, this.cachedShulkerValues);
ShulkerData.Color.addEntityDataIfNotDefaultValue((byte) 0, this.cachedShulkerValues);
// ShulkerData.AttachFace.addEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(direction), this.cachedShulkerValues);
// ShulkerData.AttachFace.addEntityDataIfNotDefaultValue(DirectionUtils.toNMSDirection(direction), this.cachedShulkerValues);
ShulkerData.NoGravity.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues);
ShulkerData.Silent.addEntityDataIfNotDefaultValue(true, this.cachedShulkerValues);
ShulkerData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, this.cachedShulkerValues); // 无ai
@@ -119,16 +119,27 @@ public class ShulkerHitBox extends AbstractHitBox {
public void addSpawnPackets(int[] entityIds, double x, double y, double z, float yaw, Quaternionf conjugated, BiConsumer<Object, Boolean> packets) {
Vector3f offset = conjugated.transform(new Vector3f(position()));
try {
double originalY = y + offset.y;
double integerPart = Math.floor(originalY);
double fractionalPart = originalY - integerPart;
double processedY = (fractionalPart >= 0.5) ? integerPart + 1 : originalY;
packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityIds[0], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
entityIds[0], UUID.randomUUID(), x + offset.x, originalY, z - offset.z, 0, yaw,
Reflections.instance$EntityType$ITEM_DISPLAY, 0, Reflections.instance$Vec3$Zero, 0
), false);
packets.accept(Reflections.constructor$ClientboundAddEntityPacket.newInstance(
entityIds[1], UUID.randomUUID(), x + offset.x, y + offset.y, z - offset.z, 0, yaw,
entityIds[1], UUID.randomUUID(), x + offset.x, processedY, z - offset.z, 0, yaw,
Reflections.instance$EntityType$SHULKER, 0, Reflections.instance$Vec3$Zero, 0
), false);
packets.accept(Reflections.constructor$ClientboundSetEntityDataPacket.newInstance(entityIds[1], List.copyOf(this.cachedShulkerValues)), false);
packets.accept(FastNMS.INSTANCE.constructor$ClientboundSetPassengersPacket(entityIds[0], entityIds[1]), false);
if (originalY != processedY) {
double deltaY = originalY - processedY;
short ya = (short) (deltaY * 8192);
packets.accept(Reflections.constructor$ClientboundMoveEntityPacket$Pos.newInstance(
entityIds[1], (short) 0, ya, (short) 0, true
), false);
}
if (VersionHelper.isVersionNewerThan1_20_5()) {
Object attributeInstance = Reflections.constructor$AttributeInstance.newInstance(Reflections.instance$Holder$Attribute$scale, (Consumer<?>) (o) -> {});
Reflections.method$AttributeInstance$setBaseValue.invoke(attributeInstance, scale);

View File

@@ -4,9 +4,9 @@ import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.font.AbstractImageManager;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.font.AbstractFontManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.CharacterUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -19,11 +19,11 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Consumer;
public class BukkitImageManager extends AbstractImageManager implements Listener {
public class BukkitFontManager extends AbstractFontManager implements Listener {
private final BukkitCraftEngine plugin;
private final Object serializer;
public BukkitImageManager(BukkitCraftEngine plugin) {
public BukkitFontManager(BukkitCraftEngine plugin) {
super(plugin);
this.plugin = plugin;
try {
@@ -48,22 +48,22 @@ public class BukkitImageManager extends AbstractImageManager implements Listener
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
@SuppressWarnings("UnstableApiUsage")
public void onChat(AsyncChatDecorateEvent event) {
if (!ConfigManager.filterChat()) return;
if (!Config.filterChat()) return;
this.processChatEvent(event);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
@SuppressWarnings("UnstableApiUsage")
public void onChatCommand(AsyncChatCommandDecorateEvent event) {
if (!ConfigManager.filterChat()) return;
if (!Config.filterChat()) return;
this.processChatEvent(event);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void onCommand(PlayerCommandPreprocessEvent event) {
if (!ConfigManager.filterCommand()) return;
if (!Config.filterCommand()) return;
if (!this.isDefaultFontInUse()) return;
if (event.getPlayer().hasPermission(ImageManager.BYPASS_COMMAND)) {
if (event.getPlayer().hasPermission(FontManager.BYPASS_COMMAND)) {
return;
}
runIfContainsIllegalCharacter(event.getMessage(), event::setMessage);
@@ -74,7 +74,7 @@ public class BukkitImageManager extends AbstractImageManager implements Listener
Player player = event.player();
if (player == null) return;
if (!this.isDefaultFontInUse()) return;
if (player.hasPermission(ImageManager.BYPASS_CHAT)) {
if (player.hasPermission(FontManager.BYPASS_CHAT)) {
return;
}
try {
@@ -101,7 +101,7 @@ public class BukkitImageManager extends AbstractImageManager implements Listener
boolean hasIllegal = false;
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (!isIllegalCharacter(codepoint)) {
if (!isIllegalCodepoint(codepoint)) {
newCodepoints[i] = codepoint;
} else {
newCodepoints[i] = '*';

View File

@@ -18,13 +18,17 @@ import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
import net.momirealms.craftengine.core.item.modifier.ItemModelModifier;
import net.momirealms.craftengine.core.pack.LegacyOverridesModel;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration;
import net.momirealms.craftengine.core.pack.model.*;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.pack.model.select.ChargeTypeSelectProperty;
import net.momirealms.craftengine.core.pack.model.select.TrimMaterialSelectProperty;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
@@ -59,6 +63,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
private final BukkitCraftEngine plugin;
private final ItemEventListener itemEventListener;
private final DebugStickListener debugStickListener;
private final ItemParser itemParser;
public BukkitItemManager(BukkitCraftEngine plugin) {
super(plugin);
@@ -66,6 +71,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.factory = BukkitItemFactory.create(plugin);
this.itemEventListener = new ItemEventListener(plugin);
this.debugStickListener = new DebugStickListener(plugin);
this.itemParser = new ItemParser();
this.registerAllVanillaItems();
instance = this;
}
@@ -120,6 +126,11 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
HandlerList.unregisterAll(this.debugStickListener);
}
@Override
public ConfigSectionParser parser() {
return this.itemParser;
}
@Override
public ItemStack buildCustomItemStack(Key id, Player player) {
return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1)).orElse(null);
@@ -176,173 +187,209 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.id();
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
// just register for recipes
Holder.Reference<Key> holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id)
.orElseGet(() -> ((WritableRegistry<Key>) BuiltInRegistries.OPTIMIZED_ITEM_ID)
.register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id));
public class ItemParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"};
String materialStringId = (String) section.get("material");
Material material = MaterialUtils.getMaterial(materialStringId);
if (material == null) {
plugin.logger().warn(path, "material " + Optional.ofNullable(materialStringId).map(it -> it + " ").orElse("") + "does not exist for item " + id);
return;
}
Key materialId = Key.of(material.getKey().namespace(), material.getKey().value());
int customModelData = MiscUtils.getAsInt(section.getOrDefault("custom-model-data", 0));
Key itemModelKey = null;
CustomItem.Builder<ItemStack> itemBuilder = BukkitCustomItem.builder().id(id).material(materialId);
itemBuilder.modifier(new IdModifier<>(id));
boolean hasItemModelSection = section.containsKey("item-model");
// To get at least one model provider
// Sets some basic model info
if (customModelData != 0) {
itemBuilder.modifier(new CustomModelDataModifier<>(customModelData));
}
// Requires the item to have model before apply item-model
else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isVersionNewerThan1_21_2()) {
// check server version here because components require 1.21.2+
// customize or use the id
itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString());
itemBuilder.modifier(new ItemModelModifier<>(itemModelKey));
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
if (hasItemModelSection) {
itemModelKey = Key.from(section.get("item-model").toString());
itemBuilder.modifier(new ItemModelModifier<>(itemModelKey));
@Override
public int loadingSequence() {
return LoadingSequence.ITEM;
}
// Get item behaviors
Object behaviorConfig = section.get("behavior");
if (behaviorConfig instanceof List<?>) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> behavior = (List<Map<String, Object>>) behaviorConfig;
List<ItemBehavior> behaviors = new ArrayList<>();
for (Map<String, Object> behaviorMap : behavior) {
behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap));
}
itemBuilder.behavior(behaviors);
} else if (behaviorConfig instanceof Map<?, ?>) {
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.get("behavior"), true);
if (behaviorSection != null) {
itemBuilder.behavior(ItemBehaviors.fromMap(pack, path, id, behaviorSection));
}
}
// Get item data
Map<String, Object> dataSection = MiscUtils.castToMap(section.get("data"), true);
if (dataSection != null) {
for (Map.Entry<String, Object> dataEntry : dataSection.entrySet()) {
Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> {
try {
itemBuilder.modifier(function.apply(dataEntry.getValue()));
} catch (IllegalArgumentException e) {
plugin.logger().warn("Invalid data format", e);
}
});
}
}
if (section.containsKey("settings")) {
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), false);
itemBuilder.settings(ItemSettings.fromMap(settings));
}
CustomItem<ItemStack> customItem = itemBuilder.build();
this.customItems.put(id, customItem);
this.cachedSuggestions.add(Suggestion.suggestion(id.toString()));
// post process
// register tags
Set<Key> tags = customItem.settings().tags();
for (Key tag : tags) {
this.customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(holder);
}
// create trims
EquipmentGeneration equipment = customItem.settings().equipment();
if (equipment != null) {
EquipmentData modern = equipment.modernData();
// 1.21.2+
if (modern != null) {
this.equipmentsToGenerate.add(equipment);
}
// TODO 1.20
}
// add it to category
if (section.containsKey("category")) {
this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
}
// model part, can be null
// but if it exists, either custom model data or item model should be configured
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
if (modelSection == null) {
return;
}
boolean hasModel = false;
if (customModelData != 0) {
hasModel= true;
// use custom model data
// check conflict
Map<Integer, Key> conflict = this.cmdConflictChecker.computeIfAbsent(materialId, k -> new HashMap<>());
if (conflict.containsKey(customModelData)) {
plugin.logger().warn(path, "Failed to create model for " + id + " because custom-model-data " + customModelData + " already occupied by item " + conflict.get(customModelData).toString());
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (customItems.containsKey(id)) {
TranslationManager.instance().log("warning.config.item.duplicated", path.toString(), id.toString());
return;
}
conflict.put(customModelData, id);
// just register for recipes
Holder.Reference<Key> holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id)
.orElseGet(() -> ((WritableRegistry<Key>) BuiltInRegistries.OPTIMIZED_ITEM_ID)
.register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id));
// Parse models
ItemModel model = ItemModels.fromMap(modelSection);
for (ModelGeneration generation : model.modelsToGenerate()) {
prepareModelGeneration(generation);
String materialStringId = (String) section.get("material");
if (materialStringId == null) {
TranslationManager.instance().log("warning.config.item.lack_material", path.toString(), id.toString());
return;
}
if (ConfigManager.packMaxVersion() > 21.39f) {
TreeMap<Integer, ItemModel> map = this.modernOverrides.computeIfAbsent(materialId, k -> new TreeMap<>());
map.put(customModelData, model);
Material material = MaterialUtils.getMaterial(materialStringId);
if (material == null) {
TranslationManager.instance().log("warning.config.item.invalid_material", path.toString(), id.toString(), materialStringId);
return;
}
Key materialId = Key.of(material.getKey().namespace(), material.getKey().value());
int customModelData = MiscUtils.getAsInt(section.getOrDefault("custom-model-data", 0));
Key itemModelKey = null;
CustomItem.Builder<ItemStack> itemBuilder = BukkitCustomItem.builder().id(id).material(materialId);
itemBuilder.modifier(new IdModifier<>(id));
boolean hasItemModelSection = section.containsKey("item-model");
// To get at least one model provider
// Sets some basic model info
if (customModelData != 0) {
itemBuilder.modifier(new CustomModelDataModifier<>(customModelData));
}
// Requires the item to have model before apply item-model
else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isVersionNewerThan1_21_2()) {
// check server version here because components require 1.21.2+
// customize or use the id
itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString());
if (ResourceLocation.isValid(itemModelKey.toString())) {
itemBuilder.modifier(new ItemModelModifier<>(itemModelKey));
} else {
itemModelKey = null;
}
}
if (ConfigManager.packMinVersion() < 21.39f) {
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, customModelData);
TreeSet<LegacyOverridesModel> lom = this.legacyOverrides.computeIfAbsent(materialId, k -> new TreeSet<>());
lom.addAll(legacyOverridesModels);
}
}
if (itemModelKey != null) {
hasModel = true;
// use components
ItemModel model = ItemModels.fromMap(modelSection);
for (ModelGeneration generation : model.modelsToGenerate()) {
prepareModelGeneration(generation);
if (hasItemModelSection) {
itemModelKey = Key.from(section.get("item-model").toString());
itemBuilder.modifier(new ItemModelModifier<>(itemModelKey));
}
if (ConfigManager.packMaxVersion() > 21.39f) {
this.modernItemModels1_21_4.put(itemModelKey, model);
// Get item behaviors
Object behaviorConfig = section.get("behavior");
if (behaviorConfig instanceof List<?>) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> behavior = (List<Map<String, Object>>) behaviorConfig;
List<ItemBehavior> behaviors = new ArrayList<>();
for (Map<String, Object> behaviorMap : behavior) {
behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap));
}
itemBuilder.behavior(behaviors);
} else if (behaviorConfig instanceof Map<?, ?>) {
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.get("behavior"), true);
if (behaviorSection != null) {
itemBuilder.behavior(ItemBehaviors.fromMap(pack, path, id, behaviorSection));
}
}
if (ConfigManager.packMaxVersion() > 21.19f && ConfigManager.packMinVersion() < 21.39f) {
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, 0);
if (legacyOverridesModels.isEmpty()) {
plugin.logger().warn(path, "Can't convert " + id + "'s model to legacy format.");
// Get item data
Map<String, Object> dataSection = MiscUtils.castToMap(section.get("data"), true);
if (dataSection != null) {
for (Map.Entry<String, Object> dataEntry : dataSection.entrySet()) {
Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> {
try {
itemBuilder.modifier(function.apply(dataEntry.getValue()));
} catch (IllegalArgumentException e) {
plugin.logger().warn("Invalid data format", e);
}
});
}
}
if (section.containsKey("settings")) {
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), false);
itemBuilder.settings(ItemSettings.fromMap(settings));
}
CustomItem<ItemStack> customItem = itemBuilder.build();
customItems.put(id, customItem);
// cache command suggestions
cachedSuggestions.add(Suggestion.suggestion(id.toString()));
if (material == Material.TOTEM_OF_UNDYING)
cachedTotemSuggestions.add(Suggestion.suggestion(id.toString()));
// post process
// register tags
Set<Key> tags = customItem.settings().tags();
for (Key tag : tags) {
customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(holder);
}
// create trims
EquipmentGeneration equipment = customItem.settings().equipment();
if (equipment != null) {
EquipmentData modern = equipment.modernData();
// 1.21.2+
if (modern != null) {
equipmentsToGenerate.add(equipment);
}
// TODO 1.20
}
// add it to category
if (section.containsKey("category")) {
plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
}
// model part, can be null
// but if it exists, either custom model data or item model should be configured
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
if (modelSection == null) {
return;
}
boolean hasModel = false;
if (customModelData != 0) {
hasModel= true;
// use custom model data
// check conflict
Map<Integer, Key> conflict = cmdConflictChecker.computeIfAbsent(materialId, k -> new HashMap<>());
if (conflict.containsKey(customModelData)) {
TranslationManager.instance().log("warning.config.item.custom_model_data_conflict", path.toString(), id.toString(), String.valueOf(customModelData), conflict.get(customModelData).toString());
return;
}
legacyOverridesModels.sort(LegacyOverridesModel::compareTo);
this.modernItemModels1_21_2.put(itemModelKey, legacyOverridesModels);
if (customModelData > 16_777_216) {
TranslationManager.instance().log("warning.config.item.bad_custom_model_data_value", path.toString(), id.toString(), String.valueOf(customModelData));
}
conflict.put(customModelData, id);
// Parse models
ItemModel model = ItemModels.fromMap(modelSection);
for (ModelGeneration generation : model.modelsToGenerate()) {
prepareModelGeneration(path, id, generation);
}
if (Config.packMaxVersion() > 21.39f) {
TreeMap<Integer, ItemModel> map = modernOverrides.computeIfAbsent(materialId, k -> new TreeMap<>());
map.put(customModelData, model);
}
if (Config.packMinVersion() < 21.39f) {
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, customModelData);
TreeSet<LegacyOverridesModel> lom = legacyOverrides.computeIfAbsent(materialId, k -> new TreeSet<>());
lom.addAll(legacyOverridesModels);
}
}
if (itemModelKey != null) {
hasModel = true;
// use components
ItemModel model = ItemModels.fromMap(modelSection);
for (ModelGeneration generation : model.modelsToGenerate()) {
prepareModelGeneration(path, id, generation);
}
if (Config.packMaxVersion() > 21.39f) {
modernItemModels1_21_4.put(itemModelKey, model);
}
if (Config.packMaxVersion() > 21.19f && Config.packMinVersion() < 21.39f) {
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, 0);
if (!legacyOverridesModels.isEmpty()) {
legacyOverridesModels.sort(LegacyOverridesModel::compareTo);
modernItemModels1_21_2.put(itemModelKey, legacyOverridesModels);
} else {
plugin.debug(() -> "Can't convert " + id + "'s model to legacy format.");
}
}
}
if (!hasModel) {
TranslationManager.instance().log("warning.config.item.lack_model_id", path.toString(), id.toString());
}
}
if (!hasModel) {
plugin.logger().warn(path, "No custom-model-data/item-model configured for " + id);
}
}

View File

@@ -60,7 +60,7 @@ public class AxeItemBehavior extends ItemBehavior {
return InteractionResult.PASS;
}
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().getBlock(blockBehavior.stripped());
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().blockById(blockBehavior.stripped());
if (optionalNewCustomBlock.isEmpty()) {
CraftEngine.instance().logger().warn("stripped block " + blockBehavior.stripped() + " does not exist");
return InteractionResult.FAIL;

View File

@@ -61,7 +61,7 @@ public class BlockItemBehavior extends ItemBehavior {
if (!context.canPlace()) {
return InteractionResult.FAIL;
}
Optional<CustomBlock> optionalBlock = BukkitBlockManager.instance().getBlock(this.blockId);
Optional<CustomBlock> optionalBlock = BukkitBlockManager.instance().blockById(this.blockId);
if (optionalBlock.isEmpty()) {
CraftEngine.instance().logger().warn("Failed to place unknown block " + this.blockId);
return InteractionResult.FAIL;
@@ -176,7 +176,7 @@ public class BlockItemBehavior extends ItemBehavior {
throw new IllegalArgumentException("Missing required parameter 'block' for block_item behavior");
}
if (id instanceof Map<?, ?> map) {
BukkitBlockManager.instance().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
return new BlockItemBehavior(key);
} else {
return new BlockItemBehavior(Key.of(id.toString()));

View File

@@ -47,7 +47,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
}
public InteractionResult place(UseOnContext context) {
Optional<CustomFurniture> optionalCustomFurniture = BukkitFurnitureManager.instance().getFurniture(this.id);
Optional<CustomFurniture> optionalCustomFurniture = BukkitFurnitureManager.instance().furnitureById(this.id);
if (optionalCustomFurniture.isEmpty()) {
CraftEngine.instance().logger().warn("Furniture " + this.id + " not found");
return InteractionResult.FAIL;
@@ -139,7 +139,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
throw new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior");
}
if (id instanceof Map<?,?> map) {
BukkitFurnitureManager.instance().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
return new FurnitureItemBehavior(key);
} else {
return new FurnitureItemBehavior(Key.of(id.toString()));

View File

@@ -67,7 +67,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
}
int offset = MiscUtils.getAsInt(arguments.getOrDefault("y-offset", 1));
if (id instanceof Map<?, ?> map) {
BukkitBlockManager.instance().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
return new LiquidCollisionBlockItemBehavior(key, offset);
} else {
return new LiquidCollisionBlockItemBehavior(Key.of(id.toString()), offset);

View File

@@ -0,0 +1,10 @@
package net.momirealms.craftengine.bukkit.item.recipe;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.inventory.ItemStack;
public interface BukkitRecipeConvertor<T extends Recipe<ItemStack>> {
Runnable convert(Key id, T recipe);
}

View File

@@ -9,7 +9,7 @@ import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
@@ -39,7 +39,7 @@ public class CrafterEventListener implements Listener {
@EventHandler
public void onCrafting(CrafterCraftEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
CraftingRecipe recipe = event.getRecipe();
if (!(event.getBlock().getState() instanceof Crafter crafter)) {
return;
@@ -82,12 +82,12 @@ public class CrafterEventListener implements Listener {
return;
}
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPELESS, input);
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPELESS, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.result(ItemBuildContext.EMPTY));
return;
}
ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPED, input);
ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPED, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.result(ItemBuildContext.EMPTY));
return;

View File

@@ -17,7 +17,7 @@ import net.momirealms.craftengine.core.item.recipe.*;
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
import net.momirealms.craftengine.core.item.recipe.input.SmithingInput;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.AdventureHelper;
@@ -85,7 +85,7 @@ public class RecipeEventListener implements Listener {
recipeType = RecipeTypes.SMOKING;
}
Recipe<ItemStack> ceRecipe = recipeManager.getRecipe(recipeType, input);
Recipe<ItemStack> ceRecipe = recipeManager.recipeByInput(recipeType, input);
// The item is an ingredient, we should never consider it as fuel firstly
if (ceRecipe != null) return;
@@ -261,7 +261,7 @@ public class RecipeEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onFurnaceInventoryOpen(InventoryOpenEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
if (!(event.getInventory() instanceof FurnaceInventory furnaceInventory)) {
return;
}
@@ -277,7 +277,7 @@ public class RecipeEventListener implements Listener {
// for 1.20.1-1.21.1
@EventHandler(ignoreCancelled = true)
public void onBlockIgnite(BlockIgniteEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
if (VersionHelper.isVersionNewerThan1_21_2()) return;
Block block = event.getBlock();
Material material = block.getType();
@@ -295,7 +295,7 @@ public class RecipeEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlaceBlock(BlockPlaceEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
Block block = event.getBlock();
Material material = block.getType();
if (material == Material.FURNACE || material == Material.BLAST_FURNACE || material == Material.SMOKER) {
@@ -322,7 +322,7 @@ public class RecipeEventListener implements Listener {
// for 1.21.2+
@EventHandler(ignoreCancelled = true)
public void onPutItemOnCampfire(PlayerInteractEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
if (!VersionHelper.isVersionNewerThan1_21_2()) return;
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
Block clicked = event.getClickedBlock();
@@ -345,7 +345,7 @@ public class RecipeEventListener implements Listener {
try {
@SuppressWarnings("unchecked")
Optional<Object> optionalMCRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor1.invoke(
BukkitRecipeManager.minecraftRecipeManager(),
BukkitRecipeManager.nmsRecipeManager(),
Reflections.instance$RecipeType$CAMPFIRE_COOKING,
Reflections.constructor$SingleRecipeInput.newInstance(Reflections.method$CraftItemStack$asNMSCopy.invoke(null, itemStack)),
FastNMS.INSTANCE.field$CraftWorld$ServerLevel(event.getPlayer().getWorld()),
@@ -360,7 +360,7 @@ public class RecipeEventListener implements Listener {
return;
}
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input);
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.recipeByInput(RecipeTypes.CAMPFIRE_COOKING, input);
if (ceRecipe == null) {
event.setCancelled(true);
}
@@ -373,7 +373,7 @@ public class RecipeEventListener implements Listener {
@SuppressWarnings("UnstableApiUsage")
@EventHandler(ignoreCancelled = true)
public void onCampfireCook(CampfireStartEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
if (!VersionHelper.isVersionNewerThan1_21_2()) return;
CampfireRecipe recipe = event.getRecipe();
Key recipeId = new Key(recipe.getKey().namespace(), recipe.getKey().value());
@@ -392,7 +392,7 @@ public class RecipeEventListener implements Listener {
}
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input);
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.recipeByInput(RecipeTypes.CAMPFIRE_COOKING, input);
if (ceRecipe == null) {
event.setTotalCookTime(Integer.MAX_VALUE);
return;
@@ -404,7 +404,7 @@ public class RecipeEventListener implements Listener {
// for 1.21.2+
@EventHandler(ignoreCancelled = true)
public void onCampfireCook(BlockCookEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
if (!VersionHelper.isVersionNewerThan1_21_2()) return;
Material type = event.getBlock().getType();
if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) return;
@@ -427,7 +427,7 @@ public class RecipeEventListener implements Listener {
}
SingleItemInput<ItemStack> input = new SingleItemInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input);
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.recipeByInput(RecipeTypes.CAMPFIRE_COOKING, input);
if (ceRecipe == null) {
event.setCancelled(true);
return;
@@ -743,7 +743,7 @@ public class RecipeEventListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onCraftingRecipe(PrepareItemCraftEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
org.bukkit.inventory.Recipe recipe = event.getRecipe();
if (recipe == null)
return;
@@ -802,14 +802,14 @@ public class RecipeEventListener implements Listener {
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
Key lastRecipe = serverPlayer.lastUsedRecipe();
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPELESS, input, lastRecipe);
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPELESS, input, lastRecipe);
if (ceRecipe != null) {
inventory.setResult(ceRecipe.result(new ItemBuildContext(serverPlayer, ContextHolder.EMPTY)));
serverPlayer.setLastUsedRecipe(ceRecipe.id());
correctCraftingRecipeUsed(inventory, ceRecipe);
return;
}
ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SHAPED, input, lastRecipe);
ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPED, input, lastRecipe);
if (ceRecipe != null) {
inventory.setResult(ceRecipe.result(new ItemBuildContext(serverPlayer, ContextHolder.EMPTY)));
serverPlayer.setLastUsedRecipe(ceRecipe.id());
@@ -821,7 +821,7 @@ public class RecipeEventListener implements Listener {
}
private void correctCraftingRecipeUsed(CraftingInventory inventory, Recipe<ItemStack> recipe) {
Object holderOrRecipe = recipeManager.getRecipeHolderByRecipe(recipe);
Object holderOrRecipe = recipeManager.nmsRecipeHolderByRecipe(recipe);
if (holderOrRecipe == null) {
// it's a vanilla recipe but not injected
return;
@@ -836,7 +836,7 @@ public class RecipeEventListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onSmithingTransform(PrepareSmithingEvent event) {
if (!ConfigManager.enableRecipeSystem()) return;
if (!Config.enableRecipeSystem()) return;
SmithingInventory inventory = event.getInventory();
if (!(inventory.getRecipe() instanceof SmithingTransformRecipe recipe)) return;
@@ -857,7 +857,7 @@ public class RecipeEventListener implements Listener {
getOptimizedIDItem(addition)
);
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SMITHING_TRANSFORM, input);
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SMITHING_TRANSFORM, input);
if (ceRecipe == null) {
event.setResult(null);
return;
@@ -878,7 +878,7 @@ public class RecipeEventListener implements Listener {
}
private void correctSmithingRecipeUsed(SmithingInventory inventory, Recipe<ItemStack> recipe) {
Object holderOrRecipe = recipeManager.getRecipeHolderByRecipe(recipe);
Object holderOrRecipe = recipeManager.nmsRecipeHolderByRecipe(recipe);
if (holderOrRecipe == null) {
// it's a vanilla recipe but not injected
return;

View File

@@ -8,14 +8,16 @@ import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.AbstractVanillaLootManager;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.loot.VanillaLoot;
import net.momirealms.craftengine.core.loot.VanillaLootManager;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.PreConditions;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.Vec3d;
@@ -30,18 +32,19 @@ import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import java.nio.file.Path;
import java.util.*;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
// note: block listeners are in BlockEventListener to reduce performance cost
public class BukkitVanillaLootManager implements VanillaLootManager, Listener {
public class BukkitVanillaLootManager extends AbstractVanillaLootManager implements Listener {
private final BukkitCraftEngine plugin;
private final Map<Integer, VanillaLoot> blockLoots;
private final Map<Key, VanillaLoot> entityLoots;
private final VanillaLootParser vanillaLootParser;
public BukkitVanillaLootManager(BukkitCraftEngine plugin) {
this.plugin = plugin;
this.blockLoots = new HashMap<>();
this.entityLoots = new HashMap<>();
this.vanillaLootParser = new VanillaLootParser();
}
@Override
@@ -54,17 +57,6 @@ public class BukkitVanillaLootManager implements VanillaLootManager, Listener {
HandlerList.unregisterAll(this);
}
@Override
public void unload() {
this.blockLoots.clear();
this.entityLoots.clear();
}
@Override
public Optional<VanillaLoot> getBlockLoot(int vanillaBlockState) {
return Optional.ofNullable(this.blockLoots.get(vanillaBlockState));
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onEntityDeath(EntityDeathEvent event) {
Entity entity = event.getEntity();
@@ -97,45 +89,65 @@ public class BukkitVanillaLootManager implements VanillaLootManager, Listener {
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
String type = (String) section.get("type");
if (PreConditions.isNull(type, () -> this.plugin.logger().warn(path, "`type` option is required for vanilla-loot " + id))) {
return;
public ConfigSectionParser parser() {
return this.vanillaLootParser;
}
public class VanillaLootParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"vanilla-loots", "vanilla-loot", "loots", "loot"};
@Override
public int loadingSequence() {
return LoadingSequence.VANILLA_LOOTS;
}
VanillaLoot.Type typeEnum = VanillaLoot.Type.valueOf(type.toUpperCase(Locale.ENGLISH));
boolean override = (boolean) section.getOrDefault("override", false);
List<String> targets = MiscUtils.getAsStringList(section.getOrDefault("target", List.of()));
LootTable<?> lootTable = LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), false));
switch (typeEnum) {
case BLOCK -> {
for (String target : targets) {
if (target.endsWith("]") && target.contains("[")) {
java.lang.Object blockState = BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(target));
if (blockState == Reflections.instance$Blocks$AIR$defaultState) {
this.plugin.logger().warn(path, "Failed to load " + id + ". Invalid target " + target);
return;
}
VanillaLoot vanillaLoot = this.blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK));
vanillaLoot.addLootTable(lootTable);
} else {
for (Object blockState : BlockStateUtils.getAllVanillaBlockStates(Key.of(target))) {
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
String type = (String) section.get("type");
if (type == null) {
TranslationManager.instance().log("warning.config.vanilla_loot.type_not_exist", path.toString(), id.toString());
return;
}
VanillaLoot.Type typeEnum = VanillaLoot.Type.valueOf(type.toUpperCase(Locale.ENGLISH));
boolean override = (boolean) section.getOrDefault("override", false);
List<String> targets = MiscUtils.getAsStringList(section.getOrDefault("target", List.of()));
LootTable<?> lootTable = LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), false));
switch (typeEnum) {
case BLOCK -> {
for (String target : targets) {
if (target.endsWith("]") && target.contains("[")) {
java.lang.Object blockState = BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(target));
if (blockState == Reflections.instance$Blocks$AIR$defaultState) {
this.plugin.logger().warn(path, "Failed to load " + id + ". Invalid target " + target);
TranslationManager.instance().log("warning.config.vanilla_loot.block.invalid_target", path.toString(), id.toString(), target);
return;
}
VanillaLoot vanillaLoot = this.blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK));
if (override) vanillaLoot.override(true);
VanillaLoot vanillaLoot = blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK));
vanillaLoot.addLootTable(lootTable);
} else {
for (Object blockState : BlockStateUtils.getAllVanillaBlockStates(Key.of(target))) {
if (blockState == Reflections.instance$Blocks$AIR$defaultState) {
TranslationManager.instance().log("warning.config.vanilla_loot.block.invalid_target", path.toString(), id.toString(), target);
return;
}
VanillaLoot vanillaLoot = blockLoots.computeIfAbsent(BlockStateUtils.blockStateToId(blockState), k -> new VanillaLoot(VanillaLoot.Type.BLOCK));
if (override) vanillaLoot.override(true);
vanillaLoot.addLootTable(lootTable);
}
}
}
}
}
case ENTITY -> {
for (String target : targets) {
Key key = Key.of(target);
VanillaLoot vanillaLoot = this.entityLoots.computeIfAbsent(key, k -> new VanillaLoot(VanillaLoot.Type.ENTITY));
vanillaLoot.addLootTable(lootTable);
if (override) vanillaLoot.override(true);
case ENTITY -> {
for (String target : targets) {
Key key = Key.of(target);
VanillaLoot vanillaLoot = entityLoots.computeIfAbsent(key, k -> new VanillaLoot(VanillaLoot.Type.ENTITY));
vanillaLoot.addLootTable(lootTable);
if (override) vanillaLoot.override(true);
}
}
}
}

View File

@@ -11,7 +11,7 @@ import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.pack.host.HostMode;
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.util.VersionHelper;
@@ -52,7 +52,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) {
// for 1.20.1 servers, not recommended to use
if (ConfigManager.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) {
if (Config.sendPackOnJoin() && !VersionHelper.isVersionNewerThan1_20_2()) {
this.sendResourcePack(plugin.networkManager().getUser(event.getPlayer()), null);
}
}
@@ -60,7 +60,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onResourcePackStatus(PlayerResourcePackStatusEvent event) {
// for 1.20.1 servers, not recommended to use
if (ConfigManager.sendPackOnJoin() && ConfigManager.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) {
if (Config.sendPackOnJoin() && Config.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) {
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED || event.getStatus() == PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD) {
event.getPlayer().kick();
}
@@ -73,34 +73,34 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
// update server properties
if (VersionHelper.isVersionNewerThan1_20_2()) {
if (ConfigManager.hostMode() == HostMode.SELF_HOST) {
if (Config.hostMode() == HostMode.SELF_HOST) {
if (Files.exists(resourcePackPath())) {
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, ConfigManager.kickOnDeclined(), ConfigManager.resourcePackPrompt());
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, Config.kickOnDeclined(), Config.resourcePackPrompt());
}
} else if (ConfigManager.hostMode() == HostMode.EXTERNAL_HOST) {
updateResourcePackSettings(ConfigManager.externalPackUUID(), ConfigManager.externalPackUrl(), ConfigManager.externalPackSha1(), ConfigManager.kickOnDeclined(), ConfigManager.resourcePackPrompt());
} else if (Config.hostMode() == HostMode.EXTERNAL_HOST) {
updateResourcePackSettings(Config.externalPackUUID(), Config.externalPackUrl(), Config.externalPackSha1(), Config.kickOnDeclined(), Config.resourcePackPrompt());
}
}
if (ConfigManager.sendPackOnReload()) {
if (Config.sendPackOnReload()) {
if (this.previousHostMode == HostMode.SELF_HOST) {
this.previousHostUUID = super.packUUID;
}
// unload packs if user changed to none host
if (ConfigManager.hostMode() == HostMode.NONE && this.previousHostMode != HostMode.NONE) {
if (Config.hostMode() == HostMode.NONE && this.previousHostMode != HostMode.NONE) {
unloadResourcePackForOnlinePlayers(this.previousHostUUID);
}
// load new external resource pack on reload
if (ConfigManager.hostMode() == HostMode.EXTERNAL_HOST) {
if (Config.hostMode() == HostMode.EXTERNAL_HOST) {
if (this.previousHostMode == HostMode.NONE) {
updateResourcePackForOnlinePlayers(null);
} else {
updateResourcePackForOnlinePlayers(this.previousHostUUID);
}
// record previous host uuid here
this.previousHostUUID = ConfigManager.externalPackUUID();
this.previousHostUUID = Config.externalPackUUID();
}
if (ConfigManager.hostMode() == HostMode.SELF_HOST && this.previousHostMode != HostMode.SELF_HOST) {
if (Config.hostMode() == HostMode.SELF_HOST && this.previousHostMode != HostMode.SELF_HOST) {
if (ReloadCommand.RELOAD_PACK_FLAG) {
ReloadCommand.RELOAD_PACK_FLAG = false;
if (this.previousHostMode == HostMode.NONE) {
@@ -111,7 +111,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
}
}
}
this.previousHostMode = ConfigManager.hostMode();
this.previousHostMode = Config.hostMode();
}
@Override
@@ -134,12 +134,12 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
super.generateResourcePack();
// update server properties
if (VersionHelper.isVersionNewerThan1_20_2()) {
if (ConfigManager.hostMode() == HostMode.SELF_HOST) {
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, ConfigManager.kickOnDeclined(), ConfigManager.resourcePackPrompt());
if (Config.hostMode() == HostMode.SELF_HOST) {
updateResourcePackSettings(super.packUUID, ResourcePackHost.instance().url(), super.packHash, Config.kickOnDeclined(), Config.resourcePackPrompt());
}
}
// resend packs
if (ConfigManager.hostMode() == HostMode.SELF_HOST && ConfigManager.sendPackOnReload()) {
if (Config.hostMode() == HostMode.SELF_HOST && Config.sendPackOnReload()) {
updateResourcePackForOnlinePlayers(this.previousHostUUID);
}
}
@@ -162,7 +162,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
}
private void updateResourcePackSettings(UUID uuid, String url, String sha1, boolean required, Component prompt) {
if (!ConfigManager.sendPackOnJoin()) {
if (!Config.sendPackOnJoin()) {
resetResourcePackSettings();
return;
}
@@ -182,36 +182,36 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
}
public void sendResourcePack(NetWorkUser user, @Nullable UUID previousPack) {
if (ConfigManager.hostMode() == HostMode.NONE) return;
if (Config.hostMode() == HostMode.NONE) return;
String url;
String sha1;
UUID uuid;
if (ConfigManager.hostMode() == HostMode.SELF_HOST) {
if (Config.hostMode() == HostMode.SELF_HOST) {
url = ResourcePackHost.instance().url();
sha1 = super.packHash;
uuid = super.packUUID;
if (!Files.exists(resourcePackPath())) return;
} else {
url = ConfigManager.externalPackUrl();
sha1 = ConfigManager.externalPackSha1();
uuid = ConfigManager.externalPackUUID();
url = Config.externalPackUrl();
sha1 = Config.externalPackSha1();
uuid = Config.externalPackUUID();
if (uuid.equals(previousPack)) return;
}
Object packPrompt = ComponentUtils.adventureToMinecraft(ConfigManager.resourcePackPrompt());
Object packPrompt = ComponentUtils.adventureToMinecraft(Config.resourcePackPrompt());
try {
Object packPacket;
if (VersionHelper.isVersionNewerThan1_20_5()) {
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
uuid, url, sha1, ConfigManager.kickOnDeclined(), Optional.of(packPrompt)
uuid, url, sha1, Config.kickOnDeclined(), Optional.of(packPrompt)
);
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
uuid, url, sha1, ConfigManager.kickOnDeclined(), packPrompt
uuid, url, sha1, Config.kickOnDeclined(), packPrompt
);
} else {
packPacket = Reflections.constructor$ClientboundResourcePackPushPacket.newInstance(
url + uuid, sha1, ConfigManager.kickOnDeclined(), packPrompt
url + uuid, sha1, Config.kickOnDeclined(), packPrompt
);
}
if (user.decoderState() == ConnectionState.PLAY) {

View File

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.entity.furniture.hitbox.BukkitHitBoxTypes;
import net.momirealms.craftengine.bukkit.font.BukkitImageManager;
import net.momirealms.craftengine.bukkit.font.BukkitFontManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.behavior.BukkitItemBehaviors;
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
@@ -29,7 +29,7 @@ import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.classpath.ReflectionClassPathAppender;
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
import net.momirealms.craftengine.core.plugin.dependency.Dependency;
import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl;
@@ -65,17 +65,20 @@ public class BukkitCraftEngine extends CraftEngine {
private boolean hasPlaceholderAPI;
public BukkitCraftEngine(JavaPlugin bootstrap) {
VersionHelper.init(serverVersion());
super((p) -> {
CraftEngineReloadEvent event = new CraftEngineReloadEvent((BukkitCraftEngine) p);
EventUtils.fireAndForget(event);
});
instance = this;
this.bootstrap = bootstrap;
super.classPathAppender = new ReflectionClassPathAppender(this);
super.scheduler = new BukkitSchedulerAdapter(this);
super.logger = new JavaPluginLogger(bootstrap.getLogger());
// find mod class if present
Class<?> modClass = ReflectionUtils.getClazz(MOD_CLASS);
if (modClass != null) {
Field isSuccessfullyRegistered = ReflectionUtils.getDeclaredField(modClass, "isSuccessfullyRegistered");
try {
assert isSuccessfullyRegistered != null;
requiresRestart = !(boolean) isSuccessfullyRegistered.get(null);
hasMod = true;
} catch (Exception ignore) {
@@ -84,13 +87,11 @@ public class BukkitCraftEngine extends CraftEngine {
}
@Override
public void load() {
super.load();
public void onPluginLoad() {
super.onPluginLoad();
Reflections.init();
BukkitInjector.init();
super.networkManager = new BukkitNetworkManager(this);
super.networkManager.init();
// load mappings and inject blocks
super.blockManager = new BukkitBlockManager(this);
super.furnitureManager = new BukkitFurnitureManager(this);
this.successfullyLoaded = true;
@@ -105,8 +106,8 @@ public class BukkitCraftEngine extends CraftEngine {
}
@Override
public void enable() {
if (successfullyEnabled) {
public void onPluginEnable() {
if (this.successfullyEnabled) {
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
@@ -154,8 +155,37 @@ public class BukkitCraftEngine extends CraftEngine {
super.worldManager = new BukkitWorldManager(this);
super.soundManager = new BukkitSoundManager(this);
super.vanillaLootManager = new BukkitVanillaLootManager(this);
this.imageManager = new BukkitImageManager(this);
super.enable();
super.fontManager = new BukkitFontManager(this);
super.onPluginEnable();
// compatibility
// register expansion
if (this.isPluginEnabled("PlaceholderAPI")) {
PlaceholderAPIUtils.registerExpansions(this);
this.hasPlaceholderAPI = true;
}
}
@Override
public void onPluginDisable() {
super.onPluginDisable();
if (this.tickTask != null) this.tickTask.cancel();
if (!Bukkit.getServer().isStopping()) {
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
logger().severe("Please do not disable plugins at runtime.");
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
Bukkit.getServer().shutdown();
}
}
@Override
public void platformDelayedEnable() {
if (Config.metrics()) {
new Metrics(this.bootstrap(), 24333);
}
// tick task
if (VersionHelper.isFolia()) {
this.tickTask = this.scheduler().sync().runRepeating(() -> {
@@ -172,69 +202,6 @@ public class BukkitCraftEngine extends CraftEngine {
}
}, 1, 1);
}
// compatibility
// register expansion
if (this.isPluginEnabled("PlaceholderAPI")) {
PlaceholderAPIUtils.registerExpansions(this);
this.hasPlaceholderAPI = true;
}
}
@Override
public void disable() {
super.disable();
if (this.tickTask != null) this.tickTask.cancel();
if (!Bukkit.getServer().isStopping()) {
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
logger().severe("Please do not disable plugins at runtime.");
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
Bukkit.getServer().shutdown();
}
}
@Override
public void reload() {
super.reload();
CraftEngineReloadEvent event = new CraftEngineReloadEvent(this);
EventUtils.fireAndForget(event);
}
@Override
public void delayedEnable() {
if (ConfigManager.metrics()) {
new Metrics(this.bootstrap(), 24333);
}
}
@Override
protected void registerParsers() {
// register template parser
this.packManager.registerConfigSectionParser(this.templateManager);
// register font parser
this.packManager.registerConfigSectionParser(this.imageManager);
// register item parser
this.packManager.registerConfigSectionParser(this.itemManager);
// register furniture parser
this.packManager.registerConfigSectionParser(this.furnitureManager);
// register block parser
this.packManager.registerConfigSectionParser(this.blockManager);
// register recipe parser
this.packManager.registerConfigSectionParser(this.recipeManager);
// register category parser
this.packManager.registerConfigSectionParser(this.itemBrowserManager);
// register translation parser
this.packManager.registerConfigSectionParser(this.translationManager);
this.packManager.registerConfigSectionParser(this.translationManager.clientLangManager());
// register sound parser
this.packManager.registerConfigSectionParser(this.soundManager);
this.packManager.registerConfigSectionParser(this.soundManager.jukeboxSongManager());
// register vanilla loot parser
this.packManager.registerConfigSectionParser(this.vanillaLootManager);
}
@Override

View File

@@ -45,7 +45,8 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new DebugItemDataCommand(this, plugin),
new DebugSetBlockCommand(this, plugin),
new DebugSpawnFurnitureCommand(this, plugin),
new DebugTargetBlockCommand(this, plugin)
new DebugTargetBlockCommand(this, plugin),
new TotemAnimationCommand(this, plugin)
));
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);

View File

@@ -7,6 +7,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
import net.momirealms.craftengine.core.util.Tristate;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
@@ -79,6 +80,12 @@ public class BukkitSenderFactory extends SenderFactory<BukkitCraftEngine, Comman
return sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender;
}
@SuppressWarnings("unchecked")
@Override
protected <C extends CommandSender> C consoleCommandSender() {
return (C) Bukkit.getConsoleSender();
}
@Override
public void close() {
super.close();

View File

@@ -47,7 +47,7 @@ public class DebugSpawnFurnitureCommand extends BukkitCommandFeature<CommandSend
NamespacedKey namespacedKey = context.get("id");
Key id = KeyUtils.namespacedKey2Key(namespacedKey);
BukkitFurnitureManager furnitureManager = BukkitFurnitureManager.instance();
Optional<CustomFurniture> optionalCustomFurniture = furnitureManager.getFurniture(id);
Optional<CustomFurniture> optionalCustomFurniture = furnitureManager.furnitureById(id);
if (optionalCustomFurniture.isEmpty()) {
return;
}

View File

@@ -36,10 +36,27 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
if (argument == ReloadArgument.CONFIG) {
try {
RELOAD_PACK_FLAG = true;
long time1 = System.currentTimeMillis();
plugin().reload();
long time2 = System.currentTimeMillis();
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS, Component.text(time2 - time1));
plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), false).thenAccept(reloadResult -> {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime()),
Component.text(reloadResult.asyncTime()),
Component.text(reloadResult.syncTime())
);
});
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_FAILURE);
plugin().logger().warn("Failed to reload config", e);
}
} else if (argument == ReloadArgument.RECIPE) {
try {
RELOAD_PACK_FLAG = true;
plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), true).thenAccept(reloadResult -> {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime()),
Component.text(reloadResult.asyncTime()),
Component.text(reloadResult.syncTime())
);
});
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_CONFIG_FAILURE);
plugin().logger().warn("Failed to reload config", e);
@@ -50,29 +67,30 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
long time1 = System.currentTimeMillis();
plugin().packManager().generateResourcePack();
long time2 = System.currentTimeMillis();
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_SUCCESS, Component.text(time2 - time1));
long packTime = time2 - time1;
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_SUCCESS, Component.text(packTime));
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_PACK_FAILURE);
plugin().logger().warn("Failed to generate resource pack", e);
}
});
} else if (argument == ReloadArgument.ALL) {
long time1 = System.currentTimeMillis();
try {
plugin().reload();
plugin().scheduler().executeAsync(() -> {
try {
plugin().packManager().generateResourcePack();
long time2 = System.currentTimeMillis();
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS, Component.text(time2 - time1));
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_FAILURE);
plugin().logger().warn("Failed to generate resource pack", e);
}
});
plugin().reloadPlugin(plugin().scheduler().async(), r -> plugin().scheduler().sync().run(r), true).thenAcceptAsync(reloadResult -> {
long time1 = System.currentTimeMillis();
plugin().packManager().generateResourcePack();
long time2 = System.currentTimeMillis();
long packTime = time2 - time1;
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_SUCCESS,
Component.text(reloadResult.asyncTime() + reloadResult.syncTime() + packTime),
Component.text(reloadResult.asyncTime()),
Component.text(reloadResult.syncTime()),
Component.text(packTime)
);
}, plugin().scheduler().async());
} catch (Exception e) {
handleFeedback(context, MessageConstants.COMMAND_RELOAD_ALL_FAILURE);
plugin().logger().warn("Failed to reload config", e);
plugin().logger().warn("Failed to generate resource pack", e);
}
}
});
@@ -85,6 +103,7 @@ public class ReloadCommand extends BukkitCommandFeature<CommandSender> {
public enum ReloadArgument {
CONFIG,
RECIPE,
PACK,
ALL
}

View File

@@ -47,7 +47,7 @@ public class SearchRecipeAdminCommand extends BukkitCommandFeature<CommandSender
for (Player player : players) {
BukkitServerPlayer serverPlayer = plugin().adapt(player);
Key itemId = Key.of(namespacedKey.namespace(), namespacedKey.value());
List<Recipe<Object>> inRecipes = plugin().recipeManager().getRecipeByResult(itemId);
List<Recipe<Object>> inRecipes = plugin().recipeManager().recipeByResult(itemId);
if (!inRecipes.isEmpty()) {
plugin().itemBrowserManager().openRecipePage(serverPlayer, null, inRecipes, 0, 0, false);
}

View File

@@ -35,7 +35,7 @@ public class SearchRecipePlayerCommand extends BukkitCommandFeature<CommandSende
return;
}
Key itemId = item.id();
List<Recipe<Object>> inRecipes = plugin().recipeManager().getRecipeByResult(itemId);
List<Recipe<Object>> inRecipes = plugin().recipeManager().recipeByResult(itemId);
if (!inRecipes.isEmpty()) {
plugin().itemBrowserManager().openRecipePage(serverPlayer, null, inRecipes, 0, 0, false);
} else {

View File

@@ -47,7 +47,7 @@ public class SearchUsageAdminCommand extends BukkitCommandFeature<CommandSender>
for (Player player : players) {
BukkitServerPlayer serverPlayer = plugin().adapt(player);
Key itemId = Key.of(namespacedKey.namespace(), namespacedKey.value());
List<Recipe<Object>> inRecipes = plugin().recipeManager().getRecipeByIngredient(itemId);
List<Recipe<Object>> inRecipes = plugin().recipeManager().recipeByIngredient(itemId);
if (!inRecipes.isEmpty()) {
plugin().itemBrowserManager().openRecipePage(serverPlayer, null, inRecipes, 0, 0, false);
}

View File

@@ -35,7 +35,7 @@ public class SearchUsagePlayerCommand extends BukkitCommandFeature<CommandSender
return;
}
Key itemId = item.id();
List<Recipe<Object>> inRecipes = plugin().recipeManager().getRecipeByIngredient(itemId);
List<Recipe<Object>> inRecipes = plugin().recipeManager().recipeByIngredient(itemId);
if (!inRecipes.isEmpty()) {
plugin().itemBrowserManager().openRecipePage(serverPlayer, null, inRecipes, 0, 0, false);
} else {

View File

@@ -0,0 +1,68 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.bukkit.data.MultiplePlayerSelector;
import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.concurrent.CompletableFuture;
public class TotemAnimationCommand extends BukkitCommandFeature<CommandSender> {
public TotemAnimationCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.required("players", MultiplePlayerSelectorParser.multiplePlayerSelectorParser())
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().itemManager().cachedTotemSuggestions());
}
}))
.handler(context -> {
NamespacedKey namespacedKey = context.get("id");
Key key = Key.of(namespacedKey.namespace(), namespacedKey.value());
CustomItem<ItemStack> item = plugin().itemManager().getCustomItem(key).orElse(null);
if (item == null || MaterialUtils.getMaterial(item.material()) != Material.TOTEM_OF_UNDYING) {
handleFeedback(context, MessageConstants.COMMAND_TOTEM_NOT_TOTEM, Component.text(key.toString()));
return;
}
ItemStack totem = item.buildItemStack();
MultiplePlayerSelector selector = context.get("players");
for (Player player : selector.values()) {
PlayerUtils.sendTotemAnimation(player, totem);
}
});
}
@Override
public String getFeatureID() {
return "totem_animation";
}
}

View File

@@ -32,7 +32,7 @@ import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
@@ -357,7 +357,7 @@ public class BukkitInjector {
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object mcRecipeManager = BukkitRecipeManager.nmsRecipeManager();
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object type = injectedCacheCheck.recipeType();
Object lastRecipe = injectedCacheCheck.lastRecipe();
@@ -394,13 +394,13 @@ public class BukkitInjector {
CustomCookingRecipe<ItemStack> ceRecipe;
Key lastCustomRecipe = injectedCacheCheck.lastCustomRecipe();
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMELTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.BLASTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMOKING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
} else {
return Optional.empty();
}
@@ -412,7 +412,7 @@ public class BukkitInjector {
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
// It doesn't matter at all
injectedCacheCheck.lastRecipe(resourceLocation);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(pair.getSecond()));
return Optional.of(Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe)).orElse(pair.getSecond()));
} else {
return Optional.empty();
}
@@ -425,7 +425,7 @@ public class BukkitInjector {
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object mcRecipeManager = BukkitRecipeManager.nmsRecipeManager();
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object type = injectedCacheCheck.recipeType();
Object lastRecipe = injectedCacheCheck.lastRecipe();
@@ -462,13 +462,13 @@ public class BukkitInjector {
CustomCookingRecipe<ItemStack> ceRecipe;
Key lastCustomRecipe = injectedCacheCheck.lastCustomRecipe();
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMELTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.BLASTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMOKING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
} else {
return Optional.empty();
}
@@ -480,7 +480,7 @@ public class BukkitInjector {
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
// It doesn't matter at all
injectedCacheCheck.lastRecipe(id);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
return Optional.of(Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe)).orElse(holder));
} else {
return Optional.empty();
}
@@ -493,7 +493,7 @@ public class BukkitInjector {
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object mcRecipeManager = BukkitRecipeManager.nmsRecipeManager();
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object type = injectedCacheCheck.recipeType();
Object lastRecipe = injectedCacheCheck.lastRecipe();
@@ -522,13 +522,13 @@ public class BukkitInjector {
CustomCookingRecipe<ItemStack> ceRecipe;
Key lastCustomRecipe = injectedCacheCheck.lastCustomRecipe();
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMELTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.BLASTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMOKING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$CAMPFIRE_COOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.CAMPFIRE_COOKING, input, lastCustomRecipe);
} else {
return Optional.empty();
}
@@ -540,7 +540,7 @@ public class BukkitInjector {
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
// It doesn't matter at all
injectedCacheCheck.lastRecipe(id);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
return Optional.of(Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe)).orElse(holder));
} else {
return Optional.empty();
}
@@ -553,7 +553,7 @@ public class BukkitInjector {
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object mcRecipeManager = BukkitRecipeManager.nmsRecipeManager();
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object type = injectedCacheCheck.recipeType();
Object lastRecipe = injectedCacheCheck.lastRecipe();
@@ -583,11 +583,11 @@ public class BukkitInjector {
CustomCookingRecipe<ItemStack> ceRecipe;
Key lastCustomRecipe = injectedCacheCheck.lastCustomRecipe();
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMELTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.BLASTING, input, lastCustomRecipe);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input, lastCustomRecipe);
ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(RecipeTypes.SMOKING, input, lastCustomRecipe);
} else {
return Optional.empty();
}
@@ -599,7 +599,7 @@ public class BukkitInjector {
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
// It doesn't matter at all
injectedCacheCheck.lastRecipe(id);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
return Optional.of(Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe)).orElse(holder));
} else {
return Optional.empty();
}
@@ -623,14 +623,14 @@ public class BukkitInjector {
CESection section = holder.ceSection();
if (BlockStateUtils.isVanillaBlock(stateId)) {
section.setBlockState(x, y, z, EmptyBlock.INSTANCE.defaultState());
if (ConfigManager.enableLightSystem() && ConfigManager.forceUpdateLight()) {
if (Config.enableLightSystem() && Config.forceUpdateLight()) {
updateLightIfChanged(holder, previousState, newState, null, y, z, x);
}
} else {
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
section.setBlockState(x, y, z, immutableBlockState);
if (!immutableBlockState.isEmpty()) {
if (ConfigManager.enableLightSystem()) {
if (Config.enableLightSystem()) {
updateLightIfChanged(holder, previousState, newState, immutableBlockState.vanillaBlockState().handle(), y, z, x);
}
}

View File

@@ -37,20 +37,21 @@ import java.util.function.BiConsumer;
public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener {
private static BukkitNetworkManager instance;
private static final Map<Class<?>, TriConsumer<NetWorkUser, NMSPacketEvent, Object>> nmsPacketFunctions = new HashMap<>();
private static final Map<Integer, BiConsumer<NetWorkUser, ByteBufPacketEvent>> byteBufPacketFunctions = new HashMap<>();
private static final Map<Class<?>, TriConsumer<NetWorkUser, NMSPacketEvent, Object>> NMS_PACKET_HANDLERS = new HashMap<>();
private static final Map<Integer, BiConsumer<NetWorkUser, ByteBufPacketEvent>> BYTE_BUFFER_PACKET_HANDLERS = new HashMap<>();
private static void registerNMSPacketConsumer(final TriConsumer<NetWorkUser, NMSPacketEvent, Object> function, @Nullable Class<?> packet) {
if (packet == null) return;
nmsPacketFunctions.put(packet, function);
NMS_PACKET_HANDLERS.put(packet, function);
}
private static void registerByteBufPacketConsumer(final BiConsumer<NetWorkUser, ByteBufPacketEvent> function, int id) {
byteBufPacketFunctions.put(id, function);
BYTE_BUFFER_PACKET_HANDLERS.put(id, function);
}
private final BiConsumer<Object, List<Object>> packetsConsumer;
private final BiConsumer<Object, Object> delayedPacketConsumer;
private final BiConsumer<Object, List<Object>> immediatePacketsConsumer;
private final BiConsumer<Object, Object> packetConsumer;
private final BiConsumer<Object, Object> immediatePacketConsumer;
private final BukkitCraftEngine plugin;
@@ -67,35 +68,22 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private static final String PACKET_ENCODER = "craftengine_encoder";
private static final String PACKET_DECODER = "craftengine_decoder";
private boolean active;
private boolean init;
private static boolean hasModelEngine;
public static boolean hasModelEngine() {
return hasModelEngine;
}
public BukkitNetworkManager(BukkitCraftEngine plugin) {
instance = this;
hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null;
this.plugin = plugin;
if (VersionHelper.isVersionNewerThan1_21_5()) {
this.packetIds = new PacketIds1_21_5();
} else if (VersionHelper.isVersionNewerThan1_21_2()) {
this.packetIds = new PacketIds1_21_2();
} else if (VersionHelper.isVersionNewerThan1_20_5()) {
this.packetIds = new PacketIds1_20_5();
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
this.packetIds = new PacketIds1_20_3();
} else if (VersionHelper.isVersionNewerThan1_20_2()) {
this.packetIds = new PacketIds1_20_2();
} else {
this.packetIds = new PacketIds1_20();
}
this.registerConsumers();
// set up packet id
this.packetIds = setupPacketIds();
// register packet handlers
this.registerPacketHandlers();
// set up packet senders
this.packetConsumer = FastNMS.INSTANCE::sendPacket;
this.packetsConsumer = ((serverPlayer, packets) -> {
Object bundle = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
FastNMS.INSTANCE.sendPacket(serverPlayer, bundle);
this.packetConsumer.accept(serverPlayer, bundle);
});
this.delayedPacketConsumer = FastNMS.INSTANCE::sendPacket;
this.immediatePacketConsumer = (serverPlayer, packet) -> {
try {
Reflections.method$Connection$sendPacketImmediate.invoke(FastNMS.INSTANCE.field$Player$connection$connection(serverPlayer), packet, null, true);
@@ -103,12 +91,45 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
plugin.logger().warn("Failed to invoke send packet", e);
}
};
this.active = true;
hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null;
instance = this;
this.immediatePacketsConsumer = (serverPlayer, packets) -> {
Object bundle = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.immediatePacketConsumer.accept(serverPlayer, bundle);
};
// set up mod channel
this.plugin.bootstrap().getServer().getMessenger().registerIncomingPluginChannel(this.plugin.bootstrap(), MOD_CHANNEL, this);
this.plugin.bootstrap().getServer().getMessenger().registerOutgoingPluginChannel(this.plugin.bootstrap(), MOD_CHANNEL);
// Inject server channel
try {
Object server = Reflections.method$MinecraftServer$getServer.invoke(null);
Object serverConnection = Reflections.field$MinecraftServer$connection.get(server);
@SuppressWarnings("unchecked")
List<ChannelFuture> channels = (List<ChannelFuture>) Reflections.field$ServerConnectionListener$channels.get(serverConnection);
ListMonitor<ChannelFuture> monitor = new ListMonitor<>(channels, (future) -> {
Channel channel = future.channel();
injectServerChannel(channel);
this.injectedChannels.add(channel);
}, (object) -> {});
Reflections.field$ServerConnectionListener$channels.set(serverConnection, monitor);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to init server connection", e);
}
}
private void registerConsumers() {
private PacketIds setupPacketIds() {
if (VersionHelper.isVersionNewerThan1_21()) {
return new PacketIds1_21();
} else if (VersionHelper.isVersionNewerThan1_20_5()) {
return new PacketIds1_20_5();
} else if (VersionHelper.isVersionNewerThan1_20_3()) {
return new PacketIds1_20_3();
} else if (VersionHelper.isVersionNewerThan1_20_2()) {
return new PacketIds1_20_2();
} else {
return new PacketIds1_20();
}
}
private void registerPacketHandlers() {
registerNMSPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, Reflections.clazz$ClientboundLevelChunkWithLightPacket);
registerNMSPacketConsumer(PacketConsumers.PLAYER_ACTION, Reflections.clazz$ServerboundPlayerActionPacket);
registerNMSPacketConsumer(PacketConsumers.SWING_HAND, Reflections.clazz$ServerboundSwingPacket);
@@ -128,6 +149,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, Reflections.clazz$ServerboundSignUpdatePacket);
registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, Reflections.clazz$ServerboundEditBookPacket);
registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD, Reflections.clazz$ServerboundCustomPayloadPacket);
registerNMSPacketConsumer(PacketConsumers.SET_ENTITY_DATA, Reflections.clazz$ClientboundSetEntityDataPacket);
registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());
registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket());
registerByteBufPacketConsumer(PacketConsumers.LEVEL_PARTICLE, this.packetIds.clientboundLevelParticlesPacket());
@@ -149,7 +171,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
}
@EventHandler
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
Channel channel = getChannel(player);
@@ -169,40 +191,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
return this.onlineUserArray;
}
@Override
// 保留仅注册入频道用
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) {}
@Override
public void init() {
if (init) return;
try {
this.plugin.bootstrap().getServer().getMessenger().registerIncomingPluginChannel(this.plugin.bootstrap(), MOD_CHANNEL, this);
this.plugin.bootstrap().getServer().getMessenger().registerOutgoingPluginChannel(this.plugin.bootstrap(), MOD_CHANNEL);
Object server = Reflections.method$MinecraftServer$getServer.invoke(null);
Object serverConnection = Reflections.field$MinecraftServer$connection.get(server);
@SuppressWarnings("unchecked")
List<ChannelFuture> channels = (List<ChannelFuture>) Reflections.field$ServerConnectionListener$channels.get(serverConnection);
ListMonitor<ChannelFuture> monitor = new ListMonitor<>(channels, (future) -> {
if (!this.active) return;
Channel channel = future.channel();
injectServerChannel(channel);
this.injectedChannels.add(channel);
}, (object) -> {});
Reflections.field$ServerConnectionListener$channels.set(serverConnection, monitor);
this.init = true;
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to init server connection", e);
}
}
@Override
public void enable() {
Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap());
}
@Override
public void shutdown() {
public void disable() {
HandlerList.unregisterAll(this);
for (Channel channel : injectedChannels) {
uninjectServerChannel(channel);
@@ -211,7 +210,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
handleDisconnection(getChannel(player));
}
injectedChannels.clear();
active = false;
}
@Override
@@ -258,16 +256,26 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
}
@Override
public void sendPacket(@NotNull NetWorkUser player, Object packet, boolean immediately) {
if (immediately) {
this.immediatePacketConsumer.accept(player.serverPlayer(), packet);
} else {
this.delayedPacketConsumer.accept(player.serverPlayer(), packet);
this.packetConsumer.accept(player.serverPlayer(), packet);
}
}
public void sendPackets(@NotNull NetWorkUser player, List<Object> packet) {
this.packetsConsumer.accept(player.serverPlayer(), packet);
@Override
public void sendPackets(@NotNull NetWorkUser player, List<Object> packet, boolean immediately) {
if (immediately) {
this.immediatePacketsConsumer.accept(player.serverPlayer(), packet);
} else {
this.packetsConsumer.accept(player.serverPlayer(), packet);
}
}
public static boolean hasModelEngine() {
return hasModelEngine;
}
public void receivePacket(@NotNull NetWorkUser player, Object packet) {
@@ -542,13 +550,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
protected void handleNMSPacket(NetWorkUser user, NMSPacketEvent event, Object packet) {
Optional.ofNullable(nmsPacketFunctions.get(packet.getClass()))
Optional.ofNullable(NMS_PACKET_HANDLERS.get(packet.getClass()))
.ifPresent(function -> function.accept(user, event, packet));
}
protected void handleByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) {
int packetID = event.packetID();
Optional.ofNullable(byteBufPacketFunctions.get(packetID))
Optional.ofNullable(BYTE_BUFFER_PACKET_HANDLERS.get(packetID))
.ifPresent(function -> function.accept(user, event));
}

View File

@@ -18,9 +18,9 @@ import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
@@ -61,6 +61,11 @@ public class PacketConsumers {
mappingsMOD[entry.getKey()] = entry.getValue();
}
}
for (int i = 0; i < mappingsMOD.length; i++) {
if (BlockStateUtils.isVanillaBlock(i)) {
mappingsMOD[i] = remap(i);
}
}
BLOCK_LIST = new IntIdentityList(registrySize);
BIOME_LIST = new IntIdentityList(RegistryUtils.currentBiomeRegistrySize());
}
@@ -70,8 +75,7 @@ public class PacketConsumers {
}
public static int remapMOD(int stateId) {
int modStateId = mappingsMOD[stateId];
return BlockStateUtils.isVanillaBlock(modStateId) ? remap(modStateId) : modStateId;
return mappingsMOD[stateId];
}
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> LEVEL_CHUNK_WITH_LIGHT = (user, event, packet) -> {
@@ -296,7 +300,7 @@ public class PacketConsumers {
int stateId = BlockStateUtils.blockStateToId(blockState);
// not a custom block
if (BlockStateUtils.isVanillaBlock(stateId)) {
if (ConfigManager.enableSoundSystem()) {
if (Config.enableSoundSystem()) {
Object blockOwner = Reflections.field$StateHolder$owner.get(blockState);
if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) {
player.startMiningBlock(world, pos, blockState, false, null);
@@ -559,7 +563,7 @@ public class PacketConsumers {
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> PICK_ITEM_FROM_ENTITY = (user, event, packet) -> {
try {
int entityId = (int) Reflections.field$ServerboundPickItemFromEntityPacket$id.get(packet);
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByEntityId(entityId);
LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId);
if (furniture == null) return;
Player player = (Player) user.platformPlayer();
if (player == null) return;
@@ -618,18 +622,18 @@ public class PacketConsumers {
} else if (entityType == Reflections.instance$EntityType$ITEM_DISPLAY) {
// Furniture
int entityId = (int) Reflections.field$ClientboundAddEntityPacket$entityId.get(packet);
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(entityId);
LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId);
if (furniture != null) {
user.furnitureView().computeIfAbsent(furniture.baseEntityId(), k -> new ArrayList<>()).addAll(furniture.fakeEntityIds());
user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false);
if (ConfigManager.hideBaseEntity() && !furniture.hasExternalModel()) {
if (Config.hideBaseEntity() && !furniture.hasExternalModel()) {
event.setCancelled(true);
}
}
} else if (entityType == Reflections.instance$EntityType$SHULKER) {
// Cancel collider entity packet
int entityId = (int) Reflections.field$ClientboundAddEntityPacket$entityId.get(packet);
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByRealEntityId(entityId);
LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId);
if (furniture != null) {
event.setCancelled(true);
}
@@ -690,7 +694,7 @@ public class PacketConsumers {
Object action = Reflections.field$ServerboundInteractPacket$action.get(packet);
Object actionType = Reflections.method$ServerboundInteractPacket$Action$getType.invoke(action);
if (actionType == null) return;
LoadedFurniture furniture = BukkitFurnitureManager.instance().getLoadedFurnitureByEntityId(entityId);
LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId);
if (furniture == null) return;
Location location = furniture.baseEntity().getLocation();
BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user;
@@ -770,13 +774,13 @@ public class PacketConsumers {
// we handle it on packet level to prevent it from being captured by plugins
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> RENAME_ITEM = (user, event, packet) -> {
try {
if (!ConfigManager.filterAnvil()) return;
if (!Config.filterAnvil()) return;
String message = (String) Reflections.field$ServerboundRenameItemPacket$name.get(packet);
if (message != null && !message.isEmpty()) {
ImageManager manager = CraftEngine.instance().imageManager();
FontManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
// check bypass
if (((BukkitServerPlayer) user).hasPermission(ImageManager.BYPASS_ANVIL)) {
if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_ANVIL)) {
return;
}
runIfContainsIllegalCharacter(message, manager, (s) -> {
@@ -795,12 +799,12 @@ public class PacketConsumers {
// we handle it on packet level to prevent it from being captured by plugins
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SIGN_UPDATE = (user, event, packet) -> {
try {
if (!ConfigManager.filterSign()) return;
if (!Config.filterSign()) return;
String[] lines = (String[]) Reflections.field$ServerboundSignUpdatePacket$lines.get(packet);
ImageManager manager = CraftEngine.instance().imageManager();
FontManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
// check bypass
if (((BukkitServerPlayer) user).hasPermission(ImageManager.BYPASS_SIGN)) {
if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_SIGN)) {
return;
}
for (int i = 0; i < lines.length; i++) {
@@ -822,11 +826,11 @@ public class PacketConsumers {
@SuppressWarnings("unchecked")
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> EDIT_BOOK = (user, event, packet) -> {
try {
if (!ConfigManager.filterBook()) return;
ImageManager manager = CraftEngine.instance().imageManager();
if (!Config.filterBook()) return;
FontManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
// check bypass
if (((BukkitServerPlayer) user).hasPermission(ImageManager.BYPASS_BOOK)) {
if (((BukkitServerPlayer) user).hasPermission(FontManager.BYPASS_BOOK)) {
return;
}
@@ -875,7 +879,7 @@ public class PacketConsumers {
}
};
private static Pair<Boolean, String> processClientString(String original, ImageManager manager) {
private static Pair<Boolean, String> processClientString(String original, FontManager manager) {
if (original.isEmpty()) {
return Pair.of(false, original);
}
@@ -884,7 +888,7 @@ public class PacketConsumers {
boolean hasIllegal = false;
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (manager.isIllegalCharacter(codepoint)) {
if (manager.isIllegalCodepoint(codepoint)) {
newCodepoints[i] = '*';
hasIllegal = true;
} else {
@@ -894,14 +898,14 @@ public class PacketConsumers {
return hasIllegal ? Pair.of(true, new String(newCodepoints, 0, newCodepoints.length)) : Pair.of(false, original);
}
private static void runIfContainsIllegalCharacter(String string, ImageManager manager, Consumer<String> callback) {
private static void runIfContainsIllegalCharacter(String string, FontManager manager, Consumer<String> callback) {
if (string.isEmpty()) return;
int[] codepoints = CharacterUtils.charsToCodePoints(string.toCharArray());
int[] newCodepoints = new int[codepoints.length];
boolean hasIllegal = false;
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (!manager.isIllegalCharacter(codepoint)) {
if (!manager.isIllegalCodepoint(codepoint)) {
newCodepoints[i] = codepoint;
} else {
newCodepoints[i] = '*';
@@ -957,4 +961,44 @@ public class PacketConsumers {
CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e);
}
};
@SuppressWarnings("unchecked")
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> SET_ENTITY_DATA = (user, event, packet) -> {
try {
int id = (int) Reflections.field$ClientboundSetEntityDataPacket$id.get(packet);
Object player = user.serverPlayer();
Object level = Reflections.method$Entity$level.invoke(player);
Object entityLookup = Reflections.method$Level$moonrise$getEntityLookup.invoke(level);
Object entity = Reflections.method$EntityLookup$get.invoke(entityLookup, id);
if (entity == null) return;
Object entityType = Reflections.method$Entity$getType.invoke(entity);
if (entityType == Reflections.instance$EntityType$BLOCK_DISPLAY) {
List<Object> packedItems = (List<Object>) Reflections.field$ClientboundSetEntityDataPacket$packedItems.get(packet);
for (int i = 0; i < packedItems.size(); i++) {
Object packedItem = packedItems.get(i);
int entityDataId = (int) Reflections.field$SynchedEntityData$DataValue$id.get(packedItem);
if ((VersionHelper.isVersionNewerThan1_20_2() && entityDataId != 23)
|| (!VersionHelper.isVersionNewerThan1_20_2() && entityDataId != 22)) {
continue;
}
Object blockState = Reflections.field$SynchedEntityData$DataValue$value.get(packedItem);
Object serializer = Reflections.field$SynchedEntityData$DataValue$serializer.get(packedItem);
int stateId = BlockStateUtils.blockStateToId(blockState);
int newStateId;
if (!user.clientModEnabled()) {
newStateId = remap(stateId);
} else {
newStateId = remapMOD(stateId);
}
packedItems.set(i, Reflections.constructor$SynchedEntityData$DataValue.newInstance(
entityDataId, serializer, BlockStateUtils.idToBlockState(newStateId)
));
break;
}
}
// todo修改其他实体的物品的方块谓词
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEntityDataPacket", e);
}
};
}

View File

@@ -0,0 +1,33 @@
package net.momirealms.craftengine.bukkit.plugin.network.impl;
import com.google.gson.JsonElement;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import java.util.HashMap;
import java.util.Map;
public class PacketIdFinder {
private static final Map<String, Map<String, Integer>> gamePacketIds = new HashMap<>();
static {
try {
Object packetReport = Reflections.constructor$PacketReport.newInstance((Object) null);
JsonElement jsonElement = (JsonElement) Reflections.method$PacketReport$serializePackets.invoke(packetReport);
var play = jsonElement.getAsJsonObject().get("play");
for (var entry : play.getAsJsonObject().entrySet()) {
Map<String, Integer> ids = new HashMap<>();
gamePacketIds.put(entry.getKey(), ids);
for (var entry2 : entry.getValue().getAsJsonObject().entrySet()) {
ids.put(entry2.getKey(), entry2.getValue().getAsJsonObject().get("protocol_id").getAsInt());
}
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to get packets", e);
}
}
public static int clientboundByName(String packetName) {
return gamePacketIds.get("clientbound").get(packetName);
}
}

View File

@@ -2,30 +2,30 @@ package net.momirealms.craftengine.bukkit.plugin.network.impl;
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
public class PacketIds1_21_5 implements PacketIds {
public class PacketIds1_21 implements PacketIds {
@Override
public int clientboundBlockUpdatePacket() {
return 8;
return PacketIdFinder.clientboundByName("minecraft:block_update");
}
@Override
public int clientboundSectionBlocksUpdatePacket() {
return 72;
return PacketIdFinder.clientboundByName("minecraft:section_blocks_update");
}
@Override
public int clientboundLevelParticlesPacket() {
return 40;
return PacketIdFinder.clientboundByName("minecraft:level_particles");
}
@Override
public int clientboundLevelEventPacket() {
return 39;
return PacketIdFinder.clientboundByName("minecraft:level_event");
}
@Override
public int clientboundAddEntityPacket() {
return 1;
return PacketIdFinder.clientboundByName("minecraft:add_entity");
}
}

View File

@@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.plugin.network.impl;
import net.momirealms.craftengine.bukkit.plugin.network.PacketIds;
@Deprecated
public class PacketIds1_21_2 implements PacketIds {
@Override

View File

@@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.plugin.user;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
@@ -13,6 +14,7 @@ import net.momirealms.craftengine.core.block.PackedBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.util.Direction;
@@ -437,16 +439,26 @@ public class BukkitServerPlayer extends Player {
return;
}
}
this.miningProgress = (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(this.destroyedState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos) + miningProgress;
float progressToAdd = (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(this.destroyedState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos);
int id = BlockStateUtils.blockStateToId(this.destroyedState);
ImmutableBlockState customState = BukkitBlockManager.instance().getImmutableBlockState(id);
if (customState != null && !customState.isEmpty()
&& !customState.settings().isCorrectTool(item == null ? ItemKeys.AIR : item.id())) {
progressToAdd *= customState.settings().incorrectToolSpeed();
}
this.miningProgress = progressToAdd + miningProgress;
int packetStage = (int) (this.miningProgress * 10.0F);
if (packetStage != this.lastSentState) {
this.lastSentState = packetStage;
broadcastDestroyProgress(player, hitPos, blockPos, packetStage);
}
if (this.miningProgress >= 1f) {
//Reflections.method$ServerLevel$levelEvent.invoke(Reflections.field$CraftWorld$ServerLevel.get(player.getWorld()), null, 2001, blockPos, BlockStateUtils.blockStateToId(this.destroyedState));
Reflections.method$ServerPlayerGameMode$destroyBlock.invoke(gameMode, blockPos);
Object levelEventPacket = Reflections.constructor$ClientboundLevelEventPacket.newInstance(2001, blockPos, BlockStateUtils.blockStateToId(this.destroyedState), false);
Object levelEventPacket = Reflections.constructor$ClientboundLevelEventPacket.newInstance(2001, blockPos, id, false);
sendPacket(levelEventPacket, false);
this.stopMiningBlock();
}

View File

@@ -1,58 +0,0 @@
package net.momirealms.craftengine.bukkit.sound;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.sound.song.AbstractJukeboxSongManager;
import net.momirealms.craftengine.core.sound.song.JukeboxSong;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class BukkitJukeboxSongManager extends AbstractJukeboxSongManager {
public BukkitJukeboxSongManager(CraftEngine plugin) {
super(plugin);
}
@Override
protected void registerSongs(Map<Key, JukeboxSong> songs) {
if (songs.isEmpty()) return;
try {
unfreezeRegistry();
for (Map.Entry<Key, JukeboxSong> entry : songs.entrySet()) {
Key id = entry.getKey();
JukeboxSong jukeboxSong = entry.getValue();
Object resourceLocation = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, id.namespace(), id.value());
Object soundId = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, jukeboxSong.sound().namespace(), jukeboxSong.sound().value());
Object song = Reflections.method$Registry$get.invoke(Reflections.instance$InternalRegistries$JUKEBOX_SONG, resourceLocation);
Object soundEvent = VersionHelper.isVersionNewerThan1_21_2() ?
Reflections.constructor$SoundEvent.newInstance(soundId, Optional.of(jukeboxSong.range())) :
Reflections.constructor$SoundEvent.newInstance(soundId, jukeboxSong.range(), false);
Object soundHolder = Reflections.method$Holder$direct.invoke(null, soundEvent);
if (song == null) {
song = Reflections.constructor$JukeboxSong.newInstance(soundHolder, ComponentUtils.adventureToMinecraft(jukeboxSong.description()), jukeboxSong.lengthInSeconds(), jukeboxSong.comparatorOutput());
Object holder = Reflections.method$Registry$registerForHolder.invoke(null, Reflections.instance$InternalRegistries$JUKEBOX_SONG, resourceLocation, song);
Reflections.method$Holder$Reference$bindValue.invoke(holder, song);
Reflections.field$Holder$Reference$tags.set(holder, Set.of());
}
}
freezeRegistry();
} catch (Exception e) {
plugin.logger().warn("Failed to register jukebox songs.", e);
}
}
private void unfreezeRegistry() throws IllegalAccessException {
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$InternalRegistries$JUKEBOX_SONG, false);
}
private void freezeRegistry() throws IllegalAccessException {
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$InternalRegistries$JUKEBOX_SONG, true);
}
}

View File

@@ -1,8 +1,16 @@
package net.momirealms.craftengine.bukkit.sound;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.sound.AbstractSoundManager;
import net.momirealms.craftengine.core.sound.song.JukeboxSongManager;
import net.momirealms.craftengine.core.sound.JukeboxSong;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class BukkitSoundManager extends AbstractSoundManager {
@@ -11,7 +19,40 @@ public class BukkitSoundManager extends AbstractSoundManager {
}
@Override
protected JukeboxSongManager createJukeboxSongManager() {
return new BukkitJukeboxSongManager(super.plugin);
protected void registerSongs(Map<Key, JukeboxSong> songs) {
if (songs.isEmpty()) return;
try {
unfreezeRegistry();
for (Map.Entry<Key, JukeboxSong> entry : songs.entrySet()) {
Key id = entry.getKey();
JukeboxSong jukeboxSong = entry.getValue();
Object resourceLocation = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, id.namespace(), id.value());
Object soundId = Reflections.method$ResourceLocation$fromNamespaceAndPath.invoke(null, jukeboxSong.sound().namespace(), jukeboxSong.sound().value());
Object song = Reflections.method$Registry$get.invoke(Reflections.instance$InternalRegistries$JUKEBOX_SONG, resourceLocation);
Object soundEvent = VersionHelper.isVersionNewerThan1_21_2() ?
Reflections.constructor$SoundEvent.newInstance(soundId, Optional.of(jukeboxSong.range())) :
Reflections.constructor$SoundEvent.newInstance(soundId, jukeboxSong.range(), false);
Object soundHolder = Reflections.method$Holder$direct.invoke(null, soundEvent);
if (song == null) {
song = Reflections.constructor$JukeboxSong.newInstance(soundHolder, ComponentUtils.adventureToMinecraft(jukeboxSong.description()), jukeboxSong.lengthInSeconds(), jukeboxSong.comparatorOutput());
Object holder = Reflections.method$Registry$registerForHolder.invoke(null, Reflections.instance$InternalRegistries$JUKEBOX_SONG, resourceLocation, song);
Reflections.method$Holder$Reference$bindValue.invoke(holder, song);
Reflections.field$Holder$Reference$tags.set(holder, Set.of());
}
}
freezeRegistry();
} catch (Exception e) {
plugin.logger().warn("Failed to register jukebox songs.", e);
}
}
private void unfreezeRegistry() throws IllegalAccessException {
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$InternalRegistries$JUKEBOX_SONG, false);
}
private void freezeRegistry() throws IllegalAccessException {
Reflections.field$MappedRegistry$frozen.set(Reflections.instance$InternalRegistries$JUKEBOX_SONG, true);
}
}

View File

@@ -39,7 +39,7 @@ public class BlockStateUtils {
} else {
String blockTypeString = blockState.substring(0, index);
Key block = Key.of(blockTypeString);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(block);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().blockById(block);
if (optionalCustomBlock.isPresent()) {
ImmutableBlockState state = BlockStateParser.deserialize(blockState);
if (state == null) {
@@ -55,7 +55,7 @@ public class BlockStateUtils {
}
public static List<Object> getAllBlockStates(Key block) {
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().getBlock(block);
Optional<CustomBlock> optionalCustomBlock = BukkitBlockManager.instance().blockById(block);
return optionalCustomBlock.map(customBlock -> customBlock.variantProvider().states().stream().map(it -> it.customBlockState().handle()).toList())
.orElseGet(() -> getAllVanillaBlockStates(block));
}
@@ -80,7 +80,7 @@ public class BlockStateUtils {
}
public static BlockData fromBlockData(Object blockState) {
return (BlockData) FastNMS.INSTANCE.method$CraftBlockData$fromData(blockState);
return FastNMS.INSTANCE.method$CraftBlockData$fromData(blockState);
}
public static int blockDataToId(BlockData blockData) {

View File

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Direction;
@@ -77,16 +77,16 @@ public class InteractUtils {
return false;
});
register(BlockKeys.SOUL_CAMPFIRE, (player, item, blockState, result) -> {
if (!ConfigManager.enableRecipeSystem()) return false;
if (!Config.enableRecipeSystem()) return false;
Optional<Holder.Reference<Key>> optional = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(item.id());
return optional.filter(keyReference -> BukkitRecipeManager.instance().getRecipe(RecipeTypes.CAMPFIRE_COOKING, new SingleItemInput<>(new OptimizedIDItem<>(
return optional.filter(keyReference -> BukkitRecipeManager.instance().recipeByInput(RecipeTypes.CAMPFIRE_COOKING, new SingleItemInput<>(new OptimizedIDItem<>(
keyReference, item.getItem()
))) != null).isPresent();
});
register(BlockKeys.CAMPFIRE, (player, item, blockState, result) -> {
if (!ConfigManager.enableRecipeSystem()) return false;
if (!Config.enableRecipeSystem()) return false;
Optional<Holder.Reference<Key>> optional = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(item.id());
return optional.filter(keyReference -> BukkitRecipeManager.instance().getRecipe(RecipeTypes.CAMPFIRE_COOKING, new SingleItemInput<>(new OptimizedIDItem<>(
return optional.filter(keyReference -> BukkitRecipeManager.instance().recipeByInput(RecipeTypes.CAMPFIRE_COOKING, new SingleItemInput<>(new OptimizedIDItem<>(
keyReference, item.getItem()
))) != null).isPresent();
});

View File

@@ -1,14 +1,14 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
public class NoteBlockChainUpdateUtils {
private NoteBlockChainUpdateUtils() {}
public static void noteBlockChainUpdate(Object level, Object chunkSource, Object direction, Object blockPos, int times) throws ReflectiveOperationException {
if (times >= ConfigManager.maxChainUpdate()) return;
if (times >= Config.maxChainUpdate()) return;
Object relativePos = Reflections.method$BlockPos$relative.invoke(blockPos, direction);
Object state = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, relativePos);
if (BlockStateUtils.isClientSideNoteBlock(state)) {

View File

@@ -1,5 +1,9 @@
package net.momirealms.craftengine.bukkit.util;
import com.mojang.datafixers.util.Pair;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.core.util.RandomUtils;
import org.bukkit.Location;
import org.bukkit.entity.Item;
@@ -11,6 +15,9 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
public class PlayerUtils {
@@ -140,4 +147,28 @@ public class PlayerUtils {
return actualAmount;
}
public static void sendTotemAnimation(Player player, ItemStack totem) {
ItemStack offhandItem = player.getInventory().getItemInOffHand();
List<Object> packets = new ArrayList<>();
try {
Object previousItem = Reflections.method$CraftItemStack$asNMSCopy.invoke(null, offhandItem);
Object totemItem = Reflections.method$CraftItemStack$asNMSCopy.invoke(null, totem);
Object packet1 = Reflections.constructor$ClientboundSetEquipmentPacket
.newInstance(player.getEntityId(), List.of(Pair.of(Reflections.instance$EquipmentSlot$OFFHAND, totemItem)));
Object packet2 = Reflections.constructor$ClientboundEntityEventPacket
.newInstance(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player), (byte) 35);
Object packet3 = Reflections.constructor$ClientboundSetEquipmentPacket
.newInstance(player.getEntityId(), List.of(Pair.of(Reflections.instance$EquipmentSlot$OFFHAND, previousItem)));
packets.add(packet1);
packets.add(packet2);
packets.add(packet3);
Object bundlePacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
BukkitNetworkManager.instance().sendPacket(player, bundlePacket);
} catch (ReflectiveOperationException e) {
BukkitCraftEngine.instance().logger().warn("Failed to send totem animation");
}
}
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.bukkit.util;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonElement;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@@ -809,6 +810,12 @@ public class Reflections {
)
);
public static final Field field$SynchedEntityData$DataValue$serializer = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$SynchedEntityData$DataValue, 1
)
);
public static final Field field$SynchedEntityData$DataValue$value = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$SynchedEntityData$DataValue, 2
@@ -3027,6 +3034,32 @@ public class Reflections {
}
}
public static final Class<?> clazz$ClientboundSetEquipmentPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundSetEquipmentPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutEntityEquipment")
)
);
public static final Constructor<?> constructor$ClientboundSetEquipmentPacket = requireNonNull(
ReflectionUtils.getConstructor(
clazz$ClientboundSetEquipmentPacket, int.class, List.class
)
);
public static final Class<?> clazz$ClientboundEntityEventPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundEntityEventPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutEntityStatus")
)
);
public static final Constructor<?> constructor$ClientboundEntityEventPacket = requireNonNull(
ReflectionUtils.getConstructor(
clazz$ClientboundEntityEventPacket, clazz$Entity, byte.class
)
);
public static final Method method$Block$defaultBlockState = requireNonNull(
ReflectionUtils.getMethod(
clazz$Block, clazz$BlockState
@@ -3666,6 +3699,7 @@ public class Reflections {
public static final Object instance$EntityType$TEXT_DISPLAY;
public static final Object instance$EntityType$ITEM_DISPLAY;
public static final Object instance$EntityType$BLOCK_DISPLAY;
public static final Object instance$EntityType$FALLING_BLOCK;
public static final Object instance$EntityType$INTERACTION;
public static final Object instance$EntityType$SHULKER;
@@ -3676,6 +3710,8 @@ public class Reflections {
instance$EntityType$TEXT_DISPLAY = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, textDisplay);
Object itemDisplay = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "item_display");
instance$EntityType$ITEM_DISPLAY = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, itemDisplay);
Object blockDisplay = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "block_display");
instance$EntityType$BLOCK_DISPLAY = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, blockDisplay);
Object fallingBlock = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "falling_block");
instance$EntityType$FALLING_BLOCK = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fallingBlock);
Object interaction = method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "interaction");
@@ -6050,4 +6086,58 @@ public class Reflections {
clazz$BlockStateBase, clazz$BlockState, clazz$Mirror
)
);
public static final Constructor<?> constructor$ClientboundMoveEntityPacket$Pos = requireNonNull(
ReflectionUtils.getDeclaredConstructor(
clazz$ClientboundMoveEntityPacket$Pos, int.class, short.class, short.class, short.class, boolean.class
)
);
public static final Method method$Entity$getType = requireNonNull(
ReflectionUtils.getMethod(
clazz$Entity, clazz$EntityType
)
);
public static final Constructor<?> constructor$SynchedEntityData$DataValue = requireNonNull(
ReflectionUtils.getConstructor(
clazz$SynchedEntityData$DataValue, int.class, clazz$EntityDataSerializer, Object.class
)
);
public static final Class<?> clazz$EntityLookup = requireNonNull(
ReflectionUtils.getClazz(
"ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup",
"io.papermc.paper.chunk.system.entity.EntityLookup"
)
);
public static final Method method$Level$moonrise$getEntityLookup = requireNonNull(
ReflectionUtils.getMethod(
VersionHelper.isVersionNewerThan1_21() ? clazz$Level : clazz$ServerLevel,
clazz$EntityLookup
)
);
public static final Method method$EntityLookup$get = requireNonNull(
ReflectionUtils.getMethod(
clazz$EntityLookup, clazz$Entity, int.class
)
);
// 1.21+
public static final Class<?> clazz$PacketReport =
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("data.info.PacketReport")
);
// 1.21+
public static final Constructor<?> constructor$PacketReport = Optional.ofNullable(clazz$PacketReport)
.map(it -> ReflectionUtils.getConstructor(it, 0))
.orElse(null);
// 1.21+
public static final Method method$PacketReport$serializePackets = Optional.ofNullable(clazz$PacketReport)
.map(it -> ReflectionUtils.getDeclaredMethod(it, JsonElement.class))
.orElse(null);
}

View File

@@ -1,7 +1,7 @@
package net.momirealms.craftengine.bukkit.world;
import net.momirealms.craftengine.bukkit.util.LightUtils;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.SectionPosUtils;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World;
@@ -20,7 +20,7 @@ public class BukkitCEWorld extends CEWorld {
@Override
public void tick() {
if (ConfigManager.enableLightSystem()) {
if (Config.enableLightSystem()) {
LightUtils.updateChunkLight((org.bukkit.World) world.platformWorld(), SectionPosUtils.toMap(super.updatedSectionPositions, world.worldHeight().getMinSection() - 1, world.worldHeight().getMaxSection() + 1));
super.updatedSectionPositions.clear();
}

View File

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.CEWorld;
@@ -265,7 +265,7 @@ public class BukkitWorldManager implements WorldManager, Listener {
plugin.logger().warn("Failed to write chunk tag at " + chunk.getX() + " " + chunk.getZ(), e);
return;
} finally {
if (ConfigManager.restoreVanillaBlocks()) {
if (Config.restoreVanillaBlocks()) {
CESection[] ceSections = ceChunk.sections();
Object worldServer = FastNMS.INSTANCE.field$CraftChunk$worldServer(chunk);
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
@@ -313,7 +313,7 @@ public class BukkitWorldManager implements WorldManager, Listener {
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
if (ConfigManager.syncCustomBlocks()) {
if (Config.syncCustomBlocks()) {
Object statesContainer = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
Object data = Reflections.varHandle$PalettedContainer$data.get(statesContainer);
Object palette = Reflections.field$PalettedContainer$Data$palette.get(data);
@@ -362,7 +362,7 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
}
}
if (ConfigManager.restoreCustomBlocks()) {
if (Config.restoreCustomBlocks()) {
if (!ceSection.statesContainer().isEmpty()) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
@@ -378,7 +378,7 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
BukkitInjector.injectLevelChunkSection(section, ceSection, ceWorld, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z));
}
if (ConfigManager.enableRecipeSystem()) {
if (Config.enableRecipeSystem()) {
@SuppressWarnings("unchecked")
Map<Object, Object> blockEntities = (Map<Object, Object>) FastNMS.INSTANCE.field$ChunkAccess$blockEntities(levelChunk);
for (Object blockEntity : blockEntities.values()) {

View File

@@ -40,7 +40,7 @@ tasks.remapJar {
inputFile.set(tasks.shadowJar.get().archiveFile)
destinationDirectory.set(file("$rootDir/target"))
archiveFileName.set("${base.archivesName.get()}-${project.version}.jar")
archiveFileName.set("${base.archivesName.get()}-${project.version}+${rootProject.properties["latest_minecraft_version"]}.jar")
}
loom {

View File

@@ -49,6 +49,8 @@ dependencies {
compileOnly("commons-io:commons-io:${rootProject.properties["commons_io_version"]}")
// Data Fixer Upper
compileOnly("com.mojang:datafixerupper:${rootProject.properties["datafixerupper_version"]}")
// Aho-Corasick java implementation
compileOnly("org.ahocorasick:ahocorasick:${rootProject.properties["ahocorasick_version"]}")
}
java {
@@ -75,6 +77,7 @@ tasks {
relocate("com.saicone.rtag", "net.momirealms.craftengine.libraries.rtag")
relocate("org.yaml.snakeyaml", "net.momirealms.craftengine.libraries.snakeyaml")
relocate("net.kyori", "net.momirealms.craftengine.libraries")
relocate("org.ahocorasick", "net.momirealms.craftengine.libraries.ahocorasick")
relocate("net.momirealms.sparrow.nbt", "net.momirealms.craftengine.libraries.nbt")
}
}

View File

@@ -2,10 +2,62 @@ package net.momirealms.craftengine.core.block;
import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import org.incendo.cloud.suggestion.Suggestion;
import java.util.*;
public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager {
// CraftEngine objects
protected final Map<Key, CustomBlock> byId = new HashMap<>();
// Cached command suggestions
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
// Cached Namespace
protected final Set<String> namespacesInUse = new HashSet<>();
public AbstractBlockManager(CraftEngine plugin) {
super(plugin);
}
@Override
public Map<Key, CustomBlock> blocks() {
return Collections.unmodifiableMap(this.byId);
}
@Override
public Optional<CustomBlock> blockById(Key id) {
return Optional.ofNullable(this.byId.get(id));
}
@Override
public void unload() {
super.clearModelsToGenerate();
this.cachedSuggestions.clear();
this.byId.clear();
}
@Override
public Collection<Suggestion> cachedSuggestions() {
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
public Set<String> namespacesInUse() {
return Collections.unmodifiableSet(this.namespacesInUse);
}
protected void initSuggestions() {
this.cachedSuggestions.clear();
this.namespacesInUse.clear();
Set<String> states = new HashSet<>();
for (CustomBlock block : this.byId.values()) {
states.add(block.id().toString());
this.namespacesInUse.add(block.id().namespace());
for (ImmutableBlockState state : block.variantProvider().states()) {
states.add(state.toString());
}
}
for (String state : states) {
this.cachedSuggestions.add(Suggestion.suggestion(state));
}
}
}

View File

@@ -1,10 +1,9 @@
package net.momirealms.craftengine.core.block;
import com.google.gson.JsonElement;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.Key;
import org.incendo.cloud.suggestion.Suggestion;
@@ -13,12 +12,9 @@ import java.util.Collection;
import java.util.Map;
import java.util.Optional;
public interface BlockManager extends Reloadable, ModelGenerator, ConfigSectionParser {
String CONFIG_SECTION_NAME = "blocks";
public interface BlockManager extends Manageable, ModelGenerator {
default String sectionId() {
return CONFIG_SECTION_NAME;
}
ConfigSectionParser parser();
Collection<ModelGeneration> modelsToGenerate();
@@ -28,19 +24,9 @@ public interface BlockManager extends Reloadable, ModelGenerator, ConfigSectionP
Map<Key, CustomBlock> blocks();
Optional<CustomBlock> getBlock(Key key);
Optional<CustomBlock> blockById(Key key);
Collection<Suggestion> cachedSuggestions();
Map<Key, Key> soundMapper();
void initSuggestions();
void delayedLoad();
void delayedInit();
default int loadingSequence() {
return LoadingSequence.BLOCK;
}
}

View File

@@ -28,6 +28,7 @@ public class BlockSettings {
@Nullable
Key itemId;
Set<Key> tags = Set.of();
float incorrectToolSpeed = 0.3f;
Set<Key> correctTools = Set.of();
String name;
@@ -81,6 +82,7 @@ public class BlockSettings {
newSettings.fluidState = settings.fluidState;
newSettings.blockLight = settings.blockLight;
newSettings.name = settings.name;
newSettings.incorrectToolSpeed = settings.incorrectToolSpeed;
return newSettings;
}
@@ -124,6 +126,10 @@ public class BlockSettings {
return canOcclude;
}
public float incorrectToolSpeed() {
return incorrectToolSpeed;
}
public String name() {
return name;
}
@@ -243,6 +249,11 @@ public class BlockSettings {
return this;
}
public BlockSettings incorrectToolSpeed(float incorrectToolSpeed) {
this.incorrectToolSpeed = incorrectToolSpeed;
return this;
}
public BlockSettings isRandomlyTicking(boolean isRandomlyTicking) {
this.isRandomlyTicking = isRandomlyTicking;
return this;
@@ -381,6 +392,10 @@ public class BlockSettings {
List<String> tools = MiscUtils.getAsStringList(value);
return settings -> settings.correctTools(tools.stream().map(Key::of).collect(Collectors.toSet()));
}));
registerFactory("incorrect-tool-dig-speed", (value -> {
float floatValue = MiscUtils.getAsFloat(value);
return settings -> settings.incorrectToolSpeed(floatValue);
}));
registerFactory("name", (value -> {
String name = value.toString();
return settings -> settings.name(name);

View File

@@ -36,7 +36,7 @@ public class Properties {
public static Property<?> fromMap(String name, Map<String, Object> map) {
String type = (String) map.getOrDefault("type", "empty");
if (type == null) {
throw new NullPointerException("behavior type cannot be null");
throw new NullPointerException("Property type cannot be null");
}
Key key = Key.withDefaultNamespace(type, "craftengine");
PropertyFactory factory = BuiltInRegistries.PROPERTY_FACTORY.getValue(key);

View File

@@ -0,0 +1,40 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.util.Key;
import org.incendo.cloud.suggestion.Suggestion;
import java.util.*;
public abstract class AbstractFurnitureManager implements FurnitureManager {
protected final Map<Key, CustomFurniture> byId = new HashMap<>();
// Cached command suggestions
private final List<Suggestion> cachedSuggestions = new ArrayList<>();
@Override
public void delayedLoad() {
this.initSuggestions();
}
@Override
public void initSuggestions() {
this.cachedSuggestions.clear();
for (Key key : this.byId.keySet()) {
this.cachedSuggestions.add(Suggestion.suggestion(key.toString()));
}
}
@Override
public Collection<Suggestion> cachedSuggestions() {
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
@Override
public Optional<CustomFurniture> furnitureById(Key id) {
return Optional.ofNullable(this.byId.get(id));
}
@Override
public void unload() {
this.byId.clear();
}
}

View File

@@ -0,0 +1,49 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector3f;
import java.util.Optional;
import java.util.UUID;
public interface Furniture {
void initializeColliders();
Vec3d position();
World world();
boolean isValid();
void destroy();
void destroySeats();
Optional<Seat> findFirstAvailableSeat(int targetEntityId);
boolean removeOccupiedSeat(Vector3f seat);
default boolean removeOccupiedSeat(Seat seat) {
return this.removeOccupiedSeat(seat.offset());
}
boolean tryOccupySeat(Seat seat);
UUID uuid();
int baseEntityId();
@NotNull AnchorType anchorType();
@NotNull Key id();
@NotNull CustomFurniture config();
boolean hasExternalModel();
void spawnSeatEntityForPlayer(Player player, Seat seat);
}

View File

@@ -1,37 +1,40 @@
package net.momirealms.craftengine.core.entity.furniture;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.entity.Entity;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import org.incendo.cloud.suggestion.Suggestion;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Optional;
public interface FurnitureManager extends Reloadable, ConfigSectionParser {
String CONFIG_SECTION_NAME = "furniture";
public interface FurnitureManager extends Manageable {
String FURNITURE_ADMIN_NODE = "craftengine.furniture.admin";
void delayedLoad();
ConfigSectionParser parser();
void initSuggestions();
Collection<Suggestion> cachedSuggestions();
void delayedInit();
Furniture place(CustomFurniture furniture, Vec3d vec3d, World world, AnchorType anchorType, boolean playSound);
@Override
default String sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
default int loadingSequence() {
return LoadingSequence.FURNITURE;
}
Optional<CustomFurniture> getFurniture(Key id);
Optional<CustomFurniture> furnitureById(Key id);
boolean isFurnitureRealEntity(int entityId);
@Nullable
Furniture loadedFurnitureByRealEntityId(int entityId);
@Nullable
default Furniture loadedFurnitureByRealEntity(Entity entity) {
return loadedFurnitureByRealEntityId(entity.entityID());
}
@Nullable
Furniture loadedFurnitureByEntityId(int entityId);
}

View File

@@ -0,0 +1,248 @@
package net.momirealms.craftengine.core.font;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public abstract class AbstractFontManager implements FontManager {
private final CraftEngine plugin;
// namespace:font font
private final Map<Key, Font> fonts = new HashMap<>();
// namespace:id image
private final Map<Key, BitmapImage> images = new HashMap<>();
private final Set<Integer> illegalChars = new HashSet<>();
private final ImageParser imageParser;
private final EmojiParser emojiParser;
private OffsetFont offsetFont;
public AbstractFontManager(CraftEngine plugin) {
this.plugin = plugin;
this.imageParser = new ImageParser();
this.emojiParser = new EmojiParser();
}
@Override
public void load() {
this.offsetFont = Optional.ofNullable(plugin.config().settings().getSection("offset-characters"))
.map(OffsetFont::new)
.orElse(null);
}
@Override
public void unload() {
this.fonts.clear();
this.images.clear();
this.illegalChars.clear();
}
@Override
public ConfigSectionParser[] parsers() {
return new ConfigSectionParser[] {this.imageParser, this.emojiParser};
}
@Override
public void delayedLoad() {
Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> this.illegalChars.addAll(font.codepointsInUse()));
}
@Override
public boolean isDefaultFontInUse() {
return !this.illegalChars.isEmpty();
}
@Override
public boolean isIllegalCodepoint(int codepoint) {
return this.illegalChars.contains(codepoint);
}
@Override
public Collection<Font> fonts() {
return Collections.unmodifiableCollection(this.fonts.values());
}
@Override
public Optional<BitmapImage> bitmapImageByCodepoint(Key font, int codepoint) {
return fontById(font).map(f -> f.bitmapImageByCodepoint(codepoint));
}
@Override
public Optional<BitmapImage> bitmapImageByImageId(Key id) {
return Optional.ofNullable(this.images.get(id));
}
@Override
public int codepointByImageId(Key key, int x, int y) {
BitmapImage image = this.images.get(key);
if (image == null) return -1;
return image.codepointAt(x, y);
}
@Override
public String createOffsets(int offset, FontTagFormatter tagFormatter) {
return Optional.ofNullable(this.offsetFont).map(it -> it.createOffset(offset, tagFormatter)).orElse("");
}
@Override
public Optional<Font> fontById(Key id) {
return Optional.ofNullable(this.fonts.get(id));
}
private Font getOrCreateFont(Key key) {
return this.fonts.computeIfAbsent(key, Font::new);
}
public class EmojiParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"emoji", "emojis"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.EMOJI;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
}
}
public class ImageParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"images", "image"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.IMAGE;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (images.containsKey(id)) {
TranslationManager.instance().log("warning.config.image.duplicated", path.toString(), id.toString());
return;
}
Object heightObj = section.get("height");
if (heightObj == null) {
TranslationManager.instance().log("warning.config.image.lack_height", path.toString(), id.toString());
return;
}
int height = MiscUtils.getAsInt(heightObj);
int ascent = MiscUtils.getAsInt(section.getOrDefault("ascent", height - 1));
if (height < ascent) {
TranslationManager.instance().log("warning.config.image.height_smaller_than_ascent", path.toString(), id.toString());
return;
}
Object file = section.get("file");
if (file == null) {
TranslationManager.instance().log("warning.config.image.no_file", path.toString(), id.toString());
return;
}
String resourceLocation = file.toString().replace("\\", "/");
if (!ResourceLocation.isValid(resourceLocation)) {
TranslationManager.instance().log("warning.config.image.invalid_resource_location", path.toString(), id.toString(), resourceLocation);
return;
}
String fontName = (String) section.getOrDefault("font", "minecraft:default");
if (!ResourceLocation.isValid(fontName)) {
TranslationManager.instance().log("warning.config.image.invalid_font_name", path.toString(), id.toString(), fontName);
return;
}
Key fontKey = Key.withDefaultNamespace(fontName, id.namespace());
Font font = getOrCreateFont(fontKey);
List<char[]> chars;
if (section.containsKey("chars")) {
chars = MiscUtils.getAsStringList(section.get("chars")).stream().map(it -> {
if (it.startsWith("\\u")) {
return CharacterUtils.decodeUnicodeToChars(it);
} else {
return it.toCharArray();
}
}).toList();
} else {
String character = (String) section.get("char");
if (character == null) {
TranslationManager.instance().log("warning.config.image.lack_char", path.toString(), id.toString());
return;
}
if (character.length() == 1) {
chars = List.of(character.toCharArray());
} else {
chars = List.of(CharacterUtils.decodeUnicodeToChars(character));
}
}
int size = -1;
int[][] codepointGrid = new int[chars.size()][];
for (int i = 0; i < chars.size(); ++i) {
int[] codepoints = CharacterUtils.charsToCodePoints(chars.get(i));
for (int codepoint : codepoints) {
if (font.isCodepointInUse(codepoint)) {
BitmapImage image = font.bitmapImageByCodepoint(codepoint);
TranslationManager.instance().log("warning.config.image.codepoint_in_use",
path.toString(),
id.toString(),
fontKey.toString(),
CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)),
new String(Character.toChars(codepoint)),
image.id().toString()
);
return;
}
}
codepointGrid[i] = codepoints;
if (size == -1) size = codepoints.length;
if (size != codepoints.length) {
TranslationManager.instance().log("warning.config.image.invalid_codepoint_grid", path.toString(), id.toString());
return;
}
}
if (!resourceLocation.endsWith(".png")) resourceLocation += ".png";
Key namespacedPath = Key.of(resourceLocation);
Path targetImagePath = pack.resourcePackFolder()
.resolve("assets")
.resolve(namespacedPath.namespace())
.resolve("textures")
.resolve(namespacedPath.value());
if (!Files.exists(targetImagePath)) {
TranslationManager.instance().log("warning.config.image.file_not_exist", path.toString(), id.toString(), targetImagePath.toString());
// DO NOT RETURN, JUST GIVE WARNINGS
}
BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, resourceLocation, codepointGrid);
for (int[] y : codepointGrid) {
for (int x : y) {
font.addBitMapImage(x, bitmapImage);
}
}
AbstractFontManager.this.images.put(id, bitmapImage);
}
}
}

View File

@@ -1,172 +0,0 @@
package net.momirealms.craftengine.core.font;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.PreConditions;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.BiFunction;
public abstract class AbstractImageManager implements ImageManager {
private final CraftEngine plugin;
// namespace:font font
private final HashMap<Key, Font> fonts = new HashMap<>();
// namespace:id image
private final HashMap<Key, BitmapImage> images = new HashMap<>();
private final Set<Integer> illegalChars = new HashSet<>();
private OffsetFont offsetFont;
public AbstractImageManager(CraftEngine plugin) {
this.plugin = plugin;
}
@Override
public void load() {
this.offsetFont = Optional.ofNullable(plugin.configManager().settings().getSection("offset-characters"))
.map(OffsetFont::new)
.orElse(null);
}
@Override
public void unload() {
this.fonts.clear();
this.images.clear();
this.illegalChars.clear();
}
@Override
public void delayedLoad() {
Optional.ofNullable(this.fonts.get(DEFAULT_FONT)).ifPresent(font -> {
this.illegalChars.addAll(font.codepointsInUse());
});
}
@Override
public boolean isDefaultFontInUse() {
return !this.illegalChars.isEmpty();
}
@Override
public boolean isIllegalCharacter(int codepoint) {
return this.illegalChars.contains(codepoint);
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
int height = MiscUtils.getAsInt(section.get("height"));
int ascent = MiscUtils.getAsInt(section.get("ascent"));
if (PreConditions.runIfTrue(height < ascent,
() -> this.plugin.logger().warn(path, "Illegal ascent found at " + id + ". Height should be no lower than ascent"))) return;
String file = (String) section.get("file");
if (PreConditions.isNull(file,
() -> this.plugin.logger().warn(path, "`file` option is not set in image " + id))) return;
String fontName = (String) section.getOrDefault("font", "minecraft:default");
if (PreConditions.isNull(fontName,
() -> this.plugin.logger().warn(path, "`font` option is not set in image " + id))) return;
Key fontKey = Key.withDefaultNamespace(fontName, id.namespace());
// get the font
Font font = this.getOrCreateFont(fontKey);
List<char[]> chars;
if (section.containsKey("chars")) {
chars = MiscUtils.getAsStringList(section.get("chars")).stream().map(it -> {
if (it.startsWith("\\u")) {
return CharacterUtils.decodeUnicodeToChars(it);
} else {
return it.toCharArray();
}
}).toList();
} else {
String character = (String) section.get("char");
if (PreConditions.isNull(character,
() -> this.plugin.logger().warn(path, "`char` option is not set in image " + id))) return;
if (character.length() == 1) {
chars = List.of(character.toCharArray());
} else {
chars = List.of(CharacterUtils.decodeUnicodeToChars(character));
}
}
int size = -1;
int[][] codepointGrid = new int[chars.size()][];
for (int i = 0; i < chars.size(); ++i) {
int[] codepoints = CharacterUtils.charsToCodePoints(chars.get(i));
for (int codepoint : codepoints) {
if (PreConditions.runIfTrue(font.isCodepointInUse(codepoint),
() -> this.plugin.logger().warn(path, String.format("Codepoint [%s (%s)] is already used in font [%s]", CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)), new String(Character.toChars(codepoint)), font.key().toString())))) return;
}
codepointGrid[i] = codepoints;
if (size == -1) size = codepoints.length;
if (PreConditions.runIfTrue(size != codepoints.length,
() -> this.plugin.logger().warn(path, "Illegal chars format found at " + id))) return;
}
if (PreConditions.runIfTrue(size == -1,
() -> this.plugin.logger().warn(path, "Illegal chars format found at " + id))) return;
if (!file.endsWith(".png")) file += ".png";
file = file.replace("\\", "/");
Key namespacedPath = Key.of(file);
Path targetImageFile = pack.resourcePackFolder()
.resolve("assets")
.resolve(namespacedPath.namespace())
.resolve("textures")
.resolve(namespacedPath.value());
if (PreConditions.runIfTrue(!Files.exists(targetImageFile),
() -> this.plugin.logger().warn(targetImageFile, "PNG file not found for image " + id))) return;
BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, file, codepointGrid);
for (int[] y : codepointGrid) {
for (int x : y) {
font.registerCodepoint(x, bitmapImage);
}
}
this.images.put(id, bitmapImage);
}
@Override
public Collection<Font> fontsInUse() {
return new ArrayList<>(this.fonts.values());
}
@Override
public Optional<BitmapImage> bitmapImageByCodepoint(Key font, int codepoint) {
return getFontInUse(font).map(f -> f.getImageByCodepoint(codepoint));
}
@Override
public Optional<BitmapImage> bitmapImageByImageId(Key id) {
return Optional.ofNullable(this.images.get(id));
}
@Override
public int codepointByImageId(Key key, int x, int y) {
BitmapImage image = this.images.get(key);
if (image == null) return -1;
return image.codepointAt(x, y);
}
@Override
public String createOffsets(int offset, BiFunction<String, String, String> tagFormatter) {
return Optional.ofNullable(this.offsetFont).map(it -> it.createOffset(offset, tagFormatter)).orElse("");
}
@Override
public Optional<Font> getFontInUse(Key key) {
return Optional.ofNullable(fonts.get(key));
}
private Font getOrCreateFont(Key key) {
return this.fonts.computeIfAbsent(key, Font::new);
}
}

View File

@@ -7,15 +7,15 @@ import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.Key;
public class BitmapImage implements FontProvider {
private final Key imageId;
private final Key id;
private final Key font;
private final int height;
private final int ascent;
private final String file;
private final int[][] codepointGrid;
public BitmapImage(Key imageId, Key font, int height, int ascent, String file, int[][] codepointGrid) {
this.imageId = imageId;
public BitmapImage(Key id, Key font, int height, int ascent, String file, int[][] codepointGrid) {
this.id = id;
this.font = font;
this.height = height;
this.ascent = ascent;
@@ -39,16 +39,16 @@ public class BitmapImage implements FontProvider {
return font;
}
public Key imageId() {
return imageId;
public Key id() {
return id;
}
public int[][] codepointGrid() {
return codepointGrid;
return codepointGrid.clone();
}
public int codepointAt(int row, int column) {
if (row < 0 || row >= codepointGrid.length || column < 0 || column >= codepointGrid[row].length) {
if (!isValidCoordinate(row, column)) {
throw new IndexOutOfBoundsException("Invalid index: (" + row + ", " + column + ")");
}
return codepointGrid[row][column];
@@ -69,16 +69,16 @@ public class BitmapImage implements FontProvider {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
BitmapImage image = (BitmapImage) object;
return imageId.equals(image.imageId);
return id.equals(image.id);
}
@Override
public int hashCode() {
return imageId.hashCode();
return id.hashCode();
}
@Override
public JsonObject getJson() {
public JsonObject get() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("type", "bitmap");
jsonObject.addProperty("height", height);

View File

@@ -4,12 +4,12 @@ import net.momirealms.craftengine.core.util.Key;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class Font {
private final Key key;
private final HashMap<Integer, BitmapImage> idToCodepoint = new LinkedHashMap<>();
private final Map<Integer, BitmapImage> idToCodepoint = new LinkedHashMap<>();
public Font(Key key) {
this.key = key;
@@ -24,11 +24,11 @@ public class Font {
return Collections.unmodifiableCollection(this.idToCodepoint.keySet());
}
public BitmapImage getImageByCodepoint(int codepoint) {
public BitmapImage bitmapImageByCodepoint(int codepoint) {
return this.idToCodepoint.get(codepoint);
}
public void registerCodepoint(int codepoint, BitmapImage image) {
public void addBitMapImage(int codepoint, BitmapImage image) {
this.idToCodepoint.put(codepoint, image);
}
@@ -37,7 +37,7 @@ public class Font {
}
public Collection<BitmapImage> bitmapImages() {
return idToCodepoint.values().stream().distinct().toList();
return this.idToCodepoint.values().stream().distinct().toList();
}
@Override

View File

@@ -1,7 +1,6 @@
package net.momirealms.craftengine.core.font;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.CharacterUtils;
import net.momirealms.craftengine.core.util.FormatUtils;
@@ -9,10 +8,8 @@ import net.momirealms.craftengine.core.util.Key;
import java.util.Collection;
import java.util.Optional;
import java.util.function.BiFunction;
public interface ImageManager extends Reloadable, ConfigSectionParser {
String CONFIG_SECTION_NAME = "images";
public interface FontManager extends Manageable {
Key DEFAULT_FONT = Key.of("minecraft:default");
String BYPASS_BOOK = "craftengine.filter.bypass.book";
String BYPASS_SIGN = "craftengine.filter.bypass.sign";
@@ -20,27 +17,23 @@ public interface ImageManager extends Reloadable, ConfigSectionParser {
String BYPASS_COMMAND = "craftengine.filter.bypass.command";
String BYPASS_ANVIL = "craftengine.filter.bypass.anvil";
default String sectionId() {
return CONFIG_SECTION_NAME;
}
void delayedLoad();
ConfigSectionParser[] parsers();
boolean isDefaultFontInUse();
boolean isIllegalCharacter(int codepoint);
boolean isIllegalCodepoint(int codepoint);
Collection<Font> fontsInUse();
Collection<Font> fonts();
Optional<BitmapImage> bitmapImageByCodepoint(Key font, int codepoint);
default Optional<BitmapImage> getBitmapImageByChars(Key font, char[] chars) {
default Optional<BitmapImage> bitmapImageByChars(Key font, char[] chars) {
return bitmapImageByCodepoint(font, CharacterUtils.charsToCodePoint(chars));
}
Optional<BitmapImage> bitmapImageByImageId(Key imageId);
Optional<Font> getFontInUse(Key font);
Optional<Font> fontById(Key font);
int codepointByImageId(Key imageId, int x, int y);
@@ -48,15 +41,15 @@ public interface ImageManager extends Reloadable, ConfigSectionParser {
return this.codepointByImageId(imageId, 0, 0);
}
default char[] getCharsByImageId(Key imageId) {
return getCharsByImageId(imageId, 0, 0);
default char[] charsByImageId(Key imageId) {
return charsByImageId(imageId, 0, 0);
}
default char[] getCharsByImageId(Key imageId, int x, int y) {
default char[] charsByImageId(Key imageId, int x, int y) {
return Character.toChars(this.codepointByImageId(imageId, x, y));
}
String createOffsets(int offset, BiFunction<String, String, String> tagFormatter);
String createOffsets(int offset, FontTagFormatter tagFormatter);
default String createMiniMessageOffsets(int offset) {
return createOffsets(offset, FormatUtils::miniMessageFont);
@@ -69,10 +62,4 @@ public interface ImageManager extends Reloadable, ConfigSectionParser {
default String createRawOffsets(int offset) {
return createOffsets(offset, (raw, font) -> raw);
}
default int loadingSequence() {
return LoadingSequence.FONT;
}
void delayedInit();
}

View File

@@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.font;
import com.google.gson.JsonObject;
public interface FontProvider {
import java.util.function.Supplier;
JsonObject getJson();
public interface FontProvider extends Supplier<JsonObject> {
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.font;
import java.util.function.BiFunction;
public interface FontTagFormatter extends BiFunction<String, String, String> {
}

View File

@@ -35,6 +35,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
protected final Set<EquipmentGeneration> equipmentsToGenerate;
// Cached command suggestions
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
protected final List<Suggestion> cachedTotemSuggestions = new ArrayList<>();
protected void registerDataFunction(Function<Object, ItemModifier<I>> function, String... alias) {
for (String a : alias) {
@@ -65,6 +66,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
super.clearModelsToGenerate();
this.customItems.clear();
this.cachedSuggestions.clear();
this.cachedTotemSuggestions.clear();
this.legacyOverrides.clear();
this.modernOverrides.clear();
this.customItemTags.clear();
@@ -120,6 +122,11 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
return Collections.unmodifiableCollection(this.cachedSuggestions);
}
@Override
public Collection<Suggestion> cachedTotemSuggestions() {
return Collections.unmodifiableCollection(this.cachedTotemSuggestions);
}
@Override
public Optional<List<ItemBehavior>> getItemBehavior(Key key) {
Optional<CustomItem<I>> customItemOptional = getCustomItem(key);

View File

@@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.item;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.plugin.minimessage.*;
import net.momirealms.craftengine.core.plugin.text.minimessage.*;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@@ -3,11 +3,10 @@ package net.momirealms.craftengine.core.item;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.pack.LegacyOverridesModel;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration;
import net.momirealms.craftengine.core.pack.model.ItemModel;
import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
@@ -16,12 +15,9 @@ import org.incendo.cloud.suggestion.Suggestion;
import javax.annotation.Nullable;
import java.util.*;
public interface ItemManager<T> extends Reloadable, ModelGenerator, ConfigSectionParser {
String CONFIG_SECTION_NAME = "items";
public interface ItemManager<T> extends Manageable, ModelGenerator {
default String sectionId() {
return CONFIG_SECTION_NAME;
}
ConfigSectionParser parser();
Map<Key, TreeSet<LegacyOverridesModel>> legacyItemOverrides();
@@ -79,13 +75,9 @@ public interface ItemManager<T> extends Reloadable, ModelGenerator, ConfigSectio
int fuelTime(Key id);
default int loadingSequence() {
return LoadingSequence.ITEM;
}
Collection<Suggestion> cachedSuggestions();
void delayedInit();
Collection<Suggestion> cachedTotemSuggestions();
Object encodeJava(Key componentType, @Nullable Object component);
}

View File

@@ -2,14 +2,14 @@ package net.momirealms.craftengine.core.item.modifier;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.AdventureHelper;
public class DisplayNameModifier<I> implements ItemModifier<I> {
private final String argument;
public DisplayNameModifier(String argument) {
this.argument = ConfigManager.nonItalic() ? "<!i>" + argument : argument;
this.argument = Config.nonItalic() ? "<!i>" + argument : argument;
}
@Override

View File

@@ -2,14 +2,14 @@ package net.momirealms.craftengine.core.item.modifier;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.AdventureHelper;
public class ItemNameModifier<I> implements ItemModifier<I> {
private final String argument;
public ItemNameModifier(String argument) {
this.argument = ConfigManager.nonItalic() ? "<!i>" + argument : argument;
this.argument = Config.nonItalic() ? "<!i>" + argument : argument;
}
@Override

View File

@@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.item.modifier;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.AdventureHelper;
import java.util.List;
@@ -11,7 +11,7 @@ public class LoreModifier<I> implements ItemModifier<I> {
private final List<String> argument;
public LoreModifier(List<String> argument) {
this.argument = ConfigManager.nonItalic() ? argument.stream().map(it -> "<!i>" + it).toList() : argument;
this.argument = Config.nonItalic() ? argument.stream().map(it -> "<!i>" + it).toList() : argument;
}
@Override

View File

@@ -0,0 +1,183 @@
package net.momirealms.craftengine.core.item.recipe;
import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.item.recipe.vanilla.VanillaRecipeReader;
import net.momirealms.craftengine.core.item.recipe.vanilla.reader.VanillaRecipeReader1_20;
import net.momirealms.craftengine.core.item.recipe.vanilla.reader.VanillaRecipeReader1_20_5;
import net.momirealms.craftengine.core.item.recipe.vanilla.reader.VanillaRecipeReader1_21_2;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.*;
public abstract class AbstractRecipeManager<T> implements RecipeManager<T> {
protected final VanillaRecipeReader recipeReader;
protected final Map<Key, List<Recipe<T>>> byType = new HashMap<>();
protected final Map<Key, Recipe<T>> byId = new HashMap<>();
protected final Map<Key, List<Recipe<T>>> byResult = new HashMap<>();
protected final Map<Key, List<Recipe<T>>> byIngredient = new HashMap<>();
protected final Set<Key> dataPackRecipes = new HashSet<>();
protected final Set<Key> customRecipes = new HashSet<>();
private final RecipeParser recipeParser;
public AbstractRecipeManager() {
this.recipeReader = initVanillaRecipeReader();
this.recipeParser = new RecipeParser();
}
@Override
public ConfigSectionParser parser() {
return this.recipeParser;
}
private VanillaRecipeReader initVanillaRecipeReader() {
if (VersionHelper.isVersionNewerThan1_21_2()) {
return new VanillaRecipeReader1_21_2();
} else if (VersionHelper.isVersionNewerThan1_20_5()) {
return new VanillaRecipeReader1_20_5();
} else {
return new VanillaRecipeReader1_20();
}
}
@Override
public void unload() {
this.dataPackRecipes.clear();
this.byType.clear();
this.byId.clear();
this.byResult.clear();
this.byIngredient.clear();
for (Key key : this.customRecipes) {
unregisterPlatformRecipe(key);
}
this.customRecipes.clear();
}
protected void markAsDataPackRecipe(Key key) {
this.dataPackRecipes.add(key);
}
protected void markAsCustomRecipe(Key key) {
this.customRecipes.add(key);
}
@Override
public boolean isDataPackRecipe(Key key) {
return this.dataPackRecipes.contains(key);
}
@Override
public boolean isCustomRecipe(Key key) {
return this.byId.containsKey(key);
}
@Override
public Optional<Recipe<T>> recipeById(Key key) {
return Optional.ofNullable(this.byId.get(key));
}
@Override
public List<Recipe<T>> recipesByType(Key type) {
return this.byType.getOrDefault(type, List.of());
}
@Override
public List<Recipe<T>> recipeByResult(Key result) {
return this.byResult.getOrDefault(result, List.of());
}
@Override
public List<Recipe<T>> recipeByIngredient(Key ingredient) {
return this.byIngredient.getOrDefault(ingredient, List.of());
}
@Nullable
@Override
public Recipe<T> recipeByInput(Key type, RecipeInput input) {
List<Recipe<T>> recipes = this.byType.get(type);
if (recipes == null) return null;
for (Recipe<T> recipe : recipes) {
if (recipe.matches(input)) {
return recipe;
}
}
return null;
}
@Nullable
@Override
public Recipe<T> recipeByInput(Key type, RecipeInput input, Key lastRecipe) {
if (lastRecipe != null) {
Recipe<T> last = byId.get(lastRecipe);
if (last != null && last.matches(input)) {
return last;
}
}
return recipeByInput(type, input);
}
protected void registerInternalRecipe(Key id, Recipe<T> recipe) {
this.byType.computeIfAbsent(recipe.type(), k -> new ArrayList<>()).add(recipe);
this.byId.put(id, recipe);
this.byResult.computeIfAbsent(recipe.result().item().id(), k -> new ArrayList<>()).add(recipe);
HashSet<Key> usedKeys = new HashSet<>();
for (Ingredient<T> ingredient : recipe.ingredientsInUse()) {
for (Holder<Key> holder : ingredient.items()) {
Key key = holder.value();
if (usedKeys.add(key)) {
this.byIngredient.computeIfAbsent(key, k -> new ArrayList<>()).add(recipe);
}
}
}
}
public class RecipeParser implements ConfigSectionParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"recipes", "recipe"};
@Override
public int loadingSequence() {
return LoadingSequence.RECIPE;
}
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (!Config.enableRecipeSystem()) return;
if (AbstractRecipeManager.this.byId.containsKey(id)) {
TranslationManager.instance().log("warning.config.recipe.duplicated", path.toString(), id.toString());
return;
}
Recipe<T> recipe;
try {
recipe = RecipeTypes.fromMap(id, section);
} catch (Exception e) {
CraftEngine.instance().logger().warn(path, "Failed to create recipe: " + id, e);
return;
}
try {
markAsCustomRecipe(id);
registerInternalRecipe(id, recipe);
registerPlatformRecipe(id, recipe);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to register custom recipe " + id, e);
}
}
}
protected abstract void unregisterPlatformRecipe(Key key);
protected abstract void registerPlatformRecipe(Key key, Recipe<T> recipe);
}

View File

@@ -1,45 +1,33 @@
package net.momirealms.craftengine.core.item.recipe;
import net.momirealms.craftengine.core.item.recipe.input.RecipeInput;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public interface RecipeManager<T> extends Reloadable, ConfigSectionParser {
String CONFIG_SECTION_NAME = "recipes";
public interface RecipeManager<T> extends Manageable {
default String sectionId() {
return CONFIG_SECTION_NAME;
}
ConfigSectionParser parser();
boolean isDataPackRecipe(Key key);
boolean isCustomRecipe(Key key);
Optional<Recipe<T>> getRecipeById(Key id);
Optional<Recipe<T>> recipeById(Key id);
List<Recipe<T>> getRecipes(Key type);
List<Recipe<T>> recipesByType(Key type);
List<Recipe<T>> getRecipeByResult(Key result);
List<Recipe<T>> recipeByResult(Key result);
List<Recipe<T>> getRecipeByIngredient(Key ingredient);
List<Recipe<T>> recipeByIngredient(Key ingredient);
@Nullable
Recipe<T> getRecipe(Key type, RecipeInput input);
Recipe<T> recipeByInput(Key type, RecipeInput input);
@Nullable Recipe<T> getRecipe(Key type, RecipeInput input, @Nullable Key lastRecipe);
CompletableFuture<Void> delayedLoad();
void delayedInit();
default int loadingSequence() {
return LoadingSequence.RECIPE;
}
@Nullable
Recipe<T> recipeByInput(Key type, RecipeInput input, @Nullable Key lastRecipe);
}

View File

@@ -0,0 +1,32 @@
package net.momirealms.craftengine.core.loot;
import net.momirealms.craftengine.core.util.Key;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public abstract class AbstractVanillaLootManager implements VanillaLootManager {
protected final Map<Integer, VanillaLoot> blockLoots = new HashMap<>();
// TODO More entity NBT
protected final Map<Key, VanillaLoot> entityLoots = new HashMap<>();
public AbstractVanillaLootManager() {
}
@Override
public void unload() {
this.blockLoots.clear();
this.entityLoots.clear();
}
@Override
public Optional<VanillaLoot> getBlockLoot(int vanillaBlockState) {
return Optional.ofNullable(this.blockLoots.get(vanillaBlockState));
}
@Override
public Optional<VanillaLoot> getEntityLoot(Key entity) {
return Optional.ofNullable(this.entityLoots.get(entity));
}
}

View File

@@ -1,25 +1,16 @@
package net.momirealms.craftengine.core.loot;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.util.Key;
import java.util.Optional;
public interface VanillaLootManager extends ConfigSectionParser, Reloadable {
String CONFIG_SECTION_NAME = "vanilla-loots";
public interface VanillaLootManager extends Manageable {
@Override
default int loadingSequence() {
return LoadingSequence.VANILLA_LOOTS;
}
@Override
default String sectionId() {
return CONFIG_SECTION_NAME;
}
void delayedInit();
ConfigSectionParser parser();
Optional<VanillaLoot> getBlockLoot(int blockState);
Optional<VanillaLoot> getEntityLoot(Key entity);
}

View File

@@ -15,10 +15,9 @@ import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator;
import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor;
import net.momirealms.craftengine.core.plugin.config.template.TemplateManager;
import net.momirealms.craftengine.core.plugin.locale.I18NData;
import net.momirealms.craftengine.core.sound.AbstractSoundManager;
import net.momirealms.craftengine.core.sound.SoundEvent;
@@ -39,6 +38,7 @@ import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import static net.momirealms.craftengine.core.util.MiscUtils.castToMap;
@@ -50,7 +50,6 @@ public abstract class AbstractPackManager implements PackManager {
public static final Set<Key> VANILLA_ITEM_TEXTURES = new HashSet<>();
public static final Set<Key> VANILLA_BLOCK_TEXTURES = new HashSet<>();
public static final Set<Key> VANILLA_FONT_TEXTURES = new HashSet<>();
public static final List<byte[]> SHULKER_PNG = new ArrayList<>(1);
private final CraftEngine plugin;
private final BiConsumer<Path, Path> eventDispatcher;
@@ -86,8 +85,6 @@ public abstract class AbstractPackManager implements PackManager {
loadInternalList("internal/textures/block/_list.json", VANILLA_BLOCK_TEXTURES::add);
loadInternalList("internal/textures/item/_list.json", VANILLA_ITEM_TEXTURES::add);
loadInternalList("internal/textures/font/_list.json", VANILLA_FONT_TEXTURES::add);
loadInternalPng("internal/textures/entity/shulker/shulker.png", SHULKER_PNG::add);
}
private void loadInternalData(String path, BiConsumer<Key, JsonObject> callback) {
@@ -140,18 +137,22 @@ public abstract class AbstractPackManager implements PackManager {
@Override
public void load() {
this.loadPacks();
this.loadConfigs();
this.calculateHash();
if (ConfigManager.hostMode() == HostMode.SELF_HOST) {
Path path = ConfigManager.hostResourcePackPath().startsWith(".") ? plugin.dataFolderPath().resolve(ConfigManager.hostResourcePackPath()) : Path.of(ConfigManager.hostResourcePackPath());
ResourcePackHost.instance().enable(ConfigManager.hostIP(), ConfigManager.hostPort(), path);
ResourcePackHost.instance().setRateLimit(ConfigManager.requestRate(), ConfigManager.requestInterval(), TimeUnit.SECONDS);
if (Config.hostMode() == HostMode.SELF_HOST) {
Path path = Config.hostResourcePackPath().startsWith(".") ? plugin.dataFolderPath().resolve(Config.hostResourcePackPath()) : Path.of(Config.hostResourcePackPath());
ResourcePackHost.instance().enable(Config.hostIP(), Config.hostPort(), path);
ResourcePackHost.instance().setRateLimit(Config.requestRate(), Config.requestInterval(), TimeUnit.SECONDS);
} else {
ResourcePackHost.instance().disable();
}
}
@Override
public void loadResources(boolean recipe) {
this.loadPacks();
this.loadResourceConfigs(recipe ? (p) -> true : (p) -> p.loadingSequence() != LoadingSequence.RECIPE);
}
@Override
public void unload() {
this.loadedPacks.clear();
@@ -192,8 +193,12 @@ public abstract class AbstractPackManager implements PackManager {
@Override
public boolean registerConfigSectionParser(ConfigSectionParser parser) {
if (this.sectionParsers.containsKey(parser.sectionId())) return false;
this.sectionParsers.put(parser.sectionId(), parser);
for (String id : parser.sectionId()) {
if (this.sectionParsers.containsKey(id)) return false;
}
for (String id : parser.sectionId()) {
this.sectionParsers.put(id, parser);
}
return true;
}
@@ -205,7 +210,7 @@ public abstract class AbstractPackManager implements PackManager {
}
public Path selfHostPackPath() {
return ConfigManager.hostResourcePackPath().startsWith(".") ? plugin.dataFolderPath().resolve(ConfigManager.hostResourcePackPath()) : Path.of(ConfigManager.hostResourcePackPath());
return Config.hostResourcePackPath().startsWith(".") ? plugin.dataFolderPath().resolve(Config.hostResourcePackPath()) : Path.of(Config.hostResourcePackPath());
}
private void loadPacks() {
@@ -228,7 +233,7 @@ public abstract class AbstractPackManager implements PackManager {
String version = null;
String author = null;
if (Files.exists(metaFile) && Files.isRegularFile(metaFile)) {
YamlDocument metaYML = ConfigManager.instance().loadYamlData(metaFile.toFile());
YamlDocument metaYML = Config.instance().loadYamlData(metaFile.toFile());
namespace = metaYML.getString("namespace", namespace);
description = metaYML.getString("description");
version = metaYML.getString("version");
@@ -372,7 +377,7 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png.mcmeta");
}
private void loadConfigs() {
private void loadResourceConfigs(Predicate<ConfigSectionParser> predicate) {
long o1 = System.nanoTime();
for (Pack pack : loadedPacks()) {
Pair<List<Path>, List<Path>> files = FileUtils.getConfigsDeeply(pack.configurationFolder());
@@ -404,32 +409,31 @@ public abstract class AbstractPackManager implements PackManager {
this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms");
for (Map.Entry<ConfigSectionParser, List<CachedConfig>> entry : this.cachedConfigs.entrySet()) {
ConfigSectionParser parser = entry.getKey();
boolean isTemplate = parser.sectionId().equals(TemplateManager.CONFIG_SECTION_NAME);
long t1 = System.nanoTime();
for (CachedConfig cached : entry.getValue()) {
for (Map.Entry<String, Object> configEntry : cached.config().entrySet()) {
String key = configEntry.getKey();
try {
Key id = Key.withDefaultNamespace(key, cached.pack().namespace());
if (isTemplate) {
((TemplateManager) parser).addTemplate(cached.pack(), cached.filePath(), id, configEntry.getValue());
} else {
if (parser.isTemplate()) {
this.plugin.templateManager().addTemplate(cached.pack(), cached.filePath(), id, configEntry.getValue());
} else if (predicate.test(parser)) {
if (configEntry.getValue() instanceof Map<?, ?> configSection0) {
Map<String, Object> configSection1 = castToMap(configSection0, false);
if ((boolean) configSection1.getOrDefault("enable", true)) {
parser.parseSection(cached.pack(), cached.filePath(), id, plugin.templateManager().applyTemplates(configSection1));
}
} else {
this.plugin.logger().warn(cached.filePath(), "Configuration section is required for " + parser.sectionId() + "." + configEntry.getKey() + " - ");
this.plugin.logger().warn(cached.filePath(), "Configuration section is required for " + parser.sectionId()[0] + "." + configEntry.getKey() + " - ");
}
}
} catch (Exception e) {
this.plugin.logger().warn(cached.filePath(), "Error loading " + parser.sectionId() + "." + key, e);
this.plugin.logger().warn(cached.filePath(), "Error loading " + parser.sectionId()[0] + "." + key, e);
}
}
}
long t2 = System.nanoTime();
this.plugin.logger().info("Loaded " + parser.sectionId() + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms");
this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms");
}
this.cachedConfigs.clear();
}
@@ -440,10 +444,8 @@ public abstract class AbstractPackManager implements PackManager {
int hashIndex = key.indexOf('#');
String configType = hashIndex != -1 ? key.substring(0, hashIndex) : key;
Optional.ofNullable(this.sectionParsers.get(configType))
.ifPresent(parser -> {
this.cachedConfigs.computeIfAbsent(parser, k -> new ArrayList<>())
.add(new CachedConfig(castToMap(typeSections0, false), path, pack));
});
.ifPresent(parser -> this.cachedConfigs.computeIfAbsent(parser, k -> new ArrayList<>())
.add(new CachedConfig(castToMap(typeSections0, false), path, pack)));
}
}
@@ -466,7 +468,7 @@ public abstract class AbstractPackManager implements PackManager {
try {
List<Path> folders = new ArrayList<>();
folders.addAll(loadedPacks().stream().map(Pack::resourcePackFolder).toList());
folders.addAll(ConfigManager.foldersToMerge().stream().map(it -> plugin.dataFolderPath().getParent().resolve(it)).filter(Files::exists).toList());
folders.addAll(Config.foldersToMerge().stream().map(it -> plugin.dataFolderPath().getParent().resolve(it)).filter(Files::exists).toList());
List<Pair<Path, List<Path>>> duplicated = mergeFolder(folders, generatedPackPath);
if (!duplicated.isEmpty()) {
@@ -528,7 +530,7 @@ public abstract class AbstractPackManager implements PackManager {
private void generateEquipments(Path generatedPackPath) {
for (EquipmentGeneration generator : this.plugin.itemManager().equipmentsToGenerate()) {
EquipmentData equipmentData = generator.modernData();
if (equipmentData != null && ConfigManager.packMaxVersion() >= 21.4f) {
if (equipmentData != null && Config.packMaxVersion() >= 21.4f) {
Path equipmentPath = generatedPackPath
.resolve("assets")
.resolve(equipmentData.assetId().namespace())
@@ -561,7 +563,7 @@ public abstract class AbstractPackManager implements PackManager {
this.plugin.logger().severe("Error writing equipment file", e);
}
}
if (equipmentData != null && ConfigManager.packMaxVersion() >= 21.2f && ConfigManager.packMinVersion() < 21.4f) {
if (equipmentData != null && Config.packMaxVersion() >= 21.2f && Config.packMinVersion() < 21.4f) {
Path equipmentPath = generatedPackPath
.resolve("assets")
.resolve(equipmentData.assetId().namespace())
@@ -599,7 +601,7 @@ public abstract class AbstractPackManager implements PackManager {
}
private void generateClientLang(Path generatedPackPath) {
for (Map.Entry<String, I18NData> entry : this.plugin.translationManager().clientLangManager().langData().entrySet()) {
for (Map.Entry<String, I18NData> entry : this.plugin.translationManager().clientLangData().entrySet()) {
JsonObject json = new JsonObject();
for (Map.Entry<String, String> pair : entry.getValue().translations.entrySet()) {
json.addProperty(pair.getKey(), pair.getValue());
@@ -663,7 +665,7 @@ public abstract class AbstractPackManager implements PackManager {
}
private void generateOverrideSounds(Path generatedPackPath) {
if (!ConfigManager.enableSoundSystem()) return;
if (!Config.enableSoundSystem()) return;
Path soundPath = generatedPackPath
.resolve("assets")
@@ -753,7 +755,7 @@ public abstract class AbstractPackManager implements PackManager {
private void generateBlockOverrides(Path generatedPackPath) {
File blockStatesFile = new File(plugin.dataFolderFile(), "blockstates.yml");
if (!blockStatesFile.exists()) plugin.saveResource("blockstates.yml");
YamlDocument preset = ConfigManager.instance().loadYamlData(blockStatesFile);
YamlDocument preset = Config.instance().loadYamlData(blockStatesFile);
for (Map.Entry<Key, Map<String, JsonElement>> entry : plugin.blockManager().blockOverrides().entrySet()) {
Key key = entry.getKey();
Path overridedBlockPath = generatedPackPath
@@ -788,7 +790,7 @@ public abstract class AbstractPackManager implements PackManager {
}
}
if (!ConfigManager.generateModAssets()) return;
if (!Config.generateModAssets()) return;
for (Map.Entry<Key, JsonElement> entry : plugin.blockManager().modBlockStates().entrySet()) {
Key key = entry.getKey();
Path overridedBlockPath = generatedPackPath
@@ -815,8 +817,8 @@ public abstract class AbstractPackManager implements PackManager {
}
private void generateModernItemModels1_21_2(Path generatedPackPath) {
if (ConfigManager.packMaxVersion() < 21.19f) return;
if (ConfigManager.packMinVersion() > 21.39f) return;
if (Config.packMaxVersion() < 21.19f) return;
if (Config.packMinVersion() > 21.39f) return;
boolean has = false;
for (Map.Entry<Key, List<LegacyOverridesModel>> entry : plugin.itemManager().modernItemModels1_21_2().entrySet()) {
@@ -868,13 +870,13 @@ public abstract class AbstractPackManager implements PackManager {
}
}
if (ConfigManager.packMinVersion() < 21.19f && has) {
plugin.logger().warn("You are using item-model component for models which requires 1.21.2+. But the min supported version is " + "1." + ConfigManager.packMinVersion());
if (Config.packMinVersion() < 21.19f && has) {
plugin.logger().warn("You are using item-model component for models which requires 1.21.2+. But the min supported version is " + "1." + Config.packMinVersion());
}
}
private void generateModernItemModels1_21_4(Path generatedPackPath) {
if (ConfigManager.packMaxVersion() < 21.39f) return;
if (Config.packMaxVersion() < 21.39f) return;
for (Map.Entry<Key, ItemModel> entry : plugin.itemManager().modernItemModels1_21_4().entrySet()) {
Key key = entry.getKey();
Path itemPath = generatedPackPath
@@ -907,7 +909,7 @@ public abstract class AbstractPackManager implements PackManager {
}
private void generateModernItemOverrides(Path generatedPackPath) {
if (ConfigManager.packMaxVersion() < 21.39f) return;
if (Config.packMaxVersion() < 21.39f) return;
for (Map.Entry<Key, TreeMap<Integer, ItemModel>> entry : plugin.itemManager().modernItemOverrides().entrySet()) {
Key key = entry.getKey();
Path overridedItemPath = generatedPackPath
@@ -967,7 +969,7 @@ public abstract class AbstractPackManager implements PackManager {
}
private void generateLegacyItemOverrides(Path generatedPackPath) {
if (ConfigManager.packMinVersion() > 21.39f) return;
if (Config.packMinVersion() > 21.39f) return;
for (Map.Entry<Key, TreeSet<LegacyOverridesModel>> entry : plugin.itemManager().legacyItemOverrides().entrySet()) {
Key key = entry.getKey();
Path overridedItemPath = generatedPackPath
@@ -1020,7 +1022,7 @@ public abstract class AbstractPackManager implements PackManager {
private void generateFonts(Path generatedPackPath) {
// generate image font json
for (Font font : plugin.imageManager().fontsInUse()) {
for (Font font : plugin.imageManager().fonts()) {
Key namespacedKey = font.key();
Path fontPath = generatedPackPath.resolve("assets")
.resolve(namespacedKey.namespace())
@@ -1054,7 +1056,7 @@ public abstract class AbstractPackManager implements PackManager {
}
for (BitmapImage image : font.bitmapImages()) {
providers.add(image.getJson());
providers.add(image.get());
}
try (FileWriter fileWriter = new FileWriter(fontPath.toFile())) {
@@ -1064,7 +1066,7 @@ public abstract class AbstractPackManager implements PackManager {
}
}
if (ConfigManager.resourcePack$overrideUniform()) {
if (Config.resourcePack$overrideUniform()) {
Path fontPath = generatedPackPath.resolve("assets")
.resolve("minecraft")
.resolve("font")
@@ -1115,7 +1117,7 @@ public abstract class AbstractPackManager implements PackManager {
Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING);
conflicts.add(file);
} else {
for (ConditionalResolution resolution : ConfigManager.resolutions()) {
for (ConditionalResolution resolution : Config.resolutions()) {
if (resolution.matcher().test(relative)) {
resolution.resolution().run(targetPath, file);
return FileVisitResult.CONTINUE;

View File

@@ -7,10 +7,11 @@ public class LoadingSequence {
public static final int BLOCK = 30;
public static final int ITEM = 40;
public static final int FURNITURE = 50;
public static final int FONT = 60;
public static final int IMAGE = 60;
public static final int RECIPE = 70;
public static final int CATEGORY = 80;
public static final int SOUND = 90;
public static final int JUKEBOX_SONG = 100;
public static final int VANILLA_LOOTS = 110;
public static final int EMOJI = 120;
}

View File

@@ -1,26 +1,34 @@
package net.momirealms.craftengine.core.pack;
import net.momirealms.craftengine.core.plugin.Reloadable;
import net.momirealms.craftengine.core.plugin.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigSectionParser;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Collection;
public interface PackManager extends Reloadable {
public interface PackManager extends Manageable {
void loadResources(boolean recipe);
@NotNull
Collection<Pack> loadedPacks();
boolean registerConfigSectionParser(ConfigSectionParser parser);
boolean unregisterConfigSectionParser(String id);
default boolean unregisterConfigSectionParser(ConfigSectionParser parser) {
return this.unregisterConfigSectionParser(parser.sectionId());
default void registerConfigSectionParsers(ConfigSectionParser[] parsers) {
for (ConfigSectionParser parser : parsers) {
registerConfigSectionParser(parser);
}
}
void delayedInit();
boolean unregisterConfigSectionParser(String id);
default void unregisterConfigSectionParser(ConfigSectionParser parser) {
for (String id : parser.sectionId()) {
unregisterConfigSectionParser(id);
}
}
void generateResourcePack();

View File

@@ -0,0 +1,39 @@
package net.momirealms.craftengine.core.pack;
public class ResourceLocation {
public static boolean isValid(final String resourceLocation) {
int index = resourceLocation.indexOf(":");
if (index == -1) {
return isValidPath(resourceLocation);
} else {
return isValidNamespace(resourceLocation.substring(0, index)) && isValidPath(resourceLocation.substring(index + 1));
}
}
public static boolean validPathChar(char character) {
return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '/' || character == '.';
}
private static boolean validNamespaceChar(char character) {
return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '.';
}
public static boolean isValidNamespace(String namespace) {
for(int i = 0; i < namespace.length(); ++i) {
if (!validNamespaceChar(namespace.charAt(i))) {
return false;
}
}
return true;
}
public static boolean isValidPath(String path) {
for(int i = 0; i < path.length(); ++i) {
if (!validPathChar(path.charAt(i))) {
return false;
}
}
return true;
}
}

View File

@@ -4,7 +4,7 @@ import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import java.io.IOException;
import java.io.OutputStream;
@@ -26,7 +26,7 @@ public class ResourcePackHost {
private long rateLimitInterval = 1000;
public String url() {
return ConfigManager.hostProtocol() + "://" + ip + ":" + port + "/";
return Config.hostProtocol() + "://" + ip + ":" + port + "/";
}
public void enable(String ip, int port, Path resourcePackPath) {
@@ -77,7 +77,7 @@ public class ResourcePackHost {
private class ResourcePackHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (ConfigManager.denyNonMinecraftRequest()) {
if (Config.denyNonMinecraftRequest()) {
String userAgent = exchange.getRequestHeaders().getFirst("User-Agent");
if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) {
CraftEngine.instance().debug(() -> "Blocked non-Minecraft Java client. User-Agent: " + userAgent);

View File

@@ -1,8 +1,11 @@
package net.momirealms.craftengine.core.pack.model.generation;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.Key;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -25,16 +28,23 @@ public abstract class AbstractModelGenerator implements ModelGenerator {
this.modelsToGenerate.clear();
}
@Override
public void prepareModelGeneration(ModelGeneration model) {
ModelGeneration generation = this.modelsToGenerate.get(model.path());
if (generation != null) {
if (generation.equals(model)) {
public void prepareModelGeneration(Path path, Key id, ModelGeneration model) {
ModelGeneration conflict = this.modelsToGenerate.get(model.path());
if (conflict != null) {
if (conflict.equals(model)) {
return;
}
this.plugin.logger().severe("Two or more configurations attempt to generate different json models with the same path: " + model.path());
TranslationManager.instance().log("warning.config.model.generation.conflict", path.toString(), id.toString(), model.path().toString());
return;
}
if (!ResourceLocation.isValid(model.parentModelPath())) {
TranslationManager.instance().log("warning.config.model.generation.parent.invalid_resource_location", path.toString(), id.toString(), model.parentModelPath());
}
for (Map.Entry<String, String> texture : model.texturesOverride().entrySet()) {
if (!ResourceLocation.isValid(texture.getValue())) {
TranslationManager.instance().log("warning.config.model.generation.texture.invalid_resource_location", path.toString(), id.toString(), texture.getKey(), texture.getValue());
}
}
this.modelsToGenerate.put(model.path(), model);
}
}

View File

@@ -6,6 +6,4 @@ public interface ModelGenerator {
Collection<ModelGeneration> modelsToGenerate();
void clearModelsToGenerate();
void prepareModelGeneration(ModelGeneration model);
}

View File

@@ -46,7 +46,7 @@ public class ObfC {
}
private static String normalizeCharset(String input) {
return input.toLowerCase(Locale.ROOT);
return input.toLowerCase(Locale.ENGLISH);
}
private static String generateDefaultCharset() {

View File

@@ -101,7 +101,7 @@ public final class ObfF {
}
private static Section 九转大肠() {
return CraftEngine.instance().configManager().settings()
return CraftEngine.instance().config().settings()
.getSection("resource-pack.protection");
}

View File

@@ -2,7 +2,7 @@ package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.RecipeManager;
import net.momirealms.craftengine.core.loot.VanillaLootManager;
@@ -11,7 +11,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.template.TemplateManager;
import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl;
import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
@@ -29,11 +29,14 @@ import net.momirealms.craftengine.core.plugin.logger.filter.LogFilter;
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter;
import net.momirealms.craftengine.core.sound.SoundManager;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.WorldManager;
import org.apache.logging.log4j.LogManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -41,13 +44,15 @@ public abstract class CraftEngine implements Plugin {
public static final String MOD_CLASS = "net.momirealms.craftengine.mod.CraftEnginePlugin";
public static final String NAMESPACE = "craftengine";
private static CraftEngine instance;
protected PluginLogger logger;
protected Consumer<Supplier<String>> debugger = (s) -> {};
protected Config config;
protected ClassPathAppender classPathAppender;
protected DependencyManager dependencyManager;
protected SchedulerAdapter<?> scheduler;
protected NetworkManager networkManager;
protected ClassPathAppender classPathAppender;
protected ImageManager imageManager;
protected FontManager fontManager;
protected PackManager packManager;
protected ConfigManager configManager;
protected ItemManager<?> itemManager;
protected RecipeManager<?> recipeManager;
protected BlockManager blockManager;
@@ -62,8 +67,7 @@ public abstract class CraftEngine implements Plugin {
protected SoundManager soundManager;
protected VanillaLootManager vanillaLootManager;
protected PluginLogger logger;
protected Consumer<Supplier<String>> debugger = (s) -> {};
private final Consumer<CraftEngine> reloadEventDispatcher;
private boolean isReloading;
private String buildByBit = "%%__BUILTBYBIT__%%";
@@ -72,8 +76,10 @@ public abstract class CraftEngine implements Plugin {
private String user = "%%__USER__%%";
private String username = "%%__USERNAME__%%";
protected CraftEngine() {
protected CraftEngine(Consumer<CraftEngine> reloadEventDispatcher) {
instance = this;
this.reloadEventDispatcher = reloadEventDispatcher;
VersionHelper.init(serverVersion());
}
public static CraftEngine instance() {
@@ -83,8 +89,7 @@ public abstract class CraftEngine implements Plugin {
return instance;
}
@Override
public void load() {
public void onPluginLoad() {
((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addFilter(new LogFilter());
((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter());
this.dependencyManager = new DependencyManagerImpl(this);
@@ -93,78 +98,124 @@ public abstract class CraftEngine implements Plugin {
dependenciesToLoad.addAll(platformDependencies());
this.dependencyManager.loadDependencies(dependenciesToLoad);
this.translationManager = new TranslationManagerImpl(this);
this.configManager = new ConfigManager(this);
this.config = new Config(this);
}
// TODO Make most things async
@Override
public void reload() {
if (this.isReloading) return;
this.isReloading = true;
try {
this.configManager.reload();
public record ReloadResult(boolean success, long asyncTime, long syncTime) {
static ReloadResult failure() {
return new ReloadResult(false, -1L, -1L);
}
static ReloadResult success(long asyncTime, long syncTime) {
return new ReloadResult(true, asyncTime, syncTime);
}
}
public CompletableFuture<ReloadResult> reloadPlugin(Executor asyncExecutor, Executor syncExecutor, boolean reloadRecipe) {
CompletableFuture<ReloadResult> future = new CompletableFuture<>();
asyncExecutor.execute(() -> {
if (this.isReloading) {
future.complete(ReloadResult.failure());
return;
}
this.isReloading = true;
long time1 = System.currentTimeMillis();
// firstly reload main config
this.config.load();
// reset debugger
this.debugger = Config.debug() ? (s) -> logger.info("[Debug] " + s.get()) : (s) -> {};
// now we reload the translations
this.translationManager.reload();
// clear the outdated cache by reloading the managers
this.templateManager.reload();
this.furnitureManager.reload();
this.imageManager.reload();
this.fontManager.reload();
this.itemManager.reload();
this.soundManager.reload();
this.recipeManager.reload();
this.itemBrowserManager.reload();
this.blockManager.reload();
this.worldManager.reload();
this.vanillaLootManager.reload();
// load configs here
this.packManager.reload();
// load at last
this.guiManager.reload();
// delayed load
this.translationManager.delayedLoad();
this.blockManager.delayedLoad();
this.furnitureManager.delayedLoad();
this.itemBrowserManager.delayedLoad();
this.soundManager.delayedLoad();
this.imageManager.delayedLoad();
// reset debugger
if (ConfigManager.debug()) {
this.debugger = (s) -> logger.info("[Debug] " + s.get());
} else {
this.debugger = (s) -> {};
this.packManager.reload();
if (reloadRecipe) {
this.recipeManager.reload();
}
} finally {
this.recipeManager.delayedLoad().thenRun(() -> this.isReloading = false);
}
// now we load resources
this.packManager.loadResources(reloadRecipe);
// handle some special client lang for instance block_name
this.translationManager.delayedLoad();
// init suggestions and packet mapper
this.blockManager.delayedLoad();
// init suggestions
this.furnitureManager.delayedLoad();
// sort the categories
this.itemBrowserManager.delayedLoad();
// collect illegal characters from minecraft:default font
this.fontManager.delayedLoad();
if (reloadRecipe) {
// convert data pack recipes
this.recipeManager.delayedLoad();
}
long time2 = System.currentTimeMillis();
long asyncTime = time2 - time1;
syncExecutor.execute(() -> {
try {
long time3 = System.currentTimeMillis();
// register songs
this.soundManager.runDelayedSyncTasks();
// register recipes
if (reloadRecipe) {
this.recipeManager.runDelayedSyncTasks();
}
long time4 = System.currentTimeMillis();
long syncTime = time4 - time3;
this.reloadEventDispatcher.accept(this);
future.complete(ReloadResult.success(asyncTime, syncTime));
} finally {
this.isReloading = false;
}
});
});
return future;
}
@Override
public void enable() {
this.networkManager.enable();
this.templateManager = new TemplateManagerImpl(this);
public void onPluginEnable() {
this.networkManager.init();
this.templateManager = new TemplateManagerImpl();
this.itemBrowserManager = new ItemBrowserManagerImpl(this);
this.commandManager.registerDefaultFeatures();
// delay the reload so other plugins can register some parsers
// delay the reload so other plugins can register some custom parsers
this.scheduler.sync().runDelayed(() -> {
this.registerParsers();
this.registerDefaultParsers();
// hook external item plugins
this.itemManager.delayedInit();
this.reload();
// hook worldedit
this.blockManager.delayedInit();
// register listeners and tasks
this.guiManager.delayedInit();
this.recipeManager.delayedInit();
this.blockManager.delayedInit();
this.worldManager.delayedInit();
this.packManager.delayedInit();
this.furnitureManager.delayedInit();
this.imageManager.delayedInit();
this.fontManager.delayedInit();
this.vanillaLootManager.delayedInit();
this.delayedEnable();
// reload the plugin
try {
this.reloadPlugin(Runnable::run, Runnable::run, true);
} catch (Exception e) {
this.logger.warn("Failed to reload plugin on enable stage", e);
}
// must be after reloading because this process loads furniture
this.worldManager.delayedInit();
this.furnitureManager.delayedInit();
// set up some platform extra tasks
this.platformDelayedEnable();
});
}
@Override
public void disable() {
if (this.senderFactory != null) this.senderFactory.close();
if (this.commandManager != null) this.commandManager.unregisterFeatures();
if (this.networkManager != null) this.networkManager.shutdown();
if (this.imageManager != null) this.imageManager.disable();
public void onPluginDisable() {
if (this.networkManager != null) this.networkManager.disable();
if (this.fontManager != null) this.fontManager.disable();
if (this.packManager != null) this.packManager.disable();
if (this.itemManager != null) this.itemManager.disable();
if (this.blockManager != null) this.blockManager.disable();
@@ -176,14 +227,38 @@ public abstract class CraftEngine implements Plugin {
if (this.guiManager != null) this.guiManager.disable();
if (this.soundManager != null) this.soundManager.disable();
if (this.vanillaLootManager != null) this.vanillaLootManager.disable();
if (this.translationManager != null) this.translationManager.disable();
if (this.scheduler != null) this.scheduler.shutdownScheduler();
if (this.scheduler != null) this.scheduler.shutdownExecutor();
if (this.commandManager != null) this.commandManager.unregisterFeatures();
if (this.senderFactory != null) this.senderFactory.close();
ResourcePackHost.instance().disable();
}
protected abstract void registerParsers();
protected void registerDefaultParsers() {
// register template parser
this.packManager.registerConfigSectionParser(this.templateManager.parser());
// register font parser
this.packManager.registerConfigSectionParsers(this.fontManager.parsers());
// register item parser
this.packManager.registerConfigSectionParser(this.itemManager.parser());
// register furniture parser
this.packManager.registerConfigSectionParser(this.furnitureManager.parser());
// register block parser
this.packManager.registerConfigSectionParser(this.blockManager.parser());
// register recipe parser
this.packManager.registerConfigSectionParser(this.recipeManager.parser());
// register category parser
this.packManager.registerConfigSectionParser(this.itemBrowserManager.parser());
// register translation parser
this.packManager.registerConfigSectionParsers(this.translationManager.parsers());
// register sound parser
this.packManager.registerConfigSectionParsers(this.soundManager.parsers());
// register vanilla loot parser
this.packManager.registerConfigSectionParser(this.vanillaLootManager.parser());
}
protected abstract void delayedEnable();
protected abstract void platformDelayedEnable();
protected abstract List<Dependency> platformDependencies();
@@ -203,15 +278,11 @@ public abstract class CraftEngine implements Plugin {
Dependencies.BOOSTED_YAML,
Dependencies.MINIMESSAGE,
Dependencies.TEXT_SERIALIZER_GSON,
Dependencies.TEXT_SERIALIZER_JSON
Dependencies.TEXT_SERIALIZER_JSON,
Dependencies.AHO_CORASICK
);
}
@Override
public DependencyManager dependencyManager() {
return dependencyManager;
}
@SuppressWarnings("unchecked")
@Override
public <W> SchedulerAdapter<W> scheduler() {
@@ -223,6 +294,33 @@ public abstract class CraftEngine implements Plugin {
return classPathAppender;
}
@Override
public Config config() {
return config;
}
@Override
public PluginLogger logger() {
return logger;
}
@Override
public void debug(Supplier<String> message) {
debugger.accept(message);
}
@Override
public boolean isReloading() {
return isReloading;
}
public abstract boolean hasPlaceholderAPI();
@Override
public DependencyManager dependencyManager() {
return dependencyManager;
}
@SuppressWarnings("unchecked")
@Override
public <T> ItemManager<T> itemManager() {
@@ -240,18 +338,8 @@ public abstract class CraftEngine implements Plugin {
}
@Override
public ImageManager imageManager() {
return imageManager;
}
@Override
public ConfigManager configManager() {
return configManager;
}
@Override
public PluginLogger logger() {
return logger;
public FontManager imageManager() {
return fontManager;
}
@Override
@@ -280,9 +368,10 @@ public abstract class CraftEngine implements Plugin {
return (RecipeManager<T>) recipeManager;
}
@SuppressWarnings("unchecked")
@Override
public SenderFactory<? extends Plugin, ?> senderFactory() {
return senderFactory;
public <P extends Plugin, C> SenderFactory<P, C> senderFactory() {
return (SenderFactory<P, C>) senderFactory;
}
@Override
@@ -309,15 +398,4 @@ public abstract class CraftEngine implements Plugin {
public VanillaLootManager vanillaLootManager() {
return vanillaLootManager;
}
@Override
public void debug(Supplier<String> message) {
debugger.accept(message);
}
public boolean isReloading() {
return isReloading;
}
public abstract boolean hasPlaceholderAPI();
}

View File

@@ -0,0 +1,39 @@
package net.momirealms.craftengine.core.plugin;
public interface Manageable {
// on plugin enable
default void init() {
}
// after all plugins enabled
default void delayedInit() {
}
// async reload
default void reload() {
unload();
load();
}
// async unload
default void unload() {
}
// async load
default void load() {
}
// on plugin disable
default void disable() {
unload();
}
// after all modules reloaded
default void delayedLoad() {
}
// delayed tasks on main thread
default void runDelayedSyncTasks() {
}
}

View File

@@ -3,14 +3,14 @@ package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.FontManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.RecipeManager;
import net.momirealms.craftengine.core.loot.VanillaLootManager;
import net.momirealms.craftengine.core.pack.PackManager;
import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender;
import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.template.TemplateManager;
import net.momirealms.craftengine.core.plugin.dependency.DependencyManager;
import net.momirealms.craftengine.core.plugin.gui.GuiManager;
@@ -27,7 +27,7 @@ import java.io.InputStream;
import java.nio.file.Path;
import java.util.function.Supplier;
public interface Plugin extends Reloadable {
public interface Plugin {
InputStream resourceStream(String filePath);
@@ -39,6 +39,8 @@ public interface Plugin extends Reloadable {
Path dataFolderPath();
boolean isReloading();
DependencyManager dependencyManager();
<W> SchedulerAdapter<W> scheduler();
@@ -55,9 +57,9 @@ public interface Plugin extends Reloadable {
NetworkManager networkManager();
ImageManager imageManager();
FontManager imageManager();
ConfigManager configManager();
Config config();
TranslationManager translationManager();
@@ -69,7 +71,7 @@ public interface Plugin extends Reloadable {
<T> RecipeManager<T> recipeManager();
SenderFactory<? extends Plugin, ?> senderFactory();
<P extends Plugin, C> SenderFactory<P, C> senderFactory();
WorldManager worldManager();

View File

@@ -1,22 +0,0 @@
package net.momirealms.craftengine.core.plugin;
public interface Reloadable {
default void reload() {
unload();
load();
}
default void enable() {
}
default void unload() {
}
default void load() {
}
default void disable() {
unload();
}
}

View File

@@ -12,7 +12,7 @@ import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.TranslatableComponent;
import net.momirealms.craftengine.core.plugin.Plugin;
import net.momirealms.craftengine.core.plugin.command.sender.Sender;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.locale.CraftEngineCaptionFormatter;
import net.momirealms.craftengine.core.plugin.locale.CraftEngineCaptionProvider;
import net.momirealms.craftengine.core.util.ArrayUtils;
@@ -75,7 +75,7 @@ public abstract class AbstractCommandManager<C> implements CraftEngineCommandMan
injectExceptionHandler(CommandExecutionException.class, MinecraftExceptionHandler.createDefaultCommandExecutionHandler(), StandardCaptionKeys.EXCEPTION_UNEXPECTED);
}
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
private void injectExceptionHandler(Class<? extends Throwable> type, MinecraftExceptionHandler.MessageFactory<C, ?> factory, Caption key) {
getCommandManager().exceptionController().registerHandler(type, ctx -> {
final @Nullable ComponentLike message = factory.message(captionFormatter, (ExceptionContext) ctx);
@@ -126,7 +126,7 @@ public abstract class AbstractCommandManager<C> implements CraftEngineCommandMan
@Override
public void registerDefaultFeatures() {
YamlDocument document = ConfigManager.instance().loadYamlConfig(commandsFile,
YamlDocument document = Config.instance().loadYamlConfig(commandsFile,
GeneralSettings.DEFAULT,
LoaderSettings
.builder()

Some files were not shown because too many files have changed in this diff Show More