From d7164b386e6e2d8db441ea376f46c6c2fa40790b Mon Sep 17 00:00:00 2001 From: lcy0x1 Date: Thu, 20 Jun 2024 16:41:01 +0800 Subject: [PATCH] init --- .gitignore | 31 ++ LICENSE | 504 ++++++++++++++++++ build.gradle | 184 +++++++ gradle.properties | 33 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 245 +++++++++ gradlew.bat | 92 ++++ libs/l2modularblocks-3.0.0-pre0.jar | Bin 0 -> 39906 bytes libs/l2serial-3.0.0-pre1-sources.jar | Bin 0 -> 45584 bytes libs/l2serial-3.0.0-pre1.jar | Bin 0 -> 107976 bytes settings.gradle | 11 + .../f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 | 1 + .../l2core/base/effects/ClientEffectCap.java | 17 + .../l2core/base/effects/EffectBuilder.java | 43 ++ .../l2core/base/effects/EffectProperties.java | 18 + .../l2core/base/effects/EffectToClient.java | 18 + .../xkmc/l2core/base/effects/EffectUtil.java | 84 +++ .../base/effects/ForceAddEffectEvent.java | 22 + .../base/effects/api/ClientRenderEffect.java | 11 + .../base/effects/api/DelayedEntityRender.java | 20 + .../effects/api/FirstPlayerRenderEffect.java | 10 + .../l2core/base/effects/api/ForceEffect.java | 4 + .../base/effects/api/IconOverlayEffect.java | 16 + .../base/effects/api/IconRenderRegion.java | 19 + .../base/effects/api/InherentEffect.java | 20 + .../l2core/base/effects/package-info.java | 8 + .../xkmc/l2core/base/entity/BaseEntity.java | 49 ++ .../xkmc/l2core/base/entity/package-info.java | 8 + .../l2core/base/explosion/BaseExplosion.java | 29 + .../base/explosion/BaseExplosionContext.java | 6 + .../base/explosion/ExplosionHandler.java | 37 ++ .../base/explosion/ModExplosionContext.java | 12 + .../explosion/ParticleExplosionContext.java | 9 + .../explosion/VanillaExplosionContext.java | 36 ++ .../l2core/base/explosion/package-info.java | 8 + .../base/menu/base/BaseContainerMenu.java | 298 +++++++++++ .../base/menu/base/BaseContainerScreen.java | 32 ++ .../base/menu/base/MenuLayoutConfig.java | 237 ++++++++ .../xkmc/l2core/base/menu/base/PredSlot.java | 150 ++++++ .../l2core/base/menu/base/SlotLocked.java | 24 + .../l2core/base/menu/base/SpriteManager.java | 16 + .../l2core/base/menu/base/package-info.java | 8 + .../base/menu/data/BoolArrayDataSlot.java | 32 ++ .../l2core/base/menu/data/DoubleDataSlot.java | 21 + .../l2core/base/menu/data/FloatDataSlot.java | 21 + .../l2core/base/menu/data/IntDataSlot.java | 24 + .../l2core/base/menu/data/LongDataSlot.java | 23 + .../l2core/base/menu/data/package-info.java | 8 + .../l2core/base/menu/scroller/Scroller.java | 77 +++ .../base/menu/scroller/ScrollerMenu.java | 8 + .../base/menu/scroller/ScrollerScreen.java | 12 + .../base/menu/scroller/package-info.java | 8 + .../l2core/base/menu/stacked/CellEntry.java | 5 + .../menu/stacked/StackedRenderHandle.java | 133 +++++ .../base/menu/stacked/TextButtonHandle.java | 35 ++ .../l2core/base/menu/stacked/TextEntry.java | 6 + .../base/menu/stacked/package-info.java | 8 + .../xkmc/l2core/base/overlay/InfoSideBar.java | 40 ++ .../l2core/base/overlay/ItemSelSideBar.java | 46 ++ .../base/overlay/L2TooltipRenderUtil.java | 43 ++ .../xkmc/l2core/base/overlay/OverlayUtil.java | 105 ++++ .../l2core/base/overlay/SelectionSideBar.java | 61 +++ .../dev/xkmc/l2core/base/overlay/SideBar.java | 95 ++++ .../dev/xkmc/l2core/base/overlay/TextBox.java | 30 ++ .../l2core/base/overlay/package-info.java | 8 + .../l2core/base/tile/BaseBlockEntity.java | 63 +++ .../xkmc/l2core/base/tile/BaseContainer.java | 107 ++++ .../base/tile/BaseContainerListener.java | 18 + .../dev/xkmc/l2core/base/tile/BaseTank.java | 213 ++++++++ .../l2core/base/tile/CombinedTankWrapper.java | 182 +++++++ .../xkmc/l2core/base/tile/package-info.java | 8 + .../capability/attachment/AttachmentDef.java | 55 ++ .../capability/attachment/BaseAttachment.java | 7 + .../attachment/GeneralCapabilityHolder.java | 45 ++ .../attachment/GeneralCapabilityTemplate.java | 14 + .../capability/attachment/package-info.java | 8 + .../conditionals/ClientDataHandler.java | 19 + .../conditionals/ConditionalData.java | 48 ++ .../conditionals/ConditionalToken.java | 16 + .../capability/conditionals/Context.java | 4 + .../conditionals/NetworkSensitiveToken.java | 16 + .../capability/conditionals/TokenKey.java | 15 + .../conditionals/TokenProvider.java | 8 + .../conditionals/TokenToClient.java | 20 + .../capability/conditionals/package-info.java | 8 + .../capability/level/BaseSavedData.java | 24 + .../l2core/capability/level/package-info.java | 8 + .../capability/player/ClientSyncHandler.java | 15 + .../capability/player/PlayerCapToClient.java | 44 ++ .../player/PlayerCapabilityHolder.java | 33 ++ .../PlayerCapabilityNetworkHandler.java | 26 + .../player/PlayerCapabilityTemplate.java | 17 + .../capability/player/package-info.java | 8 + .../compat/curios/CurioEntityBuilder.java | 11 + .../compat/curios/CurioSlotBuilder.java | 28 + .../l2core/compat/curios/SlotCondition.java | 19 + .../l2core/compat/curios/package-info.java | 8 + .../java/dev/xkmc/l2core/init/L2Core.java | 47 ++ .../java/dev/xkmc/l2core/init/L2LibReg.java | 49 ++ .../dev/xkmc/l2core/init/L2LibraryClient.java | 6 + .../dev/xkmc/l2core/init/L2LibraryConfig.java | 81 +++ .../init/events/BaseCapabilityEvents.java | 57 ++ .../init/events/BaseJsonReloadListener.java | 31 ++ .../init/events/ClientEffectRenderEvents.java | 206 +++++++ .../events/ClientGeneralEventHandler.java | 21 + .../l2core/init/events/EffectSyncEvents.java | 100 ++++ .../init/events/GeneralEventHandler.java | 67 +++ .../xkmc/l2core/init/events/package-info.java | 8 + .../dev/xkmc/l2core/init/package-info.java | 8 + .../l2core/init/reg/datapack/DataMapReg.java | 29 + .../l2core/init/reg/datapack/DatapackReg.java | 28 + .../xkmc/l2core/init/reg/datapack/ValSet.java | 15 + .../init/reg/datapack/package-info.java | 8 + .../init/reg/registrate/L2Registrate.java | 166 ++++++ .../init/reg/registrate/NamedEntry.java | 40 ++ .../init/reg/registrate/package-info.java | 8 + .../xkmc/l2core/init/reg/simple/AttReg.java | 82 +++ .../xkmc/l2core/init/reg/simple/AttVal.java | 25 + .../xkmc/l2core/init/reg/simple/CdcReg.java | 32 ++ .../xkmc/l2core/init/reg/simple/CdcVal.java | 7 + .../xkmc/l2core/init/reg/simple/IngReg.java | 35 ++ .../xkmc/l2core/init/reg/simple/IngVal.java | 9 + .../dev/xkmc/l2core/init/reg/simple/Reg.java | 72 +++ .../dev/xkmc/l2core/init/reg/simple/SR.java | 28 + .../dev/xkmc/l2core/init/reg/simple/Val.java | 7 + .../l2core/init/reg/simple/package-info.java | 8 + .../conditions/BooleanValueCondition.java | 31 ++ .../conditions/DoubleValueCondition.java | 31 ++ .../serial/conditions/IntValueCondition.java | 37 ++ .../conditions/ListStringValueCondition.java | 36 ++ .../conditions/StringValueCondition.java | 35 ++ .../serial/conditions/package-info.java | 8 + .../xkmc/l2core/serial/config/BaseConfig.java | 65 +++ .../l2core/serial/config/BaseConfigType.java | 29 + .../l2core/serial/config/CollectType.java | 5 + .../l2core/serial/config/ConfigCollect.java | 17 + .../serial/config/ConfigDataProvider.java | 69 +++ .../serial/config/ConfigLoadOnStart.java | 8 + .../l2core/serial/config/ConfigMerger.java | 98 ++++ .../l2core/serial/config/ConfigTypeEntry.java | 36 ++ .../serial/config/MergedConfigType.java | 30 ++ .../config/PacketHandlerWithConfig.java | 143 +++++ .../serial/config/RecordDataProvider.java | 48 ++ .../xkmc/l2core/serial/config/SyncPacket.java | 20 + .../l2core/serial/config/package-info.java | 8 + .../ingredients/EnchantmentIngredient.java | 45 ++ .../serial/ingredients/PotionIngredient.java | 39 ++ .../serial/ingredients/package-info.java | 8 + .../serial/recipe/AbstractShapedRecipe.java | 54 ++ .../recipe/AbstractShapelessRecipe.java | 61 +++ .../serial/recipe/AbstractSmithingRecipe.java | 55 ++ .../xkmc/l2core/serial/recipe/BaseRecipe.java | 57 ++ .../serial/recipe/BaseRecipeBuilder.java | 72 +++ .../serial/recipe/BaseRecipeCategory.java | 48 ++ .../recipe/ConditionalRecipeWrapper.java | 24 + .../serial/recipe/CustomShapedBuilder.java | 47 ++ .../serial/recipe/CustomShapelessBuilder.java | 44 ++ .../serial/recipe/CustomSmithingBuilder.java | 40 ++ .../serial/recipe/NBTRecipeWrapper.java | 32 ++ .../l2core/serial/recipe/RecSerializer.java | 43 ++ .../l2core/serial/recipe/package-info.java | 8 + .../dev/xkmc/l2core/util/DataGenOnly.java | 11 + .../dev/xkmc/l2core/util/DoubleSidedCall.java | 14 + .../xkmc/l2core/util/LootTableTemplate.java | 126 +++++ .../java/dev/xkmc/l2core/util/MathHelper.java | 50 ++ src/main/java/dev/xkmc/l2core/util/Proxy.java | 30 ++ .../java/dev/xkmc/l2core/util/ServerOnly.java | 14 + .../dev/xkmc/l2core/util/package-info.java | 8 + .../resources/META-INF/accesstransformer.cfg | 67 +++ src/main/resources/META-INF/mods.toml | 23 + .../assets/l2library/lang/zh_cn.json | 5 + src/main/resources/l2core.mixins.json | 14 + src/main/resources/pack.mcmeta | 6 + src/test/java/organize/GUIGenerator.java | 267 ++++++++++ src/test/java/organize/ResourceOrganizer.java | 122 +++++ .../java/organize/sub/ArmorFileOrganizer.java | 22 + src/test/java/organize/sub/AssetMisc.java | 38 ++ .../java/organize/sub/BlockFileOrganizer.java | 44 ++ src/test/java/organize/sub/DataMisc.java | 32 ++ src/test/java/organize/sub/GeckoMisc.java | 40 ++ .../java/organize/sub/ItemFileOrganizer.java | 43 ++ .../java/organize/sub/LangFileOrganizer.java | 82 +++ .../organize/sub/RecipeFileOrganizer.java | 118 ++++ src/test/java/util/ScalePic.java | 65 +++ .../textures/gui/container/curios_3.png | Bin 0 -> 2100 bytes .../textures/gui/container/curios_4.png | Bin 0 -> 2182 bytes .../textures/gui/container/curios_5.png | Bin 0 -> 2262 bytes .../textures/gui/container/curios_6.png | Bin 0 -> 2340 bytes .../assets/textures/gui/coords/curios_3.json | 21 + .../assets/textures/gui/coords/curios_4.json | 21 + .../assets/textures/gui/coords/curios_5.json | 21 + .../assets/textures/gui/coords/curios_6.json | 21 + .../data/l2library/gui/coords/curios_3.json | 21 + .../data/l2library/gui/coords/curios_4.json | 21 + .../data/l2library/gui/coords/curios_5.json | 21 + .../data/l2library/gui/coords/curios_6.json | 21 + .../container/l2library/curios_3.json | 15 + .../container/l2library/curios_4.json | 15 + .../container/l2library/curios_5.json | 15 + .../container/l2library/curios_6.json | 15 + .../l2library/gui/-templates/info.json | 148 +++++ .../gui/-templates/sprites/altas/_belt.png | Bin 0 -> 118 bytes .../gui/-templates/sprites/altas/_body.png | Bin 0 -> 124 bytes .../-templates/sprites/altas/_boost_main.png | Bin 0 -> 141 bytes .../-templates/sprites/altas/_boost_sub.png | Bin 0 -> 116 bytes .../-templates/sprites/altas/_bracelet.png | Bin 0 -> 134 bytes .../-templates/sprites/altas/_disabled.png | Bin 0 -> 1138 bytes .../gui/-templates/sprites/altas/_head.png | Bin 0 -> 133 bytes .../-templates/sprites/altas/_necklace.png | Bin 0 -> 128 bytes .../sprites/altas/_stat_container.png | Bin 0 -> 150 bytes .../gui/-templates/sprites/arrow/_0.png | Bin 0 -> 189 bytes .../gui/-templates/sprites/arrow/_0r.png | Bin 0 -> 191 bytes .../gui/-templates/sprites/arrow/_1.png | Bin 0 -> 189 bytes .../gui/-templates/sprites/arrow/_2.png | Bin 0 -> 206 bytes .../gui/-templates/sprites/arrow/_3.png | Bin 0 -> 188 bytes .../gui/-templates/sprites/bottom.png | Bin 0 -> 549 bytes .../gui/-templates/sprites/bottom_screen.png | Bin 0 -> 1112 bytes .../gui/-templates/sprites/button/_1.png | Bin 0 -> 150 bytes .../gui/-templates/sprites/button/_1p.png | Bin 0 -> 154 bytes .../gui/-templates/sprites/button/_2.png | Bin 0 -> 160 bytes .../gui/-templates/sprites/button/_2p.png | Bin 0 -> 160 bytes .../gui/-templates/sprites/delete/_off.png | Bin 0 -> 224 bytes .../gui/-templates/sprites/delete/_on.png | Bin 0 -> 223 bytes .../gui/-templates/sprites/empty_slot.png | Bin 0 -> 105 bytes .../gui/-templates/sprites/exchange/_in.png | Bin 0 -> 157 bytes .../gui/-templates/sprites/exchange/_out.png | Bin 0 -> 164 bytes .../gui/-templates/sprites/fire/_0.png | Bin 0 -> 166 bytes .../gui/-templates/sprites/fire/_1.png | Bin 0 -> 318 bytes .../gui/-templates/sprites/middle.png | Bin 0 -> 111 bytes .../gui/-templates/sprites/result_slot.png | Bin 0 -> 135 bytes .../gui/-templates/sprites/slider/_bottom.png | Bin 0 -> 88 bytes .../gui/-templates/sprites/slider/_dark.png | Bin 0 -> 131 bytes .../gui/-templates/sprites/slider/_light.png | Bin 0 -> 141 bytes .../gui/-templates/sprites/slider/_middle.png | Bin 0 -> 96 bytes .../gui/-templates/sprites/slider/_top.png | Bin 0 -> 93 bytes .../l2library/gui/-templates/sprites/slot.png | Bin 0 -> 123 bytes .../gui/-templates/sprites/sort/_1.png | Bin 0 -> 116 bytes .../gui/-templates/sprites/sort/_1p.png | Bin 0 -> 120 bytes .../gui/-templates/sprites/toggle_slot/_0.png | Bin 0 -> 123 bytes .../gui/-templates/sprites/toggle_slot/_1.png | Bin 0 -> 129 bytes .../gui/-templates/sprites/toggle_slot/_2.png | Bin 0 -> 129 bytes .../l2library/gui/-templates/sprites/top.png | Bin 0 -> 192 bytes .../gui/-templates/sprites/upgrade/_off.png | Bin 0 -> 184 bytes .../gui/-templates/sprites/upgrade/_on.png | Bin 0 -> 179 bytes 245 files changed, 8668 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 libs/l2modularblocks-3.0.0-pre0.jar create mode 100644 libs/l2serial-3.0.0-pre1-sources.jar create mode 100644 libs/l2serial-3.0.0-pre1.jar create mode 100644 settings.gradle create mode 100644 src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java create mode 100644 src/main/java/dev/xkmc/l2core/base/effects/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java create mode 100644 src/main/java/dev/xkmc/l2core/base/entity/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java create mode 100644 src/main/java/dev/xkmc/l2core/base/explosion/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java create mode 100644 src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java create mode 100644 src/main/java/dev/xkmc/l2core/base/overlay/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java create mode 100644 src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java create mode 100644 src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java create mode 100644 src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java create mode 100644 src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java create mode 100644 src/main/java/dev/xkmc/l2core/base/tile/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/level/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java create mode 100644 src/main/java/dev/xkmc/l2core/capability/player/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java create mode 100644 src/main/java/dev/xkmc/l2core/compat/curios/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/init/L2Core.java create mode 100644 src/main/java/dev/xkmc/l2core/init/L2LibReg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java create mode 100644 src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java create mode 100644 src/main/java/dev/xkmc/l2core/init/events/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/init/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java create mode 100644 src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/CollectType.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/config/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java create mode 100644 src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java create mode 100644 src/main/java/dev/xkmc/l2core/util/DataGenOnly.java create mode 100644 src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java create mode 100644 src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java create mode 100644 src/main/java/dev/xkmc/l2core/util/MathHelper.java create mode 100644 src/main/java/dev/xkmc/l2core/util/Proxy.java create mode 100644 src/main/java/dev/xkmc/l2core/util/ServerOnly.java create mode 100644 src/main/java/dev/xkmc/l2core/util/package-info.java create mode 100644 src/main/resources/META-INF/accesstransformer.cfg create mode 100644 src/main/resources/META-INF/mods.toml create mode 100644 src/main/resources/assets/l2library/lang/zh_cn.json create mode 100644 src/main/resources/l2core.mixins.json create mode 100644 src/main/resources/pack.mcmeta create mode 100644 src/test/java/organize/GUIGenerator.java create mode 100644 src/test/java/organize/ResourceOrganizer.java create mode 100644 src/test/java/organize/sub/ArmorFileOrganizer.java create mode 100644 src/test/java/organize/sub/AssetMisc.java create mode 100644 src/test/java/organize/sub/BlockFileOrganizer.java create mode 100644 src/test/java/organize/sub/DataMisc.java create mode 100644 src/test/java/organize/sub/GeckoMisc.java create mode 100644 src/test/java/organize/sub/ItemFileOrganizer.java create mode 100644 src/test/java/organize/sub/LangFileOrganizer.java create mode 100644 src/test/java/organize/sub/RecipeFileOrganizer.java create mode 100644 src/test/java/util/ScalePic.java create mode 100644 src/test/resources/l2library/assets/textures/gui/container/curios_3.png create mode 100644 src/test/resources/l2library/assets/textures/gui/container/curios_4.png create mode 100644 src/test/resources/l2library/assets/textures/gui/container/curios_5.png create mode 100644 src/test/resources/l2library/assets/textures/gui/container/curios_6.png create mode 100644 src/test/resources/l2library/assets/textures/gui/coords/curios_3.json create mode 100644 src/test/resources/l2library/assets/textures/gui/coords/curios_4.json create mode 100644 src/test/resources/l2library/assets/textures/gui/coords/curios_5.json create mode 100644 src/test/resources/l2library/assets/textures/gui/coords/curios_6.json create mode 100644 src/test/resources/l2library/data/l2library/gui/coords/curios_3.json create mode 100644 src/test/resources/l2library/data/l2library/gui/coords/curios_4.json create mode 100644 src/test/resources/l2library/data/l2library/gui/coords/curios_5.json create mode 100644 src/test/resources/l2library/data/l2library/gui/coords/curios_6.json create mode 100644 src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json create mode 100644 src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json create mode 100644 src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json create mode 100644 src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json create mode 100644 src/test/resources/l2library/gui/-templates/info.json create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_belt.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_body.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_boost_main.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_boost_sub.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_bracelet.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_disabled.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_head.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_necklace.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/altas/_stat_container.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/arrow/_0.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/arrow/_0r.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/arrow/_1.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/arrow/_2.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/arrow/_3.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/bottom.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/bottom_screen.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/button/_1.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/button/_1p.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/button/_2.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/button/_2p.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/delete/_off.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/delete/_on.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/empty_slot.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/exchange/_in.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/exchange/_out.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/fire/_0.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/fire/_1.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/middle.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/result_slot.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/slider/_bottom.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/slider/_dark.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/slider/_light.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/slider/_middle.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/slider/_top.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/slot.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/sort/_1.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/sort/_1p.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_0.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_1.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_2.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/top.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/upgrade/_off.png create mode 100644 src/test/resources/l2library/gui/-templates/sprites/upgrade/_on.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b18a17a --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea +.gradle +build +run + +.DS_Store +/temp/ +runs/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8000a6f --- /dev/null +++ b/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..272135f --- /dev/null +++ b/build.gradle @@ -0,0 +1,184 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version '7.0.145' +} + +version = mod_version +group = 'dev.kxmc' + +repositories { + mavenLocal() +} + +base { + archivesName = mod_id +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') + +runs { + configureEach { + systemProperty 'forge.logging.markers', 'REGISTRIES' + systemProperty 'forge.logging.console.level', 'debug' + modSource project.sourceSets.main + } + client { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + server { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' + } + gameTestServer { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + data { + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } +} + +sourceSets.main.resources { srcDir 'src/generated/resources' } + + +dependencies { + implementation "net.neoforged:neoforge:${neo_version}" +} + +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, + neo_version : neo_version, neo_version_range: neo_version_range, + loader_version_range: loader_version_range, + mod_id : mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, + mod_authors : mod_authors, mod_description: mod_description, + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/mods.toml']) { + expand replaceProperties + [project: project] + } +} + +// Example configuration to allow publishing using the maven-publish plugin +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} + +if (lljij.toBoolean()) jarJar.enable() + +jar { + manifest { + attributes([ + "Specification-Title" : "${mod_id}", + "Specification-Vendor" : "xkmc", + "Specification-Version" : "1", // We are version 1 of ourselves + "Implementation-Title" : project.name, + "Implementation-Version" : project.jar.archiveVersion, + "Implementation-Vendor" : "xkmc", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'MixinConfigs' : "${mod_id}.mixins.json" + ]) + } +} + +if (lljij.toBoolean()) { + + tasks.jarJar.configure { + archiveClassifier.set('') + } + + jar { + archiveClassifier.set('slim') + } + +} + +dependencies { + //annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" + //compileOnly fg.deobf("com.tterrag.registrate:Registrate:${registrate_version}") + if (rootMod.toBoolean()) { + //jarJar(group: 'com.tterrag.registrate', name: 'Registrate', version: "[MC1.20,MC1.21)") + } +} + +// project specific + +repositories { + flatDir { + dirs 'libs' + } + maven { // Registrate + url "https://maven.tterrag.com/" + } + maven { + url = "https://maven.theillusivec4.top/" + } + maven { + // Location of the maven for vazkii's mods + name 'blamejared' + url 'https://maven.blamejared.com' + } + maven { + url 'https://www.cursemaven.com' + content { + includeGroup "curse.maven" + } + } + maven { + // Location of the maven that hosts JEI files (and TiC) + name 'Progwml6 maven' + url 'https://dvs1.progwml6.com/files/maven' + } + mavenLocal(); +} + +dependencies { + // base dep + implementation "mezz.jei:jei-${jei_minecraft_version}:${jei_version}" + //implementation "top.theillusivec4.curios:curios-neoforge:${curios_version}" + + implementation "dev.xkmc:l2serial:${l2serial_ver}" + + //runtimeOnly fg.deobf("dev.xkmc.l2damagetracker:l2damagetracker:0.2.4") + //runtimeOnly fg.deobf("dev.xkmc.l2backpack:l2backpack:2.4.12-slim") + //runtimeOnly fg.deobf("dev.xkmc.l2complements:l2complements:2.4.18-slim") + //runtimeOnly fg.deobf('dev.xkmc.modulargolems:modulargolems:2.4.16-slim') + //runtimeOnly fg.deobf("dev.xkmc.l2archery:l2archery:2.4.9") + //runtimeOnly fg.deobf("dev.xkmc.l2weaponry:l2weaponry:2.4.18") + //runtimeOnly fg.deobf("dev.xkmc.l2artifacts:l2artifacts:2.4.8-slim") + + //runtimeOnly fg.deobf("curse.maven:create-328085:4626108") + //implementation fg.deobf("curse.maven:just-enough-effect-descriptions-jeed-532286:4599236") + + //runtimeOnly fg.deobf("curse.maven:badpackets-615134:4438956") + //runtimeOnly fg.deobf("curse.maven:wthit-forge-455982:4596739") + //runtimeOnly fg.deobf("curse.maven:attributefix-280510:4588114") + //runtimeOnly fg.deobf("curse.maven:bookshelf-228525:4581675") + //runtimeOnly fg.deobf("curse.maven:enchantment-descriptions-250419:4587429") + //runtimeOnly fg.deobf("curse.maven:appleskin-248787:4605078") + //implementation fg.deobf("curse.maven:patchouli-306770:4636277") + + //runtimeOnly fg.deobf("dev.xkmc.traderefresh:traderefresh:2.1.1-slim") + //runtimeOnly fg.deobf("dev.xkmc.lasertransport:lasertransport:2.2.0.pre5-slim") + + //runtimeOnly fg.deobf('curse.maven:max-health-fix-492246:4447240') + //runtimeOnly fg.deobf('curse.maven:the-twilight-forest-227639:4516391') +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1929a4b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,33 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.debug=false + +#read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings +# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started +neogradle.subsystems.parchment.minecraftVersion=1.20.6 +neogradle.subsystems.parchment.mappingsVersion=2024.06.02 + +minecraft_version=1.21 +minecraft_version_range=[1.21,1.22) +neo_version=21.0.19-beta +neo_version_range=[21.0,) +loader_version_range=[2,) + +## Mod Properties +mod_id=l2core +mod_name=L2Core +mod_license=LGPL-2.1 +mod_version=3.0.0-pre0 +mod_group_id=dev.xkmc +mod_authors=lcy0x1 +mod_description=Core Library mod for all L2 mods + + +jei_minecraft_version = 1.21-neoforge +jei_version = 19.0.0.7 + +lljij = false +rootMod = false + +l2serial_ver = 3.0.0-pre1 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..c1962a79e29d3e0ab67b14947c167a862655af9b GIT binary patch literal 62076 zcmb5VV{~QRw)Y#`wrv{~+qP{x72B%VwzFc}c2cp;N~)5ZbDrJayPv(!dGEd-##*zr z)#n-$y^sH|_dchh3@8{H5D*j;5D<{i*8l5IFJ|DjL!e)upfGNX(kojugZ3I`oH1PvW`wFW_ske0j@lB9bX zO;2)`y+|!@X(fZ1<2n!Qx*)_^Ai@Cv-dF&(vnudG?0CsddG_&Wtae(n|K59ew)6St z#dj7_(Cfwzh$H$5M!$UDd8=4>IQsD3xV=lXUq($;(h*$0^yd+b{qq63f0r_de#!o_ zXDngc>zy`uor)4A^2M#U*DC~i+dc<)Tb1Tv&~Ev@oM)5iJ4Sn#8iRw16XXuV50BS7 zdBL5Mefch(&^{luE{*5qtCZk$oFr3RH=H!c3wGR=HJ(yKc_re_X9pD` zJ;uxPzUfVpgU>DSq?J;I@a+10l0ONXPcDkiYcihREt5~T5Gb}sT0+6Q;AWHl`S5dV>lv%-p9l#xNNy7ZCr%cyqHY%TZ8Q4 zbp&#ov1*$#grNG#1vgfFOLJCaNG@K|2!W&HSh@3@Y%T?3YI75bJp!VP*$*!< z;(ffNS_;@RJ`=c7yX04!u3JP*<8jeqLHVJu#WV&v6wA!OYJS4h<_}^QI&97-;=ojW zQ-1t)7wnxG*5I%U4)9$wlv5Fr;cIizft@&N+32O%B{R1POm$oap@&f| zh+5J{>U6ftv|vAeKGc|zC=kO(+l7_cLpV}-D#oUltScw})N>~JOZLU_0{Ka2e1evz z{^a*ZrLr+JUj;)K&u2CoCAXLC2=fVScI(m_p~0FmF>>&3DHziouln?;sxW`NB}cSX z8?IsJB)Z=aYRz!X=yJn$kyOWK%rCYf-YarNqKzmWu$ZvkP12b4qH zhS9Q>j<}(*frr?z<%9hl*i^#@*O2q(Z^CN)c2c z>1B~D;@YpG?G!Yk+*yn4vM4sO-_!&m6+`k|3zd;8DJnxsBYtI;W3We+FN@|tQ5EW= z!VU>jtim0Mw#iaT8t_<+qKIEB-WwE04lBd%Letbml9N!?SLrEG$nmn7&W(W`VB@5S zaY=sEw2}i@F_1P4OtEw?xj4@D6>_e=m=797#hg}f*l^`AB|Y0# z9=)o|%TZFCY$SzgSjS|8AI-%J4x}J)!IMxY3_KYze`_I=c1nmrk@E8c9?MVRu)7+Ue79|)rBX7tVB7U|w4*h(;Gi3D9le49B38`wuv zp7{4X^p+K4*$@gU(Tq3K1a#3SmYhvI42)GzG4f|u zwQFT1n_=n|jpi=70-yE9LA+d*T8u z`=VmmXJ_f6WmZveZPct$Cgu^~gFiyL>Lnpj*6ee>*0pz=t$IJ}+rE zsf@>jlcG%Wx;Cp5x)YSVvB1$yyY1l&o zvwX=D7k)Dn;ciX?Z)Pn8$flC8#m`nB&(8?RSdBvr?>T9?E$U3uIX7T?$v4dWCa46 z+&`ot8ZTEgp7G+c52oHJ8nw5}a^dwb_l%MOh(ebVj9>_koQP^$2B~eUfSbw9RY$_< z&DDWf2LW;b0ZDOaZ&2^i^g+5uTd;GwO(-bbo|P^;CNL-%?9mRmxEw~5&z=X^Rvbo^WJW=n_%*7974RY}JhFv46> zd}`2|qkd;89l}R;i~9T)V-Q%K)O=yfVKNM4Gbacc7AOd>#^&W&)Xx!Uy5!BHnp9kh z`a(7MO6+Ren#>R^D0K)1sE{Bv>}s6Rb9MT14u!(NpZOe-?4V=>qZ>}uS)!y~;jEUK z&!U7Fj&{WdgU#L0%bM}SYXRtM5z!6M+kgaMKt%3FkjWYh=#QUpt$XX1!*XkpSq-pl zhMe{muh#knk{9_V3%qdDcWDv}v)m4t9 zQhv{;} zc{}#V^N3H>9mFM8`i`0p+fN@GqX+kl|M94$BK3J-X`Hyj8r!#x6Vt(PXjn?N)qedP z=o1T^#?1^a{;bZ&x`U{f?}TMo8ToN zkHj5v|}r}wDEi7I@)Gj+S1aE-GdnLN+$hw!=DzglMaj#{qjXi_dwpr|HL(gcCXwGLEmi|{4&4#OZ4ChceA zKVd4K!D>_N=_X;{poT~4Q+!Le+ZV>=H7v1*l%w`|`Dx8{)McN@NDlQyln&N3@bFpV z_1w~O4EH3fF@IzJ9kDk@7@QctFq8FbkbaH7K$iX=bV~o#gfh?2JD6lZf(XP>~DACF)fGFt)X%-h1yY~MJU{nA5 ze2zxWMs{YdX3q5XU*9hOH0!_S24DOBA5usB+Ws$6{|AMe*joJ?RxfV}*7AKN9V*~J zK+OMcE@bTD>TG1*yc?*qGqjBN8mgg@h1cJLDv)0!WRPIkC` zZrWXrceVw;fB%3`6kq=a!pq|hFIsQ%ZSlo~)D z|64!aCnw-?>}AG|*iOl44KVf8@|joXi&|)1rB;EQWgm+iHfVbgllP$f!$Wf42%NO5b(j9Bw6L z;0dpUUK$5GX4QbMlTmLM_jJt!ur`_0~$b#BB7FL*%XFf<b__1o)Ao3rlobbN8-(T!1d-bR8D3S0@d zLI!*GMb5s~Q<&sjd}lBb8Nr0>PqE6_!3!2d(KAWFxa{hm`@u|a(%#i(#f8{BP2wbs zt+N_slWF4IF_O|{w`c~)Xvh&R{Au~CFmW#0+}MBd2~X}t9lz6*E7uAD`@EBDe$>7W zzPUkJx<`f$0VA$=>R57^(K^h86>09?>_@M(R4q($!Ck6GG@pnu-x*exAx1jOv|>KH zjNfG5pwm`E-=ydcb+3BJwuU;V&OS=6yM^4Jq{%AVqnTTLwV`AorIDD}T&jWr8pB&j28fVtk_y*JRP^t@l*($UZ z6(B^-PBNZ+z!p?+e8@$&jCv^EWLb$WO=}Scr$6SM*&~B95El~;W_0(Bvoha|uQ1T< zO$%_oLAwf1bW*rKWmlD+@CP&$ObiDy=nh1b2ejz%LO9937N{LDe7gle4i!{}I$;&Y zkexJ9Ybr+lrCmKWg&}p=`2&Gf10orS?4$VrzWidT=*6{KzOGMo?KI0>GL0{iFWc;C z+LPq%VH5g}6V@-tg2m{C!-$fapJ9y}c$U}aUmS{9#0CM*8pC|sfer!)nG7Ji>mfRh z+~6CxNb>6eWKMHBz-w2{mLLwdA7dA-qfTu^A2yG1+9s5k zcF=le_UPYG&q!t5Zd_*E_P3Cf5T6821bO`daa`;DODm8Ih8k89=RN;-asHIigj`n=ux>*f!OC5#;X5i;Q z+V!GUy0|&Y_*8k_QRUA8$lHP;GJ3UUD08P|ALknng|YY13)}!!HW@0z$q+kCH%xet zlWf@BXQ=b=4}QO5eNnN~CzWBbHGUivG=`&eWK}beuV*;?zt=P#pM*eTuy3 zP}c#}AXJ0OIaqXji78l;YrP4sQe#^pOqwZUiiN6^0RCd#D271XCbEKpk`HI0IsN^s zES7YtU#7=8gTn#lkrc~6)R9u&SX6*Jk4GFX7){E)WE?pT8a-%6P+zS6o&A#ml{$WX zABFz#i7`DDlo{34)oo?bOa4Z_lNH>n;f0nbt$JfAl~;4QY@}NH!X|A$KgMmEsd^&Y zt;pi=>AID7ROQfr;MsMtClr5b0)xo|fwhc=qk33wQ|}$@?{}qXcmECh>#kUQ-If0$ zseb{Wf4VFGLNc*Rax#P8ko*=`MwaR-DQ8L8V8r=2N{Gaips2_^cS|oC$+yScRo*uF zUO|5=?Q?{p$inDpx*t#Xyo6=s?bbN}y>NNVxj9NZCdtwRI70jxvm3!5R7yiWjREEd zDUjrsZhS|P&|Ng5r+f^kA6BNN#|Se}_GF>P6sy^e8kBrgMv3#vk%m}9PCwUWJg-AD zFnZ=}lbi*mN-AOm zCs)r=*YQAA!`e#1N>aHF=bb*z*hXH#Wl$z^o}x##ZrUc=kh%OHWhp=7;?8%Xj||@V?1c ziWoaC$^&04;A|T)!Zd9sUzE&$ODyJaBpvqsw19Uiuq{i#VK1!htkdRWBnb z`{rat=nHArT%^R>u#CjjCkw-7%g53|&7z-;X+ewb?OLWiV|#nuc8mp*LuGSi3IP<<*Wyo9GKV7l0Noa4Jr0g3p_$ z*R9{qn=?IXC#WU>48-k5V2Oc_>P;4_)J@bo1|pf=%Rcbgk=5m)CJZ`caHBTm3%!Z9 z_?7LHr_BXbKKr=JD!%?KhwdYSdu8XxPoA{n8^%_lh5cjRHuCY9Zlpz8g+$f@bw@0V z+6DRMT9c|>1^3D|$Vzc(C?M~iZurGH2pXPT%F!JSaAMdO%!5o0uc&iqHx?ImcX6fI zCApkzc~OOnfzAd_+-DcMp&AOQxE_EsMqKM{%dRMI5`5CT&%mQO?-@F6tE*xL?aEGZ z8^wH@wRl`Izx4sDmU>}Ym{ybUm@F83qqZPD6nFm?t?(7>h*?`fw)L3t*l%*iw0Qu#?$5eq!Qc zpQvqgSxrd83NsdO@lL6#{%lsYXWen~d3p4fGBb7&5xqNYJ)yn84!e1PmPo7ChVd%4 zHUsV0Mh?VpzZD=A6%)Qrd~i7 z96*RPbid;BN{Wh?adeD_p8YU``kOrGkNox3D9~!K?w>#kFz!4lzOWR}puS(DmfjJD z`x0z|qB33*^0mZdM&6$|+T>fq>M%yoy(BEjuh9L0>{P&XJ3enGpoQRx`v6$txXt#c z0#N?b5%srj(4xmPvJxrlF3H%OMB!jvfy z;wx8RzU~lb?h_}@V=bh6p8PSb-dG|-T#A?`c&H2`_!u+uenIZe`6f~A7r)`9m8atC zt(b|6Eg#!Q*DfRU=Ix`#B_dK)nnJ_+>Q<1d7W)eynaVn`FNuN~%B;uO2}vXr5^zi2 z!ifIF5@Zlo0^h~8+ixFBGqtweFc`C~JkSq}&*a3C}L?b5Mh-bW=e)({F_g4O3 zb@SFTK3VD9QuFgFnK4Ve_pXc3{S$=+Z;;4+;*{H}Rc;845rP?DLK6G5Y-xdUKkA6E3Dz&5f{F^FjJQ(NSpZ8q-_!L3LL@H* zxbDF{gd^U3uD;)a)sJwAVi}7@%pRM&?5IaUH%+m{E)DlA_$IA1=&jr{KrhD5q&lTC zAa3c)A(K!{#nOvenH6XrR-y>*4M#DpTTOGQEO5Jr6kni9pDW`rvY*fs|ItV;CVITh z=`rxcH2nEJpkQ^(;1c^hfb8vGN;{{oR=qNyKtR1;J>CByul*+=`NydWnSWJR#I2lN zTvgnR|MBx*XFsfdA&;tr^dYaqRZp*2NwkAZE6kV@1f{76e56eUmGrZ>MDId)oqSWw z7d&r3qfazg+W2?bT}F)4jD6sWaw`_fXZGY&wnGm$FRPFL$HzVTH^MYBHWGCOk-89y zA+n+Q6EVSSCpgC~%uHfvyg@ufE^#u?JH?<73A}jj5iILz4Qqk5$+^U(SX(-qv5agK znUkfpke(KDn~dU0>gdKqjTkVk`0`9^0n_wzXO7R!0Thd@S;U`y)VVP&mOd-2 z(hT(|$=>4FY;CBY9#_lB$;|Wd$aOMT5O_3}DYXEHn&Jrc3`2JiB`b6X@EUOD zVl0S{ijm65@n^19T3l%>*;F(?3r3s?zY{thc4%AD30CeL_4{8x6&cN}zN3fE+x<9; zt2j1RRVy5j22-8U8a6$pyT+<`f+x2l$fd_{qEp_bfxfzu>ORJsXaJn4>U6oNJ#|~p z`*ZC&NPXl&=vq2{Ne79AkQncuxvbOG+28*2wU$R=GOmns3W@HE%^r)Fu%Utj=r9t` zd;SVOnA(=MXgnOzI2@3SGKHz8HN~Vpx&!Ea+Df~`*n@8O=0!b4m?7cE^K*~@fqv9q zF*uk#1@6Re_<^9eElgJD!nTA@K9C732tV~;B`hzZ321Ph=^BH?zXddiu{Du5*IPg} zqDM=QxjT!Rp|#Bkp$(mL)aar)f(dOAXUiw81pX0DC|Y4;>Vz>>DMshoips^8Frdv} zlTD=cKa48M>dR<>(YlLPOW%rokJZNF2gp8fwc8b2sN+i6&-pHr?$rj|uFgktK@jg~ zIFS(%=r|QJ=$kvm_~@n=ai1lA{7Z}i+zj&yzY+!t$iGUy|9jH#&oTNJ;JW-3n>DF+ z3aCOzqn|$X-Olu_p7brzn`uk1F*N4@=b=m;S_C?#hy{&NE#3HkATrg?enaVGT^$qIjvgc61y!T$9<1B@?_ibtDZ{G zeXInVr5?OD_nS_O|CK3|RzzMmu+8!#Zb8Ik;rkIAR%6?$pN@d<0dKD2c@k2quB%s( zQL^<_EM6ow8F6^wJN1QcPOm|ehA+dP(!>IX=Euz5qqIq}Y3;ibQtJnkDmZ8c8=Cf3 zu`mJ!Q6wI7EblC5RvP*@)j?}W=WxwCvF3*5Up_`3*a~z$`wHwCy)2risye=1mSp%p zu+tD6NAK3o@)4VBsM!@);qgsjgB$kkCZhaimHg&+k69~drbvRTacWKH;YCK(!rC?8 zP#cK5JPHSw;V;{Yji=55X~S+)%(8fuz}O>*F3)hR;STU`z6T1aM#Wd+FP(M5*@T1P z^06O;I20Sk!bxW<-O;E081KRdHZrtsGJflFRRFS zdi5w9OVDGSL3 zNrC7GVsGN=b;YH9jp8Z2$^!K@h=r-xV(aEH@#JicPy;A0k1>g1g^XeR`YV2HfmqXY zYbRwaxHvf}OlCAwHoVI&QBLr5R|THf?nAevV-=~V8;gCsX>jndvNOcFA+DI+zbh~# zZ7`qNk&w+_+Yp!}j;OYxIfx_{f0-ONc?mHCiCUak=>j>~>YR4#w# zuKz~UhT!L~GfW^CPqG8Lg)&Rc6y^{%3H7iLa%^l}cw_8UuG;8nn9)kbPGXS}p3!L_ zd#9~5CrH8xtUd?{d2y^PJg+z(xIfRU;`}^=OlehGN2=?}9yH$4Rag}*+AWotyxfCJ zHx=r7ZH>j2kV?%7WTtp+-HMa0)_*DBBmC{sd$)np&GEJ__kEd`xB5a2A z*J+yx>4o#ZxwA{;NjhU*1KT~=ZK~GAA;KZHDyBNTaWQ1+;tOFFthnD)DrCn`DjBZ% zk$N5B4^$`n^jNSOr=t(zi8TN4fpaccsb`zOPD~iY=UEK$0Y70bG{idLx@IL)7^(pL z{??Bnu=lDeguDrd%qW1)H)H`9otsOL-f4bSu};o9OXybo6J!Lek`a4ff>*O)BDT_g z<6@SrI|C9klY(>_PfA^qai7A_)VNE4c^ZjFcE$Isp>`e5fLc)rg@8Q_d^Uk24$2bn z9#}6kZ2ZxS9sI(RqT7?El2@B+($>eBQrNi_k#CDJ8D9}8$mmm z4oSKO^F$i+NG)-HE$O6s1--6EzJa?C{x=QgK&c=)b(Q9OVoAXYEEH20G|q$}Hue%~ zO3B^bF=t7t48sN zWh_zA`w~|){-!^g?6Mqf6ieV zFx~aPUOJGR=4{KsW7I?<=J2|lY`NTU=lt=%JE9H1vBpkcn=uq(q~=?iBt_-r(PLBM zP-0dxljJO>4Wq-;stY)CLB4q`-r*T$!K2o}?E-w_i>3_aEbA^MB7P5piwt1dI-6o!qWCy0 ztYy!x9arGTS?kabkkyv*yxvsPQ7Vx)twkS6z2T@kZ|kb8yjm+^$|sEBmvACeqbz)RmxkkDQX-A*K!YFziuhwb|ym>C$}U|J)4y z$(z#)GH%uV6{ec%Zy~AhK|+GtG8u@c884Nq%w`O^wv2#A(&xH@c5M`Vjk*SR_tJnq z0trB#aY)!EKW_}{#L3lph5ow=@|D5LzJYUFD6 z7XnUeo_V0DVSIKMFD_T0AqAO|#VFDc7c?c-Q%#u00F%!_TW1@JVnsfvm@_9HKWflBOUD~)RL``-!P;(bCON_4eVdduMO>?IrQ__*zE@7(OX zUtfH@AX*53&xJW*Pu9zcqxGiM>xol0I~QL5B%Toog3Jlenc^WbVgeBvV8C8AX^Vj& z^I}H})B=VboO%q1;aU5ACMh{yK4J;xlMc`jCnZR^!~LDs_MP&8;dd@4LDWw~*>#OT zeZHwdQWS!tt5MJQI~cw|Ka^b4c|qyd_ly(+Ql2m&AAw^ zQeSXDOOH!!mAgzAp0z)DD>6Xo``b6QwzUV@w%h}Yo>)a|xRi$jGuHQhJVA%>)PUvK zBQ!l0hq<3VZ*RnrDODP)>&iS^wf64C;MGqDvx>|p;35%6(u+IHoNbK z;Gb;TneFo*`zUKS6kwF*&b!U8e5m4YAo03a_e^!5BP42+r)LFhEy?_7U1IR<; z^0v|DhCYMSj<-;MtY%R@Fg;9Kky^pz_t2nJfKWfh5Eu@_l{^ph%1z{jkg5jQrkvD< z#vdK!nku*RrH~TdN~`wDs;d>XY1PH?O<4^U4lmA|wUW{Crrv#r%N>7k#{Gc44Fr|t z@UZP}Y-TrAmnEZ39A*@6;ccsR>)$A)S>$-Cj!=x$rz7IvjHIPM(TB+JFf{ehuIvY$ zsDAwREg*%|=>Hw$`us~RP&3{QJg%}RjJKS^mC_!U;E5u>`X`jW$}P`Mf}?7G7FX#{ zE(9u1SO;3q@ZhDL9O({-RD+SqqPX)`0l5IQu4q)49TUTkxR(czeT}4`WV~pV*KY&i zAl3~X%D2cPVD^B43*~&f%+Op)wl<&|D{;=SZwImydWL6@_RJjxP2g)s=dH)u9Npki zs~z9A+3fj0l?yu4N0^4aC5x)Osnm0qrhz@?nwG_`h(71P znbIewljU%T*cC=~NJy|)#hT+lx#^5MuDDnkaMb*Efw9eThXo|*WOQzJ*#3dmRWm@! zfuSc@#kY{Um^gBc^_Xdxnl!n&y&}R4yAbK&RMc+P^Ti;YIUh|C+K1|=Z^{nZ}}rxH*v{xR!i%qO~o zTr`WDE@k$M9o0r4YUFFeQO7xCu_Zgy)==;fCJ94M_rLAv&~NhfvcLWCoaGg2ao~3e zBG?Ms9B+efMkp}7BhmISGWmJsKI@a8b}4lLI48oWKY|8?zuuNc$lt5Npr+p7a#sWu zh!@2nnLBVJK!$S~>r2-pN||^w|fY`CT{TFnJy`B|e5;=+_v4l8O-fkN&UQbA4NKTyntd zqK{xEKh}U{NHoQUf!M=2(&w+eef77VtYr;xs%^cPfKLObyOV_9q<(%76-J%vR>w9!us-0c-~Y?_EVS%v!* z15s2s3eTs$Osz$JayyH|5nPAIPEX=U;r&p;K14G<1)bvn@?bM5kC{am|C5%hyxv}a z(DeSKI5ZfZ1*%dl8frIX2?);R^^~LuDOpNpk-2R8U1w92HmG1m&|j&J{EK=|p$;f9 z7Rs5|jr4r8k5El&qcuM+YRlKny%t+1CgqEWO>3;BSRZi(LA3U%Jm{@{y+A+w(gzA< z7dBq6a1sEWa4cD0W7=Ld9z0H7RI^Z7vl(bfA;72j?SWCo`#5mVC$l1Q2--%V)-uN* z9ha*s-AdfbDZ8R8*fpwjzx=WvOtmSzGFjC#X)hD%Caeo^OWjS(3h|d9_*U)l%{Ab8 zfv$yoP{OuUl@$(-sEVNt{*=qi5P=lpxWVuz2?I7Dc%BRc+NGNw+323^ z5BXGfS71oP^%apUo(Y#xkxE)y?>BFzEBZ}UBbr~R4$%b7h3iZu3S(|A;&HqBR{nK& z$;GApNnz=kNO^FL&nYcfpB7Qg;hGJPsCW44CbkG1@l9pn0`~oKy5S777uH)l{irK!ru|X+;4&0D;VE*Ii|<3P zUx#xUqvZT5kVQxsF#~MwKnv7;1pR^0;PW@$@T7I?s`_rD1EGUdSA5Q(C<>5SzE!vw z;{L&kKFM-MO>hy#-8z`sdVx})^(Dc-dw;k-h*9O2_YZw}|9^y-|8RQ`BWJUJL(Cer zP5Z@fNc>pTXABbTRY-B5*MphpZv6#i802giwV&SkFCR zGMETyUm(KJbh+&$8X*RB#+{surjr;8^REEt`2&Dubw3$mx>|~B5IKZJ`s_6fw zKAZx9&PwBqW1Oz0r0A4GtnZd7XTKViX2%kPfv+^X3|_}RrQ2e3l=KG_VyY`H?I5&CS+lAX5HbA%TD9u6&s#v!G> zzW9n4J%d5ye7x0y`*{KZvqyXUfMEE^ZIffzI=Hh|3J}^yx7eL=s+TPH(Q2GT-sJ~3 zI463C{(ag7-hS1ETtU;_&+49ABt5!A7CwLwe z=SoA8mYZIQeU;9txI=zcQVbuO%q@E)JI+6Q!3lMc=Gbj(ASg-{V27u>z2e8n;Nc*pf}AqKz1D>p9G#QA+7mqqrEjGfw+85Uyh!=tTFTv3|O z+)-kFe_8FF_EkTw!YzwK^Hi^_dV5x-Ob*UWmD-})qKj9@aE8g240nUh=g|j28^?v7 zHRTBo{0KGaWBbyX2+lx$wgXW{3aUab6Bhm1G1{jTC7ota*JM6t+qy)c5<@ zpc&(jVdTJf(q3xB=JotgF$X>cxh7k*(T`-V~AR+`%e?YOeALQ2Qud( zz35YizXt(aW3qndR}fTw1p()Ol4t!D1pitGNL95{SX4ywzh0SF;=!wf=?Q?_h6!f* zh7<+GFi)q|XBsvXZ^qVCY$LUa{5?!CgwY?EG;*)0ceFe&=A;!~o`ae}Z+6me#^sv- z1F6=WNd6>M(~ z+092z>?Clrcp)lYNQl9jN-JF6n&Y0mp7|I0dpPx+4*RRK+VQI~>en0Dc;Zfl+x z_e_b7s`t1_A`RP3$H}y7F9_na%D7EM+**G_Z0l_nwE+&d_kc35n$Fxkd4r=ltRZhh zr9zER8>j(EdV&Jgh(+i}ltESBK62m0nGH6tCBr90!4)-`HeBmz54p~QP#dsu%nb~W z7sS|(Iydi>C@6ZM(Us!jyIiszMkd)^u<1D+R@~O>HqZIW&kearPWmT>63%_t2B{_G zX{&a(gOYJx!Hq=!T$RZ&<8LDnxsmx9+TBL0gTk$|vz9O5GkK_Yx+55^R=2g!K}NJ3 zW?C;XQCHZl7H`K5^BF!Q5X2^Mj93&0l_O3Ea3!Ave|ixx+~bS@Iv18v2ctpSt4zO{ zp#7pj!AtDmti$T`e9{s^jf(ku&E|83JIJO5Qo9weT6g?@vX!{7)cNwymo1+u(YQ94 zopuz-L@|5=h8A!(g-MXgLJC0MA|CgQF8qlonnu#j z;uCeq9ny9QSD|p)9sp3ebgY3rk#y0DA(SHdh$DUm^?GI<>%e1?&}w(b zdip1;P2Z=1wM+$q=TgLP$}svd!vk+BZ@h<^4R=GS2+sri7Z*2f`9 z5_?i)xj?m#pSVchk-SR!2&uNhzEi+#5t1Z$o0PoLGz*pT64%+|Wa+rd5Z}60(j?X= z{NLjtgRb|W?CUADqOS@(*MA-l|E342NxRaxLTDqsOyfWWe%N(jjBh}G zm7WPel6jXijaTiNita+z(5GCO0NM=Melxud57PP^d_U## zbA;9iVi<@wr0DGB8=T9Ab#2K_#zi=$igyK48@;V|W`fg~7;+!q8)aCOo{HA@vpSy-4`^!ze6-~8|QE||hC{ICKllG9fbg_Y7v z$jn{00!ob3!@~-Z%!rSZ0JO#@>|3k10mLK0JRKP-Cc8UYFu>z93=Ab-r^oL2 zl`-&VBh#=-?{l1TatC;VweM^=M7-DUE>m+xO7Xi6vTEsReyLs8KJ+2GZ&rxw$d4IT zPXy6pu^4#e;;ZTsgmG+ZPx>piodegkx2n0}SM77+Y*j^~ICvp#2wj^BuqRY*&cjmL zcKp78aZt>e{3YBb4!J_2|K~A`lN=u&5j!byw`1itV(+Q_?RvV7&Z5XS1HF)L2v6ji z&kOEPmv+k_lSXb{$)of~(BkO^py&7oOzpjdG>vI1kcm_oPFHy38%D4&A4h_CSo#lX z2#oqMCTEP7UvUR3mwkPxbl8AMW(e{ARi@HCYLPSHE^L<1I}OgZD{I#YH#GKnpRmW3 z2jkz~Sa(D)f?V?$gNi?6)Y;Sm{&?~2p=0&BUl_(@hYeX8YjaRO=IqO7neK0RsSNdYjD zaw$g2sG(>JR=8Iz1SK4`*kqd_3-?;_BIcaaMd^}<@MYbYisWZm2C2|Np_l|8r9yM|JkUngSo@?wci(7&O9a z%|V(4C1c9pps0xxzPbXH=}QTxc2rr7fXk$9`a6TbWKPCz&p=VsB8^W96W=BsB|7bc zf(QR8&Ktj*iz)wK&mW`#V%4XTM&jWNnDF56O+2bo<3|NyUhQ%#OZE8$Uv2a@J>D%t zMVMiHh?es!Ex19q&6eC&L=XDU_BA&uR^^w>fpz2_`U87q_?N2y;!Z!bjoeKrzfC)} z?m^PM=(z{%n9K`p|7Bz$LuC7!>tFOuN74MFELm}OD9?%jpT>38J;=1Y-VWtZAscaI z_8jUZ#GwWz{JqvGEUmL?G#l5E=*m>`cY?m*XOc*yOCNtpuIGD+Z|kn4Xww=BLrNYS zGO=wQh}Gtr|7DGXLF%|`G>J~l{k^*{;S-Zhq|&HO7rC_r;o`gTB7)uMZ|WWIn@e0( zX$MccUMv3ABg^$%_lNrgU{EVi8O^UyGHPNRt%R!1#MQJn41aD|_93NsBQhP80yP<9 zG4(&0u7AtJJXLPcqzjv`S~5;Q|5TVGccN=Uzm}K{v)?f7W!230C<``9(64}D2raRU zAW5bp%}VEo{4Rko`bD%Ehf=0voW?-4Mk#d3_pXTF!-TyIt6U+({6OXWVAa;s-`Ta5 zTqx&8msH3+DLrVmQOTBOAj=uoxKYT3DS1^zBXM?1W+7gI!aQNPYfUl{3;PzS9*F7g zWJN8x?KjBDx^V&6iCY8o_gslO16=kh(|Gp)kz8qlQ`dzxQv;)V&t+B}wwdi~uBs4? zu~G|}y!`3;8#vIMUdyC7YEx6bb^1o}G!Jky4cN?BV9ejBfN<&!4M)L&lRKiuMS#3} z_B}Nkv+zzxhy{dYCW$oGC&J(Ty&7%=5B$sD0bkuPmj7g>|962`(Q{ZZMDv%YMuT^KweiRDvYTEop3IgFv#)(w>1 zSzH>J`q!LK)c(AK>&Ib)A{g`Fdykxqd`Yq@yB}E{gnQV$K!}RsgMGWqC3DKE(=!{}ekB3+(1?g}xF>^icEJbc z5bdxAPkW90atZT+&*7qoLqL#p=>t-(-lsnl2XMpZcYeW|o|a322&)yO_8p(&Sw{|b zn(tY$xn5yS$DD)UYS%sP?c|z>1dp!QUD)l;aW#`%qMtQJjE!s2z`+bTSZmLK7SvCR z=@I4|U^sCwZLQSfd*ACw9B@`1c1|&i^W_OD(570SDLK`MD0wTiR8|$7+%{cF&){$G zU~|$^Ed?TIxyw{1$e|D$050n8AjJvvOWhLtLHbSB|HIfjMp+gu>DraHZJRrdO53(= z+o-f{+qNog+qSLB%KY;5>Av6X(>-qYk3IIEwZ5~6a+P9lMpC^ z8CJ0q>rEpjlsxCvJm=kms@tlN4+sv}He`xkr`S}bGih4t`+#VEIt{1veE z{ZLtb_pSbcfcYPf4=T1+|BtR!x5|X#x2TZEEkUB6kslKAE;x)*0x~ES0kl4Dex4e- zT2P~|lT^vUnMp{7e4OExfxak0EE$Hcw;D$ehTV4a6hqxru0$|Mo``>*a5=1Ym0u>BDJKO|=TEWJ5jZu!W}t$Kv{1!q`4Sn7 zrxRQOt>^6}Iz@%gA3&=5r;Lp=N@WKW;>O!eGIj#J;&>+3va^~GXRHCY2}*g#9ULab zitCJt-OV0*D_Q3Q`p1_+GbPxRtV_T`jyATjax<;zZ?;S+VD}a(aN7j?4<~>BkHK7bO8_Vqfdq1#W&p~2H z&w-gJB4?;Q&pG9%8P(oOGZ#`!m>qAeE)SeL*t8KL|1oe;#+uOK6w&PqSDhw^9-&Fa zuEzbi!!7|YhlWhqmiUm!muO(F8-F7|r#5lU8d0+=;<`{$mS=AnAo4Zb^{%p}*gZL! zeE!#-zg0FWsSnablw!9$<&K(#z!XOW z;*BVx2_+H#`1b@>RtY@=KqD)63brP+`Cm$L1@ArAddNS1oP8UE$p05R=bvZoYz+^6 z<)!v7pRvi!u_-V?!d}XWQR1~0q(H3{d^4JGa=W#^Z<@TvI6J*lk!A zZ*UIKj*hyO#5akL*Bx6iPKvR3_2-^2mw|Rh-3O_SGN3V9GRo52Q;JnW{iTGqb9W99 z7_+F(Op6>~3P-?Q8LTZ-lwB}xh*@J2Ni5HhUI3`ct|*W#pqb>8i*TXOLn~GlYECIj zhLaa_rBH|1jgi(S%~31Xm{NB!30*mcsF_wgOY2N0XjG_`kFB+uQuJbBm3bIM$qhUyE&$_u$gb zpK_r{99svp3N3p4yHHS=#csK@j9ql*>j0X=+cD2dj<^Wiu@i>c_v zK|ovi7}@4sVB#bzq$n3`EgI?~xDmkCW=2&^tD5RuaSNHf@Y!5C(Is$hd6cuyoK|;d zO}w2AqJPS`Zq+(mc*^%6qe>1d&(n&~()6-ZATASNPsJ|XnxelLkz8r1x@c2XS)R*H(_B=IN>JeQUR;T=i3<^~;$<+8W*eRKWGt7c#>N`@;#!`kZ!P!&{9J1>_g8Zj zXEXxmA=^{8A|3=Au+LfxIWra)4p<}1LYd_$1KI0r3o~s1N(x#QYgvL4#2{z8`=mXy zQD#iJ0itk1d@Iy*DtXw)Wz!H@G2St?QZFz zVPkM%H8Cd2EZS?teQN*Ecnu|PrC!a7F_XX}AzfZl3fXfhBtc2-)zaC2eKx*{XdM~QUo4IwcGgVdW69 z1UrSAqqMALf^2|(I}hgo38l|Ur=-SC*^Bo5ej`hb;C$@3%NFxx5{cxXUMnTyaX{>~ zjL~xm;*`d08bG_K3-E+TI>#oqIN2=An(C6aJ*MrKlxj?-;G zICL$hi>`F%{xd%V{$NhisHSL~R>f!F7AWR&7b~TgLu6!3s#~8|VKIX)KtqTH5aZ8j zY?wY)XH~1_a3&>#j7N}0az+HZ;is;Zw(Am{MX}YhDTe(t{ZZ;TG}2qWYO+hdX}vp9 z@uIRR8g#y~-^E`Qyem(31{H0&V?GLdq9LEOb2(ea#e-$_`5Q{T%E?W(6 z(XbX*Ck%TQM;9V2LL}*Tf`yzai{0@pYMwBu%(I@wTY!;kMrzcfq0w?X`+y@0ah510 zQX5SU(I!*Fag4U6a7Lw%LL;L*PQ}2v2WwYF(lHx_Uz2ceI$mnZ7*eZ?RFO8UvKI0H z9Pq-mB`mEqn6n_W9(s~Jt_D~j!Ln9HA)P;owD-l~9FYszs)oEKShF9Zzcmnb8kZ7% zQ`>}ki1kwUO3j~ zEmh140sOkA9v>j@#56ymn_RnSF`p@9cO1XkQy6_Kog?0ivZDb`QWOX@tjMd@^Qr(p z!sFN=A)QZm!sTh(#q%O{Ovl{IxkF!&+A)w2@50=?a-+VuZt6On1;d4YtUDW{YNDN_ zG@_jZi1IlW8cck{uHg^g=H58lPQ^HwnybWy@@8iw%G! zwB9qVGt_?~M*nFAKd|{cGg+8`+w{j_^;nD>IrPf-S%YjBslSEDxgKH{5p)3LNr!lD z4ii)^%d&cCXIU7UK?^ZQwmD(RCd=?OxmY(Ko#+#CsTLT;p#A%{;t5YpHFWgl+@)N1 zZ5VDyB;+TN+g@u~{UrWrv)&#u~k$S&GeW)G{M#&Di)LdYk?{($Cq zZGMKeYW)aMtjmKgvF0Tg>Mmkf9IB#2tYmH-s%D_9y3{tfFmX1BSMtbe<(yqAyWX60 zzkgSgKb3c{QPG2MalYp`7mIrYg|Y<4Jk?XvJK)?|Ecr+)oNf}XLPuTZK%W>;<|r+% zTNViRI|{sf1v7CsWHvFrkQ$F7+FbqPQ#Bj7XX=#M(a~9^80}~l-DueX#;b}Ajn3VE z{BWI}$q{XcQ3g{(p>IOzFcAMDG0xL)H%wA)<(gl3I-oVhK~u_m=hAr&oeo|4lZbf} z+pe)c34Am<=z@5!2;_lwya;l?xV5&kWe}*5uBvckm(d|7R>&(iJNa6Y05SvlZcWBlE{{%2- z`86)Y5?H!**?{QbzGG~|k2O%eA8q=gxx-3}&Csf6<9BsiXC)T;x4YmbBIkNf;0Nd5 z%whM^!K+9zH>on_<&>Ws?^v-EyNE)}4g$Fk?Z#748e+GFp)QrQQETx@u6(1fk2!(W zWiCF~MomG*y4@Zk;h#2H8S@&@xwBIs|82R*^K(i*0MTE%Rz4rgO&$R zo9Neb;}_ulaCcdn3i17MO3NxzyJ=l;LU*N9ztBJ30j=+?6>N4{9YXg$m=^9@Cl9VY zbo^{yS@gU=)EpQ#;UIQBpf&zfCA;00H-ee=1+TRw@(h%W=)7WYSb5a%$UqNS@oI@= zDrq|+Y9e&SmZrH^iA>Of8(9~Cf-G(P^5Xb%dDgMMIl8gk6zdyh`D3OGNVV4P9X|EvIhplXDld8d z^YWtYUz@tpg*38Xys2?zj$F8%ivA47cGSl;hjD23#*62w3+fwxNE7M7zVK?x_`dBSgPK zWY_~wF~OEZi9|~CSH8}Xi>#8G73!QLCAh58W+KMJJC81{60?&~BM_0t-u|VsPBxn* zW7viEKwBBTsn_A{g@1!wnJ8@&h&d>!qAe+j_$$Vk;OJq`hrjzEE8Wjtm)Z>h=*M25 zOgETOM9-8xuuZ&^@rLObtcz>%iWe%!uGV09nUZ*nxJAY%&KAYGY}U1WChFik7HIw% zZP$3Bx|TG_`~19XV7kfi2GaBEhKap&)Q<9`aPs#^!kMjtPb|+-fX66z3^E)iwyXK7 z8)_p<)O{|i&!qxtgBvWXx8*69WO$5zACl++1qa;)0zlXf`eKWl!0zV&I`8?sG)OD2Vy?reNN<{eK+_ za4M;Hh%&IszR%)&gpgRCP}yheQ+l#AS-GnY81M!kzhWxIR?PW`G3G?} z$d%J28uQIuK@QxzGMKU_;r8P0+oIjM+k)&lZ39i#(ntY)*B$fdJnQ3Hw3Lsi8z&V+ zZly2}(Uzpt2aOubRjttzqrvinBFH4jrN)f0hy)tj4__UTwN)#1fj3-&dC_Vh7}ri* zfJ=oqLMJ-_<#rwVyN}_a-rFBe2>U;;1(7UKH!$L??zTbbzP#bvyg7OQBGQklJ~DgP zd<1?RJ<}8lWwSL)`jM53iG+}y2`_yUvC!JkMpbZyb&50V3sR~u+lok zT0uFRS-yx@8q4fPRZ%KIpLp8R#;2%c&Ra4p(GWRT4)qLaPNxa&?8!LRVdOUZ)2vrh zBSx&kB%#Y4!+>~)<&c>D$O}!$o{<1AB$M7-^`h!eW;c(3J~ztoOgy6Ek8Pwu5Y`Xion zFl9fb!k2`3uHPAbd(D^IZmwR5d8D$495nN2`Ue&`W;M-nlb8T-OVKt|fHk zBpjX$a(IR6*-swdNk@#}G?k6F-~c{AE0EWoZ?H|ZpkBxqU<0NUtvubJtwJ1mHV%9v?GdDw; zAyXZiD}f0Zdt-cl9(P1la+vQ$Er0~v}gYJVwQazv zH#+Z%2CIfOf90fNMGos|{zf&N`c0@x0N`tkFv|_9af3~<0z@mnf*e;%r*Fbuwl-IW z{}B3=(mJ#iwLIPiUP`J3SoP~#)6v;aRXJ)A-pD2?_2_CZ#}SAZ<#v7&Vk6{*i(~|5 z9v^nC`T6o`CN*n%&9+bopj^r|E(|pul;|q6m7Tx+U|UMjWK8o-lBSgc3ZF=rP{|l9 zc&R$4+-UG6i}c==!;I#8aDIbAvgLuB66CQLRoTMu~jdw`fPlKy@AKYWS-xyZzPg&JRAa@m-H43*+ne!8B7)HkQY4 zIh}NL4Q79a-`x;I_^>s$Z4J4-Ngq=XNWQ>yAUCoe&SMAYowP>r_O}S=V+3=3&(O=h zNJDYNs*R3Y{WLmBHc?mFEeA4`0Y`_CN%?8qbDvG2m}kMAiqCv`_BK z_6a@n`$#w6Csr@e2YsMx8udNWtNt=kcqDZdWZ-lGA$?1PA*f4?X*)hjn{sSo8!bHz zb&lGdAgBx@iTNPK#T_wy`KvOIZvTWqSHb=gWUCKXAiB5ckQI`1KkPx{{%1R*F2)Oc z(9p@yG{fRSWE*M9cdbrO^)8vQ2U`H6M>V$gK*rz!&f%@3t*d-r3mSW>D;wYxOhUul zk~~&ip5B$mZ~-F1orsq<|1bc3Zpw6)Ws5;4)HilsN;1tx;N6)tuePw& z==OlmaN*ybM&-V`yt|;vDz(_+UZ0m&&9#{9O|?0I|4j1YCMW;fXm}YT$0%EZ5^YEI z4i9WV*JBmEU{qz5O{#bs`R1wU%W$qKx?bC|e-iS&d*Qm7S=l~bMT{~m3iZl+PIXq{ zn-c~|l)*|NWLM%ysfTV-oR0AJ3O>=uB-vpld{V|cWFhI~sx>ciV9sPkC*3i0Gg_9G!=4ar*-W?D9)?EFL1=;O+W8}WGdp8TT!Fgv z{HKD`W>t(`Cds_qliEzuE!r{ihwEv1l5o~iqlgjAyGBi)$%zNvl~fSlg@M=C{TE;V zQkH`zS8b&!ut(m)%4n2E6MB>p*4(oV>+PT51#I{OXs9j1vo>9I<4CL1kv1aurV*AFZ^w_qfVL*G2rG@D2 zrs87oV3#mf8^E5hd_b$IXfH6vHe&lm@7On~Nkcq~YtE!}ad~?5*?X*>y`o;6Q9lkk zmf%TYonZM`{vJg$`lt@MXsg%*&zZZ0uUSse8o=!=bfr&DV)9Y6$c!2$NHyYAQf*Rs zk{^?gl9E z5Im8wlAsvQ6C2?DyG@95gUXZ3?pPijug25g;#(esF_~3uCj3~94}b*L>N2GSk%Qst z=w|Z>UX$m!ZOd(xV*2xvWjN&c5BVEdVZ0wvmk)I+YxnyK%l~caR=7uNQ=+cnNTLZ@&M!I$Mj-r{!P=; z`C2)D=VmvK8@T5S9JZoRtN!S*D_oqOxyy!q6Zk|~4aT|*iRN)fL)c>-yycR>-is0X zKrko-iZw(f(!}dEa?hef5yl%p0-v-8#8CX8!W#n2KNyT--^3hq6r&`)5Y@>}e^4h- zlPiDT^zt}Ynk&x@F8R&=)k8j$=N{w9qUcIc&)Qo9u4Y(Ae@9tA`3oglxjj6c{^pN( zQH+Uds2=9WKjH#KBIwrQI%bbs`mP=7V>rs$KG4|}>dxl_k!}3ZSKeEen4Iswt96GGw`E6^5Ov)VyyY}@itlj&sao|>Sb5 zeY+#1EK(}iaYI~EaHQkh7Uh>DnzcfIKv8ygx1Dv`8N8a6m+AcTa-f;17RiEed>?RT zk=dAksmFYPMV1vIS(Qc6tUO+`1jRZ}tcDP? zt)=7B?yK2RcAd1+Y!$K5*ds=SD;EEqCMG6+OqPoj{&8Y5IqP(&@zq@=A7+X|JBRi4 zMv!czlMPz)gt-St2VZwDD=w_S>gRpc-g zUd*J3>bXeZ?Psjohe;z7k|d<*T21PA1i)AOi8iMRwTBSCd0ses{)Q`9o&p9rsKeLaiY zluBw{1r_IFKR76YCAfl&_S1*(yFW8HM^T()&p#6y%{(j7Qu56^ZJx1LnN`-RTwimdnuo*M8N1ISl+$C-%=HLG-s} zc99>IXRG#FEWqSV9@GFW$V8!{>=lSO%v@X*pz*7()xb>=yz{E$3VE;e)_Ok@A*~El zV$sYm=}uNlUxV~6e<6LtYli1!^X!Ii$L~j4e{sI$tq_A(OkGquC$+>Rw3NFObV2Z)3Rt~Jr{oYGnZaFZ^g5TDZlg;gaeIP} z!7;T{(9h7mv{s@piF{-35L=Ea%kOp;^j|b5ZC#xvD^^n#vPH=)lopYz1n?Kt;vZmJ z!FP>Gs7=W{sva+aO9S}jh0vBs+|(B6Jf7t4F^jO3su;M13I{2rd8PJjQe1JyBUJ5v zcT%>D?8^Kp-70bP8*rulxlm)SySQhG$Pz*bo@mb5bvpLAEp${?r^2!Wl*6d7+0Hs_ zGPaC~w0E!bf1qFLDM@}zso7i~(``)H)zRgcExT_2#!YOPtBVN5Hf5~Ll3f~rWZ(UsJtM?O*cA1_W0)&qz%{bDoA}{$S&-r;0iIkIjbY~ zaAqH45I&ALpP=9Vof4OapFB`+_PLDd-0hMqCQq08>6G+C;9R~}Ug_nm?hhdkK$xpI zgXl24{4jq(!gPr2bGtq+hyd3%Fg%nofK`psHMs}EFh@}sdWCd!5NMs)eZg`ZlS#O0 zru6b8#NClS(25tXqnl{|Ax@RvzEG!+esNW-VRxba(f`}hGoqci$U(g30i}2w9`&z= zb8XjQLGN!REzGx)mg~RSBaU{KCPvQx8)|TNf|Oi8KWgv{7^tu}pZq|BS&S<53fC2K4Fw6>M^s$R$}LD*sUxdy6Pf5YKDbVet;P!bw5Al-8I1Nr(`SAubX5^D9hk6$agWpF}T#Bdf{b9-F#2WVO*5N zp+5uGgADy7m!hAcFz{-sS0kM7O)qq*rC!>W@St~^OW@R1wr{ajyYZq5H!T?P0e+)a zaQ%IL@X_`hzp~vRH0yUblo`#g`LMC%9}P;TGt+I7qNcBSe&tLGL4zqZqB!Bfl%SUa z6-J_XLrnm*WA`34&mF+&e1sPCP9=deazrM=Pc4Bn(nV;X%HG^4%Afv4CI~&l!Sjzb z{rHZ3od0!Al{}oBO>F*mOFAJrz>gX-vs!7>+_G%BB(ljWh$252j1h;9p~xVA=9_`P z5KoFiz96_QsTK%B&>MSXEYh`|U5PjX1(+4b#1PufXRJ*uZ*KWdth1<0 zsAmgjT%bowLyNDv7bTUGy|g~N34I-?lqxOUtFpTLSV6?o?<7-UFy*`-BEUsrdANh} zBWkDt2SAcGHRiqz)x!iVoB~&t?$yn6b#T=SP6Ou8lW=B>=>@ik93LaBL56ub`>Uo!>0@O8?e)$t(sgy$I z6tk3nS@yFFBC#aFf?!d_3;%>wHR;A3f2SP?Na8~$r5C1N(>-ME@HOpv4B|Ty7%jAv zR}GJwsiJZ5@H+D$^Cwj#0XA_(m^COZl8y7Vv(k=iav1=%QgBOVzeAiw zaDzzdrxzj%sE^c9_uM5D;$A_7)Ln}BvBx^=)fO+${ou%B*u$(IzVr-gH3=zL6La;G zu0Kzy5CLyNGoKRtK=G0-w|tnwI)puPDOakRzG(}R9fl7#<|oQEX;E#yCWVg95 z;NzWbyF&wGg_k+_4x4=z1GUcn6JrdX4nOVGaAQ8#^Ga>aFvajQN{!+9rgO-dHP zIp@%&ebVg}IqnRWwZRTNxLds+gz2@~VU(HI=?Epw>?yiEdZ>MjajqlO>2KDxA>)cj z2|k%dhh%d8SijIo1~20*5YT1eZTDkN2rc^zWr!2`5}f<2f%M_$to*3?Ok>e9$X>AV z2jYmfAd)s|(h?|B(XYrIfl=Wa_lBvk9R1KaP{90-z{xKi+&8=dI$W0+qzX|ZovWGOotP+vvYR(o=jo?k1=oG?%;pSqxcU* zWVGVMw?z__XQ9mnP!hziHC`ChGD{k#SqEn*ph6l46PZVkm>JF^Q{p&0=MKy_6apts z`}%_y+Tl_dSP(;Ja&sih$>qBH;bG;4;75)jUoVqw^}ee=ciV;0#t09AOhB^Py7`NC z-m+ybq1>_OO+V*Z>dhk}QFKA8V?9Mc4WSpzj{6IWfFpF7l^au#r7&^BK2Ac7vCkCn{m0uuN93Ee&rXfl1NBY4NnO9lFUp zY++C1I;_{#OH#TeP2Dp?l4KOF8ub?m6zE@XOB5Aiu$E~QNBM@;r+A5mF2W1-c7>ex zHiB=WJ&|`6wDq*+xv8UNLVUy4uW1OT>ey~Xgj@MMpS@wQbHAh>ysYvdl-1YH@&+Q! z075(Qd4C!V`9Q9jI4 zSt{HJRvZec>vaL_brKhQQwbpQd4_Lmmr0@1GdUeU-QcC{{8o=@nwwf>+dIKFVzPriGNX4VjHCa zTbL9w{Y2V87c2ofX%`(48A+4~mYTiFFl!e{3K^C_k%{&QTsgOd0*95KmWN)P}m zTRr{`f7@=v#+z_&fKYkQT!mJn{*crj%ZJz#(+c?>cD&2Lo~FFAWy&UG*Op^pV`BR^I|g?T>4l5;b|5OQ@t*?_Slp`*~Y3`&RfKD^1uLezIW(cE-Dq2z%I zBi8bWsz0857`6e!ahet}1>`9cYyIa{pe53Kl?8|Qg2RGrx@AlvG3HAL-^9c^1GW;)vQt8IK+ zM>!IW*~682A~MDlyCukldMd;8P|JCZ&oNL(;HZgJ>ie1PlaInK7C@Jg{3kMKYui?e!b`(&?t6PTb5UPrW-6DVU%^@^E`*y-Fd(p|`+JH&MzfEq;kikdse ziFOiDWH(D< zyV7Rxt^D0_N{v?O53N$a2gu%1pxbeK;&ua`ZkgSic~$+zvt~|1Yb=UfKJW2F7wC^evlPf(*El+#}ZBy0d4kbVJsK- z05>;>?HZO(YBF&v5tNv_WcI@O@LKFl*VO?L(!BAd!KbkVzo;v@~3v`-816GG?P zY+H3ujC>5=Am3RIZDdT#0G5A6xe`vGCNq88ZC1aVXafJkUlcYmHE^+Z{*S->ol%-O znm9R0TYTr2w*N8Vs#s-5=^w*{Y}qp5GG)Yt1oLNsH7y~N@>Eghms|K*Sdt_u!&I}$ z+GSdFTpbz%KH+?B%Ncy;C`uW6oWI46(tk>r|5|-K6)?O0d_neghUUOa9BXHP*>vi; z={&jIGMn-92HvInCMJcyXwHTJ42FZp&Wxu+9Rx;1x(EcIQwPUQ@YEQQ`bbMy4q3hP zNFoq~Qd0=|xS-R}k1Im3;8s{BnS!iaHIMLx)aITl)+)?Yt#fov|Eh>}dv@o6R{tG>uHsy&jGmWN5+*wAik|78(b?jtysPHC#e+Bzz~V zS3eEXv7!Qn4uWi!FS3B?afdD*{fr9>B~&tc671fi--V}~E4un;Q|PzZRwk-azprM$4AesvUb5`S`(5x#5VJ~4%ET6&%GR$}muHV-5lTsCi_R|6KM(g2PCD@|yOpKluT zakH!1V7nKN)?6JmC-zJoA#ciFux8!)ajiY%K#RtEg$gm1#oKUKX_Ms^%hvKWi|B=~ zLbl-L)-=`bfhl`>m!^sRR{}cP`Oim-{7}oz4p@>Y(FF5FUEOfMwO!ft6YytF`iZRq zfFr{!&0Efqa{1k|bZ4KLox;&V@ZW$997;+Ld8Yle91he{BfjRhjFTFv&^YuBr^&Pe zswA|Bn$vtifycN8Lxr`D7!Kygd7CuQyWqf}Q_PM}cX~S1$-6xUD%-jrSi24sBTFNz(Fy{QL2AmNbaVggWOhP;UY4D>S zqKr!UggZ9Pl9Nh_H;qI`-WoH{ceXj?m8y==MGY`AOJ7l0Uu z)>M%?dtaz2rjn1SW3k+p`1vs&lwb%msw8R!5nLS;upDSxViY98IIbxnh{}mRfEp=9 zbrPl>HEJeN7J=KnB6?dwEA6YMs~chHNG?pJsEj#&iUubdf3JJwu=C(t?JpE6xMyhA3e}SRhunDC zn-~83*9=mADUsk^sCc%&&G1q5T^HR9$P#2DejaG`Ui*z1hI#h7dwpIXg)C{8s< z%^#@uQRAg-$z&fmnYc$Duw63_Zopx|n{Bv*9Xau{a)2%?H<6D>kYY7_)e>OFT<6TT z0A}MQLgXbC2uf`;67`mhlcUhtXd)Kbc$PMm=|V}h;*_%vCw4L6r>3Vi)lE5`8hkSg zNGmW-BAOO)(W((6*e_tW&I>Nt9B$xynx|sj^ux~?q?J@F$L4;rnm_xy8E*JYwO-02u9_@@W0_2@?B@1J{y~Q39N3NX^t7#`=34Wh)X~sU&uZWgS1Z09%_k|EjA4w_QqPdY`oIdv$dJZ;(!k)#U8L+|y~gCzn+6WmFt#d{OUuKHqh1-uX_p*Af8pFYkYvKPKBxyid4KHc}H` z*KcyY;=@wzXYR{`d{6RYPhapShXIV?0cg_?ahZ7do)Ot#mxgXYJYx}<%E1pX;zqHd zf!c(onm{~#!O$2`VIXezECAHVd|`vyP)Uyt^-075X@NZDBaQt<>trA3nY-Dayki4S zZ^j6CCmx1r46`4G9794j-WC0&R9(G7kskS>=y${j-2;(BuIZTLDmAyWTG~`0)Bxqk zd{NkDe9ug|ms@0A>JVmB-IDuse9h?z9nw!U6tr7t-Lri5H`?TjpV~8(gZWFq4Vru4 z!86bDB;3lpV%{rZ`3gtmcRH1hjj!loI9jN>6stN6A*ujt!~s!2Q+U1(EFQEQb(h4E z6VKuRouEH`G6+8Qv2C)K@^;ldIuMVXdDDu}-!7FS8~k^&+}e9EXgx~)4V4~o6P^52 z)a|`J-fOirL^oK}tqD@pqBZi_;7N43%{IQ{v&G9^Y^1?SesL`;Z(dt!nn9Oj5Odde%opv&t zxJ><~b#m+^KV&b?R#)fRi;eyqAJ_0(nL*61yPkJGt;gZxSHY#t>ATnEl-E%q$E16% zZdQfvhm5B((y4E3Hk6cBdwGdDy?i5CqBlCVHZr-rI$B#>Tbi4}Gcvyg_~2=6O9D-8 zY2|tKrNzbVR$h57R?Pe+gUU_il}ZaWu|Az#QO@};=|(L-RVf0AIW zq#pO+RfM7tdV`9lI6g;{qABNId`fG%U9Va^ravVT^)CklDcx)YJKeJdGpM{W1v8jg z@&N+mR?BPB=K1}kNwXk_pj44sd>&^;d!Z~P>O78emE@Qp@&8PyB^^4^2f7e)gekMv z2aZNvP@;%i{+_~>jK7*2wQc6nseT^n6St9KG#1~Y@$~zR_=AcO2hF5lCoH|M&c{vR zSp(GRVVl=T*m~dIA;HvYm8HOdCkW&&4M~UDd^H)`p__!4k+6b)yG0Zcek8OLw$C^K z3-BbLiG_%qX|ZYpXJ$(c@aa7b4-*IQkDF}=gZSV`*ljP|5mWuHSCcf$5qqhZTv&P?I$z^>}qP(q!Aku2yA5vu38d8x*q{6-1`%PrE_r0-9Qo?a#7Zbz#iGI7K<(@k^|i4QJ1H z4jx?{rZbgV!me2VT72@nBjucoT zUM9;Y%TCoDop?Q5fEQ35bCYk7!;gH*;t9t-QHLXGmUF;|vm365#X)6b2Njsyf1h9JW#x$;@x5Nx2$K$Z-O3txa%;OEbOn6xBzd4n4v)Va=sj5 z%rb#j7{_??Tjb8(Hac<^&s^V{yO-BL*uSUk2;X4xt%NC8SjO-3?;Lzld{gM5A=9AV z)DBu-Z8rRvXXwSVDH|dL-3FODWhfe1C_iF``F05e{dl(MmS|W%k-j)!7(ARkV?6r~ zF=o42y+VapxdZn;GnzZfGu<6oG-gQ7j7Zvgo7Am@jYxC2FpS@I;Jb%EyaJDBQC(q% zKlZ}TVu!>;i3t~OAgl@QYy1X|T~D{HOyaS*Bh}A}S#a9MYS{XV{R-|niEB*W%GPW! zP^NU(L<}>Uab<;)#H)rYbnqt|dOK(-DCnY==%d~y(1*{D{Eo1cqIV8*iMfx&J*%yh zx=+WHjt0q2m*pLx8=--UqfM6ZWjkev>W-*}_*$Y(bikH`#-Gn#!6_ zIA&kxn;XYI;eN9yvqztK-a113A%97in5CL5Z&#VsQ4=fyf&3MeKu70)(x^z_uw*RG zo2Pv&+81u*DjMO6>Mrr7vKE2CONqR6C0(*;@4FBM;jPIiuTuhQ-0&C)JIzo_k>TaS zN_hB;_G=JJJvGGpB?uGgSeKaix~AkNtYky4P7GDTW6{rW{}V9K)Cn^vBYKe*OmP!; zohJs=l-0sv5&phSCi&8JSrokrKP$LVa!LbtlN#T^cedgH@ijt5T-Acxd9{fQY z4qsg1O{|U5Rzh_j;9QD(g*j+*=xULyi-FY|-mUXl7-2O`TYQny<@jSQ%^ye*VW_N< z4mmvhrDYBJ;QSoPvwgi<`7g*Pwg5ANA8i%Kum;<=i|4lwEdN+`)U3f2%bcRZRK!P z70kd~`b0vX=j20UM5rBO#$V~+grM)WRhmzb15ya^Vba{SlSB4Kn}zf#EmEEhGruj| zBn0T2n9G2_GZXnyHcFkUlzdRZEZ0m&bP-MxNr zd;kl7=@l^9TVrg;Y6J(%!p#NV*Lo}xV^Nz0#B*~XRk0K2hgu5;7R9}O=t+R(r_U%j z$`CgPL|7CPH&1cK5vnBo<1$P{WFp8#YUP%W)rS*a_s8kKE@5zdiAh*cjmLiiKVoWD z!y$@Cc5=Wj^VDr$!04FI#%pu6(a9 zM_FAE+?2tp2<$Sqp5VtADB>yY*cRR+{OeZ5g2zW=`>(tA~*-T)X|ahF{xQmypWp%2X{385+=0S|Jyf`XA-c7wAx`#5n2b-s*R>m zP30qtS8aUXa1%8KT8p{=(yEvm2Gvux5z22;isLuY5kN{IIGwYE1Pj);?AS@ex~FEt zQ`Gc|)o-eOyCams!|F0_;YF$nxcMl^+z0sSs@ry01hpsy3p<|xOliR zr-dxK0`DlAydK!br?|Xi(>buASy4@C8)ccRCJ3w;v&tA1WOCaieifLl#(J% zODPi5fr~ASdz$Hln~PVE6xekE{Xb286t(UtYhDWo8JWN6sNyRVkIvC$unIl8QMe@^ z;1c<0RO5~Jv@@gtDGPDOdqnECOurq@l02NC#N98-suyq_)k(`G=O`dJU8I8LcP!4z z8fkgqViqFbR+3IkwLa)^>Z@O{qxTLU63~^lod{@${q;-l?S|4Tq0)As-Gz!D(*P)Vf6wm6B8GGWi7B)Q^~T?sseZeI+}LyBAG!LRZn_ktDlht1j2ok@ljteyuNUkG67 zipkCx-7k(FZQhYjZ%T9X7`tO99$Wj~K`9r0IkWhPul`Q_t1YnVK=YI1dMc_b!FEU4 zkv=PGf{5$P#w{|m92tfVnsnfd%%KW;1a*cLmga4bSYl^*49M4cs+Fe>P!n=$G6hL6 z>IM&0+c(Nvr0I!5CGx7WK*Z3V^w0+QcF=hU0B4=+;=tn*+XDxKa;NB-z4O~I zf}TSb^Z;L_Og>!D1`;w@zf@GCqCUNY%N?IPmEkTco^}bX~BWM_Hamu05>#B zBh%QfUeHPu`MsYVQQ3hOT;HmP_C|nOl zjluk7vaSICyQ01h`^c)DWp>cxPjGEc6D^~2L79hyK_J#<9H#8o`&XM4=aB`@< z<|1oR6Djf))P1l2C{qSwa4u-&LDG{FLz#ym_@I+vo}D}#%;vNN%& zW&9||THv_^B!1Fo+$3A6hEAed$I-{a^6FVvwMtT~e%*&RvY5mj<@(-{y^xn6ZCYqNK|#v^xbWpy15YL18z#Y&5YwOnd!A*@>k^7CaX0~4*6QB{Bgh$KJqesFc(lSQ{iQAKY%Ge}2CeuFJ{4YmgrP(gpcH zXJQjSH^cw`Z0tV^axT&RkOBP2A~#fvmMFrL&mwdDn<*l3;3A425_lzHL`+6sT9LeY zu@TH0u4tj199jQBzz*~Up5)7=4OP%Ok{rxQYNb!hphAoW-BFJn>O=%ov*$ir?dIx% z56Y`>?(1YQ8Fc(D7pq2`9swz@*RIoTAvMT%CPbt;$P%eG(P%*ZMjklLoXqTE*Jg^T zlEQbMi@_E|ll_>pTJ!(-x41R}4sY<5A2VVQ^#4eE{imHt#NEi+#p#EBC2C=9B4A|n zqe03T*czDqQ-VxZ+jPQG!}!M0SlFm^@wTW?otBZ+q~xkk29u1i7Q|kaJ(9{AiP1`p zbEe5&!>V;1wnQ1-Qpyn2B5!S(lh=38hl6IilCC6n4|yz~q94S9_5+Od*$c)%r|)f~ z;^-lf=6POs>Ur4i-F>-wm;3(v7Y_itzt)*M!b~&oK%;re(p^>zS#QZ+Rt$T#Y%q1{ zx+?@~+FjR1MkGr~N`OYBSsVr}lcBZ+ij!0SY{^w((2&U*M`AcfSV9apro+J{>F&tX zT~e zMvsv$Q)AQl_~);g8OOt4plYESr8}9?T!yO(Wb?b~1n0^xVG;gAP}d}#%^9wqN7~F5 z!jWIpqxZ28LyT|UFH!u?V>F6&Hd~H|<(3w*o{Ps>G|4=z`Ws9oX5~)V=uc?Wmg6y< zJKnB4Opz^9v>vAI)ZLf2$pJdm>ZwOzCX@Yw0;-fqB}Ow+u`wglzwznQAP(xbs`fA7 zylmol=ea)g}&;8;)q0h7>xCJA+01w+RY`x`RO% z9g1`ypy?w-lF8e5xJXS4(I^=k1zA46V)=lkCv?k-3hR9q?oZPzwJl$yOHWeMc9wFuE6;SObNsmC4L6;eWPuAcfHoxd59gD7^Xsb$lS_@xI|S-gb? z*;u@#_|4vo*IUEL2Fxci+@yQY6<&t=oNcWTVtfi1Ltveqijf``a!Do0s5e#BEhn5C zBXCHZJY-?lZAEx>nv3k1lE=AN10vz!hpeUY9gy4Xuy940j#Rq^yH`H0W2SgXtn=X1 zV6cY>fVbQhGwQIaEG!O#p)aE8&{gAS z^oVa-0M`bG`0DE;mV)ATVNrt;?j-o*?Tdl=M&+WrW12B{+5Um)qKHd_HIv@xPE+;& zPI|zXfrErYzDD2mOhtrZLAQ zP#f9e!vqBSyoKZ#{n6R1MAW$n8wH~)P3L~CSeBrk4T0dzIp&g9^(_5zY*7$@l%%nL zG$Z}u8pu^Mw}%{_KDBaDjp$NWes|DGAn~WKg{Msbp*uPiH9V|tJ_pLQROQY?T0Pmt zs4^NBZbn7B^L%o#q!-`*+cicZS9Ycu+m)rDb98CJ+m1u}e5ccKwbc0|q)ICBEnLN# zV)8P1s;r@hE3sG2wID0@`M9XIn~hm+W1(scCZr^Vs)w4PKIW_qasyjbOBC`ixG8K$ z9xu^v(xNy4HV{wu2z-B87XG#yWu~B6@|*X#BhR!_jeF*DG@n_RupAvc{DsC3VCHT# za6Z&9k#<*y?O0UoK3MLlSX6wRh`q&E>DOZTG=zRxj0pR0c3vskjPOqkh9;o>a1>!P zxD|LU0qw6S4~iN8EIM2^$k72(=a6-Tk?%1uSj@0;u$0f*LhC%|mC`m`w#%W)IK zN_UvJkmzdP84ZV7CP|@k>j^ zPa%;PDu1TLyNvLQdo!i1XA|49nN}DuTho6=z>Vfduv@}mpM({Jh289V%W@9opFELb z?R}D#CqVew1@W=XY-SoMNul(J)zX(BFP?#@9x<&R!D1X&d|-P;VS5Gmd?Nvu$eRNM zG;u~o*~9&A2k&w}IX}@x>LMHv`ith+t6`uQGZP8JyVimg>d}n$0dDw$Av{?qU=vRq zU@e2worL8vTFtK@%pdbaGdUK*BEe$XE=pYxE_q{(hUR_Gzkn=c#==}ZS^C6fKBIfG z@hc);p+atn`3yrTY^x+<y`F0>p02jUL8cgLa|&yknDj;g73m&Sm&@ju91?uG*w?^d%Yap&d2Bp3v7KlQmh z(N<38o-iRk9*UV?wFirV>|46JqxOZ_o8xv_eJ1dv} zw&zDHZOU%`U{9ckU8DS$lB6J!B`JuThCnwKphODv`3bd?_=~tjNHstM>xoA53-p#F zLCVB^E`@r_D>yHLr10Sm4NRX8FQ+&zw)wt)VsPmLK|vLwB-}}jwEIE!5fLE;(~|DA ztMr8D0w^FPKp{trPYHXI7-;UJf;2+DOpHt%*qRgdWawy1qdsj%#7|aRSfRmaT=a1> zJ8U>fcn-W$l-~R3oikH+W$kRR&a$L!*HdKD_g}2eu*3p)twz`D+NbtVCD|-IQdJlFnZ0%@=!g`nRA(f!)EnC0 zm+420FOSRm?OJ;~8D2w5HD2m8iH|diz%%gCWR|EjYI^n7vRN@vcBrsyQ;zha15{uh zJ^HJ`lo+k&C~bcjhccoiB77-5=SS%s7UC*H!clrU$4QY@aPf<9 z0JGDeI(6S%|K-f@U#%SP`{>6NKP~I#&rSHBTUUvHn#ul4*A@BcRR`#yL%yfZj*$_% zAa$P%`!8xJp+N-Zy|yRT$gj#4->h+eV)-R6l}+)9_3lq*A6)zZ)bnogF9`5o!)ub3 zxCx|7GPCqJlnRVPb&!227Ok@-5N2Y6^j#uF6ihXjTRfbf&ZOP zVc$!`$ns;pPW_=n|8Kw4*2&qx+WMb9!DQ7lC1f@DZyr|zeQcC|B6ma*0}X%BSmFJ6 zeDNWGf=Pmmw5b{1)OZ6^CMK$kw2z*fqN+oup2J8E^)mHj?>nWhBIN|hm#Km4eMyL= zXRqzro9k7(ulJi5J^<`KHJAh-(@W=5x>9+YMFcx$6A5dP-5i6u!k*o-zD z37IkyZqjlNh*%-)rAQrCjJo)u9Hf9Yb1f3-#a=nY&M%a{t0g7w6>{AybZ9IY46i4+%^u zwq}TCN@~S>i7_2T>GdvrCkf&=-OvQV9V3$RR_Gk7$t}63L}Y6d_4l{3b#f9vup-7s z3yKz5)54OVLzH~Ty=HwVC=c$Tl=cvi1L?R>*#ki4t6pgqdB$sx6O(IIvYO8Q>&kq;c3Y-T?b z*6XAc?orv>?V7#vxmD7geKjf%v~%yjbp%^`%e>dw96!JAm4ybAJLo0+4=TB% zShgMl)@@lgdotD?C1Ok^o&hFRYfMbmlbfk677k%%Qy-BG3V9txEjZmK+QY5nlL2D$Wq~04&rwN`-ujpp)wUm5YQc}&tK#zUR zW?HbbHFfSDsT{Xh&RoKiGp)7WPX4 zD^3(}^!TS|hm?YC16YV59v9ir>ypihBLmr?LAY87PIHgRv*SS>FqZwNJKgf6hy8?9 zaGTxa*_r`ZhE|U9S*pn5Mngb7&%!as3%^ifE@zDvX`GP+=oz@p)rAl2KL}ZO1!-us zY`+7ln`|c!2=?tVsO{C}=``aibcdc1N#;c^$BfJr84=5DCy+OT4AB1BUWkDw1R$=FneVh*ajD&(j2IcWH8stMShVcMe zAi6d7p)>hgPJbcb(=NMw$Bo;gQ}3=hCQsi{6{2s~=ZEOizY(j{zYY-W8RiNjycv00 z8(JpE{}=CHx0ib3(nZgo776X=wBUbfk$y2r*}aNG@A0_zOa4k3?1EeH7Z43{@IP>{^M+M`M)0w*@Go z>kg~UfgP1{vH+IU(0p(VRVlLNMHN1C&3cFnp*}4d1a*kwHJL)rjf`Fi5z)#RGTr7E zOhWfTtQyCo&8_N(zIYEugQI}_k|2X(=dMA43Nt*e93&otv`ha-i;ACB$tIK% zRDOtU^1CD5>7?&Vbh<+cz)(CBM}@a)qZ^ld?uYfp3OjiZOCP7u6~H# zMU;=U=1&DQ9Qp|7j4qpN5Dr7sH(p^&Sqy|{uH)lIv3wk?xoVuN`ILg}HUCLs1Bp2^ za8&M?ZQVWFX>Rg4_i$C$U`89i6O(RmWQ4&O=?B6@6`a8fI)Q6q0t{&o%)|n7jN)7V z{S;u+{UzXnUJN}bCE&4u5wBxaFv7De0huAjhy#o~6NH&1X{OA4Y>v0$F-G*gZqFym zhTZ7~nfaMdN8I&2ri;fk*`LhES$vkyq-dBuRF!BC)q%;lt0`Z(*=Sl>uvU`LAvbyt zL1|M@Jas<@1hK!prK}$@&fbf70o7>3&CovCKi815v$6T7R&1GOG~R4pEu2B z%bxG{n`u$7ps(}Tt(P608J@{+>X(?=-j8CkF!T79c`1@E%?vOL%TYrMe1ozi<##IsIC1YRojP!gD%|+7|z^-Vj$a85gbmtB#unyoy%gw9m1yB z|L^-wylT%}=pNpq!QYz9zoV7>zM2g2d9lm{Q zP|dx3=De3NSNGuMWRdO_ctQJUud?_96HbrHiSKmp;{MHZhX#*L+^I11#r;grJ8_21 zt6b*wmCaAw(>A`ftjlL@vi06Z7xF<&xNOrTHrDeMHk*$$+pGK0p+|}H=Kgl{=naBy zclyQsRTraO4!uo})OTSp_x`^0jj7>|H=FOGnAbKT_LuSUiSd3QuCMq>sEhB=V63Nm zZxrtB0)U@x2A#VHqo2ab=pn~tu>kJ;TVASb_&ePAgVcic@>^YM?^LYRLr^O12>~45 z-EE?-Z$xjxsN92EaBi)~D~1OzRVH`o!)kYv7IIx??(B)>R|xa&(wmlU2gdV0+N+3% z7r$w5(L<|?@46ITJZS5koAELgVV_&KHj(9KG??A);@gL`s1th*c#t5>U(*+nb0+H% zOhJG5tth59%*>S~JIi%<0VAi;k>}&(Ojg!fyH0(fza!1kA~a}Vt{|3z{`Pt@VuYyB zFUt(kR$<`X_J&UQ%;ui2zob1!H{PL8X>>wbpGn~@&h__AfBit)4`D^#->1+Qn^MH9 zYD?%)Pa)D-xQzVGm!g)N$^_z`9)(>)gyQ+(7N@k4GO?~43wcE-|77;CPwPXHQcfcJ^I&IOOah zzL|dhoR*#m5sw{b&L=@<-30s9F|{@V05;4Wf6Z_1gpZnJ*SVN}3O7)-=yYuj2)O0d zX=I9TzzTK%QG&ujvS!F*aJ8eqt4|#VE;``yKqCx7#8QC7AmVn+zW9km3L5TN=R>{5 zLcW`6NKkTz`c{`-w!X9zMG;JZP|skLGs7qBHaWj7Ew!VR=`>n30NX)7j~-RbDmQ6b zHr)zVcn^~e2xqFCBG4P$ZCcRDml-&1^5fqN=CHgBVu1yTg32_N>tZ;N%h*TwOf^1lE#w1$yF$kXaP|V$2XuZ+3wH4Ws6%U;^iP|c6`#etHogQ+E@+~PZ1zdGAty6qTmBM z>!)Wfgq~%lD)m>avXMm)ReN}s9!T_>ic6xA|m7$(&n(Z&j} zHC=}~I(^-*PS2pc7%>)6w}F1il&p*0jX1z)jSvG%S{I3d9w$A|5;TS)4w81yzq5f8 zZVfF~`74m1KXQg|`OS>;FCgZw!AL;2PV{&8%~rG!;`eD=g!luE0k40GjIgjD!JSDNf$eW zZtPMF)&EH_#?IwVLEx&Tosh9K8Ln4Pb$`j2=><6MAezsQvhP#YNnw&cL>12xf)dPz z1tk;{SH6HDcbV0x(+5=2n;A->&iYDa5Zr9$&j?2iAz-(l1;#Vc3-ULyqRV9d0*psG7QHE! z*J=*^sKK?iTO$g*+j~C?QzzIu`6Z{2N-ANrd5*?o%x& z&WMin)$Wq%G!?{EH(2}A?Wx@ zn8|q7xPad4Gu>l^&SBl|mhUxp;S+Cb125`h5aBz9pM34$7n-GHGx*=yqAphZKkds7 z$=5Jnt*6&8@y80jNXm|>2IR<$D5frk;c2f5zLS5xe*^W>kkZa5R1+Am34;mo{Gr=Z zD=z8fgTHwx%)7hzjOo9*Cogbru8GgDzrE;3y%TR+u`|zz%c0Tyd8;#EQXdr4Rgx(2LPRzVI2FwsbXwnF;DP^fg zdYOd|zU&AqgCJ;R+?oSgEgZM`ZX>7&$A-j2m|Tcz4ictXoQkz6Tr<2zhOudU16k<7 zLdk&FCL>=a^>0gV@m#9SnMd)R$5&1mh8p2McnUbk;1|C;`7pPkYjf|o>|a6`x`z1O zt>8~Q%zHX%C=D2!;_1eo3qfbB4QQK^{ON_f*7XhLk{6sr2(KIVmax}fUtF-zHZiUd zHPb9jidV`dE;lsw?1uQH!b%MvPE|lh9-8R_z4^PC8{XAf?S73(n*FvYPoMES+LfOx zcjm4ZZOmKY>M2e${QBVT+XnBQ(oC0fAYcXi7+=}_!hS9m>Y%G@zxn3z#Pb;bJ~-kI zAHNmWgQJp$e8L-uKQ|c4B;#0BTsfRB+}pl7xe=2_1U7pahx5S$TVbRnU0oi1?Wh|A zR7ebg9TK1GgKa4@ic#q_*<;c8?CkjX zMMyq`J()_&(j-FZY7q%z6CN^a0%V{UL)jmrvEg{doZd?qIjgJ^UPr(QUs`68;qkdI zzj_XBQ|#K2U!5?fmIEtXX6^rFY;h4=Vx<-C(d;W6Bi_Xsg{ZJPL*K;I?5U$=V-BNP zn9pKiMc=hZNe**GZBw1kVs#-8c2ZRjol}}^V@^}BqY7c0=!mA;v0`d|(d;R-iT|GK z>zt>Tt3oV09%Y;^RM6=p9C-ys_a``HB_D-pnyX(CeA(GiJqx7xxFE52Y`j~iMv;sP z%jPmx#8p%5`flAU(b!c9XBvV+fygn`BP-C#lyRa;9%>YyW6~A_g?@2J+oY0HAg{qO znT4%ViCgw&eE=W8yt-0{cw`tMieWOG3wyNX#3a^qPhE8TH1?QhwhR~}Ic zZ^q$TF8$p0b0=L8aw&qaTjuAYPmr-6x;U*k*vRnOaBwb_( z5+ls5b(E!(71*l)M&(7ZEgBCtB{6Kh#ArV4u0iNnK!ml!nK5=3;9e76yD9oU4xTAK zPGsGkjtFMMY3pRP5u07;#af?b0C7u) zD^=9X@DRasHaf#c>4rF5GAT!Ggj0!7!z?Q-1_X6ZP2g|+?nVutp|rp}eFlKc8}Q&_ z17$NpDQvQolMWZfj0W0|WKm`nd_KXYH_#wRRzs1aRBYqo#feM}a?joONn30Z4Z9PG zg1c!_<52-9D53Wq4z8pUzGkEFm1@Ws(kp4}CO7csZ-7+b)^)M)(xo}_IpTLl7}5BmbBCI{4>rw>4c_gBQHtRd5Z=SW&6Qp2qMOjr3W+ZRmP;S(U+h=^BHKohhRp6Zgf zwt&$zQXhMm@kh1@SB%dIE*kFDZym3Mky$NRljX?}&JGK`PIV1C;Pf!JV{hb4y;Ju- zlpfEPUd+mV5XQH<#BRFhZ}>b#IdF?a?x;rBg-v)@fZpA?+J{3WZjbl3E zv(a&1=pGYPxP@K!6Qg5Vx=-jwc=BA{xL3+QWb&9~DGS1EFkIC+>55{dvY4LV@s5$C zKJmCjigp7?m27*GN_GROz}y+y5%iIj=*JTYccaFjvD&VN%ewfSp=0P zspdFfDqj?gs!N64cEy5uR~wD>af!1PE*xo{^a^8BPIL2=U>B!m2AM0Jf<8qWLoHxi zxQfkbbwkRXgJgLW_j{ZkCxHLBU{@D6T5u90UNs5P769Zei|C$@nA5$L$4ZvxQl1i? z8vLHg17}e{zM$=&h%8Swbfz7yw~X^N|7Chp1bC(oV72l#R8&%Ne5>F=7wR(dB; zkDX!%&fxS19JBjP<6H7+!dO`nPLvB~xn{aDh#^iHKP|A5UQlCG%v%x9@q1w2fa#&% za^UwHu!~(qrv99G%9_e4OBbJ-CkB*1M_?t6UXZ#}4JFDzB|x(1Z}ckuiY}${zj`eVo})!rN8Je z%h2CVJG1$K$2deXx^h8trLs~Han^e>_-M6@0o4C7d548|#mKtm@DvdVAX5ZzA8=*! zKq5C+cM9u)qJ%YBJ1UAcG}6Ji4=$piaZ(K@>1BiD;$R9bR*QP`dH2T=)dgW#f7U)S zZ~i#VYLOnUZt^~Iu3x8QPJaHVUxtRyipQ+tbmWKl14iW1!f6JSDvT$xt8>~7-1ZlJ zU|)Ab*lhvz-JO!$a}RBH9u8$=R)*qeD@iS@(px~OVvML-qqO5&Ujnhw1>G~**Ld{W zE+7h|!{rDZ#;ipZx4^Tcr9vnO)0>WFPzpFu*MYST(`GFzCq*@Gqse6VwDH#x?-{rs z+=dqd$W0*AuAEhzM@GC&!oZa1*lRsx>>mP>DNYigdm^A~xzo}=uV$w#iadO+!&q_~ zT>AsHXOEGsNyfcJt2V$rhGxaIcTEvZr7CMVEu=>l30N~52^71U^<_uw6h@v@`BA2! z)ViU+wF#^$=5o44TpOj?#eyq*+A&c0ghrt8%}SiK)FgLk-;-^+ zXt|1}1vcKAAuR|?L*a8;04p%!M~U2~UC-OJK)DMtBQ#+ZttJgDFNA4zchA*T)cN(E zmpIMLU*c*NrCSV^qdLXD751DsO`#V#K1BVX4qI-B3Rg(zcvlg^mgY^V3Q*5RRQ4-8 z_kAlUisma2SNEx47euK5Y#eu_-gwRW0}M90hEI}eIJ9aU?t11^jSCn4>e~XLSF7Y3 z7JF)1ZbS_P<$<#y(*u@w!jF4FW_f~bxzi%cgP~B1K5N6GFYSAf=D_s5XomU0G9I%Y zPWc{&MItPR#^Le)?zsRkQMmHx^Cnn&;TrPzRVG`wyNH*U;|r3^2NY(z0lwikP}cWF z`p%R@?dy*7H~0&3ST>L9)b7#kwg+|n0#E&-FNf+Z_t7tpa711FogBPV`S3MW_FMGQ zJ@8Z}qXR4-l%p76mvcH`{Fu(^O;8H2@#LZUH#9p6!EX$AEYV$c`s zkPimL3kv>y=WQ+?KIAuim``%cAeBhA6g8}p_*FBH(#{vKi)CIz_D)DFXPql*ccC}O zRW;+Y6V@=&*d6QJUbRxPX+-_24tc-hYHEFaP-IAj*|-P5%xbWujQvu#TF>xigr_r! znuu7b(!PyYX=O#>;+0cGRx>Sy39(3y=TCf_BZ$<%m#inup$>o(3dA1Byfsip8S975-iVe7UklFm|$4&kaJ!n66_k-7-k}Z_?){LQe&wTeJ^CR{u6p+U#4_iSZZ1wjB-1gVGNQqnkk*-wFLj(eK8Ut{waU zb1jwb2I?Wg&98jSQWom8c?2>BWt*!3WQ?>fB$KguB9_sStno%x=JXPEFrT|hh~Po2 zSPzu3IL10O?9U(3{X8OLN-!l6DJVtgr$yYXeAPh~%(FECDe;$mIY7R4Miv1GEFk9x zpw`}E5M)qTr60D^;a#OCd0xP*w8y+my1^l8Qd*V`wLoj)GFFj;;esW2PMO=sbas{yX6asXIJ$|LW< zts$A+JaxoM({kv+2d@#bhl?#V#FZn_=8tTTvup?Vq!p!46W{be)EP=VlYE|UzAU}) zz})UzJVWi;9br0k&5>}sqwa_`TP*c}^$9+q)Dks#qEVg>p)71sqKF-YLP@UF{(>lp7;CHAWK;K0TZ_+?>EtZKprfU@;52a1IU8HNx-mnoZrb8| zP8FPb#T$0VE+G-l508;d{DSfC6#dbp(j|^i^I3z9?Qmkr+(dw^w??h}WTN{_ls-GuE~lF;1Urgbtq|Ud_r>wecb@?{{z? zX>X$&Ud+(I(5}5d^>&Z2m+qy=h#vR*lS084ATwUWZLg6PX1Ft+YI`0iI)ynij}{4X zrQE!Mr1m^-?kw<|VT0mG+5J{!;j;zJT`?_=P*09n+=e``CN|7rC$u~Ksg7LSMS(Q~ z51!n1htcK0q7*K-*u0?c8ZlvPXcNwXmFe0Or2}}R@?j@{ECCNZ6va1tZ>|ZOgGZ1j z9?mRkeSK%{X4O>J$@hyFsD)7s67Uldb>O93wQQiV%-FfbEY_@q>1VUstIJs|QgB`o1z**F#s z^joAYN~5{EQ_wZ~R6-nEV#HsQbNU59dT;G zovb$}pb=LdR^{W2Nh~8yWfq*vC_DvJxM=)2N`5x+N6Sl`3{Wl@$*BYol#0^idTuM` zJ=prt$REkxn6%dimg%99{(Dt6D67sTUR6l1F@9&Z9<)XgWK#x zVohUH6>_xRuw1^V**+BCZ@dZj97T*67OBO>6UUivH`<@ray~ym^E?bO=vKqFfK3Kv z`RKxs4raHacB<(XAeH`@0G*K2@ill_U@m=icT@F{k1PU3j4VBde`ThtW8%Z~A>)45ARjQCDXbH}_rS^IxHGp#utBEj3W3KSAU+$6I4s~9OWueETo!J-f~+DV8< z+VMtdcQ?M+?S}kl&uImYiIUJ-K0-te7W4sdWpS6Fqs-I!Tj{8Qp6lMn$Zm8uU)s{X z8|O}HN%8sEl4em&qv{VBq{}$@cCG{B z5~3DY$WRYSkO~z=sxRct5^G5bPZW;LF)(zY)HREgpRrkYV@H3^BTD6u+bJE~$cqr< zw@Gb3^|n*kHZ%Vnu6~B7pB4iM0C4kDuk8Q1R^<(x%>|sCOl%CTe^N)K?Tiepg?|#m z94!og0*38u|67h%*!)SJhUdvFimsktaqp#im9IpH-$fQc79gi259qPkEZ)XU?2uWW zRg?$8`vl;V%-Tk+rwpTGaxy)h%3AmF^78<#i+Q6~M4#>J4`NNEEzy~xZ&O*9q%}@7 zs9XBO#vSKSM<-OjPIDzO9JiAYFWrK14Am{uZT=S3zaCu~K%kZo&u*=k9L#xi6vyaG zQFD76MOE&=c1G;7Zivp<%%fRq+@3wgZg>k@AYQf|*Qyzy$tqc20m?F5nGbG@V#gW` z8RMb2oBxgiqa?)_G6&-;L#(HCoaJrs_ED{IUZ^$~)+e#0iZT!AJDb2V{Sen*70TO& zyI`*~#ZdLFhYP_#DTuoqQ0OS6j0o15r{}O&YoT5wCp|x_dD{#Y;Y}0P1ta?2VEh4* ztrRN5tL6UvoH@M9L z=%FKpf@iSp2P>C(*o<-Ng4qF#A?i!AxjXLG8%Gm`$rZxw;ZqSvv5@@sZ|N*~do5fb zKWR)T_>`kxaS|MHFh`-`fc`C%=i@EFk$O&)*_OVrgP4MWsZkE2RJB(WC>w}him zb3KV>1I&nHP9};o8Kw-K$wF8`(R?UMzNB22kSIn#dEe|V-CuMw8I7|#`qSB6dpYg$ zoaDHj%zV6*;`u`VVdsTBKv&g75Q`68rdQU6O>_wkMT9d!z@)q2E)R3(j$*C4jp$Fo z2pE>*ih{4Xzh}W+5!Qw)#M*^E(0X-6-!%wj@4*^)8F=N*0Y5Or+>d= zhMNs@R~>R9;KmyP@I@bpU3&w?)jj0rGrb@q)P>wLVbz1!TZY$#+H-mK6B^0{vdvt0 zaJ0~7p%I#1PpPm1DvBzh7*UsCl^I5^`@XzPzbg+v3T_WyKN?TJ9J=57v^IUO`aQN} z@>Y>WIj+gT@-sobU-tW%L5GP(qY?Eep&I;@osY}O*3i1Ar?Sv|EI6S-pK_!~*A$K| zs-hHESqd`vv;zIzgv2ho5-hsIL5Ke~siJ(v0`Qm7W_Rms2rB67=p&HGRhA-)$p-BS zvXSmgGIGgeJMBcsgp=L8U3Ep$VPBFhvJ!3M5{pocGBS~iZj0({9Jt9nbC{Z$LVb%= zGqzRBjlqkAU{#sOX56})^QjX;jQ26M`poAFIZ#H31td9sQlgBBrfIYgDC9+kO~}s{ zb1i*{#{5tPWhv4pecAZygXG>?5xKx7iPXd?nR;QaIfhlhqNBaLDy>9Yd1Sf3P!s4~ zhfHaFGsIFy&ZM=6^qc>>V>o!zk%5Lk5BtS7oU=YfjWUN;c zrh$6Cyr%KC@QNTzTZvb)QXQkV)01MEY+EzC%CJx)Q&6MM={paB}Dp=qCn^eJ}5LeXG9Gqynt0ir>DvSIZ=i?*_xR3=% zppf1w51ypF2KL6ug zCm}eCi>&>xT;Idzh^PmtDWrU(&eC2hAt(nmd#?;W)*&4lb2Z2Ykv*XLNDEm`_1n3C z`l!wZwiF9b?mN@z?s~>v%hT01C{E3md6M5_Xi3fKD6s26Tt~Z>8|~Ao9ds!cF_Y1| zRG>!=TD0k0`|T*)oX!SlSt8g4Uh@nc(QosCoen@i*ZCSyh|IliliuhEw$8?4ZL9N2 zMQ%%S=3Tj_QilhHW@cSr1UYTtDem{A-ZxyCa$K9A%(!`X_?ieJzXbfERST|JxqmbL zHe!hSqYk|!=!$8CJ5>q}Pj63@Q#PO{gpVb+0-qHFM`j5x_s#~dxvy5u62vywq8upP z_)N)3n9cn7YEf2D8L}x0#_B_~>HT8;;8JC5q+}1gEyd%XqYvY?deQzwD1Lx{ghI3; zv?f;&6CY$H&dDL$k#)hb)5lIqUZ~oU!z)hMI!B9THhw?9!}ykqpFJ|hB?JjV9uwqb z3_70pMV^C7I<3Cg&yMi8JJ3V2gYTOMV=IopfZ#1o>&+j-mB-V${Ok(f?I3{+vR~zE_RR$?9xI~^% z53~ z&bCl+6UeKkUWJ-%mnK{9K>?(3BM3C`@xi}v8)q#;YJhMr5dWvMtAL7X``!bHv~(%m zH8d#Q4N6G~lEW}aGn9ZZNT?v9bV$emf)dg#ASDV?(nu+wpu!_X;(vL<<1zBo-~X&N z>keyizVGaP&c65DbIyEwFn2%(L`P424ZI3nFBA%w{yJ?E} zlwSKF;jIhs(!TFOdMUW|(=qHjr#U-k>`>1u1_yL5Gyy;7@WTOt_)nfIp{D9kwR8f0 z;^Fq=iF(&yd|z30&+I`FBM-P6ouHQ@96TkIe@9=pDDL#_zgXos)-ri5lX-&2D~DsI z4R>xVM$c&aFLgFjwq{1I;jpODOx|n*#@e2+Wgdkm(E(Fad_)peD`1^CJ2TpglmgoC)F(Z)F7y2rzzDU^4wvO{bzw{mzSs4tF;*qabKkC?D!j!tbF z4D_6zbqFVI>n@2-Qmg1BiDdD}>E(72)aMv1Y9duOxwlG|E!L(QmQ#j5vmN@a7v{zIt3qQSP?96^$ITE=h~sLn|N|v8YqmA~-0HWgcPHZ@!3Dzm2X{Bozc{qm>J`Ehp}`FQ%Ecbw%+|H8f`pykvo-%&0a z?&ZtJF*{#AYs8Z|z(IFI8sBiZs)L!C9#1W@;hEInZZZdPz2ZnmhoSP9VHQt7mzZUZ zhM!!5IJbe4Z@zEoMjKaxH&Px8p}1<0YmtWwcG@ZPY@*oQSteU zRy+W=Rs>sJ##v^8EJJt0=5---o<@^?fOEp=N<~xXvcf?$gXD0zVHziRMMmC#Mp3o ze(eT!dvjmXp9_C%pV_>{H=nsqYO)n1J?Ihi zjy7f00`|S<;)I!ZyUO{~#+wXX)z(BWsN|$7n9s}H%ZzE8YQv#vRTHjq@D%tYyfe=3)|7jYxRT#E16nFk&1jFC6CH5d4kiJCVq+%r_$Rec7=G!GuZ-0*$5N2GqXB(dqWPS1Um4{xgi2k=;eO_LDy&GR=Q!)bjKY{f!0yoc0Rol&!E`2BkI$5y4U^*k0=GyL-m8XJL%8prM%;fwyX9M^ zs48n3Oh#a>FVWI7dsm~*l0$^J)lxnfTTw~1ceZ73yNvNurwd`;+^1XuucaFN85M8? z$fNl!D9g*O>6IE^POaoDq`86Sw0t4%jIi`&*EEZI?wwOiEvH8(qpfyDvAe`4pWf7k z3-pFgeT{qtj)B!1ZamZ5g3z6Nd40P(%^Kf@#!uzbIk~8w`9wbhWc~1E|sw6-FsOqrhb2DLDwlaq@)Y zAi$KoA=Vyn=Yxqxtf7wu*$47Ht>WZi{AdeN79#9ws~CtE;~gC$q7T>*5yKK3VT)Q=sllRR}lBIGd17+bOu| zeUeUrMgF=Gjk-{epAyUd_KNgwZK_Pz=H$+{4~E_ZRa3IJpU~IZ5U4Z3l%u3{Ls~`H z(iysmm+!HBJTC-$EpHM9yrXUM^_FZ(3sdmsyZ6=lU8bb3V(WK>P0$l~#QA&NMj@OA z*OQ>^-s_D-bda022~!G!bTh7@FR>t!1r`Js1;4$(^_*hH-_pUPf5C}K-v$%i#KBB! zU{~a7)R>ix z#LA|<6v#rwKkB1JBLWkWu#M0#8i1J0e4dFDP3jrlFfxhkDs%Q~)e6e7fR$U?e$<{x zfZb0?UMsB|E}Fk)@|^{)_^L7O%rp1GRNig@bUX(^6}6HoGi8IXoSKpI1A(GV)uA=7 zOXG&KjZYVjYn6}2YV0yfnKsnpDlF)h$Gv--|6$BsWFg|IWnp|#sk}zOAb6Bb?vb@t zs^7=4IdiKE_rUT@rG!D4Zy zcnas#XT77V&%igMXY(lQS|)lgO{pN9!P-94KeZH_+PK5jESYCSPMN)=D(JIAVeB%D zI_>_lvD;pylkZ#Ral0IzC6ei$J$4NnGw(pnVd`&aaNT5mfq-4)aPjj(v;`VvJ6Xxjm@3DX+Kju z@9-h++s7x>idTEL zd)ptYy?P2$S*_DI;eMR0ZdAuS)~fGEZEguO&+3AwW@Sw$&KvgJr6aGK*Ar;0wx`lr z7V&!+9C7`VcV^t+Wj~AweOGQL!)0)serr$8Fez7kC(VSVRdjqpQuq964RW^2euIre zh10&Tv)|dj*CoRozrW<4y_+5}3EGRok+G7ODl3-CF1r?JYDdw&NbcVT=7ljq_K+8bMeG3uRw@3=cof?j+v+WaKI`WqwByf#7aFK3 z0+R34xQ-6nxQ&9xJKl}`C9FlUe1-h^i?5fr5kjot#MA-$%k106t>*gM+yF3m2X#=1tt07`cK)37dA^A4d8%6R>@0U-UZ~wSvzMlK$tlm~aK`%e8|quXyH`aLM0#Dcu%sqEsKV%i zVn_*W-Qbnl)h?RP>)$rZ5JL!*H;Z{ zk7(FB`lo~h&zB|S6j-Na;y$QM*rn^tkO{>#DWZN@IwJps3*Nm&ox0{{;=J~hvPb-* zvAOEPImrdq()yl~`j`Q;R1Y%CdLKKw*;gtNaM~WDO95YXsTjKCOdRD2Is@aVRTYFD zpS=_EB!@Ub&c*JmNMF=F+)Bq)52|=83IEG;M5(Ol*97!W(S-5X-5w&7->`1Pw-0Ml zpA>jaofnyPQTCzoIG}OK9j^nn>F>jC#$iSnJY8y6ue4nxs@3HtfNx01XVK7NcX#Cu z34g-z=0!7ip&@wI>>6ynJYyFTEgH6DA?b>~V%2s_@NPDza5&6cno!S(|85*74}6_M z%s1c4`B{lqMu``(4~Jk#_`^=tu36TgXPv_}{lhhyi(rrSM_uoVVNuZOuxCXom9|wg zNf&BtzX=hVi*4dG&1J!^QW;O%fQ$jVH=W74B8WR)*tM1{(@cHRqiS_W6R^h8uxd@zV>KNI zR(-LNNkLqh>e=CmL|q9sRHm#15%q$o7_GQMp8FLX-HGnJ<+(;k{Q%+Sk+!^mM+2#1y9+gG2IDZGt%;Cfk{+ zT5}^x=!i2$tnH_se6eC zkn;kK>%ICpo=X&=cSsbxQ|AjJ;5Ff;AyIj>$YA8cw*?W^Nn}S|1jrbf@Bd zr82I8KlOh4#5C0sw3oVvuC0NFPKH4S0$~F$U4JM1Im$B%%oGm_5$Lnr{#Pv}eL1k& zMP(pG$MI^8&!nYffq#$zJ^3GF|cC%2d4V@qKV#fu6u2O

k)oKu82Fu=RODzQrHPEC+Mz{hW(G7VuCl8g1ou-Ot!41bp_>OC1&@A_6e*hc)1X zMuDvzEZyB*fW1^+7dL0%ofr;-xT6B@0~|VazatI{60!X=po^uOr6UB$1POKmuI_&b zOL&O+w*!>`k+y%?Z|wm4$@_1|WC|pKM(F{k8TR$-4hs?i|GBc9)qa{vYq)~5qa(2N zsR?s}0Pp^ufVGEB8oE9VCFa0K$x0HSpem!tIyR69y0rnjg8cqjmWyz7*Kx3~X> z|BZX}Y;oVB1HX@l9_-y7dI*WgruY@?rC&64`}3W`ECA>O@Y#Q@JS<4WBF(QbwJqHM zt)fE#6jTSyZ^E8y0INaIf!omWjvS=@15`O%V2CKg+}z=M9##kLKRN0uJuK250bXVU zwzT&n@30^dzKnlL^us;wClg?CKWEtiEb#zhPVx{PxFQiwEPp^C53zN21EdZAz?3D& zC6fK|_!S5Mq&0z;xWGLEv}!zjfpRg_orp7|fXMx=uP!@X`yT@5(N_Hza}p5fBk&|)J7fZ`NQ9Nz@5xT? zi?iV$q+bG!2LZUpF)>Yl!u;DEHV3!i{ipcJm_8Gj@Dac%N3|SQVGqRhrJ;WOR|CtrwzPTW^&$A6!A$E)h7xohm>hA8p{PUZ~ z_&zeg@OL3PxPtzkfsNZAqXCZ8Is7yQ+plm~8;}|~DEkv&f@?q5hB*OGQYXuwVQOp0 z?QQ`6qyp|-$47wjuV74IE_x2I17$+grwMBE^25d<5!lYhnszuh|5Yk;RB+Uk*hk=m zu73=E^7ul{40{A^?Rg^fq0ZfZO@C1HupR*_d;J>lkFv6&x&}4N;t}1T@2}~AC^<3b zA}RxFPPZe5R{_6dIN9N-GT29Oa}RzA2ekKuEVZbuMOB?Xf**`N5&m}?)TjigdY(rF z?~+a=`0);TlDa1j)1G`AfW? zRl883QPq=w zbB|bHEx%_u*$t@Yl#Vc;y*?2W^|^NJ)DmioQFr~1&>MSBL_b(YIpGWdDm3bT=Mgm1 e+h0K+-~H6qzyuy}`;+tYAZFmzUSVSYum1yJqxCBQ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2617362 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..aeb74cb --- /dev/null +++ b/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs/l2modularblocks-3.0.0-pre0.jar b/libs/l2modularblocks-3.0.0-pre0.jar new file mode 100644 index 0000000000000000000000000000000000000000..7e01345f12cf8d008dec43c7e622202b67f6f77f GIT binary patch literal 39906 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%0D%-N!}rheY&!mS>TTCA=HrWYOjKL_mcn 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 literal 0 HcmV?d00001 diff --git a/libs/l2serial-3.0.0-pre1-sources.jar b/libs/l2serial-3.0.0-pre1-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..41b9817af45adb62d05c562120623628496863a5 GIT binary patch literal 45584 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

dRHWn<`Q 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 literal 0 HcmV?d00001 diff --git a/libs/l2serial-3.0.0-pre1.jar b/libs/l2serial-3.0.0-pre1.jar new file mode 100644 index 0000000000000000000000000000000000000000..e9f4b2f960b86adedd54eb1fc76e3f860aca5116 GIT binary patch literal 107976 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 literal 0 HcmV?d00001 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..cb5265b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,11 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + maven { url = 'https://maven.neoforged.net/releases' } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' +} \ No newline at end of file diff --git a/src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 b/src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 new file mode 100644 index 0000000..cc2ac32 --- /dev/null +++ b/src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 @@ -0,0 +1 @@ +// 1.20.1 2023-07-17T13:58:00.905748 Registrate Provider for l2library [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)] diff --git a/src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java b/src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java new file mode 100644 index 0000000..950d6f2 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java @@ -0,0 +1,17 @@ +package dev.xkmc.l2core.base.effects; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.LivingEntity; + +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +@SerialClass +public class ClientEffectCap extends GeneralCapabilityTemplate { + + public final Map map = new TreeMap<>(Comparator.comparing(MobEffect::getDescriptionId)); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java new file mode 100644 index 0000000..ae982f0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java @@ -0,0 +1,43 @@ +package dev.xkmc.l2core.base.effects; + +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; + +public class EffectBuilder { + + public final MobEffectInstance ins; + + public EffectBuilder(MobEffectInstance ins) { + this.ins = ins; + } + + public EffectBuilder(MobEffect effect) { + this.ins = new MobEffectInstance(effect, 1, 0); + } + + public EffectBuilder setAmplifier(int amplifier) { + ins.amplifier = amplifier; + return this; + } + + public EffectBuilder setDuration(int duration) { + ins.duration = duration; + return this; + } + + public EffectBuilder setVisible(boolean visible) { + ins.visible = visible; + return this; + } + + public EffectBuilder setAmbient(boolean ambient) { + ins.ambient = ambient; + return this; + } + + public EffectBuilder setShowIcon(boolean showIcon) { + ins.showIcon = showIcon; + return this; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java new file mode 100644 index 0000000..cd6ddb0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java @@ -0,0 +1,18 @@ +package dev.xkmc.l2core.base.effects; + +import net.minecraft.world.effect.MobEffectInstance; + +public class EffectProperties { + + public Boolean ambient = null; + public Boolean visible = null; + public Boolean showIcon = null; + + public MobEffectInstance set(MobEffectInstance ins) { + if (ambient != null) ins.ambient = ambient; + if (visible != null) ins.visible = visible; + if (showIcon != null) ins.showIcon = showIcon; + return ins; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java new file mode 100644 index 0000000..4a322ae --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java @@ -0,0 +1,18 @@ +package dev.xkmc.l2core.base.effects; + +import dev.xkmc.l2core.init.events.ClientEffectRenderEvents; +import dev.xkmc.l2serial.network.SerialPacketBase; +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +public record EffectToClient(int entity, MobEffect effect, boolean exist, int level) + implements SerialPacketBase { + + @Override + public void handle(@Nullable Player player) { + ClientEffectRenderEvents.sync(this); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java new file mode 100644 index 0000000..ecb2514 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java @@ -0,0 +1,84 @@ +package dev.xkmc.l2core.base.effects; + +import dev.xkmc.l2core.base.effects.api.ForceEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.EventHooks; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.function.Predicate; + +public class EffectUtil { + + public enum AddReason { + NONE, PROF, FORCE, SKILL, SELF + } + + private static final ThreadLocal REASON = new ThreadLocal<>(); + + /** + * force add effect, make hard not override + * for icon use only, such as Arcane Mark on Wither and Ender Dragon + */ + private static void forceAddEffect(LivingEntity e, MobEffectInstance ins, @Nullable Entity source) { + MobEffectInstance effectinstance = e.getActiveEffectsMap().get(ins.getEffect()); + var event = new ForceAddEffectEvent(e, ins); + NeoForge.EVENT_BUS.post(event); + if (event.getResult() == Event.Result.DENY) { + return; + } + NeoForge.EVENT_BUS.post(new MobEffectEvent.Added(e, effectinstance, ins, source)); + if (effectinstance == null) { + e.getActiveEffectsMap().put(ins.getEffect(), ins); + e.onEffectAdded(ins, source); + } else if (effectinstance.update(ins)) { + e.onEffectUpdated(effectinstance, true, source); + } + } + + public static void addEffect(LivingEntity entity, MobEffectInstance ins, AddReason reason, @Nullable Entity source) { + if (entity == source) + reason = AddReason.SELF; + if (ins.getEffect() instanceof ForceEffect) + reason = AddReason.FORCE; + ins = new MobEffectInstance(ins.getEffect(), ins.getDuration(), ins.getAmplifier(), + ins.isAmbient(), reason != AddReason.FORCE && ins.isVisible(), ins.showIcon()); + REASON.set(reason); + if (ins.getEffect() instanceof ForceEffect) + forceAddEffect(entity, ins, source); + else if (ins.getEffect().isInstantenous()) + ins.getEffect().applyInstantenousEffect(null, null, entity, ins.getAmplifier(), 1); + else entity.addEffect(ins, source); + REASON.set(AddReason.NONE); + } + + public static void refreshEffect(LivingEntity entity, MobEffectInstance ins, AddReason reason, Entity source) { + if (ins.duration < 40) ins.duration = 40; + MobEffectInstance cur = entity.getEffect(ins.getEffect()); + if (cur == null || cur.getAmplifier() < ins.getAmplifier() || cur.getAmplifier() == ins.getAmplifier() && cur.getDuration() < ins.getDuration() / 2) + addEffect(entity, ins, reason, source); + } + + public static void removeEffect(LivingEntity entity, Predicate pred) { + Iterator itr = entity.activeEffects.values().iterator(); + while (itr.hasNext()) { + MobEffectInstance effect = itr.next(); + if (pred.test(effect) && EventHooks.onEffectRemoved(entity, effect, null)) { + entity.onEffectRemoved(effect); + itr.remove(); + entity.effectsDirty = true; + } + } + } + + public static AddReason getReason() { + AddReason ans = REASON.get(); + return ans == null ? AddReason.NONE : ans; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java b/src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java new file mode 100644 index 0000000..fd6dd74 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java @@ -0,0 +1,22 @@ +package dev.xkmc.l2core.base.effects; + +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; +import org.jetbrains.annotations.NotNull; + +@Event.HasResult +public class ForceAddEffectEvent extends MobEffectEvent { + + public ForceAddEffectEvent(LivingEntity living, @NotNull MobEffectInstance effectInstance) { + super(living, effectInstance); + } + + @Override + @NotNull + public MobEffectInstance getEffectInstance() { + return super.getEffectInstance(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java new file mode 100644 index 0000000..2e33366 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java @@ -0,0 +1,11 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.world.entity.LivingEntity; + +import java.util.function.Consumer; + +public interface ClientRenderEffect { + + void render(LivingEntity entity, int lv, Consumer adder); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java b/src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java new file mode 100644 index 0000000..baa6fcf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; + +public record DelayedEntityRender(LivingEntity entity, IconRenderRegion region, ResourceLocation rl, + float tx, float ty, float tw, float th) { + + public static DelayedEntityRender icon(LivingEntity entity, ResourceLocation rl) { + return icon(entity, IconRenderRegion.identity(), rl); + } + + public static DelayedEntityRender icon(LivingEntity entity, IconRenderRegion r, ResourceLocation rl) { + return new DelayedEntityRender(entity, r, rl, 0, 0, 1, 1); + } + + public DelayedEntityRender resize(IconRenderRegion r) { + return new DelayedEntityRender(entity, r.resize(region), rl, tx, ty, tw, th); + } +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java new file mode 100644 index 0000000..1564daf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java @@ -0,0 +1,10 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.world.effect.MobEffectInstance; + +public interface FirstPlayerRenderEffect { + + void onClientLevelRender(AbstractClientPlayer player, MobEffectInstance value); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java new file mode 100644 index 0000000..07470f4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java @@ -0,0 +1,4 @@ +package dev.xkmc.l2core.base.effects.api; + +public interface ForceEffect { +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java new file mode 100644 index 0000000..7e86a5c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.world.entity.LivingEntity; + +import java.util.function.Consumer; + +public interface IconOverlayEffect extends ClientRenderEffect { + + @Override + default void render(LivingEntity entity, int lv, Consumer adder) { + adder.accept(getIcon(entity, lv)); + } + + DelayedEntityRender getIcon(LivingEntity entity, int lv); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java b/src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java new file mode 100644 index 0000000..9044a4f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java @@ -0,0 +1,19 @@ +package dev.xkmc.l2core.base.effects.api; + +public record IconRenderRegion(float x, float y, float scale) { + + public static IconRenderRegion identity() { + return new IconRenderRegion(0, 0, 1); + } + + public static IconRenderRegion of(int r, int ix, int iy, int w, int h) { + float y = ((r - h) / 2f + iy) / r; + float x = ((r - w) / 2f + ix) / r; + return new IconRenderRegion(x, y, 1f / r); + } + + public IconRenderRegion resize(IconRenderRegion inner) { + return new IconRenderRegion(x + inner.x() * scale, y + inner.y() * scale, scale * inner.scale); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java new file mode 100644 index 0000000..5d42d62 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.effect.MobEffectInstance; +import net.neoforged.neoforge.common.EffectCure; + +import java.util.Set; + +public class InherentEffect extends MobEffect { + + protected InherentEffect(MobEffectCategory category, int color) { + super(category, color); + } + + @Override + public void fillEffectCures(Set cures, MobEffectInstance effectInstance) { + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/package-info.java b/src/main/java/dev/xkmc/l2core/base/effects/package-info.java new file mode 100644 index 0000000..e855819 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.effects; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java b/src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java new file mode 100644 index 0000000..7b9fdde --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package dev.xkmc.l2core.base.entity; + +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.PacketCodec; +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; + +import javax.annotation.ParametersAreNonnullByDefault; + +@SerialClass +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class BaseEntity extends Entity implements IEntityWithComplexSpawn { + + public BaseEntity(EntityType type, Level world) { + super(type, world); + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + tag.put("auto-serial", TagCodec.toTag(new CompoundTag(), this)); + } + + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + if (!tag.contains("auto-serial")) + return; + Wrappers.run(() -> TagCodec.fromTag(tag.getCompound("auto-serial"), this.getClass(), this, f -> true)); + } + + @Override + public void writeSpawnData(FriendlyByteBuf buffer) { + PacketCodec.to(buffer, this); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void readSpawnData(FriendlyByteBuf data) { + PacketCodec.from(data, (Class) this.getClass(), this); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/entity/package-info.java b/src/main/java/dev/xkmc/l2core/base/entity/package-info.java new file mode 100644 index 0000000..b2cca45 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/entity/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.entity; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java new file mode 100644 index 0000000..b163400 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java @@ -0,0 +1,29 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Explosion; + +public class BaseExplosion extends Explosion { + + public final BaseExplosionContext base; + public final ModExplosionContext mod; + public final VanillaExplosionContext mc; + public final ParticleExplosionContext particle; + + public BaseExplosion(BaseExplosionContext base, VanillaExplosionContext mc, ModExplosionContext mod, ParticleExplosionContext particle) { + super(base.level(), mc.entity(), mc.source(), mc.calculator(), base.x(), base.y(), base.z(), base.r(), mc.fire(), mc.type(), + particle.small(), particle.large(), particle.sound()); + this.base = base; + this.mod = mod; + this.mc = mc; + this.particle = particle; + } + + /** + * return false to cancel hurt + */ + public boolean hurtEntity(Entity entity) { + return mod.hurtEntity(entity); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java new file mode 100644 index 0000000..e4c636d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java @@ -0,0 +1,6 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.level.Level; + +public record BaseExplosionContext(Level level, double x, double y, double z, float r) { +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java b/src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java new file mode 100644 index 0000000..d7231f3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java @@ -0,0 +1,37 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.network.protocol.game.ClientboundExplodePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; + +public class ExplosionHandler { + + public static void explode(BaseExplosion exp) { + if (exp.base.level().isClientSide()) return; + if (EventHooks.onExplosionStart(exp.base.level(), exp)) return; + exp.explode(); + Level level = exp.base.level(); + exp.finalizeExplosion(level.isClientSide()); + double x = exp.base.x(); + double y = exp.base.y(); + double z = exp.base.z(); + float r = exp.base.r(); + boolean flag = exp.mc.type() == Explosion.BlockInteraction.KEEP; + if (flag) { + exp.clearToBlow(); + } + for (Player player : level.players()) { + if (player instanceof ServerPlayer serverplayer) { + if (serverplayer.distanceToSqr(x, y, z) < 4096.0D) { + serverplayer.connection.send(new ClientboundExplodePacket(x, y, z, r, + exp.getToBlow(), exp.getHitPlayers().get(serverplayer), exp.mc.type(), + exp.particle.small(), exp.particle.large(), exp.particle.sound())); + } + } + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java new file mode 100644 index 0000000..3e9d641 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java @@ -0,0 +1,12 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.entity.Entity; + +public interface ModExplosionContext { + + /** + * return false to cancel damage + */ + boolean hurtEntity(Entity entity); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java new file mode 100644 index 0000000..960dc1c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java @@ -0,0 +1,9 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.sounds.SoundEvent; + +public record ParticleExplosionContext(ParticleOptions small, + ParticleOptions large, + SoundEvent sound) { +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java new file mode 100644 index 0000000..c864afd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java @@ -0,0 +1,36 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.ExplosionDamageCalculator; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; + +import javax.annotation.Nullable; + +public record VanillaExplosionContext(@Nullable Entity entity, @Nullable DamageSource source, + @Nullable ExplosionDamageCalculator calculator, + boolean fire, Explosion.BlockInteraction type) { + + public VanillaExplosionContext(Level level, @Nullable Entity entity, @Nullable DamageSource source, + @Nullable ExplosionDamageCalculator calculator, + boolean fire, Level.ExplosionInteraction type) { + this(entity, source, calculator, fire, getType(level, entity, type)); + } + + private static Explosion.BlockInteraction getType(Level level, @Nullable Entity entity, Level.ExplosionInteraction type) { + return switch (type) { + case NONE -> Explosion.BlockInteraction.KEEP; + case BLOCK -> level.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY); + case MOB -> EventHooks.getMobGriefingEvent(level, entity instanceof LivingEntity le ? le : null) ? + level.getDestroyType(GameRules.RULE_MOB_EXPLOSION_DROP_DECAY) : + Explosion.BlockInteraction.KEEP; + case TNT -> level.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY); + case BLOW -> Explosion.BlockInteraction.TRIGGER_BLOCK; + }; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/package-info.java b/src/main/java/dev/xkmc/l2core/base/explosion/package-info.java new file mode 100644 index 0000000..517acd4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java new file mode 100644 index 0000000..201078d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java @@ -0,0 +1,298 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.*; + +/** + * Base Class for ContainerMenu. + * Containers multiple helper functions. + */ +public class BaseContainerMenu> extends AbstractContainerMenu { + + private record SlotKey(String name, int i, int j) { + + private static final Comparator COMPARATOR; + + static { + Comparator comp = Comparator.comparing(SlotKey::name); + comp = comp.thenComparingInt(SlotKey::i); + comp = comp.thenComparingInt(SlotKey::j); + COMPARATOR = comp; + } + + } + + /** + * simple container that prevents looping change + */ + public static class BaseContainer> extends SimpleContainer { + + protected final T parent; + private boolean updating = false; + private int max = 64; + + public BaseContainer(int size, T menu) { + super(size); + parent = menu; + } + + + public BaseContainer setMax(int max) { + this.max = max; + return this; + } + + @Override + public int getMaxStackSize() { + return Math.min(max, super.getMaxStackSize()); + } + + @Override + public void setChanged() { + super.setChanged(); + if (!updating) { + updating = true; + parent.slotsChanged(this); + updating = false; + } + } + + } + + /** + * return items in the slot to player + */ + public static void clearSlot(Player pPlayer, Container pContainer, int index) { + if (!pPlayer.isAlive() || pPlayer instanceof ServerPlayer && ((ServerPlayer) pPlayer).hasDisconnected()) { + pPlayer.drop(pContainer.removeItemNoUpdate(index), false); + } else { + Inventory inventory = pPlayer.getInventory(); + if (inventory.player instanceof ServerPlayer) { + inventory.placeItemBackInInventory(pContainer.removeItemNoUpdate(index)); + } + } + } + + public final Inventory inventory; + public final Container container; + public final SpriteManager sprite; + protected int added = 0; + protected final boolean isVirtual; + + private boolean updating = false; + + private final Map slotMap = new TreeMap<>(SlotKey.COMPARATOR); + + /** + * This contructor will bind player inventory first, so player inventory has lower slot index. + * + * @param type registered menu type + * @param wid window id + * @param plInv player inventory + * @param manager sprite manager used for slot positioning and rendering + * @param factory container supplier + * @param isVirtual true if the slots should be cleared and item returned to player on menu close. + */ + protected BaseContainerMenu(MenuType type, int wid, Inventory plInv, SpriteManager manager, Function factory, boolean isVirtual) { + super(type, wid); + this.inventory = plInv; + container = factory.apply(Wrappers.cast(this)); + sprite = manager; + int x = manager.get().getPlInvX(); + int y = manager.get().getPlInvY(); + this.bindPlayerInventory(plInv, x, y); + this.isVirtual = isVirtual; + } + + /** + * Binds player inventory. Should not be called by others, but permits override. + */ + protected void bindPlayerInventory(Inventory plInv, int x, int y) { + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 9; ++j) + addSlot(createSlot(plInv, j + i * 9 + 9, x + j * 18, y + i * 18)); + for (int k = 0; k < 9; ++k) + addSlot(createSlot(plInv, k, x + k * 18, y + 58)); + } + + /** + * Used by bindPlayerInventory only. Create slots as needed. Some slots could be locked. + */ + protected Slot createSlot(Inventory inv, int slot, int x, int y) { + return shouldLock(inv, slot) ? new SlotLocked(inv, slot, x, y) : new Slot(inv, slot, x, y); + } + + /** + * Lock slots you don't want players modifying, such as the slot player is opening backpack in. + */ + protected boolean shouldLock(Inventory inv, int slot) { + return false; + } + + /** + * Add new slots, with item input predicate + */ + protected void addSlot(String name, Predicate pred) { + sprite.get().getSlot(name, (x, y) -> new PredSlot(container, added++, x, y, pred), this::addSlot); + } + + /** + * Add new slots, with index-sensitive item input predicate. + * The index here is relative to the first slot added by this method. + */ + protected void addSlot(String name, BiPredicate pred) { + int current = added; + sprite.get().getSlot(name, (x, y) -> { + int i = added - current; + var ans = new PredSlot(container, added, x, y, e -> pred.test(i, e)); + added++; + return ans; + }, this::addSlot); + } + + /** + * Add new slots, with other modifications to the slot. + */ + protected void addSlot(String name, Predicate pred, Consumer modifier) { + sprite.get().getSlot(name, (x, y) -> { + PredSlot s = new PredSlot(container, added++, x, y, pred); + modifier.accept(s); + return s; + }, this::addSlot); + } + + /** + * Add new slots, with index-sensitive modifications to the slot. + * The index here is relative to the first slot added by this method. + */ + protected void addSlot(String name, BiPredicate pred, BiConsumer modifier) { + int current = added; + sprite.get().getSlot(name, (x, y) -> { + int i = added - current; + var ans = new PredSlot(container, added, x, y, e -> pred.test(i, e)); + modifier.accept(i, ans); + added++; + return ans; + }, this::addSlot); + } + + /** + * internal add slot + */ + protected void addSlot(String name, int i, int j, Slot slot) { + slotMap.put(new SlotKey(name, i, j), slot); + this.addSlot(slot); + } + + /** + * get the slot by name and id in the grid + */ + protected Slot getSlot(String name, int i, int j) { + return slotMap.get(new SlotKey(name, i, j)); + } + + /** + * get a slot as PredSlot, as most slots should be + */ + public PredSlot getAsPredSlot(String name, int i, int j) { + return (PredSlot) getSlot(name, i, j); + } + + /** + * get a slot as PredSlot, as most slots should be + */ + public PredSlot getAsPredSlot(String name) { + return (PredSlot) getSlot(name, 0, 0); + } + + @Override + public ItemStack quickMoveStack(Player pl, int id) { + ItemStack stack = slots.get(id).getItem(); + int n = container.getContainerSize(); + boolean moved; + if (id >= 36) { + moved = moveItemStackTo(stack, 0, 36, true); + } else { + moved = moveItemStackTo(stack, 36, 36 + n, false); + } + if (moved) slots.get(id).setChanged(); + return ItemStack.EMPTY; + } + + @Override + public boolean stillValid(Player player) { + return player.isAlive(); + } + + @Override + public void removed(Player player) { + if (isVirtual && !player.level().isClientSide()) + clearContainerFiltered(player, container); + super.removed(player); + } + + /** + * return true (and when isVirtual is true), clear the corresponding slot on menu close. + */ + protected boolean shouldClear(Container container, int slot) { + return isVirtual; + } + + /** + * clear slots using shouldClear + */ + protected void clearContainerFiltered(Player player, Container container) { + if (!player.isAlive() || player instanceof ServerPlayer && ((ServerPlayer) player).hasDisconnected()) { + for (int j = 0; j < container.getContainerSize(); ++j) { + if (shouldClear(container, j)) { + player.drop(container.removeItemNoUpdate(j), false); + } + } + } else { + Inventory inventory = player.getInventory(); + for (int i = 0; i < container.getContainerSize(); ++i) { + if (shouldClear(container, i)) { + if (inventory.player instanceof ServerPlayer) { + inventory.placeItemBackInInventory(container.removeItemNoUpdate(i)); + } + } + } + + } + } + + @Override + public void slotsChanged(Container cont) { + if (inventory.player.level().isClientSide()) { + super.slotsChanged(cont); + } else { + if (!updating) { + updating = true; + securedServerSlotChange(cont); + updating = false; + } + super.slotsChanged(cont); + } + } + + /** + * server side only slot change detector that will not be called recursively. + */ + protected void securedServerSlotChange(Container cont) { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java new file mode 100644 index 0000000..3bb452e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2core.util.Proxy; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; + +public abstract class BaseContainerScreen> extends AbstractContainerScreen { + + public BaseContainerScreen(T cont, Inventory plInv, Component title) { + super(cont, plInv, title); + this.imageHeight = menu.sprite.get().getHeight(); + this.inventoryLabelY = menu.sprite.get().getPlInvY() - 11; + } + + @Override + public void render(GuiGraphics g, int mx, int my, float partial) { + super.render(g, mx, my, partial); + renderTooltip(g, mx, my); + } + + protected boolean click(int btn) { + if (menu.clickMenuButton(Proxy.getClientPlayer(), btn) && Minecraft.getInstance().gameMode != null) { + Minecraft.getInstance().gameMode.handleInventoryButtonClick(this.menu.containerId, btn); + return true; + } + return false; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java b/src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java new file mode 100644 index 0000000..7caf7d4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java @@ -0,0 +1,237 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.Slot; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.HashMap; + +@SuppressWarnings("unused") +@SerialClass +public record MenuLayoutConfig(int height, HashMap side, HashMap comp) { + + public static ResourceLocation getTexture(ResourceLocation id) { + return new ResourceLocation(id.getNamespace(), "textures/gui/container/" + id.getPath() + ".png"); + } + + /** + * get the location of the component on the GUI + */ + public Rect getComp(String key) { + return comp.getOrDefault(key, Rect.ZERO); + } + + /** + * Height of this GUI + */ + public int getHeight() { + return height; + } + + /** + * The X position of the player inventory + */ + public int getPlInvX() { + return 8; + } + + /** + * The Y position of the player inventory + */ + public int getPlInvY() { + return height - 82; + } + + @OnlyIn(Dist.CLIENT) + public ScreenRenderer getRenderer(ResourceLocation id, AbstractContainerScreen gui) { + return new ScreenRenderer(id, gui); + } + + @OnlyIn(Dist.CLIENT) + public ScreenRenderer getRenderer(ResourceLocation id, Screen gui, int x, int y, int w, int h) { + return new ScreenRenderer(id, gui, x, y, w, h); + } + + /** + * get the rectangle representing the sprite element on the sprite + */ + public Rect getSide(String key) { + return side.getOrDefault(key, Rect.ZERO); + } + + /** + * configure the coordinate of the slot + */ + public void getSlot(String key, SlotFactory fac, SlotAcceptor con) { + Rect c = getComp(key); + for (int j = 0; j < c.ry; j++) + for (int i = 0; i < c.rx; i++) { + var slot = fac.getSlot(c.x + i * c.w, c.y + j * c.h); + if (slot != null) { + con.addSlot(key, i, j, slot); + } + } + } + + public int getWidth() { + return 176; + } + + /** + * return if the coordinate is within the rectangle represented by the key + */ + public boolean within(String key, double x, double y) { + Rect c = getComp(key); + return x > c.x && x < c.x + c.w && y > c.y && y < c.y + c.h; + } + + public interface SlotFactory { + + @Nullable + T getSlot(int x, int y); + + } + + public interface SlotAcceptor { + + void addSlot(String name, int i, int j, Slot slot); + + } + + @SerialClass + public static class Rect { + + public static final Rect ZERO = new Rect(); + + @SerialClass.SerialField + public int x, y, w, h, rx = 1, ry = 1; + + public Rect() { + } + + } + + @OnlyIn(Dist.CLIENT) + public class ScreenRenderer { + + private final int x, y, w, h; + private final Screen scr; + + public final ResourceLocation id; + + public MenuLayoutConfig parent(){ + return MenuLayoutConfig.this; + } + + public ScreenRenderer(ResourceLocation id, Screen gui, int x, int y, int w, int h) { + scr = gui; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.id = id; + } + + private ScreenRenderer(ResourceLocation id, AbstractContainerScreen scrIn) { + x = scrIn.getGuiLeft(); + y = scrIn.getGuiTop(); + w = scrIn.getXSize(); + h = scrIn.getYSize(); + scr = scrIn; + this.id = id; + } + + /** + * Draw a side sprite on the location specified by the component + */ + public void draw(GuiGraphics g, String c, String s) { + Rect cr = getComp(c); + Rect sr = getSide(s); + g.blit(getTexture(id), x + cr.x, y + cr.y, sr.x, sr.y, sr.w, sr.h); + } + + /** + * Draw a side sprite on the location specified by the component with offsets + */ + public void draw(GuiGraphics g, String c, String s, int xoff, int yoff) { + Rect cr = getComp(c); + Rect sr = getSide(s); + g.blit(getTexture(id), x + cr.x + xoff, y + cr.y + yoff, sr.x, sr.y, sr.w, sr.h); + } + + /** + * Draw a side sprite on the location specified by the component. Draw partially + * from bottom to top + */ + public void drawBottomUp(GuiGraphics g, String c, String s, int prog, int max) { + if (prog == 0 || max == 0) + return; + Rect cr = getComp(c); + Rect sr = getSide(s); + int dh = sr.h * prog / max; + g.blit(getTexture(id), x + cr.x, y + cr.y + sr.h - dh, sr.x, sr.y + sr.h - dh, sr.w, dh); + } + + /** + * Draw a side sprite on the location specified by the component. Draw partially + * from left to right + */ + public void drawLeftRight(GuiGraphics g, String c, String s, int prog, int max) { + if (prog == 0 || max == 0) + return; + Rect cr = getComp(c); + Rect sr = getSide(s); + int dw = sr.w * prog / max; + g.blit(getTexture(id), x + cr.x, y + cr.y, sr.x, sr.y, dw, sr.h); + } + + /** + * fill an area with a sprite, repeat as tiles if not enough, start from lower + * left corner + */ + public void drawLiquid(GuiGraphics g, String c, double per, int height, int sw, int sh) { + Rect cr = getComp(c); + int base = cr.y + height; + int h = (int) Math.round(per * height); + circularBlit(g, x + cr.x, base - h, 0, -h, cr.w, h, sw, sh); + } + + /** + * bind texture, draw background color, and GUI background + */ + public void start(GuiGraphics g) { + scr.renderTransparentBackground(g); + g.blit(getTexture(id), x, y, 0, 0, w, h); + } + + private void circularBlit(GuiGraphics g, int sx, int sy, int ix, int iy, int w, int h, int iw, int ih) { + int x0 = ix, yb = iy, x1 = w, x2 = sx; + while (x0 < 0) + x0 += iw; + while (yb < ih) + yb += ih; + while (x1 > 0) { + int dx = Math.min(x1, iw - x0); + int y0 = yb, y1 = h, y2 = sy; + while (y1 > 0) { + int dy = Math.min(y1, ih - y0); + g.blit(getTexture(id), x2, y2, x0, y0, x1, y1); + y1 -= dy; + y0 += dy; + y2 += dy; + } + x1 -= dx; + x0 += dx; + x2 += dx; + } + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java new file mode 100644 index 0000000..8c689a7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java @@ -0,0 +1,150 @@ +package dev.xkmc.l2core.base.menu.base; + +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nullable; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; + +/** + * Slot added by BaseContainerMenu. Contains multiple helpers + */ +public class PredSlot extends Slot { + + private final Predicate pred; + private final int slotCache; + + @Nullable + private BooleanSupplier pickup; + + @Nullable + private BooleanSupplier inputLockPred; + + private int max = 64; + + private boolean changed = false; + private boolean lockInput = false, lockOutput = false; + + /** + * Should be called by BaseContainerMenu::addSlot only. + * Predicate supplied from subclasses pf BaseContainerMenu. + */ + public PredSlot(Container inv, int ind, int x, int y, Predicate pred) { + super(inv, ind, x, y); + this.pred = pred; + slotCache = ind; + } + + /** + * Set the condition to unlock a slot for input. + * Parallel with manual lock and item predicate. + */ + public PredSlot setInputLockPred(BooleanSupplier pred) { + this.inputLockPred = pred; + return this; + } + + /** + * Set restriction for pickup. + */ + public PredSlot setPickup(BooleanSupplier pickup) { + this.pickup = pickup; + return this; + } + + public PredSlot setMax(int max) { + this.max = max; + return this; + } + + @Override + public int getMaxStackSize() { + return Math.min(max, super.getMaxStackSize()); + } + + @Override + public boolean mayPlace(ItemStack stack) { + if (isInputLocked()) return false; + return pred.test(stack); + } + + @Override + public boolean mayPickup(Player player) { + if (isOutputLocked()) return false; + return pickup == null || pickup.getAsBoolean(); + } + + @Override + public void setChanged() { + changed = true; + super.setChanged(); + } + + /** + * run only if the content of this slot is changed + */ + public boolean clearDirty(Runnable r) { + if (changed) { + r.run(); + changed = false; + return true; + } + return false; + } + + public boolean clearDirty() { + if (changed) { + changed = false; + return true; + } + return false; + } + + /** + * eject the content of this slot if the item is no longer allowed + */ + public void updateEject(Player player) { + if (!mayPlace(getItem())) clearSlot(player); + } + + /** + * Lock the input of this slot. + * Parallel with lock conditions and item predicate + */ + public void setLockInput(boolean lock) { + lockInput = lock; + } + + /** + * See if the input is locked manually or locked by lock conditions + */ + public boolean isInputLocked() { + return lockInput || (inputLockPred != null && inputLockPred.getAsBoolean()); + } + + /** + * Lock the output of this slot. + * Parallel with pickup restrictions. + */ + public void setLockOutput(boolean lock) { + lockOutput = lock; + } + + /** + * See if the output is locked manually. + */ + public boolean isOutputLocked() { + return lockOutput; + } + + /** + * eject the content of this slot. + */ + public void clearSlot(Player player) { + BaseContainerMenu.clearSlot(player, container, slotCache); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java b/src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java new file mode 100644 index 0000000..1ef9b5c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.base.menu.base; + +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +public class SlotLocked extends Slot { + + public SlotLocked(Inventory inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java b/src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java new file mode 100644 index 0000000..e608df2 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; + +public record SpriteManager(ResourceLocation id) { + + public SpriteManager(String modid, String path) { + this(new ResourceLocation(modid, path)); + } + + public MenuLayoutConfig get() { + return L2LibReg.MENU_LAYOUT.get(id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java new file mode 100644 index 0000000..fedd4dd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.menu.base; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java new file mode 100644 index 0000000..6302c97 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.DataSlot; + +public class BoolArrayDataSlot { + + private final DataSlot[] array; + + public BoolArrayDataSlot(AbstractContainerMenu menu, int size) { + int n = size / 16 + (size % 16 == 0 ? 0 : 1); + array = new DataSlot[n]; + for (int i = 0; i < n; i++) { + array[i] = menu.addDataSlot(DataSlot.standalone()); + } + } + + public boolean get(int i) { + return (array[i >> 4].get() & (1 << (i & 0xf))) != 0; + } + + public void set(boolean pc, int i) { + int val = array[i >> 4].get(); + int mask = 1 << (i & 0xf); + boolean old = (val & mask) != 0; + if (old != pc) { + val ^= mask; + array[i >> 4].set(val); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java new file mode 100644 index 0000000..910f23a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java @@ -0,0 +1,21 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; + +public class DoubleDataSlot { + + private final LongDataSlot data; + + public DoubleDataSlot(AbstractContainerMenu menu) { + data = new LongDataSlot(menu); + } + + public double get() { + return Double.longBitsToDouble(data.get()); + } + + public void set(double pc) { + data.set(Double.doubleToLongBits(pc)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java new file mode 100644 index 0000000..5afc733 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java @@ -0,0 +1,21 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; + +public class FloatDataSlot { + + private final IntDataSlot data; + + public FloatDataSlot(AbstractContainerMenu menu) { + data = new IntDataSlot(menu); + } + + public float get() { + return Float.intBitsToFloat(data.get()); + } + + public void set(float pc) { + data.set(Float.floatToIntBits(pc)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java new file mode 100644 index 0000000..46de0b4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.DataSlot; + +public class IntDataSlot { + + private final DataSlot hi, lo; + + public IntDataSlot(AbstractContainerMenu menu) { + hi = menu.addDataSlot(DataSlot.standalone()); + lo = menu.addDataSlot(DataSlot.standalone()); + } + + public int get() { + return hi.get() << 16 | Short.toUnsignedInt((short) lo.get()); + } + + public void set(int pc) { + lo.set((short) (pc & 0xFFFF)); + hi.set(pc >> 16); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java new file mode 100644 index 0000000..b9424e7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java @@ -0,0 +1,23 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; + +public class LongDataSlot { + + private final IntDataSlot lo, hi; + + public LongDataSlot(AbstractContainerMenu menu) { + lo = new IntDataSlot(menu); + hi = new IntDataSlot(menu); + } + + public long get() { + return ((long) hi.get()) << 32 | Integer.toUnsignedLong(lo.get()); + } + + public void set(long pc) { + lo.set((int) (pc)); + hi.set((int) (pc >> 32)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java new file mode 100644 index 0000000..096314e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java new file mode 100644 index 0000000..4073443 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java @@ -0,0 +1,77 @@ +package dev.xkmc.l2core.base.menu.scroller; + +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.util.Mth; + +public class Scroller { + + private final ScrollerScreen screen; + private final MenuLayoutConfig sprite; + private final String box, light, dark; + private final int bx, by, bw, bh, sh; + + private boolean scrolling; + private double percentage; + + public Scroller(ScrollerScreen screen, MenuLayoutConfig sprite, String slider_middle, String slider_light, String slider_dark) { + this.screen = screen; + this.sprite = sprite; + this.box = slider_middle; + this.light = slider_light; + this.dark = slider_dark; + var scroller = sprite.getComp(box); + bx = scroller.x; + by = scroller.y; + bw = scroller.w; + bh = scroller.ry; + var slider = sprite.getSide(light); + sh = slider.h; + } + + public boolean mouseClicked(double mx, double my, int btn) { + this.scrolling = false; + int cx = screen.getGuiLeft() + bx; + int cy = screen.getGuiTop() + by; + if (mx >= cx && mx < cx + bw && my >= cy && my < cy + bh) { + this.scrolling = true; + return true; + } + return false; + } + + public boolean mouseDragged(double mx, double my, int btn, double dx, double dy) { + if (this.scrolling && screen.getMenu().getMaxScroll() > 0) { + int y0 = screen.getGuiTop() + by; + int y1 = y0 + bh; + percentage = (my - y0 - sh * 0.5) / ((y1 - y0) - 15.0F); + percentage = Mth.clamp(percentage, 0.0F, 1.0F); + updateIndex(); + return true; + } + return false; + } + + public boolean mouseScrolled(double mx, double my, double d) { + if (screen.getMenu().getMaxScroll() > 0) { + int i = screen.getMenu().getMaxScroll(); + double f = d / i; + percentage = Mth.clamp(percentage - f, 0, 1); + updateIndex(); + } + return true; + } + + private void updateIndex() { + screen.scrollTo((int) ((percentage * screen.getMenu().getMaxScroll()) + 0.5D)); + } + + public void render(GuiGraphics g, MenuLayoutConfig.ScreenRenderer sr) { + if (screen.getMenu().getMaxScroll() == 0) { + sr.draw(g, box, dark); + } else { + int off = (int) Math.round((bh - sh) * percentage); + sr.draw(g, box, light, 0, off); + } + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java new file mode 100644 index 0000000..9817eed --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java @@ -0,0 +1,8 @@ +package dev.xkmc.l2core.base.menu.scroller; + +public interface ScrollerMenu { + + int getMaxScroll(); + + int getScroll(); +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java new file mode 100644 index 0000000..0eada94 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java @@ -0,0 +1,12 @@ +package dev.xkmc.l2core.base.menu.scroller; + +public interface ScrollerScreen { + + ScrollerMenu getMenu(); + + int getGuiLeft(); + + int getGuiTop(); + + void scrollTo(int i); +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java new file mode 100644 index 0000000..23e5345 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java @@ -0,0 +1,8 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault + +package dev.xkmc.l2core.base.menu.scroller; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java new file mode 100644 index 0000000..46f44e4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java @@ -0,0 +1,5 @@ +package dev.xkmc.l2core.base.menu.stacked; + +public record CellEntry(int x, int y, int w, int h) { + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java new file mode 100644 index 0000000..a77d66d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java @@ -0,0 +1,133 @@ +package dev.xkmc.l2core.base.menu.stacked; + +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import java.util.ArrayList; +import java.util.List; + +public class StackedRenderHandle { + + static final int BTN_X_OFFSET = 3; + static final int TEXT_BASE_HEIGHT = 8; + + private static final int SLOT_X_OFFSET = 7, SLOT_SIZE = 18, SPRITE_OFFSET = 176; + + final Screen scr; + final GuiGraphics g; + final MenuLayoutConfig.ScreenRenderer sm; + final Font font; + final int text_color; + private final int TEXT_Y_OFFSET; + private final int TEXT_HEIGHT; + private final int text_x_offset; + + private int current_y = 3; + private int current_x = 0; + + final List textList = new ArrayList<>(); + + public StackedRenderHandle(Screen scr, GuiGraphics g, MenuLayoutConfig.ScreenRenderer sm) { + this(scr, g, sm, 3); + } + + public StackedRenderHandle(Screen scr, GuiGraphics g, MenuLayoutConfig.ScreenRenderer sm, int ty) { + this(scr, g, 8, 4210752, sm, ty); + } + + public StackedRenderHandle(Screen scr, GuiGraphics g, int x_offset, int color, MenuLayoutConfig.ScreenRenderer sm) { + this(scr, g, x_offset, color, sm, 3); + } + + public StackedRenderHandle(Screen scr, GuiGraphics g, int x_offset, int color, MenuLayoutConfig.ScreenRenderer sm, int ty) { + this.font = Minecraft.getInstance().font; + this.g = g; + this.scr = scr; + this.sm = sm; + this.text_color = color; + this.text_x_offset = x_offset; + this.TEXT_Y_OFFSET = ty; + this.TEXT_HEIGHT = font.lineHeight + ty + 1; + } + + public void drawText(Component text, boolean shadow) { + endCell(); + int y = current_y + TEXT_Y_OFFSET; + textList.add(new TextEntry(text, text_x_offset, y, text_color, shadow)); + current_y += TEXT_HEIGHT; + } + + public void drawTable(Component[][] table, int x_max, boolean shadow) { + endCell(); + int w = table[0].length; + int w1 = 0; + int ws = 0; + for (Component[] c : table) { + w1 = Math.max(w1, font.width(c[0])); + for (int i = 1; i < w; i++) { + ws = Math.max(ws, font.width(c[i])); + } + } + int sumw = w1 + ws * (w - 1); + int x0 = text_x_offset; + int x1 = x_max - text_x_offset; + float space = (x1 - x0 - sumw) * 1.0f / (w - 1); + for (Component[] c : table) { + int y = current_y + TEXT_Y_OFFSET; + float x_start = x0; + for (int i = 0; i < w; i++) { + float wi = i == 0 ? w1 : ws; + int x = Math.round(x_start); + textList.add(new TextEntry(c[i], x, y, text_color, shadow)); + x_start += wi + space; + } + current_y += TEXT_HEIGHT; + } + } + + public TextButtonHandle drawTextWithButtons(Component text, boolean shadow) { + endCell(); + int y = current_y + TEXT_Y_OFFSET; + textList.add(new TextEntry(text, text_x_offset, y, text_color, shadow)); + int x_off = text_x_offset + font.width(text) + BTN_X_OFFSET; + TextButtonHandle ans = new TextButtonHandle(this, x_off, y + font.lineHeight / 2); + current_y += TEXT_HEIGHT; + return ans; + } + + public CellEntry addCell(boolean toggled, boolean disabled) { + startCell(); + int index = toggled ? 1 : disabled ? 2 : 0; + int x = SLOT_X_OFFSET + current_x * SLOT_SIZE; + int u = SPRITE_OFFSET + index * SLOT_SIZE; + g.blit(MenuLayoutConfig.getTexture(sm.id), x, current_y, u, 0, SLOT_SIZE, SLOT_SIZE); + var ans = new CellEntry(x + 1, current_y + 1, 16, 16); + current_x++; + if (current_x == 9) { + endCell(); + } + return ans; + } + + private void startCell() { + if (current_x < 0) { + current_x = 0; + } + } + + private void endCell() { + if (current_x > 0) { + current_x = -1; + current_y += SLOT_SIZE; + } + } + + public void flushText() { + textList.forEach(e -> g.drawString(font, e.text(), e.x(), e.y(), e.color(), e.shadow())); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java new file mode 100644 index 0000000..fede7a6 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java @@ -0,0 +1,35 @@ +package dev.xkmc.l2core.base.menu.stacked; + +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import net.minecraft.network.chat.Component; + +public class TextButtonHandle { + + private final StackedRenderHandle parent; + private final int y; + + private int x; + + + protected TextButtonHandle(StackedRenderHandle parent, int x, int y) { + this.parent = parent; + this.x = x; + this.y = y; + } + + public CellEntry addButton(String btn) { + MenuLayoutConfig.Rect r = parent.sm.parent().getSide(btn); + int y0 = y - (r.h + 1) / 2; + parent.g.blit(MenuLayoutConfig.getTexture(parent.sm.id), x, y0, r.x, r.y, r.w, r.h); + CellEntry c1 = new CellEntry(x, y0, r.w, r.h); + x += r.w + StackedRenderHandle.BTN_X_OFFSET; + return c1; + } + + public void drawText(CellEntry cell, Component text, boolean shadow) { + int x0 = cell.x() + (cell.w() - parent.font.width(text) + 1) / 2; + int y0 = cell.y() + (cell.h() + 1) / 2 - parent.font.lineHeight / 2; + parent.textList.add(new TextEntry(text, x0, y0, parent.text_color, shadow)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java new file mode 100644 index 0000000..821d179 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java @@ -0,0 +1,6 @@ +package dev.xkmc.l2core.base.menu.stacked; + +import net.minecraft.network.chat.Component; + +public record TextEntry(Component text, int x, int y, int color, boolean shadow) { +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java new file mode 100644 index 0000000..f39e92a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java @@ -0,0 +1,8 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault + +package dev.xkmc.l2core.base.menu.stacked; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java new file mode 100644 index 0000000..0428260 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java @@ -0,0 +1,40 @@ +package dev.xkmc.l2core.base.overlay; + +import dev.xkmc.l2core.init.L2LibraryConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.neoforged.neoforge.client.gui.overlay.ExtendedGui; +import net.neoforged.neoforge.client.gui.overlay.IGuiOverlay; + +import java.util.List; + +public abstract class InfoSideBar> extends SideBar implements IGuiOverlay { + + public InfoSideBar(float duration, float ease) { + super(duration, ease); + } + + @Override + public void render(ExtendedGui gui, GuiGraphics g, float partialTick, int width, int height) { + if (!ease(gui.getGuiTicks() + partialTick)) + return; + var text = getText(); + if (text.isEmpty()) return; + int anchor = L2LibraryConfig.CLIENT.infoAnchor.get(); + int y = height * anchor / 2; + int w = (int) (width * L2LibraryConfig.CLIENT.infoMaxWidth.get()); + new TextBox(g, 0, anchor, getXOffset(width), y, w) + .renderLongText(Minecraft.getInstance().font, text); + } + + protected abstract List getText(); + + @Override + protected int getXOffset(int width) { + float progress = (max_ease - ease_time) / max_ease; + return Math.round(-progress * width / 2 + 8); + } + +} + diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java new file mode 100644 index 0000000..2b39dda --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java @@ -0,0 +1,46 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.item.ItemStack; + +import java.util.List; + +public abstract class ItemSelSideBar> extends SelectionSideBar { + + public ItemSelSideBar(float duration, float ease) { + super(duration, ease); + } + + @Override + protected void renderEntry(Context ctx, ItemStack stack, int i, int selected) { + boolean shift = Minecraft.getInstance().options.keyShift.isDown(); + int y = 18 * i + ctx.y0(); + renderSelection(ctx.g(), ctx.x0(), y, shift ? 127 : 64, isAvailable(stack), selected == i); + if (selected == i) { + if (!stack.isEmpty() && ease_time == max_ease) { + boolean onCenter = onCenter(); + ctx.g().renderTooltip(ctx.font(), stack.getHoverName(), 0, 0); + TextBox box = new TextBox(ctx.g(), onCenter ? 0 : 2, 1, ctx.x0() + (onCenter ? 22 : -6), y + 8, -1); + box.renderLongText(ctx.font(), List.of(stack.getHoverName())); + } + } + ctx.renderItem(stack, ctx.x0(), y); + } + + public void renderSelection(GuiGraphics g, int x, int y, int a, boolean available, boolean selected) { + if (available) { + OverlayUtil.fillRect(g, x, y, 16, 16, color(255, 255, 255, a)); + } else { + OverlayUtil.fillRect(g, x, y, 16, 16, color(255, 0, 0, a)); + } + if (selected) { + OverlayUtil.drawRect(g, x, y, 16, 16, color(255, 170, 0, 255)); + } + } + + public static int color(int r, int g, int b, int a) { + return a << 24 | r << 16 | g << 8 | b; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java b/src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java new file mode 100644 index 0000000..50a2c84 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java @@ -0,0 +1,43 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.gui.GuiGraphics; + +public record L2TooltipRenderUtil(GuiGraphics fill, int bg, int bs, int be) { + + public void renderTooltipBackground(int x, int y, int w, int h, int z) { + int x1 = x - 3; + int y1 = y - 3; + int w1 = w + 3 + 3; + int h1 = h + 3 + 3; + renderHorizontalLine(x1, y1 - 1, w1, z, bg); + renderHorizontalLine(x1, y1 + h1, w1, z, bg); + renderRectangle(x1, y1, w1, h1, z, bg); + renderVerticalLine(x1 - 1, y1, h1, z, bg); + renderVerticalLine(x1 + w1, y1, h1, z, bg); + renderFrameGradient(x1, y1 + 1, w1, h1, z, bs, be); + } + + private void renderFrameGradient(int x, int y, int w, int h, int z, int c0, int c1) { + renderVerticalLineGradient(x, y, h - 2, z, c0, c1); + renderVerticalLineGradient(x + w - 1, y, h - 2, z, c0, c1); + renderHorizontalLine(x, y - 1, w, z, c0); + renderHorizontalLine(x, y - 1 + h - 1, w, z, c1); + } + + private void renderVerticalLine(int x, int y, int h, int z, int c) { + fill.fillGradient(x, y, x + 1, y + h, z, c, c); + } + + private void renderVerticalLineGradient(int x, int y, int h, int z, int c0, int c1) { + fill.fillGradient(x, y, x + 1, y + h, z, c0, c1); + } + + private void renderHorizontalLine(int x, int y, int w, int z, int c) { + fill.fillGradient(x, y, x + w, y + 1, z, c, c); + } + + private void renderRectangle(int x, int y, int w, int h, int z, int c) { + fill.fillGradient(x, y, x + w, y + h, z, c, c); + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java b/src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java new file mode 100644 index 0000000..e781641 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java @@ -0,0 +1,105 @@ +package dev.xkmc.l2core.base.overlay; + +import dev.xkmc.l2core.init.L2LibraryConfig; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.network.chat.Component; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.List; + +public class OverlayUtil implements ClientTooltipPositioner { + + private static int getBGColor() { + return (int) (Math.round(L2LibraryConfig.CLIENT.infoAlpha.get() * 255)) << 24 | 0x100010; + } + + public int bg = getBGColor(); + public int bs = 0x505000FF; + public int be = 0x5028007f; + public int tc = 0xFFFFFFFF; + + protected final GuiGraphics g; + protected final int x0, y0, maxW; + + public OverlayUtil(GuiGraphics g, int x0, int y0, int maxW) { + this.g = g; + this.x0 = x0; + this.y0 = y0; + this.maxW = maxW < 0 ? getMaxWidth() : maxW; + } + + public int getMaxWidth() { + return g.guiWidth() / 4; + } + + public void renderLongText(Font font, List list) { + List ans = list.stream().flatMap(text -> font.split(text, maxW).stream()) + .map(ClientTooltipComponent::create).toList(); + renderTooltipInternal(font, ans); + } + + public void renderTooltipInternal(Font font, List list) { + if (list.isEmpty()) return; + int w = 0; + int h = list.size() == 1 ? -2 : 0; + for (ClientTooltipComponent c : list) { + int wi = c.getWidth(font); + if (wi > w) { + w = wi; + } + h += c.getHeight(); + } + int wf = w; + int hf = h; + Vector2ic pos = positionTooltip(g.guiWidth(), g.guiHeight(), x0, y0, wf, hf); + int xf = pos.x(); + int yf = pos.y(); + g.pose().pushPose(); + int z = 400; + g.drawManaged(() -> TooltipRenderUtil.renderTooltipBackground(g, xf, yf, wf, hf, z, bg, bg, bs, be)); + g.pose().translate(0.0F, 0.0F, z); + int yi = yf; + for (int i = 0; i < list.size(); ++i) { + ClientTooltipComponent c = list.get(i); + c.renderText(font, xf, yi, g.pose().last().pose(), g.bufferSource()); + yi += c.getHeight() + (i == 0 ? 2 : 0); + } + yi = yf; + for (int i = 0; i < list.size(); ++i) { + ClientTooltipComponent c = list.get(i); + c.renderImage(font, xf, yi, g); + yi += c.getHeight() + (i == 0 ? 2 : 0); + } + g.pose().popPose(); + } + + @Override + public Vector2ic positionTooltip(int gw, int gh, int x, int y, int tw, int th) { + if (x < 0) x = Math.round(gw / 8f); + if (y < 0) y = Math.round((gh - th) / 2f); + return new Vector2i(x, y); + } + + /** + * specifies outer size + */ + public static void fillRect(GuiGraphics g, int x, int y, int w, int h, int col) { + g.fill(x, y, x + w, y + h, col); + } + + /** + * specifies inner size + */ + public static void drawRect(GuiGraphics g, int x, int y, int w, int h, int col) { + fillRect(g, x - 1, y - 1, w + 2, 1, col); + fillRect(g, x - 1, y - 1, 1, h + 2, col); + fillRect(g, x - 1, y + h, w + 2, 1, col); + fillRect(g, x + w, y - 1, 1, h + 2, col); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java new file mode 100644 index 0000000..d005acf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java @@ -0,0 +1,61 @@ +package dev.xkmc.l2core.base.overlay; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.client.gui.overlay.ExtendedGui; +import net.neoforged.neoforge.client.gui.overlay.IGuiOverlay; + +import java.util.List; + +public abstract class SelectionSideBar> extends SideBar implements IGuiOverlay { + + public SelectionSideBar(float duration, float ease) { + super(duration, ease); + } + + public abstract Pair, Integer> getItems(); + + public abstract boolean isAvailable(T t); + + public abstract boolean onCenter(); + + public void initRender() { + + } + + @Override + public void render(ExtendedGui gui, GuiGraphics g, float partialTick, int width, int height) { + if (!ease(gui.getGuiTicks() + partialTick)) + return; + initRender(); + gui.setupOverlayRenderState(true, false); + int x0 = this.getXOffset(width); + int y0 = this.getYOffset(height); + Context ctx = new Context(gui, g, partialTick, Minecraft.getInstance().font, x0, y0); + renderContent(ctx); + } + + public void renderContent(Context ctx) { + Pair, Integer> content = getItems(); + var list = content.getFirst(); + for (int i = 0; i < list.size(); i++) { + renderEntry(ctx, list.get(i), i, content.getSecond()); + } + } + + protected abstract void renderEntry(Context ctx, T t, int index, int select); + + public record Context(ExtendedGui gui, GuiGraphics g, float pTick, Font font, int x0, int y0) { + + public void renderItem(ItemStack stack, int x, int y) { + if (!stack.isEmpty()) { + g.renderItem(stack, x, y); + g.renderItemDecorations(font, stack, x, y); + } + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java new file mode 100644 index 0000000..6d859d5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java @@ -0,0 +1,95 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.Minecraft; + +import javax.annotation.Nullable; + +public abstract class SideBar> { + + public interface Signature> { + + boolean shouldRefreshIdle(SideBar sideBar, @Nullable S old); + + } + + public record IntSignature(int val) implements Signature { + + @Override + public boolean shouldRefreshIdle(SideBar sideBar, @Nullable SideBar.IntSignature old) { + return old == null || val != old.val; + } + + } + + protected final float max_time; + protected final float max_ease; + + @Nullable + protected S prev; + + protected float idle = 0; + protected float ease_time = 0; + protected float prev_time = -1; + + public SideBar(float duration, float ease) { + this.max_time = duration; + this.max_ease = ease; + } + + public abstract S getSignature(); + + public abstract boolean isScreenOn(); + + protected boolean isOnHold() { + return Minecraft.getInstance().options.keyShift.isDown(); + } + + protected boolean ease(float current_time) { + if (!isScreenOn()) { + prev = null; + idle = max_time; + ease_time = 0; + prev_time = -1; + return false; + } + float time_diff = prev_time < 0 ? 0 : (current_time - prev_time); + prev_time = current_time; + + S signature = getSignature(); + if (signature.shouldRefreshIdle(this, prev) || isOnHold()) { + idle = 0; + } else { + idle += time_diff; + } + prev = signature; + if (idle < max_time) { + if (ease_time < max_ease) { + ease_time += time_diff; + if (ease_time > max_ease) { + ease_time = max_ease; + } + } + } else { + if (ease_time > 0) { + ease_time -= time_diff; + if (ease_time < 0) { + ease_time = 0; + } + } + } + return ease_time > 0; + } + + public boolean isRendering() { + return isScreenOn() && ease_time > 0; + } + + protected int getXOffset(int width) { + return 0; + } + + protected int getYOffset(int height) { + return 0; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java b/src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java new file mode 100644 index 0000000..f86880e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java @@ -0,0 +1,30 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.gui.GuiGraphics; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +public class TextBox extends OverlayUtil { + + private final int anchorX, anchorY; + + public TextBox(GuiGraphics g, int anchorX, int anchorY, int x, int y, int width) { + super(g, x, y, width); + this.anchorX = anchorX; + this.anchorY = anchorY; + } + + @Override + public Vector2ic positionTooltip(int gw, int gh, int x, int y, int tw, int th) { + return new Vector2i(x - tw * anchorX / 2, y - th * anchorY / 2); + } + + @Override + public int getMaxWidth() { + if (anchorX == 0) return g.guiWidth() - x0 - 8; + if (anchorX == 1) return Math.max(x0 / 2 - 4, g.guiWidth() - x0 / 2 - 4); + if (anchorX == 2) return x0 - 8; + return g.guiWidth(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/package-info.java b/src/main/java/dev/xkmc/l2core/base/overlay/package-info.java new file mode 100644 index 0000000..78715cc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java new file mode 100644 index 0000000..9532bb3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java @@ -0,0 +1,63 @@ +package dev.xkmc.l2core.base.tile; + +import dev.xkmc.l2core.util.ServerOnly; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.ParametersAreNonnullByDefault; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +@SerialClass +public class BaseBlockEntity extends BlockEntity { + + public BaseBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + @Override + public void load(CompoundTag tag) { + super.load(tag); + if (tag.contains("auto-serial")) + Wrappers.run(() -> TagCodec.fromTag(tag.getCompound("auto-serial"), getClass(), this, f -> true)); + } + + @Override + public void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); + CompoundTag ser = Wrappers.get(() -> TagCodec.toTag(new CompoundTag(), getClass(), this, f -> true)); + if (ser != null) tag.put("auto-serial", ser); + } + + @Override + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @ServerOnly + public void sync() { + if (level != null) { + level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3); + } + } + + /** + * Generate data packet from server to client, called from getUpdatePacket() + */ + @Override + public CompoundTag getUpdateTag() { + CompoundTag ans = super.getUpdateTag(); + CompoundTag ser = Wrappers.get(() -> TagCodec.toTag(new CompoundTag(), getClass(), this, SerialClass.SerialField::toClient)); + if (ser != null) ans.put("auto-serial", ser); + return ans; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java new file mode 100644 index 0000000..15f6ef1 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java @@ -0,0 +1,107 @@ +package dev.xkmc.l2core.base.tile; + +import dev.xkmc.l2serial.serialization.codec.AliasCollection; +import net.minecraft.util.Mth; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; +import java.util.function.Predicate; + +@ParametersAreNonnullByDefault +public class BaseContainer> extends SimpleContainer implements AliasCollection { + + private int max = 64; + private Predicate predicate = e -> true; + + public BaseContainer(int size) { + super(size); + } + + public T setMax(int max) { + this.max = Mth.clamp(max, 1, 64); + return getThis(); + } + + public T setPredicate(Predicate predicate) { + this.predicate = predicate; + return getThis(); + } + + public T add(BaseContainerListener t) { + addListener(t); + return getThis(); + } + + public boolean canAddWhileHaveSpace(ItemStack add, int space) { + int ans = 0; + for (ItemStack stack : items) { + if (stack.isEmpty()) + ans++; + else if (ItemStack.isSameItemSameTags(stack, add) && + stack.getCount() + add.getCount() <= + Math.min(stack.getMaxStackSize(), getMaxStackSize())) { + return true; + } + } + return ans - 1 >= space; + } + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { + return predicate.test(stack); + } + + @Override + public boolean canAddItem(ItemStack stack) { + return predicate.test(stack) && super.canAddItem(stack); + } + + public boolean canRecipeAddItem(ItemStack stack) { + stack = stack.copy(); + for (ItemStack slot : this.items) { + if (slot.isEmpty() || ItemStack.isSameItemSameTags(slot, stack)) { + int cap = Math.min(getMaxStackSize(), slot.getMaxStackSize()); + int amount = Math.min(stack.getCount(), cap - slot.getCount()); + if (amount > 0) { + stack.shrink(amount); + if (stack.getCount() == 0) { + return true; + } + } + } + } + return false; + } + + @Override + public int getMaxStackSize() { + return max; + } + + @Override + public List getAsList() { + return items; + } + + @Override + public void clear() { + super.clearContent(); + } + + @Override + public void set(int n, int i, ItemStack elem) { + items.set(i, elem); + } + + @Override + public Class getElemClass() { + return ItemStack.class; + } + + @SuppressWarnings("unchecked") + public T getThis() { + return (T) this; + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java new file mode 100644 index 0000000..b2526ac --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java @@ -0,0 +1,18 @@ +package dev.xkmc.l2core.base.tile; + +import net.minecraft.world.Container; +import net.minecraft.world.ContainerListener; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +public interface BaseContainerListener extends ContainerListener { + + void notifyTile(); + + @SuppressWarnings({"unsafe", "unchecked"}) + @Override + default void containerChanged(Container cont) { + notifyTile(); + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java new file mode 100644 index 0000000..07d946a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java @@ -0,0 +1,213 @@ +package dev.xkmc.l2core.base.tile; + +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.AliasCollection; +import net.minecraft.core.NonNullList; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; + +@SerialClass +public class BaseTank implements IFluidHandler, AliasCollection { + + private final int size, capacity; + private final List listeners = new ArrayList<>(); + + private Predicate predicate = e -> true; + private BooleanSupplier allowExtract = () -> true; + + @SerialClass.SerialField + public NonNullList list; + + private int click_max; + + public BaseTank(int size, int capacity) { + this.size = size; + this.capacity = capacity; + list = NonNullList.withSize(size, FluidStack.EMPTY); + } + + public BaseTank add(BaseContainerListener listener) { + listeners.add(listener); + return this; + } + + public BaseTank setPredicate(Predicate predicate) { + this.predicate = predicate; + return this; + } + + public BaseTank setExtract(BooleanSupplier allowExtract) { + this.allowExtract = allowExtract; + return this; + } + + public BaseTank setClickMax(int max) { + this.click_max = max; + return this; + } + + @Override + public int getTanks() { + return size; + } + + @NotNull + @Override + public FluidStack getFluidInTank(int tank) { + return list.get(tank); + } + + @Override + public int getTankCapacity(int tank) { + return capacity; + } + + @Override + public boolean isFluidValid(int tank, @NotNull FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) return 0; + if (!predicate.test(resource)) return 0; + int to_fill = click_max == 0 ? resource.getAmount() : resource.getAmount() >= click_max ? click_max : 0; + if (to_fill == 0) return 0; + int filled = 0; + for (int i = 0; i < size; i++) { + FluidStack stack = list.get(i); + if (stack.isFluidEqual(resource)) { + int remain = capacity - stack.getAmount(); + int fill = Math.min(to_fill, remain); + filled += fill; + to_fill -= fill; + if (action == FluidAction.EXECUTE) { + resource.shrink(fill); + stack.grow(fill); + } + } else if (stack.isEmpty()) { + int fill = Math.min(to_fill, capacity); + filled += fill; + to_fill -= fill; + if (action == FluidAction.EXECUTE) { + FluidStack rep = resource.copy(); + rep.setAmount(fill); + list.set(i, rep); + resource.shrink(fill); + } + } + if (resource.isEmpty() || to_fill == 0) break; + } + if (action == FluidAction.EXECUTE && filled > 0) { + setChanged(); + } + return filled; + } + + @NotNull + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) return resource; + if (!allowExtract.getAsBoolean()) return FluidStack.EMPTY; + int to_drain = resource.getAmount(); + if (click_max > 0) { + if (to_drain < click_max) return FluidStack.EMPTY; + to_drain = click_max; + } + int drained = 0; + for (int i = 0; i < size; i++) { + FluidStack stack = list.get(i); + if (stack.isFluidEqual(resource)) { + int remain = stack.getAmount(); + int drain = Math.min(to_drain, remain); + drained += drain; + to_drain -= drain; + if (action == FluidAction.EXECUTE) { + stack.shrink(drain); + } + } + if (to_drain == 0) break; + } + if (action == FluidAction.EXECUTE && drained > 0) { + setChanged(); + } + FluidStack ans = resource.copy(); + ans.setAmount(drained); + return ans; + } + + @NotNull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + if (!allowExtract.getAsBoolean()) return FluidStack.EMPTY; + FluidStack ans = null; + int to_drain = maxDrain; + if (click_max > 0) { + if (to_drain < click_max) return FluidStack.EMPTY; + to_drain = click_max; + } + int drained = 0; + for (int i = 0; i < size; i++) { + FluidStack stack = list.get(i); + if (!stack.isEmpty() && (ans == null || stack.isFluidEqual(ans))) { + int remain = stack.getAmount(); + int drain = Math.min(to_drain, remain); + drained += drain; + to_drain -= drain; + if (ans == null) { + ans = stack.copy(); + } + if (action == FluidAction.EXECUTE) { + stack.shrink(drain); + } + } + if (to_drain == 0) break; + } + if (action == FluidAction.EXECUTE && drained > 0) { + setChanged(); + } + if (ans == null) { + return FluidStack.EMPTY; + } + ans.setAmount(drained); + return ans; + } + + public void setChanged() { + listeners.forEach(BaseContainerListener::notifyTile); + } + + @Override + public List getAsList() { + return list; + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public void set(int n, int i, FluidStack elem) { + list.set(i, elem); + } + + @Override + public Class getElemClass() { + return FluidStack.class; + } + + public boolean isEmpty() { + for (FluidStack stack : list) { + if (!stack.isEmpty()) + return false; + } + return true; + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java b/src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java new file mode 100644 index 0000000..5b1b332 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java @@ -0,0 +1,182 @@ +package dev.xkmc.l2core.base.tile; + +import com.mojang.datafixers.util.Pair; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.items.wrapper.EmptyHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * from Create + */ +public class CombinedTankWrapper implements IFluidHandler { + + public enum Type { + INSERT, EXTRACT, ALL + } + + private final List> list = new ArrayList<>(); + + protected int[] baseIndex; + protected int tankCount; + protected boolean enforceVariety; + + public CombinedTankWrapper build() { + this.baseIndex = new int[list.size()]; + int index = 0; + for (int i = 0; i < list.size(); i++) { + index += list.get(i).getFirst().getTanks(); + baseIndex[i] = index; + } + this.tankCount = index; + return this; + } + + public CombinedTankWrapper add(Type type, IFluidHandler... handlers) { + for (IFluidHandler handler : handlers) { + list.add(Pair.of(handler, type)); + } + return this; + } + + + protected Iterable fillable() { + return list.stream().filter(e -> e.getSecond() != Type.EXTRACT).map(Pair::getFirst).toList(); + } + + protected Iterable drainable() { + return list.stream().filter(e -> e.getSecond() != Type.INSERT).map(Pair::getFirst).toList(); + } + + public CombinedTankWrapper enforceVariety() { + enforceVariety = true; + return this; + } + + @Override + public int getTanks() { + return tankCount; + } + + @Override + public FluidStack getFluidInTank(int tank) { + int index = getIndexForSlot(tank); + IFluidHandler handler = getHandlerFromIndex(index); + tank = getSlotFromIndex(tank, index); + return handler.getFluidInTank(tank); + } + + @Override + public int getTankCapacity(int tank) { + int index = getIndexForSlot(tank); + IFluidHandler handler = getHandlerFromIndex(index); + int localSlot = getSlotFromIndex(tank, index); + return handler.getTankCapacity(localSlot); + } + + @Override + public boolean isFluidValid(int tank, FluidStack stack) { + int index = getIndexForSlot(tank); + IFluidHandler handler = getHandlerFromIndex(index); + int localSlot = getSlotFromIndex(tank, index); + return handler.isFluidValid(localSlot, stack); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) + return 0; + + int filled = 0; + resource = resource.copy(); + + boolean fittingHandlerFound = false; + Outer: + for (boolean searchPass : new boolean[]{true, false}) { + for (IFluidHandler iFluidHandler : fillable()) { + + for (int i = 0; i < iFluidHandler.getTanks(); i++) + if (searchPass && iFluidHandler.getFluidInTank(i) + .isFluidEqual(resource)) + fittingHandlerFound = true; + + if (searchPass && !fittingHandlerFound) + continue; + + int filledIntoCurrent = iFluidHandler.fill(resource, action); + resource.shrink(filledIntoCurrent); + filled += filledIntoCurrent; + + if (resource.isEmpty() || fittingHandlerFound || enforceVariety && filledIntoCurrent != 0) + break Outer; + } + } + + return filled; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) + return resource; + + FluidStack drained = FluidStack.EMPTY; + resource = resource.copy(); + + for (IFluidHandler iFluidHandler : drainable()) { + FluidStack drainedFromCurrent = iFluidHandler.drain(resource, action); + int amount = drainedFromCurrent.getAmount(); + resource.shrink(amount); + + if (!drainedFromCurrent.isEmpty() && (drained.isEmpty() || drainedFromCurrent.isFluidEqual(drained))) + drained = new FluidStack(drainedFromCurrent.getFluid(), amount + drained.getAmount(), + drainedFromCurrent.getTag()); + if (resource.isEmpty()) + break; + } + + return drained; + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + FluidStack drained = FluidStack.EMPTY; + + for (IFluidHandler iFluidHandler : drainable()) { + FluidStack drainedFromCurrent = iFluidHandler.drain(maxDrain, action); + int amount = drainedFromCurrent.getAmount(); + maxDrain -= amount; + + if (!drainedFromCurrent.isEmpty() && (drained.isEmpty() || drainedFromCurrent.isFluidEqual(drained))) + drained = new FluidStack(drainedFromCurrent.getFluid(), amount + drained.getAmount(), + drainedFromCurrent.getTag()); + if (maxDrain == 0) + break; + } + + return drained; + } + + protected int getIndexForSlot(int slot) { + if (slot < 0) + return -1; + for (int i = 0; i < baseIndex.length; i++) + if (slot - baseIndex[i] < 0) + return i; + return -1; + } + + protected IFluidHandler getHandlerFromIndex(int index) { + if (index < 0 || index >= list.size()) + return (IFluidHandler) EmptyHandler.INSTANCE; + return list.get(index).getFirst(); + } + + protected int getSlotFromIndex(int slot, int index) { + if (index <= 0 || index >= baseIndex.length) + return slot; + return slot - baseIndex[index - 1]; + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/package-info.java b/src/main/java/dev/xkmc/l2core/base/tile/package-info.java new file mode 100644 index 0000000..53a12f3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.tile; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java b/src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java new file mode 100644 index 0000000..3a1664c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java @@ -0,0 +1,55 @@ +package dev.xkmc.l2core.capability.attachment; + +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.nbt.CompoundTag; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.attachment.IAttachmentHolder; +import net.neoforged.neoforge.attachment.IAttachmentSerializer; + +import java.util.Objects; +import java.util.function.Supplier; + +public class AttachmentDef implements IAttachmentSerializer { + private final Class cls; + private final Supplier sup; + private AttachmentType type; + + public AttachmentDef(Class cls, Supplier sup) { + this.cls = cls; + this.sup = sup; + } + + public AttachmentType type() { + if (type != null) return type; + var builder = AttachmentType.builder(sup); + builder.serialize(this); + if (copyOnDeath()) + builder.copyOnDeath(); + type = builder.build(); + return type; + } + + protected boolean copyOnDeath() { + return false; + } + + @Override + public E read(IAttachmentHolder holder, CompoundTag tag) { + return Objects.requireNonNull(Wrappers.get(() -> TagCodec.fromTag(tag, cls, null, f -> true))); + } + + @Override + public CompoundTag write(E attachment) { + return Objects.requireNonNull(TagCodec.toTag(new CompoundTag(), attachment)); + } + + public Class cls() { + return cls; + } + + public boolean isFor(IAttachmentHolder holder) { + return true; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java b/src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java new file mode 100644 index 0000000..07a74a8 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java @@ -0,0 +1,7 @@ +package dev.xkmc.l2core.capability.attachment; + +public class BaseAttachment { + + + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java new file mode 100644 index 0000000..b206ca8 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java @@ -0,0 +1,45 @@ +package dev.xkmc.l2core.capability.attachment; + +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.IAttachmentHolder; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * only entities will be automatically attached by default for efficiency + */ +public class GeneralCapabilityHolder> extends AttachmentDef { + + public static final Map> INTERNAL_MAP = new ConcurrentHashMap<>(); + + public final ResourceLocation id; + public final Class entity_class; + private final Predicate pred; + + + public GeneralCapabilityHolder(ResourceLocation id, Class holder_class, Supplier sup, + Class entity_class, Predicate pred) { + super(holder_class, sup); + this.id = id; + this.entity_class = entity_class; + this.pred = pred; + INTERNAL_MAP.put(id, this); + } + + public T get(E e) { + return e.getData(type()); + } + + public boolean isFor(IAttachmentHolder holder) { + return entity_class.isInstance(holder) && isProper(Wrappers.cast(holder)); + } + + public boolean isProper(E entity) { + return pred.test(entity); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java new file mode 100644 index 0000000..4acf0e8 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java @@ -0,0 +1,14 @@ +package dev.xkmc.l2core.capability.attachment; + +import dev.xkmc.l2serial.util.Wrappers; + +public class GeneralCapabilityTemplate> { + + public T getThis() { + return Wrappers.cast(this); + } + + public void tick(E e) { + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java b/src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java new file mode 100644 index 0000000..6a6e966 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.attachment; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java new file mode 100644 index 0000000..9858000 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java @@ -0,0 +1,19 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2core.init.L2LibReg; +import dev.xkmc.l2core.util.Proxy; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.world.entity.player.Player; + +public class ClientDataHandler { + + public static void handle(TokenKey key, T token) { + Player player = Proxy.getClientPlayer(); + if (player == null) return; + ConditionalToken old = L2LibReg.CONDITIONAL.type().get(player).data.put(key, token); + if (token instanceof NetworkSensitiveToken t) { + t.onSync(Wrappers.cast(old), player); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java new file mode 100644 index 0000000..bb19f61 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java @@ -0,0 +1,48 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate; +import dev.xkmc.l2core.init.L2LibraryConfig; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.world.entity.player.Player; + +import javax.annotation.Nullable; +import java.util.HashMap; + +@SerialClass +public class ConditionalData extends PlayerCapabilityTemplate { + + @SerialClass.SerialField + public HashMap, ConditionalToken> data = new HashMap<>(); + @SerialClass.SerialField + public int tickSinceDeath = 0; + + @Override + public void onClone(boolean isWasDeath) { + tickSinceDeath = 0; + } + + public T getOrCreateData(TokenProvider setEffect, C ent) { + return Wrappers.cast(data.computeIfAbsent(setEffect.getKey(), e -> setEffect.getData(ent))); + } + + @Nullable + public T getData(TokenKey setEffect) { + return Wrappers.cast(data.get(setEffect)); + } + + @Override + public void tick(Player player) { + tickSinceDeath++; + if (L2LibraryConfig.SERVER.restoreFullHealthOnRespawn.get() && + tickSinceDeath < 60 && player.getHealth() < player.getMaxHealth()) { + player.setHealth(player.getMaxHealth()); + } + data.entrySet().removeIf(e -> e.getValue().tick(player)); + } + + public boolean hasData(TokenKey eff) { + return data.containsKey(eff); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java new file mode 100644 index 0000000..0084709 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.entity.player.Player; + +@SerialClass +public class ConditionalToken { + + /** + * return true to remove + */ + public boolean tick(Player player) { + return false; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java new file mode 100644 index 0000000..426f5c9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java @@ -0,0 +1,4 @@ +package dev.xkmc.l2core.capability.conditionals; + +public interface Context { +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java new file mode 100644 index 0000000..b03de89 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2core.init.L2Core; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; + +import javax.annotation.Nullable; + +public interface NetworkSensitiveToken { + void onSync(@Nullable T old, Player player); + + default void sync(TokenKey key, T token, ServerPlayer sp) { + L2Core.PACKET_HANDLER.toClientPlayer(TokenToClient.of(key, token), sp); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java new file mode 100644 index 0000000..d03f6c1 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java @@ -0,0 +1,15 @@ +package dev.xkmc.l2core.capability.conditionals; + +import net.minecraft.resources.ResourceLocation; + +public record TokenKey(String type, String id) { + + public static TokenKey of(ResourceLocation id) { + return new TokenKey<>(id.getNamespace(), id.getPath()); + } + + public ResourceLocation asLocation() { + return new ResourceLocation(type, id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java new file mode 100644 index 0000000..2165214 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java @@ -0,0 +1,8 @@ +package dev.xkmc.l2core.capability.conditionals; + +public interface TokenProvider { + + T getData(C ent); + + TokenKey getKey(); +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java new file mode 100644 index 0000000..4e12cbc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2serial.network.SerialPacketBase; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +public record TokenToClient(ResourceLocation id, ConditionalToken token) + implements SerialPacketBase { + + public static TokenToClient of(TokenKey key, T token) { + return new TokenToClient(key.asLocation(), token); + } + + @Override + public void handle(@Nullable Player player) { + ClientDataHandler.handle(TokenKey.of(id), token); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java new file mode 100644 index 0000000..11ae4e9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.conditionals; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java b/src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java new file mode 100644 index 0000000..754524d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.capability.level; + +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.saveddata.SavedData; + +@SerialClass +public class BaseSavedData extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag) { + TagCodec.toTag(tag, this); + return tag; + } + + @Override + public boolean isDirty() { + return true; + } + + + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/level/package-info.java b/src/main/java/dev/xkmc/l2core/capability/level/package-info.java new file mode 100644 index 0000000..0675df0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/level/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.level; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java b/src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java new file mode 100644 index 0000000..095bbaf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java @@ -0,0 +1,15 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.util.Proxy; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.PacketCodec; + +import java.util.function.Predicate; + +public class ClientSyncHandler { + + public static > void parse(byte[] tag, PlayerCapabilityHolder holder, Predicate pred) { + PacketCodec.fromBytes(tag, holder.cls(), Proxy.getClientPlayer().getData(holder.type()), pred); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java new file mode 100644 index 0000000..56ae49c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java @@ -0,0 +1,44 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2serial.network.SerialPacketBase; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.PacketCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; +import java.util.function.Predicate; + +@SerialClass +public record PlayerCapToClient(Action action, ResourceLocation holderID, byte[] data, UUID playerID) + implements SerialPacketBase { + + public static > PlayerCapToClient + of(ServerPlayer player, Action action, PlayerCapabilityHolder holder, T handler) { + return new PlayerCapToClient(action, holder.id, + PacketCodec.toBytes(handler, holder.cls(), action.pred), + player.getUUID()); + } + + @Override + public void handle(@Nullable Player player) { + ClientSyncHandler.parse(data, PlayerCapabilityHolder.INTERNAL_MAP.get(holderID), action.pred); + } + + public enum Action { + ALL(e -> true), + CLIENT(SerialClass.SerialField::toClient), + TRACK(SerialClass.SerialField::toTracking), + ; + public final Predicate pred; + + Action(Predicate pred) { + this.pred = pred; + } + + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java new file mode 100644 index 0000000..e699a7b --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java @@ -0,0 +1,33 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +public class PlayerCapabilityHolder> extends GeneralCapabilityHolder { + + public static final Map> INTERNAL_MAP = new ConcurrentHashMap<>(); + + public final PlayerCapabilityNetworkHandler network; + + public PlayerCapabilityHolder(ResourceLocation id, Class cls, Supplier sup, NetworkFactory network) { + super(id, cls, sup, Player.class, e -> true); + this.network = network.create(this); + INTERNAL_MAP.put(id, this); + } + + protected boolean copyOnDeath() { + return true; + } + + public interface NetworkFactory> { + + PlayerCapabilityNetworkHandler create(PlayerCapabilityHolder holder); + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java new file mode 100644 index 0000000..dd972d9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java @@ -0,0 +1,26 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.init.L2Core; +import net.minecraft.server.level.ServerPlayer; + +public class PlayerCapabilityNetworkHandler> { + + public final PlayerCapabilityHolder holder; + + public PlayerCapabilityNetworkHandler(PlayerCapabilityHolder holder) { + this.holder = holder; + } + + public void toClient(ServerPlayer e) { + L2Core.PACKET_HANDLER.toClientPlayer(PlayerCapToClient.of(e, PlayerCapToClient.Action.CLIENT, holder, holder.get(e)), e); + } + + public void toTracking(ServerPlayer e) { + L2Core.PACKET_HANDLER.toTrackingOnly(PlayerCapToClient.of(e, PlayerCapToClient.Action.TRACK, holder, holder.get(e)), e); + } + + public void startTracking(ServerPlayer tracker, ServerPlayer target) { + L2Core.PACKET_HANDLER.toClientPlayer(PlayerCapToClient.of(target, PlayerCapToClient.Action.TRACK, holder, holder.get(target)), tracker); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java new file mode 100644 index 0000000..184be2b --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java @@ -0,0 +1,17 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.entity.player.Player; + +@SerialClass +public class PlayerCapabilityTemplate> extends GeneralCapabilityTemplate { + + public void init(Player player) { + } + + public void onClone(boolean isWasDeath) { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/package-info.java b/src/main/java/dev/xkmc/l2core/capability/player/package-info.java new file mode 100644 index 0000000..4fa17f6 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.player; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java b/src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java new file mode 100644 index 0000000..1e82b34 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java @@ -0,0 +1,11 @@ +package dev.xkmc.l2core.compat.curios; + +import net.minecraft.resources.ResourceLocation; + +import java.util.ArrayList; + +public record CurioEntityBuilder( + ArrayList entities, + ArrayList slots, + ArrayList conditions) { +} diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java b/src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java new file mode 100644 index 0000000..9c0d62a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java @@ -0,0 +1,28 @@ +package dev.xkmc.l2core.compat.curios; + +import java.util.ArrayList; + +import static dev.xkmc.l2core.compat.curios.CurioSlotBuilder.Operation.SET; + +public record CurioSlotBuilder(int order, String icon, int size, + Operation operation, + boolean add_cosmetic, + boolean use_native_gui, + boolean render_toggle, + boolean replace, ArrayList conditions) { + + public CurioSlotBuilder(int order, String icon) { + this(order, icon, 1, SET); + } + + public CurioSlotBuilder(int order, String icon, int size, + Operation operation) { + this(order, icon, size, operation, + false, true, true, false, SlotCondition.of()); + } + + public enum Operation { + SET, ADD, REMOVE + } + +} diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java b/src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java new file mode 100644 index 0000000..cd647d7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java @@ -0,0 +1,19 @@ +package dev.xkmc.l2core.compat.curios; + +import java.util.ArrayList; + +public record SlotCondition(String type, String modid) { + + public static ArrayList of(String... ids) { + ArrayList ans = new ArrayList<>(); + for (String id : ids) { + ans.add(new SlotCondition(id)); + } + return ans; + } + + public SlotCondition(String modid) { + this("forge:mod_loaded", modid); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/package-info.java b/src/main/java/dev/xkmc/l2core/compat/curios/package-info.java new file mode 100644 index 0000000..bc47f29 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.compat.curios; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/L2Core.java b/src/main/java/dev/xkmc/l2core/init/L2Core.java new file mode 100644 index 0000000..cc3cc34 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2Core.java @@ -0,0 +1,47 @@ +package dev.xkmc.l2core.init; + +import dev.xkmc.l2core.base.effects.EffectToClient; +import dev.xkmc.l2core.capability.conditionals.TokenToClient; +import dev.xkmc.l2core.capability.player.PlayerCapToClient; +import dev.xkmc.l2core.serial.config.SyncPacket; +import dev.xkmc.l2serial.network.PacketHandler; +import dev.xkmc.l2serial.serialization.custom_handler.Handlers; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import static dev.xkmc.l2serial.network.PacketHandler.NetDir.PLAY_TO_CLIENT; + +// The value here should match an entry in the META-INF/mods.toml file +@Mod(L2Core.MODID) +@EventBusSubscriber(modid = L2Core.MODID, bus = EventBusSubscriber.Bus.MOD) +public class L2Core { + + public static final String MODID = "l2core"; + public static final Logger LOGGER = LogManager.getLogger(); + + // TODO public static final L2Registrate REGISTRATE = new L2Registrate(MODID); + + public static final PacketHandler PACKET_HANDLER = new PacketHandler(MODID, 1, + e -> e.create(SyncPacket.class, PLAY_TO_CLIENT), + e -> e.create(EffectToClient.class, PLAY_TO_CLIENT), + e -> e.create(PlayerCapToClient.class, PLAY_TO_CLIENT), + e -> e.create(TokenToClient.class, PLAY_TO_CLIENT) + ); + + public L2Core(IEventBus bus) { + Handlers.register(); + L2LibReg.register(bus); + L2LibraryConfig.init(); + } + + @SubscribeEvent + public static void onPacketReg(RegisterPayloadHandlersEvent event) { + PACKET_HANDLER.register(event); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/L2LibReg.java b/src/main/java/dev/xkmc/l2core/init/L2LibReg.java new file mode 100644 index 0000000..7cc0a89 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2LibReg.java @@ -0,0 +1,49 @@ +package dev.xkmc.l2core.init; + +import dev.xkmc.l2core.base.effects.ClientEffectCap; +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import dev.xkmc.l2core.capability.conditionals.ConditionalData; +import dev.xkmc.l2core.capability.player.PlayerCapabilityNetworkHandler; +import dev.xkmc.l2core.init.reg.datapack.DatapackReg; +import dev.xkmc.l2core.init.reg.simple.*; +import dev.xkmc.l2core.serial.conditions.*; +import dev.xkmc.l2core.serial.ingredients.EnchantmentIngredient; +import dev.xkmc.l2core.serial.ingredients.MobEffectIngredient; +import dev.xkmc.l2core.serial.ingredients.PotionIngredient; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.common.conditions.ICondition; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +public class L2LibReg { + + public static final Reg REG = new Reg(L2Core.MODID); + + // ingredients + public static final IngReg INGREDIENT = IngReg.of(REG); + public static final IngVal ING_ENCH = INGREDIENT.reg("enchantment", EnchantmentIngredient.class); + public static final IngVal ING_POTION = INGREDIENT.reg("potion", PotionIngredient.class); + public static final IngVal ING_EFF = INGREDIENT.reg("mob_effect", MobEffectIngredient.class); + + // conditions + public static final CdcReg CONDITION = CdcReg.of(REG, NeoForgeRegistries.CONDITION_SERIALIZERS); + public static final CdcVal CONDITION_BOOL = CONDITION.reg("bool_config", BooleanValueCondition.class); + public static final CdcVal CONDITION_INT = CONDITION.reg("int_config", IntValueCondition.class); + public static final CdcVal CONDITION_DOUBLE = CONDITION.reg("double_config", DoubleValueCondition.class); + public static final CdcVal CONDITION_STR = CONDITION.reg("string_config", StringValueCondition.class); + public static final CdcVal CONDITION_LIST_STR = CONDITION.reg("string_list_config", ListStringValueCondition.class); + + // attachments + public static final AttReg ATTACHMENT = AttReg.of(REG); + public static final AttVal.CapVal EFFECT = ATTACHMENT.entity("effect", + ClientEffectCap.class, ClientEffectCap::new, LivingEntity.class, e -> e.level().isClientSide()); + public static final AttVal.PlayerVal CONDITIONAL = ATTACHMENT.player("conditionals", + ConditionalData.class, ConditionalData::new, PlayerCapabilityNetworkHandler::new); + + public static final DatapackReg MENU_LAYOUT = REG.dataReg("menu_layout", MenuLayoutConfig.class); + + public static void register(IEventBus bus) { + REG.register(bus); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java b/src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java new file mode 100644 index 0000000..0429579 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java @@ -0,0 +1,6 @@ +package dev.xkmc.l2core.init; + +//@Mod.EventBusSubscriber(value = Dist.CLIENT, path = L2Library.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class L2LibraryClient { + +} diff --git a/src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java b/src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java new file mode 100644 index 0000000..2894cad --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java @@ -0,0 +1,81 @@ +package dev.xkmc.l2core.init; + +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.config.IConfigSpec; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.neoforge.common.ModConfigSpec; +import org.apache.commons.lang3.tuple.Pair; + +public class L2LibraryConfig { + + public static class Client { + + public final ModConfigSpec.DoubleValue infoAlpha; + public final ModConfigSpec.IntValue infoAnchor; + public final ModConfigSpec.DoubleValue infoMaxWidth; + + public final ModConfigSpec.BooleanValue selectionDisplayRequireShift; + public final ModConfigSpec.BooleanValue selectionScrollRequireShift; + + + Client(ModConfigSpec.Builder builder) { + infoAlpha = builder.comment("Info background transparency. 1 means opaque.") + .defineInRange("infoAlpha", 0.5, 0, 1); + infoAnchor = builder.comment("Info alignment. 0 means top. 1 means middle. 2 means bottom.") + .defineInRange("infoAnchor", 1, 0, 2); + infoMaxWidth = builder.comment("Info max width. 0.5 means half screen. default: 0.3") + .defineInRange("infoMaxWidth", 0.3, 0, 0.5); + + selectionDisplayRequireShift = builder.comment("Render Selection only when pressing shift") + .define("selectionDisplayRequireShift", false); + selectionScrollRequireShift = builder.comment("Scroll for selection only when pressing shift") + .define("selectionScrollRequireShift", true); + + + } + + } + + public static class Server { + + public final ModConfigSpec.BooleanValue restoreFullHealthOnRespawn; + + Server(ModConfigSpec.Builder builder) { + restoreFullHealthOnRespawn = builder.comment("Restore full health on respawn") + .define("restoreFullHealthOnRespawn", true); + } + + } + + public static final ModConfigSpec CLIENT_SPEC; + public static final Client CLIENT; + + public static final ModConfigSpec SERVER_SPEC; + public static final Server SERVER; + + static { + final Pair client = new ModConfigSpec.Builder().configure(Client::new); + CLIENT_SPEC = client.getRight(); + CLIENT = client.getLeft(); + + final Pair server = new ModConfigSpec.Builder().configure(Server::new); + SERVER_SPEC = server.getRight(); + SERVER = server.getLeft(); + } + + /** + * Registers any relevant listeners for config + */ + public static void init() { + register(ModConfig.Type.CLIENT, CLIENT_SPEC); + register(ModConfig.Type.SERVER, SERVER_SPEC); + } + + private static void register(ModConfig.Type type, IConfigSpec spec) { + var mod = ModLoadingContext.get().getActiveContainer(); + String path = "l2_configs/" + mod.getModId() + "-" + type.extension() + ".toml"; + ModLoadingContext.get().registerConfig(type, spec, path); + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java b/src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java new file mode 100644 index 0000000..b28f606 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java @@ -0,0 +1,57 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.entity.living.LivingEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; + +@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class BaseCapabilityEvents { + + @SubscribeEvent + public static void onLivingTick(LivingEvent.LivingTickEvent event) { + if (event.getEntity().isAlive()) { + for (GeneralCapabilityHolder holder : GeneralCapabilityHolder.INTERNAL_MAP.values()) { + if (holder.isFor(event.getEntity())) + holder.get(Wrappers.cast(event.getEntity())).tick(Wrappers.cast(event.getEntity())); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onPlayerClone(PlayerEvent.Clone event) { + for (PlayerCapabilityHolder holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + holder.get(e).onClone(event.isWasDeath()); + holder.network.toClient(e); + holder.network.toTracking(e); + } + } + + @SubscribeEvent + public static void onServerPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + if (e != null) { + for (PlayerCapabilityHolder holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) { + holder.get(e).init(e); + holder.network.toClient(e); + holder.network.toTracking(e); + } + } + } + + @SubscribeEvent + public static void onStartTracking(PlayerEvent.StartTracking event) { + for (PlayerCapabilityHolder holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) { + if (!(event.getTarget() instanceof ServerPlayer e)) continue; + holder.network.startTracking((ServerPlayer) event.getEntity(), e); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java b/src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java new file mode 100644 index 0000000..cfc5fa4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java @@ -0,0 +1,31 @@ +package dev.xkmc.l2core.init.events; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Map; +import java.util.function.Consumer; + +@ParametersAreNonnullByDefault +public class BaseJsonReloadListener extends SimpleJsonResourceReloadListener { + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + + private final Consumer> consumer; + + public BaseJsonReloadListener(String path, Consumer> consumer) { + super(GSON, path); + this.consumer = consumer; + } + + @Override + protected void apply(Map map, ResourceManager manager, ProfilerFiller profiler) { + consumer.accept(map); + } +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java b/src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java new file mode 100644 index 0000000..f3d7dc4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java @@ -0,0 +1,206 @@ +package dev.xkmc.l2core.init.events; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.datafixers.util.Pair; +import dev.xkmc.l2core.base.effects.ClientEffectCap; +import dev.xkmc.l2core.base.effects.EffectToClient; +import dev.xkmc.l2core.base.effects.api.ClientRenderEffect; +import dev.xkmc.l2core.base.effects.api.DelayedEntityRender; +import dev.xkmc.l2core.base.effects.api.FirstPlayerRenderEffect; +import dev.xkmc.l2core.base.effects.api.IconRenderRegion; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import dev.xkmc.l2core.util.Proxy; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.neoforged.neoforge.client.event.RenderLivingEvent; +import net.neoforged.neoforge.event.TickEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class ClientEffectRenderEvents { + + private static final ArrayList ICONS = new ArrayList<>(); + + @SubscribeEvent + public static void clientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + AbstractClientPlayer player = Proxy.getClientPlayer(); + if (player != null) { + for (Map.Entry entry : player.getActiveEffectsMap().entrySet()) { + if (entry.getKey() instanceof FirstPlayerRenderEffect effect) { + effect.onClientLevelRender(player, entry.getValue()); + } + } + } + } + + @SubscribeEvent + public static void levelRenderLast(RenderLevelStageEvent event) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_WEATHER) return; + LevelRenderer renderer = event.getLevelRenderer(); + MultiBufferSource.BufferSource buffers = Minecraft.getInstance().renderBuffers().bufferSource(); + Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); + PoseStack stack = event.getPoseStack(); + + // cache the previous handler + PoseStack posestack = RenderSystem.getModelViewStack(); + var last = posestack.last(); + posestack.popPose(); + RenderSystem.applyModelViewMatrix(); + + RenderSystem.disableDepthTest(); + for (DelayedEntityRender icon : ICONS) { + renderIcon(stack, buffers, icon, event.getPartialTick(), camera, renderer.entityRenderDispatcher); + } + buffers.endBatch(); + + // restore the previous handler + posestack.pushPose(); + posestack.setIdentity(); + posestack.last().pose().mul(last.pose()); + posestack.last().normal().mul(last.normal()); + RenderSystem.applyModelViewMatrix(); + + RenderSystem.enableDepthTest(); + + ICONS.clear(); + } + + @SubscribeEvent + public static void onLivingEntityRender(RenderLivingEvent.Post event) { + LivingEntity entity = event.getEntity(); + if (!L2LibReg.EFFECT.type().isProper(entity)) return; + if (entity.getTags().contains("ClientOnly")) return; + ClientEffectCap cap = L2LibReg.EFFECT.type().get(entity); + List> l0 = new ArrayList<>(); + for (Map.Entry entry : cap.map.entrySet()) { + if (entry.getKey() instanceof ClientRenderEffect effect) { + l0.add(Pair.of(effect, entry.getValue())); + } + } + if (l0.isEmpty()) return; + List> l1 = new ArrayList<>(); + int index = 0; + + for (var e : l0) { + int lv = e.getSecond(); + int size = l1.size(); + int I = index; + e.getFirst().render(entity, lv, x -> l1.add(Pair.of(I, x))); + if (l1.size() > size) { + index++; + } + } + + + int n = index; + int w = (int) Math.ceil(Math.sqrt(n)); + int h = (int) Math.ceil(n * 1d / w); + + for (var e : l1) { + int i = e.getFirst(); + int iy = i / w; + int iw = Math.min(w, n - iy * w); + int ix = i - iy * w; + ICONS.add(e.getSecond().resize(IconRenderRegion.of(w, ix, iy, iw, h))); + } + + } + + private static void renderIcon(PoseStack pose, MultiBufferSource buffer, DelayedEntityRender icon, + float partial, Camera camera, EntityRenderDispatcher dispatcher) { + LivingEntity entity = icon.entity(); + float f = entity.getBbHeight() / 2; + + double x0 = Mth.lerp(partial, entity.xOld, entity.getX()); + double y0 = Mth.lerp(partial, entity.yOld, entity.getY()); + double z0 = Mth.lerp(partial, entity.zOld, entity.getZ()); + Vec3 offset = dispatcher.getRenderer(entity).getRenderOffset(entity, partial); + Vec3 cam_pos = camera.getPosition(); + double d2 = x0 - cam_pos.x + offset.x(); + double d3 = y0 - cam_pos.y + offset.y(); + double d0 = z0 - cam_pos.z + offset.z(); + + pose.pushPose(); + pose.translate(d2, d3 + f, d0); + pose.mulPose(camera.rotation()); + PoseStack.Pose entry = pose.last(); + VertexConsumer ivertexbuilder = buffer.getBuffer(get2DIcon(icon.rl())); + + float ix0 = -0.5f + icon.region().x(); + float ix1 = ix0 + icon.region().scale(); + float iy0 = -0.5f + icon.region().y(); + float iy1 = iy0 + icon.region().scale(); + float u0 = icon.tx(); + float v0 = icon.ty(); + float u1 = icon.tx() + icon.tw(); + float v1 = icon.ty() + icon.th(); + + iconVertex(entry, ivertexbuilder, ix1, iy0, u0, v1); + iconVertex(entry, ivertexbuilder, ix0, iy0, u1, v1); + iconVertex(entry, ivertexbuilder, ix0, iy1, u1, v0); + iconVertex(entry, ivertexbuilder, ix1, iy1, u0, v0); + pose.popPose(); + } + + private static void iconVertex(PoseStack.Pose entry, VertexConsumer builder, float x, float y, float u, float v) { + builder.vertex(entry.pose(), x, y, 0) + .uv(u, v) + .normal(entry.normal(), 0.0F, 1.0F, 0.0F) + .endVertex(); + } + + public static RenderType get2DIcon(ResourceLocation rl) { + return RenderType.create( + "entity_body_icon", + DefaultVertexFormat.POSITION_TEX, + VertexFormat.Mode.QUADS, 256, false, true, + RenderType.CompositeState.builder() + .setShaderState(RenderStateShard.RENDERTYPE_ENTITY_GLINT_SHADER) + .setTextureState(new RenderStateShard.TextureStateShard(rl, false, false)) + .setTransparencyState(RenderStateShard.ADDITIVE_TRANSPARENCY) + .setDepthTestState(RenderStateShard.NO_DEPTH_TEST) + .createCompositeState(false) + ); + } + + public static void sync(EffectToClient eff) { + if (Minecraft.getInstance().level == null) return; + Entity e = Minecraft.getInstance().level.getEntity(eff.entity()); + if (!(e instanceof LivingEntity le)) return; + if (!L2LibReg.EFFECT.type().isProper(le)) return; + ClientEffectCap cap = L2LibReg.EFFECT.type().get(le); + if (eff.exist()) { + cap.map.put(eff.effect(), eff.level()); + } else { + cap.map.remove(eff.effect()); + } + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java b/src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java new file mode 100644 index 0000000..efbf99d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java @@ -0,0 +1,21 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.util.raytrace.EntityTarget; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.TickEvent; + +@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class ClientGeneralEventHandler { + + @SubscribeEvent + public static void clientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + for (EntityTarget target : EntityTarget.LIST) { + target.tickRender(); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java b/src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java new file mode 100644 index 0000000..bdd48cc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java @@ -0,0 +1,100 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.base.effects.EffectToClient; +import dev.xkmc.l2core.init.L2Core; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; + +import java.util.HashSet; +import java.util.Set; + +@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class EffectSyncEvents { + + public static final Set TRACKED = new HashSet<>(); + + @SubscribeEvent + public static void onPotionAddedEvent(MobEffectEvent.Added event) { + if (TRACKED.contains(event.getEffectInstance().getEffect())) { + onEffectAppear(event.getEffectInstance().getEffect(), event.getEntity(), event.getEffectInstance().getAmplifier()); + } + } + + + @SubscribeEvent + public static void onPotionRemoveEvent(MobEffectEvent.Remove event) { + if (event.getEffectInstance() != null && TRACKED.contains(event.getEffectInstance().getEffect())) { + onEffectDisappear(event.getEffectInstance().getEffect(), event.getEntity()); + } + } + + + @SubscribeEvent + public static void onPotionExpiryEvent(MobEffectEvent.Expired event) { + if (event.getEffectInstance() != null && TRACKED.contains(event.getEffectInstance().getEffect())) { + onEffectDisappear(event.getEffectInstance().getEffect(), event.getEntity()); + } + } + + @SubscribeEvent + public static void onPlayerStartTracking(PlayerEvent.StartTracking event) { + if (!(event.getTarget() instanceof LivingEntity le)) + return; + for (MobEffect eff : le.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectAppear(eff, le, le.getActiveEffectsMap().get(eff).getAmplifier()); + } + } + } + + @SubscribeEvent + public static void onPlayerStopTracking(PlayerEvent.StopTracking event) { + if (!(event.getTarget() instanceof LivingEntity le)) + return; + for (MobEffect eff : le.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectDisappear(eff, le); + } + } + } + + @SubscribeEvent + public static void onServerPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + if (e != null) { + for (MobEffect eff : e.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectAppear(eff, e, e.getActiveEffectsMap().get(eff).getAmplifier()); + } + } + } + } + + @SubscribeEvent + public static void onServerPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + if (e != null) { + for (MobEffect eff : e.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectDisappear(eff, e); + } + } + } + } + + private static void onEffectAppear(MobEffect eff, LivingEntity e, int lv) { + if (e.level().isClientSide()) return; + L2Core.PACKET_HANDLER.toTrackingPlayers(new EffectToClient(e.getId(), eff, true, lv), e); + } + + private static void onEffectDisappear(MobEffect eff, LivingEntity e) { + if (e.level().isClientSide()) return; + L2Core.PACKET_HANDLER.toTrackingPlayers(new EffectToClient(e.getId(), eff, false, 0), e); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java b/src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java new file mode 100644 index 0000000..f14776e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java @@ -0,0 +1,67 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.base.explosion.BaseExplosion; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.serial.config.PacketHandlerWithConfig; +import dev.xkmc.l2core.util.raytrace.RayTraceUtil; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.OnDatapackSyncEvent; +import net.neoforged.neoforge.event.TickEvent; +import net.neoforged.neoforge.event.level.ExplosionEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; + +@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class GeneralEventHandler { + + @SubscribeEvent + public static void addReloadListeners(AddReloadListenerEvent event) { + PacketHandlerWithConfig.addReloadListeners(event); + } + + @SubscribeEvent + public static void onDatapackSync(OnDatapackSyncEvent event) { + PacketHandlerWithConfig.onDatapackSync(event); + } + + @SubscribeEvent + public static void serverTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + RayTraceUtil.serverTick(); + execute(); + } + + @SubscribeEvent + public static void onDetonate(ExplosionEvent.Detonate event) { + if (event.getExplosion() instanceof BaseExplosion exp) { + event.getAffectedEntities().removeIf(e -> !exp.hurtEntity(e)); + } + } + + private static List TASKS = new ArrayList<>(); + + public static synchronized void schedule(Runnable runnable) { + TASKS.add(() -> { + runnable.run(); + return true; + }); + } + + public static synchronized void schedulePersistent(BooleanSupplier runnable) { + TASKS.add(runnable); + } + + private static synchronized void execute() { + if (TASKS.isEmpty()) return; + var temp = TASKS; + TASKS = new ArrayList<>(); + temp.removeIf(BooleanSupplier::getAsBoolean); + temp.addAll(TASKS); + TASKS = temp; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/package-info.java b/src/main/java/dev/xkmc/l2core/init/events/package-info.java new file mode 100644 index 0000000..6cea80e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.events; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/package-info.java b/src/main/java/dev/xkmc/l2core/init/package-info.java new file mode 100644 index 0000000..6469391 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java new file mode 100644 index 0000000..5198eac --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java @@ -0,0 +1,29 @@ +package dev.xkmc.l2core.init.reg.datapack; + +import com.mojang.datafixers.util.Pair; +import dev.xkmc.l2core.util.Proxy; +import net.neoforged.neoforge.registries.datamaps.DataMapType; +import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public record DataMapReg(DataMapType reg) implements ValSet { + + public void register(final RegisterDataMapTypesEvent event) { + event.register(reg); + } + + @Nullable + public V get(K key) { + var registry = Proxy.getRegistryAccess().registry(reg.registryKey()).get(); + return registry.getData(reg, registry.getResourceKey(key).get()); + } + + public Stream> getAll() { + var registry = Proxy.getRegistryAccess().registry(reg.registryKey()).get(); + return registry.getDataMap(reg).entrySet().stream() + .map(e -> Pair.of(registry.get(e.getKey()), e.getValue())); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java new file mode 100644 index 0000000..e501a12 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java @@ -0,0 +1,28 @@ +package dev.xkmc.l2core.init.reg.datapack; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.util.Proxy; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.registries.DataPackRegistryEvent; + +import java.util.stream.Stream; + +public record DatapackReg(ResourceKey> key, Codec codec) implements ValSet { + + public void onRegister(DataPackRegistryEvent.NewRegistry event) { + event.dataPackRegistry(key, codec, codec); + } + + public T get(ResourceLocation id) { + return Proxy.getRegistryAccess().registry(key).get().get(id); + } + + public Stream> getAll() { + return Proxy.getRegistryAccess().registry(key).get().entrySet() + .stream().map(e -> Pair.of(e.getKey().location(), e.getValue())); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java new file mode 100644 index 0000000..c85ed64 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java @@ -0,0 +1,15 @@ +package dev.xkmc.l2core.init.reg.datapack; + +import com.mojang.datafixers.util.Pair; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public interface ValSet { + + @Nullable + V get(K k); + + Stream> getAll(); + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java new file mode 100644 index 0000000..0ec1b90 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.reg.datapack; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java b/src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java new file mode 100644 index 0000000..fb3c42f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java @@ -0,0 +1,166 @@ +package dev.xkmc.l2core.init.reg.registrate; + +/*TODO +public class L2Registrate extends AbstractRegistrate { + + public L2Registrate(String modid) { + super(modid); + registerEventListeners(FMLJavaModLoadingContext.get().getModEventBus()); + } + + public , P extends T> GenericBuilder generic(RegistryInstance cls, String id, NonNullSupplier

sup) { + return entry(id, cb -> new GenericBuilder<>(this, id, cb, cls.key(), sup)); + } + + public > RegistryEntry> recipe(String id) { + return simple(id, ForgeRegistries.Keys.RECIPE_TYPES, () -> new RecipeType<>() { + }); + } + + @Deprecated + @Override + public EnchantmentBuilder enchantment(String name, EnchantmentCategory type, EnchantmentBuilder.EnchantmentFactory factory) { + return super.enchantment(name, type, factory); + } + + public EnchantmentBuilder enchantment(String name, EnchantmentCategory type, EnchantmentBuilder.EnchantmentFactory factory, String desc) { + addRawLang("enchantment." + getModid() + "." + name + ".desc", desc); + return super.enchantment(name, type, factory); + } + + public NoConfigBuilder effect(String name, NonNullSupplier sup, String desc) { + addRawLang("effect." + getModid() + "." + name + ".description", desc); + return entry(name, cb -> new NoConfigBuilder<>(this, this, name, cb, ForgeRegistries.Keys.MOB_EFFECTS, sup)); + } + + @SuppressWarnings({"unchecked", "unsafe"}) + public > RegistryInstance newRegistry(String id, Class cls, Consumer> cons) { + ResourceKey> key = makeRegistry(id, () -> { + var ans = new RegistryBuilder(); + ans.onCreate((r, s) -> new RLClassHandler<>((Class) cls, () -> r)); + cons.accept(ans); + return ans; + }); + return new RegistryInstance<>(Suppliers.memoize(() -> RegistryManager.ACTIVE.getRegistry(key)), key); + } + + public > RegistryInstance newRegistry(String id, Class cls) { + return newRegistry(id, cls, e -> { + }); + } + + public synchronized RegistryEntry buildModCreativeTab(String name, String def, Consumer config) { + ResourceLocation id = new ResourceLocation(getModid(), name); + defaultCreativeTab(ResourceKey.create(Registries.CREATIVE_MODE_TAB, id)); + return buildCreativeTabImpl(name, this.addLang("itemGroup", id, def), config); + } + + public synchronized RegistryEntry buildL2CreativeTab(String name, String def, Consumer config) { + ResourceLocation id = new ResourceLocation(L2Library.MODID, name); + defaultCreativeTab(ResourceKey.create(Registries.CREATIVE_MODE_TAB, id)); + TabSorter sorter = new TabSorter(getModid() + ":" + name, id); + return L2Library.REGISTRATE.buildCreativeTabImpl(name, this.addLang("itemGroup", id, def), b -> { + config.accept(b); + sorter.sort(b); + }); + } + + private synchronized RegistryEntry buildCreativeTabImpl(String name, Component comp, Consumer config) { + return this.generic(self(), name, Registries.CREATIVE_MODE_TAB, () -> { + var builder = CreativeModeTab.builder().title(comp) + .withTabsBefore(CreativeModeTabs.SPAWN_EGGS); + config.accept(builder); + return builder.build(); + }).register(); + } + + public record RegistryInstance>(Supplier> supplier, + ResourceKey> key) implements Supplier> { + + @Override + public IForgeRegistry get() { + return supplier().get(); + } + } + + public static class GenericBuilder, P extends T> extends AbstractBuilder> { + + private final NonNullSupplier

sup; + + GenericBuilder(L2Registrate parent, String name, BuilderCallback callback, ResourceKey> registryType, NonNullSupplier

sup) { + super(parent, parent, name, callback, registryType); + this.sup = sup; + } + + @Override + protected @NonnullType @NotNull P createEntry() { + return sup.get(); + } + + public GenericBuilder defaultLang() { + return lang(NamedEntry::getDescriptionId, RegistrateLangProvider.toEnglishName(this.getName())); + } + + } + + private static class TabSorter { + + private static final TreeMap MAP = new TreeMap<>(); + private static final HashSet SET = new HashSet<>(); + + private final ResourceLocation id; + + private TabSorter(String str, ResourceLocation id) { + MAP.put(str, this); + SET.add(id); + this.id = id; + } + + public void sort(CreativeModeTab.Builder b) { + var list = new ArrayList<>(MAP.values()); + boolean after = false; + ResourceLocation before = null; + for (var e : list) { + if (e == this) { + after = true; + if (before != null) { + b.withTabsBefore(before); + } + continue; + } + if (after) { + b.withTabsAfter(e.id); + return; + } else { + before = e.id; + } + } + for (var e : BuiltInRegistries.CREATIVE_MODE_TAB.entrySet()) { + var id = e.getKey().location(); + if (known(id) || known(e.getValue())) { + continue; + } + b.withTabsAfter(id); + } + } + + private static boolean known(ResourceLocation id) { + if (id.getNamespace().equals("minecraft")) { + return true; + } + return SET.contains(id); + } + + private static boolean known(CreativeModeTab tab) { + for (var other : tab.tabsAfter) { + if (known(other)) { + return true; + } + } + return false; + } + + } + +} +*/ \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java b/src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java new file mode 100644 index 0000000..d450f05 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java @@ -0,0 +1,40 @@ +package dev.xkmc.l2core.init.reg.registrate; + +/*TODO +public class NamedEntry> { + + private final L2Registrate.RegistryInstance registry; + + private String desc = null; + + public NamedEntry(L2Registrate.RegistryInstance registry) { + this.registry = registry; + } + + public @NotNull String getDescriptionId() { + if (desc != null) + return desc; + ResourceLocation rl = getRegistryName(); + ResourceLocation reg = registry.get().getRegistryName(); + desc = reg.getPath() + "." + rl.getNamespace() + "." + rl.getPath(); + return desc; + } + + public MutableComponent getDesc() { + return Component.translatable(getDescriptionId()); + } + + public ResourceLocation getRegistryName() { + return Objects.requireNonNull(registry.get().getKey(getThis())); + } + + public String getID() { + return getRegistryName().toString(); + } + + public T getThis() { + return Wrappers.cast(this); + } + +} +*/ diff --git a/src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java b/src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java new file mode 100644 index 0000000..1d6012c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.reg.registrate; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java new file mode 100644 index 0000000..b465c53 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java @@ -0,0 +1,82 @@ +package dev.xkmc.l2core.init.reg.simple; + +import dev.xkmc.l2core.capability.attachment.AttachmentDef; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder; +import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.AttachmentHolder; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public record AttReg(DeferredRegister> att) { + + public static AttReg of(Reg reg) { + return new AttReg(reg.make(NeoForgeRegistries.ATTACHMENT_TYPES)); + } + + public > AttVal reg(String id, T type) { + return new AttValImpl<>(att.register(id, type::type), type); + } + + public > AttVal reg(String id, Function factory) { + ResourceLocation rl = new ResourceLocation(att.getNamespace(), id); + T type = factory.apply(rl); + return reg(id, type); + } + + public , H extends AttachmentHolder> AttVal.CapVal + entity(String id, Class holder_class, Supplier sup, Class entity_class, Predicate pred) { + ResourceLocation rl = new ResourceLocation(att.getNamespace(), id); + var type = new GeneralCapabilityHolder<>(rl, holder_class, sup, entity_class, pred); + return new CapValImpl<>(att.register(id, type::type), type); + } + + public > AttVal.PlayerVal + player(String id, Class holder_class, Supplier sup, PlayerCapabilityHolder.NetworkFactory network) { + ResourceLocation rl = new ResourceLocation(att.getNamespace(), id); + var type = new PlayerCapabilityHolder<>(rl, holder_class, sup, network); + return new PlayerValImpl<>(att.register(id, type::type), type); + } + + private record AttValImpl>( + DeferredHolder, AttachmentType> val, T type + ) implements AttVal { + + @Override + public AttachmentType get() { + return val.get(); + } + + } + + private record CapValImpl>( + DeferredHolder, AttachmentType> val, GeneralCapabilityHolder type + ) implements AttVal.CapVal { + + @Override + public AttachmentType get() { + return val.get(); + } + + } + + private record PlayerValImpl>( + DeferredHolder, AttachmentType> val, PlayerCapabilityHolder type + ) implements AttVal.PlayerVal { + + @Override + public AttachmentType get() { + return val.get(); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java new file mode 100644 index 0000000..dc47c58 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java @@ -0,0 +1,25 @@ +package dev.xkmc.l2core.init.reg.simple; + +import dev.xkmc.l2core.capability.attachment.AttachmentDef; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder; +import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate; +import net.neoforged.neoforge.attachment.AttachmentHolder; +import net.neoforged.neoforge.attachment.AttachmentType; + +public interface AttVal> extends Val> { + + H type(); + + interface CapVal> + extends AttVal> { + + } + + interface PlayerVal> + extends AttVal> { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java new file mode 100644 index 0000000..d661788 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2serial.serialization.codec.MapCodecAdaptor; +import net.minecraft.core.Registry; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; + +public record CdcReg(DeferredRegister> reg) { + + public static CdcReg of(Reg parent, Registry> reg) { + return new CdcReg<>(parent.make(reg)); + } + + public CdcVal reg(String id, MapCodec codec) { + return new CdcValImpl<>(reg.register(id, () -> codec)); + } + + public CdcVal reg(String id, Class cls) { + return new CdcValImpl<>(reg.register(id, () -> MapCodecAdaptor.of(cls))); + } + + private record CdcValImpl(DeferredHolder, MapCodec> val) + implements CdcVal { + + @Override + public MapCodec get() { + return val.get(); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java new file mode 100644 index 0000000..8ec0f4d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java @@ -0,0 +1,7 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.MapCodec; + +public interface CdcVal extends Val> { + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java new file mode 100644 index 0000000..8169644 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java @@ -0,0 +1,35 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2serial.serialization.codec.MapCodecAdaptor; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +public record IngReg(DeferredRegister> reg) { + + public static IngReg of(Reg reg) { + return new IngReg(reg.make(NeoForgeRegistries.INGREDIENT_TYPES)); + } + + public IngVal reg(String id, MapCodec codec) { + return new IngValImpl<>(reg.register(id, () -> new IngredientType<>(codec))); + } + + public IngVal reg(String id, Class cls) { + return new IngValImpl<>(reg.register(id, () -> new IngredientType<>(MapCodecAdaptor.of(cls)))); + } + + private record IngValImpl(DeferredHolder, IngredientType> val) + implements IngVal { + + @Override + public IngredientType get() { + return val.get(); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java new file mode 100644 index 0000000..55da7a6 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java @@ -0,0 +1,9 @@ +package dev.xkmc.l2core.init.reg.simple; + +import net.minecraft.world.item.crafting.Ingredient; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +public interface IngVal extends Val> { + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java new file mode 100644 index 0000000..3fe18ae --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java @@ -0,0 +1,72 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.reg.datapack.DataMapReg; +import dev.xkmc.l2core.init.reg.datapack.DatapackReg; +import dev.xkmc.l2serial.serialization.codec.CodecAdaptor; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.datamaps.DataMapType; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public final class Reg { + + + + private final String modid; + + private final Map, DeferredRegister> map = new LinkedHashMap<>(); + private final List> list = new ArrayList<>(); + + public Reg(String modid) { + this.modid = modid; + } + + public DeferredRegister make(Registry reg) { + return Wrappers.cast(map.computeIfAbsent(reg, r -> DeferredRegister.create(r, modid))); + } + + public DatapackReg dataReg(String id, Codec codec) { + var ans = new DatapackReg<>(ResourceKey.createRegistryKey(id(id)), codec); + list.add(bus -> bus.addListener(ans::onRegister)); + return ans; + } + + public DatapackReg dataReg(String id, Class cls) { + return dataReg(id, new CodecAdaptor<>(cls)); + } + + public DataMapReg dataMap(DataMapType type) { + var ans = new DataMapReg<>(type); + list.add(bus -> bus.addListener(ans::register)); + return ans; + } + + public DataMapReg dataMap(String id, ResourceKey> k, Codec codec, Codec network) { + return dataMap(DataMapType.builder(id(id), k, codec).synced(network, true).build()); + } + + public DataMapReg dataMap(String id, ResourceKey> k, Class cls) { + CodecAdaptor codec = new CodecAdaptor<>(cls); + return dataMap(id, k, codec, codec); + } + + public void register(IEventBus bus) { + for (var e : map.values()) e.register(bus); + for (var e : list) e.accept(bus); + } + + public ResourceLocation id(String id) { + return ResourceLocation.fromNamespaceAndPath(modid, id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java new file mode 100644 index 0000000..26a0c54 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java @@ -0,0 +1,28 @@ +package dev.xkmc.l2core.init.reg.simple; + +import net.minecraft.core.Registry; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; + +import java.util.function.Supplier; + +public record SR(DeferredRegister reg) { + + public static SR of(Reg parent, Registry reg) { + return new SR<>(parent.make(reg)); + } + + public Val reg(String id, Supplier sup) { + return new ValImpl<>(reg.register(id, sup)); + } + + private record ValImpl(DeferredHolder val) implements Val { + + @Override + public T get() { + return val.get(); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java new file mode 100644 index 0000000..71313b3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java @@ -0,0 +1,7 @@ +package dev.xkmc.l2core.init.reg.simple; + +public interface Val { + + T get(); + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java new file mode 100644 index 0000000..6519e00 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.reg.simple; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java new file mode 100644 index 0000000..63e24a9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java @@ -0,0 +1,31 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2core.init.L2LibReg; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record BooleanValueCondition(String path, ArrayList line, boolean expected) implements ICondition { + + public static BooleanValueCondition of(String file, ModConfigSpec.ConfigValue config, boolean value) { + return new BooleanValueCondition(file, new ArrayList<>(config.getPath()), value); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof Boolean bool && bool == expected; + } + + @Override + public MapCodec codec() { + return L2LibReg.CONDITION_BOOL.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java new file mode 100644 index 0000000..c32844f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java @@ -0,0 +1,31 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2LibReg; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record DoubleValueCondition(String path, ArrayList line, double low, double high) implements ICondition { + + public static DoubleValueCondition of(String file, ModConfigSpec.ConfigValue config, double low, double high) { + return new DoubleValueCondition(file, new ArrayList<>(config.getPath()), low, high); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof Double val && low <= val && val <= high; + } + + @Override + public Codec codec() { + return L2LibReg.CONDITION_DOUBLE.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java new file mode 100644 index 0000000..0555835 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java @@ -0,0 +1,37 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record IntValueCondition(String path, ArrayList line, int low, int high) implements ICondition { + + public static final ResourceLocation ID = new ResourceLocation(L2Core.MODID, "int_config"); + + public static IntValueCondition of(String file, ModConfigSpec.ConfigValue config, int low, int high) { + return new IntValueCondition(file, new ArrayList<>(config.getPath()), low, high); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof Integer val && low <= val && val <= high; + } + + + @Override + public Codec codec() { + return L2LibReg.CONDITION_INT.get(); + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java new file mode 100644 index 0000000..45c955e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java @@ -0,0 +1,36 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; +import java.util.List; + +public record ListStringValueCondition(String path, ArrayList line, String key) implements ICondition { + + public static final ResourceLocation ID = new ResourceLocation(L2Core.MODID, "string_list_config"); + + public static ListStringValueCondition of(String file, ModConfigSpec.ConfigValue> config, String key) { + return new ListStringValueCondition(file, new ArrayList<>(config.getPath()), key); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof List val && val.contains(key); + } + + @Override + public Codec codec() { + return L2LibReg.CONDITION_LIST_STR.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java new file mode 100644 index 0000000..8005ad5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java @@ -0,0 +1,35 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record StringValueCondition(String path, ArrayList line, String key) implements ICondition { + + public static final ResourceLocation ID = new ResourceLocation(L2Core.MODID, "string_config"); + + public static StringValueCondition of(String file, ModConfigSpec.ConfigValue config, String key) { + return new StringValueCondition(file, new ArrayList<>(config.getPath()), key); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof String val && val.equals(key); + } + + @Override + public Codec codec() { + return L2LibReg.CONDITION_STR.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java b/src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java new file mode 100644 index 0000000..2f1dbb0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.conditions; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java new file mode 100644 index 0000000..59da859 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java @@ -0,0 +1,65 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.resources.ResourceLocation; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +@Deprecated(forRemoval = true) +@SerialClass +public class BaseConfig { + + protected ResourceLocation id; + + public ResourceLocation getID() { + return id; + } + + /** + * called by Config Merger after it's merged + */ + protected void postMerge() { + } + + public static HashSet collectSet(List list, Function> getter) { + return list.stream().reduce(new HashSet(), (a, c) -> { + a.addAll(getter.apply(c)); + return a; + }, (a, b) -> { + a.addAll(b); + return a; + }); + } + + public static ArrayList collectList(List list, Function> getter) { + return list.stream().reduce(new ArrayList<>(), (a, c) -> { + a.addAll(getter.apply(c)); + return a; + }, (a, b) -> { + a.addAll(b); + return a; + }); + } + + public static HashMap collectMap(List list, Function> getter, Supplier gen, BiConsumer merger) { + return list.stream().reduce(new HashMap<>(), (a, c) -> { + getter.apply(c).forEach((k, v) -> merger.accept(a.computeIfAbsent(k, e -> gen.get()), v)); + return a; + }, (a, b) -> { + b.forEach((k, v) -> merger.accept(a.computeIfAbsent(k, e -> gen.get()), v)); + return a; + }); + } + + public static HashMap overrideMap(List list, Function> getter) { + HashMap ans = new HashMap<>(); + for (C c : list) { + ans.putAll(getter.apply(c)); + } + return ans; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java new file mode 100644 index 0000000..4e56d5f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java @@ -0,0 +1,29 @@ +package dev.xkmc.l2core.serial.config; + +import net.minecraft.resources.ResourceLocation; + +import java.util.HashMap; +import java.util.Map; + +public class BaseConfigType { + + public final Class cls; + public final String id; + public final PacketHandlerWithConfig parent; + + final Map configs = new HashMap<>(); + + protected BaseConfigType(PacketHandlerWithConfig parent, String id, Class cls) { + this.parent = parent; + this.id = id; + this.cls = cls; + } + + public void beforeReload() { + configs.clear(); + } + + public void afterReload() { + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/CollectType.java b/src/main/java/dev/xkmc/l2core/serial/config/CollectType.java new file mode 100644 index 0000000..07bbc34 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/CollectType.java @@ -0,0 +1,5 @@ +package dev.xkmc.l2core.serial.config; + +public enum CollectType { + OVERWRITE, COLLECT, MAP_COLLECT, MAP_OVERWRITE +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java new file mode 100644 index 0000000..c56cb5d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java @@ -0,0 +1,17 @@ +package dev.xkmc.l2core.serial.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target(FIELD) +public @interface ConfigCollect { + + CollectType value(); + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java new file mode 100644 index 0000000..4e62ee7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java @@ -0,0 +1,69 @@ +package dev.xkmc.l2core.serial.config; + +import com.google.gson.JsonElement; +import dev.xkmc.l2serial.serialization.codec.JsonCodec; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.DataProvider; +import net.minecraft.resources.ResourceLocation; + +import javax.annotation.Nullable; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public abstract class ConfigDataProvider implements DataProvider { + + private final DataGenerator generator; + private final String name; + + private final Map> map = new HashMap<>(); + + public ConfigDataProvider(DataGenerator generator, String name) { + this.generator = generator; + this.name = name; + } + + public abstract void add(Collector map); + + @Override + public CompletableFuture run(CachedOutput cache) { + Path folder = generator.getPackOutput().getOutputFolder(); + add(new Collector(map)); + List> list = new ArrayList<>(); + map.forEach((k, v) -> { + JsonElement elem = v.serialize(); + if (elem != null) { + Path path = folder.resolve(k + ".json"); + list.add(DataProvider.saveStable(cache, elem, path)); + } + }); + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)); + } + + @Override + public String getName() { + return name; + } + + public record Collector(Map> map) { + + public void add(ConfigTypeEntry type, ResourceLocation id, T config) { + map.put(type.asPath(id), new ConfigEntry<>(type, id, config)); + } + + } + + public record ConfigEntry(ConfigTypeEntry type, ResourceLocation id, T config) { + + @Nullable + public JsonElement serialize() { + return JsonCodec.toJson(config, type.cls()); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java new file mode 100644 index 0000000..b2d416f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java @@ -0,0 +1,8 @@ +package dev.xkmc.l2core.serial.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ConfigLoadOnStart { +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java new file mode 100644 index 0000000..8ce2c19 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java @@ -0,0 +1,98 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.serialization.type_cache.ClassCache; +import dev.xkmc.l2serial.serialization.type_cache.FieldCache; +import dev.xkmc.l2serial.serialization.type_cache.TypeInfo; +import dev.xkmc.l2serial.util.Wrappers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ConfigMerger { + + private final ClassCache cache; + + public ConfigMerger(Class cls) { + this.cache = ClassCache.get(cls); + } + + public T merge(List list) throws Exception { + T ans = (T) cache.create(); + for (FieldCache field : cache.getFields()) { + ConfigCollect collect = field.getAnnotation(ConfigCollect.class); + if (collect == null) continue; + switch (collect.value()) { + case OVERWRITE -> { + int n = list.size(); + if (n > 0) { + field.set(ans, field.get(list.get(n - 1))); + } + } + case COLLECT -> { + TypeInfo info = field.toType(); + assert Collection.class.isAssignableFrom(info.getAsClass()); + Collection val = (Collection) info.toCache().create(); + for (T t : list) { + val.addAll((Collection) field.get(t)); + } + field.set(ans, val); + } + case MAP_COLLECT -> { + TypeInfo info = field.toType(); + TypeInfo sub = info.getGenericType(1); + assert Map.class.isAssignableFrom(info.getAsClass()); + Map val = (Map) info.toCache().create(); + + if (Collection.class.isAssignableFrom(sub.getAsClass())) { + for (T t : list) { + Map map = (Map) field.get(t); + for (Object e : map.entrySet()) { + Map.Entry ent = (Map.Entry) e; + Collection col; + if (val.containsKey(ent.getKey())) { + col = (Collection) val.get(ent.getKey()); + } else { + val.put(ent.getKey(), col = (Collection) sub.toCache().create()); + } + col.addAll((Collection) ent.getValue()); + } + } + } else if (Map.class.isAssignableFrom(sub.getAsClass())) { + for (T t : list) { + Map map = (Map) field.get(t); + for (Object e : map.entrySet()) { + Map.Entry ent = (Map.Entry) e; + Map col; + if (val.containsKey(ent.getKey())) { + col = (Map) val.get(ent.getKey()); + } else { + val.put(ent.getKey(), col = (Map) sub.toCache().create()); + } + col.putAll((Map) ent.getValue()); + } + } + } + field.set(ans, val); + } + case MAP_OVERWRITE -> { + TypeInfo info = field.toType(); + assert Map.class.isAssignableFrom(info.getAsClass()); + Map val = (Map) info.toCache().create(); + for (T t : list) { + val.putAll((Map) field.get(t)); + } + field.set(ans, val); + } + } + } + ans.postMerge(); + return ans; + } + + public T apply(Collection s) { + return Wrappers.get(() -> this.merge(new ArrayList<>(s))); + } +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java new file mode 100644 index 0000000..1e7b7f9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java @@ -0,0 +1,36 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; + +public record ConfigTypeEntry(PacketHandlerWithConfig channel, String name, Class cls) { + + public ConfigTypeEntry(PacketHandlerWithConfig channel, String name, Class cls) { + this.channel = channel; + this.name = name; + this.cls = cls; + channel.addCachedConfig(name, cls); + } + + public String asPath(ResourceLocation rl) { + return "data/" + rl.getNamespace() + "/" + channel.config_path + "/" + name + "/" + rl.getPath(); + } + + public T getMerged() { + MergedConfigType type = Wrappers.cast(channel.types.get(name)); + return type.load(); + } + + public Collection getAll() { + MergedConfigType type = Wrappers.cast(channel.types.get(name)); + return type.configs.values(); + } + + public T getEntry(ResourceLocation id) { + MergedConfigType type = Wrappers.cast(channel.types.get(name)); + return type.configs.get(id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java b/src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java new file mode 100644 index 0000000..9f0efd4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java @@ -0,0 +1,30 @@ +package dev.xkmc.l2core.serial.config; + +import net.minecraft.resources.ResourceLocation; + +public class MergedConfigType extends BaseConfigType { + + private T result; + + MergedConfigType(PacketHandlerWithConfig parent, String id, Class cls) { + super(parent, id, cls); + } + + T load() { + if (result != null) { + return result; + } + result = new ConfigMerger<>(cls).apply(configs.values()); + result.id = new ResourceLocation(parent.name, id); + return result; + } + + @Override + public void afterReload() { + result = null; + if (cls.isAnnotationPresent(ConfigLoadOnStart.class)) { + load(); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java b/src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java new file mode 100644 index 0000000..de912cb --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java @@ -0,0 +1,143 @@ +package dev.xkmc.l2core.serial.config; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2serial.network.PacketHandler; +import dev.xkmc.l2serial.serialization.codec.JsonCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.neoforged.fml.ModList; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.OnDatapackSyncEvent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@SuppressWarnings("unused") +public class PacketHandlerWithConfig extends PacketHandler { + + static final Map INTERNAL = new ConcurrentHashMap<>(); + + public static void onDatapackSync(OnDatapackSyncEvent event) { + for (PacketHandlerWithConfig handler : INTERNAL.values()) { + SyncPacket packet = new SyncPacket(handler.name, handler.configs); + if (event.getPlayer() == null) L2Core.PACKET_HANDLER.toAllClient(packet); + else L2Core.PACKET_HANDLER.toClientPlayer(packet, event.getPlayer()); + } + } + + public static void addReloadListeners(AddReloadListenerEvent event) { + for (PacketHandlerWithConfig handler : INTERNAL.values()) { + if (handler.listener != null) + event.addListener(handler.listener); + } + } + + public ArrayList configs = new ArrayList<>(); + + public final String config_path; + + final ConfigReloadListener listener; + final List listener_before = new ArrayList<>(); + final List listener_after = new ArrayList<>(); + final Map> types = new HashMap<>(); + + @SafeVarargs + public PacketHandlerWithConfig(String id, int version, Function>... values) { + super(id, version, values); + INTERNAL.put(id, this); + config_path = id + "_config"; + listener = new ConfigReloadListener(config_path); + listener_before.add(configs::clear); + } + + public void addBeforeReloadListener(Runnable runnable) { + listener_before.add(runnable); + } + + public void addAfterReloadListener(Runnable runnable) { + listener_after.add(runnable); + } + + public void addConfig(String id, Class loader) { + BaseConfigType c = new BaseConfigType<>(this, id, loader); + types.put(id, c); + addBeforeReloadListener(c::beforeReload); + addAfterReloadListener(c::afterReload); + } + + public void addCachedConfig(String id, Class loader) { + MergedConfigType c = new MergedConfigType<>(this, id, loader); + types.put(id, c); + addBeforeReloadListener(c::beforeReload); + addAfterReloadListener(c::afterReload); + } + + T getCachedConfig(String id) { + MergedConfigType type = Wrappers.cast(types.get(id)); + return type.load(); + } + + class ConfigReloadListener extends SimpleJsonResourceReloadListener { + + public ConfigReloadListener(String path) { + super(new Gson(), path); + } + + @Override + protected void apply(Map map, ResourceManager manager, ProfilerFiller filler) { + listener_before.forEach(Runnable::run); + map.forEach((k, v) -> { + if (!k.getNamespace().startsWith("_")) { + if (!ModList.get().isLoaded(k.getNamespace())) { + return; + } + } + String id = k.getPath().split("/")[0]; + if (types.containsKey(id)) { + String name = k.getPath().substring(id.length() + 1); + ResourceLocation nk = new ResourceLocation(k.getNamespace(), name); + addJson(types.get(id), nk, v); + } + }); + listener_after.forEach(Runnable::run); + } + + private void addJson(BaseConfigType type, ResourceLocation k, JsonElement v) { + T config = JsonCodec.from(v, type.cls, null); + if (config != null) { + addConfig(type, k, config); + } + } + + private void addConfig(BaseConfigType type, ResourceLocation k, T config) { + config.id = k; + type.configs.put(k, config); + configs.add(new ConfigInstance(type.id, k, config)); + } + + /** + * Called on client side only + */ + public void apply(ArrayList list) { + listener_before.forEach(Runnable::run); + for (var e : list) { + addConfig(types.get(e.name), e.id(), Wrappers.cast(e.config)); + } + listener_after.forEach(Runnable::run); + } + } + + public record ConfigInstance(String name, ResourceLocation id, BaseConfig config) { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java b/src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java new file mode 100644 index 0000000..bf54bbe --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java @@ -0,0 +1,48 @@ +package dev.xkmc.l2core.serial.config; + +import com.google.gson.JsonElement; +import dev.xkmc.l2serial.serialization.codec.JsonCodec; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.DataProvider; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public abstract class RecordDataProvider implements DataProvider { + private final DataGenerator generator; + private final String name; + private final Map map = new HashMap<>(); + + public RecordDataProvider(DataGenerator generator, String name) { + this.generator = generator; + this.name = name; + } + + public abstract void add(BiConsumer map); + + public CompletableFuture run(CachedOutput cache) { + Path folder = this.generator.getPackOutput().getOutputFolder(); + this.add(this.map::put); + List> list = new ArrayList<>(); + this.map.forEach((k, v) -> { + JsonElement elem = JsonCodec.toJson(v); + if (elem != null) { + Path path = folder.resolve("data/" + k + ".json"); + list.add(DataProvider.saveStable(cache, elem, path)); + } + + }); + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)); + } + + public String getName() { + return this.name; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java b/src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java new file mode 100644 index 0000000..4055863 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.network.SerialPacketBase; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +public record SyncPacket(String id, ArrayList map) + implements SerialPacketBase { + + @Override + public void handle(@Nullable Player player) { + if (map != null) { + var handler = PacketHandlerWithConfig.INTERNAL.get(id); + handler.listener.apply(map); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/package-info.java b/src/main/java/dev/xkmc/l2core/serial/config/package-info.java new file mode 100644 index 0000000..f08f722 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.config; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java b/src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java new file mode 100644 index 0000000..79356bc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java @@ -0,0 +1,45 @@ +package dev.xkmc.l2core.serial.ingredients; + +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.core.Holder; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public record EnchantmentIngredient(Holder enchantment, int minLevel) implements ICustomIngredient { + + public static Ingredient of(Holder ench, int min) { + return new EnchantmentIngredient(ench, min).toVanilla(); + } + + @Override + public Stream getItems() { + var ench = enchantment.value(); + return IntStream.range(minLevel, ench.getMaxLevel() + 1) + .mapToObj(i -> EnchantedBookItem.createForEnchantment( + new EnchantmentInstance(enchantment, i))); + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public IngredientType getType() { + return L2LibReg.ING_ENCH.get(); + } + + public boolean test(ItemStack stack) { + return EnchantmentHelper.getEnchantmentsForCrafting(stack).getLevel(enchantment()) >= minLevel(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java b/src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java new file mode 100644 index 0000000..0f61235 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java @@ -0,0 +1,39 @@ +package dev.xkmc.l2core.serial.ingredients; + +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionContents; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +import java.util.stream.Stream; + +public record PotionIngredient(Holder potion) implements ICustomIngredient { + + @Override + public Stream getItems() { + ItemStack stack = new ItemStack(Items.POTION); + stack.set(DataComponents.POTION_CONTENTS, new PotionContents(potion)); + return Stream.of(stack); + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public IngredientType getType() { + return L2LibReg.ING_POTION.get(); + } + + public boolean test(ItemStack stack) { + PotionContents val = stack.get(DataComponents.POTION_CONTENTS); + return val != null && val.is(potion); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java b/src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java new file mode 100644 index 0000000..4d057cd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.ingredients; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java new file mode 100644 index 0000000..d4d059e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java @@ -0,0 +1,54 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.Codec; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapedRecipePattern; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class AbstractShapedRecipe> extends ShapedRecipe { + + public AbstractShapedRecipe(String group, ShapedRecipePattern pattern, ItemStack result) { + super(group, CraftingBookCategory.MISC, pattern, result); + } + + @Override + public abstract Serializer getSerializer(); + + @FunctionalInterface + public interface RecipeFactory> { + + T create(String group, ShapedRecipePattern pattern, ItemStack result); + + default T map(ShapedRecipe r) { + return create(r.getGroup(), r.pattern, r.result); + } + + } + + public static class Serializer> extends ShapedRecipe.Serializer { + + private final RecipeFactory factory; + + public Serializer(RecipeFactory factory) { + this.factory = factory; + } + + @Override + public Codec codec() { + return super.codec().xmap(factory::map, e -> e); + } + + public T fromNetwork(FriendlyByteBuf obj) { + return factory.map(super.fromNetwork(obj)); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java new file mode 100644 index 0000000..7a03753 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java @@ -0,0 +1,61 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.Codec; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.ShapelessRecipe; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class AbstractShapelessRecipe> extends ShapelessRecipe { + + public AbstractShapelessRecipe(String group, ItemStack result, NonNullList ingredients) { + super(group, CraftingBookCategory.MISC, result, ingredients); + } + + public List getJEIResult() { + return List.of(result); + } + + @Override + public abstract Serializer getSerializer(); + + @FunctionalInterface + public interface RecipeFactory> { + + T create(String group, ItemStack result, NonNullList ingredients); + + default T map(ShapelessRecipe r) { + return create(r.getGroup(), r.result, r.getIngredients()); + } + + } + + public static class Serializer> extends ShapelessRecipe.Serializer { + + private final RecipeFactory factory; + + public Serializer(RecipeFactory factory) { + this.factory = factory; + } + + @Override + public Codec codec() { + return super.codec().xmap(factory::map, e -> e); + } + + public T fromNetwork(FriendlyByteBuf obj) { + return factory.map(super.fromNetwork(obj)); + } + + } + +} + diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java new file mode 100644 index 0000000..5dde7e5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java @@ -0,0 +1,55 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.Codec; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class AbstractSmithingRecipe> extends SmithingTransformRecipe { + + public static final Ingredient TEMPLATE_PLACEHOLDER = Ingredient.EMPTY; + + public AbstractSmithingRecipe(Ingredient template, Ingredient base, Ingredient addition, ItemStack result) { + super(template, base, addition, result); + } + + @Override + public abstract Serializer getSerializer(); + + @FunctionalInterface + public interface RecipeFactory> { + + T create(Ingredient template, Ingredient base, Ingredient addition, ItemStack result); + + default T map(SmithingTransformRecipe r) { + return create(r.template, r.base, r.addition, r.result); + } + + } + + public static class Serializer> extends SmithingTransformRecipe.Serializer { + + private final RecipeFactory factory; + + public Serializer(RecipeFactory factory) { + this.factory = factory; + } + + @Override + public Codec codec() { + return super.codec().xmap(factory::map, e -> e); + } + + public T fromNetwork(FriendlyByteBuf obj) { + return factory.map(super.fromNetwork(obj)); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java new file mode 100644 index 0000000..48e17ad --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java @@ -0,0 +1,57 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.Level; + +import java.util.function.Supplier; + +public abstract class BaseRecipe, Inv extends Container> implements Recipe { + + private final RecType factory; + + public BaseRecipe(RecType fac) { + factory = fac; + } + + @Override + public abstract boolean matches(Inv inv, Level world); + + @Override + public abstract ItemStack assemble(Inv inv, RegistryAccess access); + + @Override + public abstract boolean canCraftInDimensions(int r, int c); + + public abstract ItemStack getResultItem(RegistryAccess access); + + @Override + public final RecipeSerializer getSerializer() { + return factory; + } + + @Override + public final RecipeType getType() { + return factory.type.get(); + } + + public interface RecInv> extends Container { + + } + + public static class RecType, Inv extends Container> extends RecSerializer { + + public final Supplier> type; + + public RecType(Class rec, Supplier> type) { + super(rec); + this.type = type; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java new file mode 100644 index 0000000..e52baec --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java @@ -0,0 +1,72 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.Criterion; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class BaseRecipeBuilder< + T extends BaseRecipeBuilder, + Rec extends SRec, + SRec extends BaseRecipe, + Inv extends Container + > implements RecipeBuilder { + + protected final BaseRecipe.RecType type; + protected final Rec recipe; + protected final Item result; + protected final Advancement.Builder advancement = Advancement.Builder.advancement(); + protected final Map> criteria = new LinkedHashMap<>(); + + public BaseRecipeBuilder(BaseRecipe.RecType type, Item result) { + this.type = type; + this.recipe = type.blank(); + this.result = result; + } + + @SuppressWarnings({"unchecked", "unsafe"}) + public T getThis() { + return (T) this; + } + + @Override + public T unlockedBy(String name, Criterion trigger) { + criteria.put(name, trigger); + return getThis(); + } + + @Override + public T group(@Nullable String pGroupName) { + return getThis(); + } + + @Override + public Item getResult() { + return result; + } + + @Override + public void save(RecipeOutput pvd, ResourceLocation id) { + Advancement.Builder builder = pvd.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id)) + .rewards(AdvancementRewards.Builder.recipe(id)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(builder::addCriterion); + id = new ResourceLocation(id.getNamespace(), "recipes/" + + BuiltInRegistries.RECIPE_SERIALIZER.getKey(type).getPath() + "/" + id.getPath()); + pvd.accept(id, recipe, builder.build(id)); + } + +} + diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java new file mode 100644 index 0000000..107d2ef --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java @@ -0,0 +1,48 @@ +package dev.xkmc.l2core.serial.recipe; + +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; +import net.minecraft.resources.ResourceLocation; + +public abstract class BaseRecipeCategory> implements IRecipeCategory { + + @SuppressWarnings("unchecked") + public static Class cast(Class cls) { + return (Class) cls; + } + + private final RecipeType type; + + protected IDrawable background, icon; + + public BaseRecipeCategory(ResourceLocation name, Class cls) { + this.type = new RecipeType<>(name, cls); + } + + @SuppressWarnings("unchecked") + public final C getThis() { + return (C) this; + } + + @Override + public final RecipeType getRecipeType() { + return type; + } + + @Override + public final IDrawable getBackground() { + return background; + } + + @Override + public final IDrawable getIcon() { + return icon; + } + + @Override + public abstract void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java b/src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java new file mode 100644 index 0000000..bae95a5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.serial.recipe; + +import dev.xkmc.l2core.util.MathHelper; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Recipe; +import net.neoforged.neoforge.common.conditions.ICondition; +import org.jetbrains.annotations.Nullable; + +public record ConditionalRecipeWrapper(RecipeOutput pvd, ICondition... conditions) implements RecipeOutput { + + @Override + public Advancement.Builder advancement() { + return pvd.advancement(); + } + + @Override + public void accept(ResourceLocation id, Recipe recipe, @Nullable AdvancementHolder advancement, ICondition... conditions) { + pvd.accept(id, recipe, advancement, MathHelper.merge(conditions(), conditions)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java new file mode 100644 index 0000000..f7db038 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java @@ -0,0 +1,47 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.ShapedRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapedRecipePattern; +import net.minecraft.world.level.ItemLike; + +import java.util.Objects; + +public class CustomShapedBuilder> extends ShapedRecipeBuilder { + + private final AbstractShapedRecipe.RecipeFactory factory; + + public CustomShapedBuilder(AbstractShapedRecipe.RecipeFactory factory, ItemLike result, int count) { + super(RecipeCategory.MISC, result, count); + this.factory = factory; + } + + @Override + public void save(RecipeOutput pRecipeOutput, ResourceLocation pId) { + ShapedRecipePattern shapedrecipepattern = this.ensureValid(pId); + Advancement.Builder advancement$builder = pRecipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(pId)) + .rewards(AdvancementRewards.Builder.recipe(pId)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement$builder::addCriterion); + ShapedRecipe shapedrecipe = new ShapedRecipe( + Objects.requireNonNullElse(this.group, ""), + RecipeBuilder.determineBookCategory(this.category), + shapedrecipepattern, + new ItemStack(this.result, this.count), + this.showNotification + ); + T rec = factory.map(shapedrecipe); + pRecipeOutput.accept(pId, rec, advancement$builder.build(pId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java new file mode 100644 index 0000000..c8f28fe --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java @@ -0,0 +1,44 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.ShapelessRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraft.world.level.ItemLike; + +import java.util.Objects; + +public class CustomShapelessBuilder> extends ShapelessRecipeBuilder { + + private final AbstractShapelessRecipe.RecipeFactory factory; + + public CustomShapelessBuilder(AbstractShapelessRecipe.RecipeFactory factory, ItemLike result, int count) { + super(RecipeCategory.MISC, result, count); + this.factory = factory; + } + + @Override + public void save(RecipeOutput pRecipeOutput, ResourceLocation pId) { + this.ensureValid(pId); + Advancement.Builder advancement$builder = pRecipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(pId)) + .rewards(AdvancementRewards.Builder.recipe(pId)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement$builder::addCriterion); + ShapelessRecipe shapelessrecipe = new ShapelessRecipe( + Objects.requireNonNullElse(this.group, ""), + RecipeBuilder.determineBookCategory(this.category), + new ItemStack(this.result, this.count), + this.ingredients + ); + pRecipeOutput.accept(pId, factory.map(shapelessrecipe), advancement$builder.build(pId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java new file mode 100644 index 0000000..e393484 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java @@ -0,0 +1,40 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.SmithingTransformRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; + +public class CustomSmithingBuilder> extends SmithingTransformRecipeBuilder { + + private final AbstractSmithingRecipe.RecipeFactory factory; + + public CustomSmithingBuilder(AbstractSmithingRecipe.RecipeFactory factory, + Ingredient template, + Ingredient base, + Ingredient add, + Item result) { + super(template, base, add, RecipeCategory.MISC, result); + this.factory = factory; + } + + public void save(RecipeOutput pRecipeOutput, ResourceLocation pRecipeId) { + this.ensureValid(pRecipeId); + Advancement.Builder advancement$builder = pRecipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(pRecipeId)) + .rewards(AdvancementRewards.Builder.recipe(pRecipeId)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement$builder::addCriterion); + SmithingTransformRecipe smithingtransformrecipe = new SmithingTransformRecipe(this.template, this.base, this.addition, new ItemStack(this.result)); + pRecipeOutput.accept(pRecipeId, factory.map(smithingtransformrecipe), advancement$builder.build(pRecipeId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java b/src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java new file mode 100644 index 0000000..511b35b --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.neoforged.neoforge.common.conditions.ICondition; +import org.jetbrains.annotations.Nullable; + +public record NBTRecipeWrapper(RecipeOutput pvd, ItemStack stack) implements RecipeOutput { + + @Override + public Advancement.Builder advancement() { + return pvd.advancement(); + } + + @Override + public void accept(ResourceLocation id, Recipe recipe, @Nullable AdvancementHolder advancement, ICondition... conditions) { + if (recipe instanceof ShapedRecipe r) { + r.result.setTag(stack.getTag()); + } + if (recipe instanceof ShapelessRecipe r) { + r.result.setTag(stack.getTag()); + } + pvd.accept(id, recipe, advancement, conditions); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java b/src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java new file mode 100644 index 0000000..2ed0bd5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java @@ -0,0 +1,43 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2serial.serialization.codec.CodecAdaptor; +import dev.xkmc.l2serial.serialization.codec.MapCodecAdaptor; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeInput; +import net.minecraft.world.item.crafting.RecipeSerializer; + +public class RecSerializer, I extends RecipeInput> implements RecipeSerializer { + + public final Class cls; + private final MapCodecAdaptor codec; + private final StreamCodec stream; + + public RecSerializer(Class cls) { + this.cls = cls; + this.codec = MapCodecAdaptor.of(cls); + this.stream = new CodecAdaptor<>(cls).toNetwork(); + } + + @Override + public MapCodec codec() { + return codec; + } + + @Override + public StreamCodec streamCodec() { + return stream; + } + + @SuppressWarnings("ConstantConditions") + public R blank() { + return Wrappers.get(() -> cls.getConstructor(ResourceLocation.class) + .newInstance(ResourceLocation.withDefaultNamespace("dummy"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java b/src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java new file mode 100644 index 0000000..681097e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/util/DataGenOnly.java b/src/main/java/dev/xkmc/l2core/util/DataGenOnly.java new file mode 100644 index 0000000..8316e88 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/DataGenOnly.java @@ -0,0 +1,11 @@ +package dev.xkmc.l2core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +public @interface DataGenOnly { +} diff --git a/src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java b/src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java new file mode 100644 index 0000000..ada7555 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java @@ -0,0 +1,14 @@ +package dev.xkmc.l2core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells if such method / field is and should only be used in logical server + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface DoubleSidedCall { +} diff --git a/src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java b/src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java new file mode 100644 index 0000000..9f458dd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java @@ -0,0 +1,126 @@ +package dev.xkmc.l2core.util; + +import net.minecraft.advancements.critereon.EnchantmentPredicate; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.advancements.critereon.MinMaxBounds; +import net.minecraft.advancements.critereon.StatePropertiesPredicate; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; +import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount; +import net.minecraft.world.level.storage.loot.functions.LootingEnchantFunction; +import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; +import net.minecraft.world.level.storage.loot.predicates.*; +import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; +import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; +import net.neoforged.neoforge.common.Tags; + +import java.util.Optional; + +@SuppressWarnings("unused") +public class LootTableTemplate { + + public static LootPool.Builder getPool(int roll, int bonus) { + return LootPool.lootPool().setRolls(ConstantValue.exactly(roll)).setBonusRolls(ConstantValue.exactly(0)); + } + + public static LootPoolSingletonContainer.Builder getItem(Item item, int count) { + return LootItem.lootTableItem(item) + .apply(SetItemCountFunction.setCount(ConstantValue.exactly(count))); + } + + public static LootPoolSingletonContainer.Builder getItem(Item item, int min, int max) { + return LootItem.lootTableItem(item) + .apply(SetItemCountFunction.setCount(UniformGenerator.between(min, max))); + } + + public static LootPoolSingletonContainer.Builder getItem(Item item, int min, int max, int add) { + return LootItem.lootTableItem(item) + .apply(SetItemCountFunction.setCount(UniformGenerator.between(min, max))) + .apply(LootingEnchantFunction.lootingMultiplier(UniformGenerator.between(0, add))); + } + + public static LootItemCondition.Builder byPlayer() { + return LootItemKilledByPlayerCondition.killedByPlayer(); + } + + public static LootItemCondition.Builder chance(float chance) { + return LootItemRandomChanceCondition.randomChance(chance); + } + + public static LootItemCondition.Builder chance(float chance, float add) { + return LootItemRandomChanceWithLootingCondition.randomChanceAndLootingBoost(chance, add); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, int low, int high) { + StatePropertiesPredicate.Builder builder = StatePropertiesPredicate.Builder.properties(); + builder.matchers.add(new StatePropertiesPredicate.PropertyMatcher(prop.getName(), + new StatePropertiesPredicate.RangedMatcher( + Optional.of(Integer.toString(low)), + Optional.of(Integer.toString(high))))); + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties(builder); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, int val) { + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties( + StatePropertiesPredicate.Builder.properties().hasProperty(prop, val) + ); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, boolean val) { + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties( + StatePropertiesPredicate.Builder.properties().hasProperty(prop, val) + ); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, String val) { + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties( + StatePropertiesPredicate.Builder.properties().hasProperty(prop, val) + ); + } + + public static LootPoolSingletonContainer.Builder cropDrop(Item item) { + return LootItem.lootTableItem(item).apply(ApplyBonusCount + .addBonusBinomialDistributionCount(Enchantments.BLOCK_FORTUNE, 0.57f, 3)); + } + + public static EnchantmentPredicate hasEnchantment(Enchantment enchant, int min) { + return new EnchantmentPredicate(enchant, MinMaxBounds.Ints.atLeast(min)); + } + + public static LootItemCondition.Builder shearOrSilk(boolean inverted) { + var ans = AnyOfCondition.anyOf( + MatchTool.toolMatches(ItemPredicate.Builder.item().of(Tags.Items.SHEARS)), + MatchTool.toolMatches(ItemPredicate.Builder.item().hasEnchantment(hasEnchantment(Enchantments.SILK_TOUCH, 1))) + ); + return inverted ? ans.invert() : ans; + } + + public static LootItemCondition.Builder silk(boolean inverted) { + LootItemCondition.Builder ans = MatchTool.toolMatches(ItemPredicate.Builder.item().hasEnchantment(hasEnchantment(Enchantments.SILK_TOUCH, 1))); + return inverted ? InvertedLootItemCondition.invert(ans) : ans; + } + + public static LootTable.Builder selfOrOther(Block block, Block base, Item other, int count) { + return LootTable.lootTable() + .withPool(LootTableTemplate.getPool(1, 0) + .add(LootTableTemplate.getItem(base.asItem(), 1)) + .when(ExplosionCondition.survivesExplosion()) + .when(LootTableTemplate.silk(true))) + .withPool(LootTableTemplate.getPool(1, 0) + .add(LootTableTemplate.getItem(other, count)) + .when(ExplosionCondition.survivesExplosion()) + .when(LootTableTemplate.silk(true))) + .withPool(LootTableTemplate.getPool(1, 0) + .add(LootTableTemplate.getItem(block.asItem(), 1)) + .when(LootTableTemplate.silk(false)) + ); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/util/MathHelper.java b/src/main/java/dev/xkmc/l2core/util/MathHelper.java new file mode 100644 index 0000000..3ba4186 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/MathHelper.java @@ -0,0 +1,50 @@ +package dev.xkmc.l2core.util; + +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.function.Function; + +public class MathHelper { + + public static double horSq(Vec3 vec3) { + return vec3.x * vec3.x + vec3.z * vec3.z; + } + + public static UUID getUUIDFromString(String str) { + int hash = str.hashCode(); + Random r = new Random(hash); + long l0 = r.nextLong(); + long l1 = r.nextLong(); + return new UUID(l0, l1); + } + + @SafeVarargs + public static T[] merge(T[] arr, T... vals) { + var ans = Arrays.copyOf(arr, arr.length + vals.length); + System.arraycopy(vals, 0, ans, arr.length, vals.length); + return ans; + } + + @Nullable + public static T pick(List list, Function func, double random) { + int total = 0; + for (T t : list) { + total += func.apply(t); + } + double val = random * total; + for (T t : list) { + val -= func.apply(t); + if (val < 0) { + return t; + } + } + return null; + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/util/Proxy.java b/src/main/java/dev/xkmc/l2core/util/Proxy.java new file mode 100644 index 0000000..f6bbea3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/Proxy.java @@ -0,0 +1,30 @@ +package dev.xkmc.l2core.util; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.MinecraftServer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.server.ServerLifecycleHooks; + +import java.util.Optional; + +public class Proxy { + + public static RegistryAccess getRegistryAccess() { + if (FMLEnvironment.dist == Dist.CLIENT) { + return Minecraft.getInstance().level.registryAccess(); + } + return ServerLifecycleHooks.getCurrentServer().registryAccess(); + } + + public static Optional getServer() { + return Optional.ofNullable(ServerLifecycleHooks.getCurrentServer()); + } + + public static LocalPlayer getClientPlayer() { + return Minecraft.getInstance().player; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/util/ServerOnly.java b/src/main/java/dev/xkmc/l2core/util/ServerOnly.java new file mode 100644 index 0000000..4c0fbd5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/ServerOnly.java @@ -0,0 +1,14 @@ +package dev.xkmc.l2core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells if such method / field is and should only be used in logical server + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface ServerOnly { +} diff --git a/src/main/java/dev/xkmc/l2core/util/package-info.java b/src/main/java/dev/xkmc/l2core/util/package-info.java new file mode 100644 index 0000000..8d6d04d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.util; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..d476d69 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,67 @@ +public net.minecraft.world.item.crafting.ShapedRecipe result +public net.minecraft.world.item.crafting.ShapedRecipe pattern +public net.minecraft.world.item.crafting.ShapelessRecipe result +public net.minecraft.world.item.crafting.SmithingTransformRecipe result +public net.minecraft.world.item.crafting.SmithingTransformRecipe template # template +public net.minecraft.world.item.crafting.SmithingTransformRecipe base +public net.minecraft.world.item.crafting.SmithingTransformRecipe addition + +public net.minecraft.data.recipes.ShapedRecipeBuilder criteria # criteria +public net.minecraft.data.recipes.ShapedRecipeBuilder group # group +public net.minecraft.data.recipes.ShapedRecipeBuilder category # category +public net.minecraft.data.recipes.ShapedRecipeBuilder result # result +public net.minecraft.data.recipes.ShapedRecipeBuilder count # count +public net.minecraft.data.recipes.ShapedRecipeBuilder showNotification # showNotification +public net.minecraft.data.recipes.ShapedRecipeBuilder ensureValid(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/world/item/crafting/ShapedRecipePattern; # ensureValid +public net.minecraft.data.recipes.ShapelessRecipeBuilder count # count +public net.minecraft.data.recipes.ShapelessRecipeBuilder result # result +public net.minecraft.data.recipes.ShapelessRecipeBuilder category # category +public net.minecraft.data.recipes.ShapelessRecipeBuilder criteria # criteria +public net.minecraft.data.recipes.ShapelessRecipeBuilder ingredients # ingredients +public net.minecraft.data.recipes.ShapelessRecipeBuilder group # group +public net.minecraft.data.recipes.ShapelessRecipeBuilder ensureValid(Lnet/minecraft/resources/ResourceLocation;)V # ensureValid +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder criteria # criteria +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder category # category +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder result # result +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder addition # addition +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder base # base +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder template # template +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder ensureValid(Lnet/minecraft/resources/ResourceLocation;)V # ensureValid + +public net.minecraft.advancements.critereon.StatePropertiesPredicate$Builder matchers # matchers +public net.minecraft.advancements.critereon.StatePropertiesPredicate$PropertyMatcher +public net.minecraft.advancements.critereon.StatePropertiesPredicate$RangedMatcher + +public net.minecraft.world.entity.LivingEntity onEffectAdded(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)V # onEffectAdded +public net.minecraft.world.entity.LivingEntity onEffectUpdated(Lnet/minecraft/world/effect/MobEffectInstance;ZLnet/minecraft/world/entity/Entity;)V # onEffectUpdated +public net.minecraft.world.entity.LivingEntity onEffectRemoved(Lnet/minecraft/world/effect/MobEffectInstance;)V # onEffectRemoved +public net.minecraft.world.entity.LivingEntity activeEffects # activeEffects +public net.minecraft.world.entity.LivingEntity effectsDirty # effectsDirty +public net.minecraft.server.level.ServerPlayer onEffectAdded(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)V # onEffectAdded +public net.minecraft.server.level.ServerPlayer onEffectUpdated(Lnet/minecraft/world/effect/MobEffectInstance;ZLnet/minecraft/world/entity/Entity;)V # onEffectUpdated +public net.minecraft.server.level.ServerPlayer onEffectRemoved(Lnet/minecraft/world/effect/MobEffectInstance;)V # onEffectRemoved +public net.minecraft.world.effect.MobEffectInstance duration # duration +public net.minecraft.world.effect.MobEffectInstance amplifier # amplifier +public net.minecraft.world.effect.MobEffectInstance ambient # ambient +public net.minecraft.world.effect.MobEffectInstance visible # visible +public net.minecraft.world.effect.MobEffectInstance showIcon # showIcon + +public net.minecraft.client.renderer.LevelRenderer entityRenderDispatcher # entityRenderDispatcher +public net.minecraft.client.renderer.RenderStateShard ADDITIVE_TRANSPARENCY # ADDITIVE_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard NO_DEPTH_TEST # NO_DEPTH_TEST +public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_GLINT_SHADER # RENDERTYPE_ENTITY_GLINT_SHADER + + +# public net.minecraft.advancements.Advancement$Builder (Z)V # Builder +# public net.minecraft.advancements.Advancement$Builder (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/advancements/DisplayInfo;Lnet/minecraft/advancements/AdvancementRewards;Ljava/util/Map;[[Ljava/lang/String;Z)V # Builder + +# public net.minecraft.client.gui.Gui f_92986_ # minecraft +# public net.minecraft.client.gui.Gui f_92977_ # screenWidth +# public net.minecraft.client.gui.Gui f_92978_ # screenHeight + +public net.minecraft.world.SimpleContainer items # items +public net.minecraft.world.inventory.AbstractContainerMenu addDataSlot(Lnet/minecraft/world/inventory/DataSlot;)Lnet/minecraft/world/inventory/DataSlot; # addDataSlot +public net.minecraft.world.entity.player.Inventory hasRemainingSpaceForItem(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z # hasRemainingSpaceForItem + +public net.minecraft.world.level.Level getDestroyType(Lnet/minecraft/world/level/GameRules$Key;)Lnet/minecraft/world/level/Explosion$BlockInteraction; # getDestroyType + diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..5955eec --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,23 @@ +modLoader = "javafml" #mandatory +loaderVersion = "${loader_version_range}" #mandatory +license = "${mod_license}" +[[mods]] #mandatory +modId = "${mod_id}" #mandatory +version = "${mod_version}" #mandatory +displayName = "${mod_name}" #mandatory +authors = "${mod_authors}" #optional +description = '''${mod_description}''' +[[mixins]] +config="${mod_id}.mixins.json" +[[dependencies.${ mod_id }]] #optional +modId = "neoforge" #mandatory +type = "required" #mandatory +versionRange = "${neo_version_range}" #mandatory +ordering = "NONE" +side = "BOTH" +[[dependencies.${ mod_id }]] +modId = "minecraft" +type = "required" +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/src/main/resources/assets/l2library/lang/zh_cn.json b/src/main/resources/assets/l2library/lang/zh_cn.json new file mode 100644 index 0000000..3b78dd5 --- /dev/null +++ b/src/main/resources/assets/l2library/lang/zh_cn.json @@ -0,0 +1,5 @@ +{ + "generic.attack_range": "攻击距离", + "generic.reachDistance": "触及距离", + "forge.swimSpeed": "游泳速度" +} \ No newline at end of file diff --git a/src/main/resources/l2core.mixins.json b/src/main/resources/l2core.mixins.json new file mode 100644 index 0000000..2c6ac9c --- /dev/null +++ b/src/main/resources/l2core.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "priority": 1000, + "package": "dev.xkmc.l2core.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + }, + "minVersion": "0.8" +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..b18e3cd --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "examplemod resources", + "pack_format": 8 + } +} diff --git a/src/test/java/organize/GUIGenerator.java b/src/test/java/organize/GUIGenerator.java new file mode 100644 index 0000000..910e5e7 --- /dev/null +++ b/src/test/java/organize/GUIGenerator.java @@ -0,0 +1,267 @@ +package organize; + +import com.google.common.io.Files; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GUIGenerator { + + public static void main(String[] args) throws Exception { + new GUIGenerator("l2library").gen(); + } + + private class Comp { + + private final String name; + private final Item it; + private final int x, y, rx, ry; + + private Comp(String str, JsonObject e) { + name = str; + it = ITEM_MAP.get(e.get("sprite").getAsString()); + x = e.get("x").getAsInt(); + y = e.get("y").getAsInt(); + rx = getInt(e, "rx", 1); + ry = getInt(e, "ry", 1); + } + + @Override + public String toString() { + return name; + } + + private void draw(Graphics g, int cx, int cy) throws IOException { + for (int i = 0; i < rx; i++) + for (int j = 0; j < ry; j++) + g.drawImage(it.getImg(), cx + i * it.w, cy + j * it.h, null); + } + + private int gety0() { + return y - it.h / 2; + } + + private int gety1() { + return gety0() + ry * it.h; + } + + } + + private class Item { + + private final String name, app; + private final int w, h, dx, dy; + + private BufferedImage bimg; + + private Item(String str, String appe, JsonObject e) { + ITEM_MAP.put(appe == null ? str : str + appe, this); + name = str; + app = appe; + w = e.get("w").getAsInt(); + h = e.get("h").getAsInt(); + dx = getInt(e, "dx", 0); + dy = getInt(e, "dy", 0); + } + + @Override + public String toString() { + return app == null ? name : name + app; + } + + private BufferedImage getImg() throws IOException { + if (bimg != null) + return bimg; + String path = GUI + "-templates/sprites/" + name; + if (app != null) + path += "/" + app; + path += ".png"; + return bimg = ImageIO.read(new File(path)); + } + + } + + private final String GUI, DST, CONT, CDST; + + GUIGenerator(String modid) { + GUI = "./src/test/resources/" + modid + "/gui/"; + DST = "./src/test/resources/" + modid + "/assets/textures/gui/"; + CDST = "./src/test/resources/" + modid + "/data/" + modid + "/gui/"; + CONT = GUI + "-templates/container/" + modid + "/"; + } + + private final Map ITEM_MAP = new HashMap<>(); + + void gen() throws IOException { + readSprites(); + File f = new File(CONT); + Item top = ITEM_MAP.get("top"); + Item middle = ITEM_MAP.get("middle"); + for (File fi : f.listFiles()) { + JsonObject e = readJsonFile(fi.getPath()).getAsJsonObject(); + JsonObject out = new JsonObject(); + List side = new ArrayList<>(); + List comp = new ArrayList<>(); + int height = 0; + if (e.has("height")) { + height = e.get("height").getAsInt(); + } + Item bottom = ITEM_MAP.get(e.get("isContainer").getAsBoolean() ? "bottom" : "bottom_screen"); + e.get("side").getAsJsonArray().forEach(s -> side.add(ITEM_MAP.get(s.getAsString()))); + for (Map.Entry ent : e.get("comp").getAsJsonObject().entrySet()) + comp.add(new Comp(ent.getKey(), ent.getValue().getAsJsonObject())); + int y0 = 0, y1 = 0; + for (Comp c : comp) { + y0 = Math.min(y0, c.gety0()); + y1 = Math.max(y1, c.gety1()); + } + if (top.h + y1 - y0 + bottom.h < height) { + y1 = height - bottom.h - top.h + y0; + } + out.addProperty("height", top.h + y1 - y0 + bottom.h); + BufferedImage bimg = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB); + Graphics g = bimg.getGraphics(); + g.drawImage(top.getImg(), 0, 0, null); + for (int i = 0; i < y1 - y0; i++) + g.drawImage(middle.getImg(), 0, top.h + i, null); + g.drawImage(bottom.getImg(), 0, top.h + y1 - y0, null); + JsonObject jarr = new JsonObject(); + for (Comp c : comp) { + int cx = c.x - c.it.w / 2; + int cy = c.y - c.it.h / 2 - y0 + top.h; + c.draw(g, cx, cy); + JsonObject co = new JsonObject(); + co.addProperty("x", cx + c.it.dx); + co.addProperty("y", cy + c.it.dy); + co.addProperty("w", c.it.w); + co.addProperty("h", c.it.h); + co.addProperty("rx", c.rx); + co.addProperty("ry", c.ry); + jarr.add(c.name, co); + } + out.add("comp", jarr); + int dx = 0, dy = 0; + Item pre = null; + JsonObject jside = new JsonObject(); + for (Item s : side) { + JsonObject so = new JsonObject(); + if (pre != null) { + if (pre.h == s.h && top.w + dx + pre.w + s.w < 256) { + dx += pre.w; + } else { + dx = 0; + dy += pre.h; + } + } + so.addProperty("x", top.w + dx); + so.addProperty("y", dy); + so.addProperty("w", s.w); + so.addProperty("h", s.h); + jside.add(s.toString(), so); + g.drawImage(s.getImg(), top.w + dx, dy, null); + pre = s; + } + out.add("side", jside); + g.dispose(); + File fx = new File(DST + "container/" + fi.getName().split("\\.")[0] + ".png"); + check(fx); + ImageIO.write(bimg, "PNG", fx); + write(DST + "coords/" + fi.getName(), out); + write(CDST + "coords/" + fi.getName(), out); + + } + + } + + private int getInt(JsonObject e, String key, int def) { + return e.has(key) ? e.get(key).getAsInt() : def; + } + + private void readSprites() throws IOException { + JsonElement e = readJsonFile(GUI + "-templates/info.json"); + e.getAsJsonObject().entrySet().forEach(ent -> { + String name = ent.getKey(); + JsonObject o = ent.getValue().getAsJsonObject(); + if (o.has("ids")) + o.get("ids").getAsJsonArray().forEach(ele -> new Item(name, ele.getAsString(), o)); + else + new Item(name, null, o); + }); + } + + private void write(String path, JsonObject obj) throws IOException { + File fy = new File(path); + check(fy); + JsonWriter jw = new JsonWriter(Files.newWriter(fy, Charset.defaultCharset())); + jw.setLenient(true); + jw.setIndent("\t"); + Streams.write(obj, jw); + jw.close(); + } + + public static void check(File f) throws IOException { + if (!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + if (!f.exists()) + f.createNewFile(); + } + + private static void delete(File f) { + if (!f.exists()) + return; + if (f.isDirectory()) + for (File fi : f.listFiles()) + delete(fi); + f.delete(); + } + + private static Map> readJson(String path) throws IOException { + JsonElement e = readJsonFile(path); + Map> ans = new HashMap<>(); + e.getAsJsonObject().entrySet().forEach(ent0 -> ent0.getValue().getAsJsonObject().entrySet().forEach(ent1 -> { + String key = ent1.getKey(); + List list; + if (ans.containsKey(key)) + list = ans.get(key); + else + ans.put(key, list = new ArrayList<>()); + ent1.getValue().getAsJsonObject().entrySet().forEach(ent2 -> { + String group = ent2.getKey(); + ent2.getValue().getAsJsonArray().forEach(ent3 -> { + String name = ent3.isJsonObject() ? ent3.toString() : ent3.getAsString(); + if (name.startsWith("_") || name.startsWith("^")) + list.add(group + name); + else if (name.endsWith("_")) + list.add(name + group); + else + list.add(name); + }); + }); + })); + return ans; + } + + private static JsonElement readJsonFile(String path) throws IOException { + File f = new File(path); + JsonReader r = new JsonReader(Files.newReader(f, Charset.defaultCharset())); + JsonElement e = new JsonParser().parse(r); + r.close(); + return e; + } + + +} diff --git a/src/test/java/organize/ResourceOrganizer.java b/src/test/java/organize/ResourceOrganizer.java new file mode 100644 index 0000000..cb6ba71 --- /dev/null +++ b/src/test/java/organize/ResourceOrganizer.java @@ -0,0 +1,122 @@ +package organize; + +import com.google.common.io.Files; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import organize.sub.AssetMisc; +import organize.sub.DataMisc; +import organize.sub.LangFileOrganizer; + +import java.io.File; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public abstract class ResourceOrganizer { + + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().setLenient().create(); + + public static final Map MAP = new LinkedHashMap<>(); + public static String MODID; + public final Type type; + public final String folder; + public final String target; + + public ResourceOrganizer(Type type, String folder, String target) { + this.type = type; + this.folder = folder; + this.target = target; + MAP.put(folder, this); + } + + public static void main(String[] args) throws Exception { + new LangFileOrganizer(); + //new ItemFileOrganizer(); + //new BlockFileOrganizer(); + //new ArmorFileOrganizer(); + //new RecipeFileOrganizer(); + new AssetMisc(); + new DataMisc(); + //new ConfigFileOrganizer(); + //new GeckoMisc(); + File f = new File("./src/test/resources"); + for (File fi : f.listFiles()) { + MODID = fi.getName(); + if (!fi.isDirectory()) + continue; + for (ResourceOrganizer obj : MAP.values()) { + File fo = new File(fi.getPath() + "/" + obj.folder); + if (!fo.exists()) + continue; + obj.organize(fo); + } + } + } + + public static void delete(File f) throws Exception { + if (f.exists()) { + if (f.isDirectory()) + for (File fi : f.listFiles()) + delete(fi); + f.delete(); + } + } + + public static void check(File f) throws Exception { + if (f.exists()) { + f.delete(); + } + if (!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + f.createNewFile(); + } + + public abstract void organize(File f) throws Exception; + + public final String getTargetFolder() { + return getResourceFolder(true) + type + "/" + MODID + "/" + target; + } + + public final String getResourceFolder(boolean main) { + return (main ? "./src/main/resources/" : "./src/test/resources/"); + } + + protected String readFile(String path) { + List list = null; + try { + list = Files.readLines(new File(path), StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + String str = ""; + for (String s : list) + str += s + "\n"; + return str.replaceAll("\\^m", MODID); + } + + protected void write(String name, String cont) throws Exception { + File f = new File(name); + check(f); + PrintStream ps = new PrintStream(f, StandardCharsets.UTF_8); + ps.println(cont); + ps.close(); + } + + public enum Type { + ASSETS("assets"), DATA("data"); + + public final String side; + + Type(String side) { + this.side = side; + } + + public String toString() { + return side; + } + } + +} diff --git a/src/test/java/organize/sub/ArmorFileOrganizer.java b/src/test/java/organize/sub/ArmorFileOrganizer.java new file mode 100644 index 0000000..279f56b --- /dev/null +++ b/src/test/java/organize/sub/ArmorFileOrganizer.java @@ -0,0 +1,22 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class ArmorFileOrganizer extends ResourceOrganizer { + + public ArmorFileOrganizer() { + super(Type.ASSETS, "armor", "textures/models/armor/"); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) { + File ti = new File(getResourceFolder(true) + "assets/minecraft/" + target + fi.getName()); + check(ti); + Files.copy(fi, ti); + } + } +} diff --git a/src/test/java/organize/sub/AssetMisc.java b/src/test/java/organize/sub/AssetMisc.java new file mode 100644 index 0000000..ae26c07 --- /dev/null +++ b/src/test/java/organize/sub/AssetMisc.java @@ -0,0 +1,38 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class AssetMisc extends ResourceOrganizer { + + public AssetMisc() { + super(Type.ASSETS, "assets", ""); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) + process(fi, getTargetFolder(), ""); + } + + private void process(File f, String path, String pre) throws Exception { + if (f.getName().startsWith(".")) + return; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String next = f.getName().startsWith("-") || f.getName().startsWith("@") ? path : path + f.getName() + "/"; + String npre = f.getName().startsWith("_") ? pre + f.getName() : + f.getName().endsWith("_") ? f.getName() + pre : + f.getName().startsWith("@") ? f.getName().substring(1) + : pre; + process(fi, next, npre); + } + } else { + File t = new File(path + pre + f.getName()); + check(t); + Files.copy(f, t); + } + } +} diff --git a/src/test/java/organize/sub/BlockFileOrganizer.java b/src/test/java/organize/sub/BlockFileOrganizer.java new file mode 100644 index 0000000..5ce49ed --- /dev/null +++ b/src/test/java/organize/sub/BlockFileOrganizer.java @@ -0,0 +1,44 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class BlockFileOrganizer extends ResourceOrganizer { + + public String texture; + + public BlockFileOrganizer() { + super(Type.ASSETS, "blocks", ""); + } + + + @Override + public void organize(File f) throws Exception { + texture = getTargetFolder() + "textures/block/"; + process("", f); + } + + private void process(String prefix, File f) throws Exception { + String filename = f.getName(); + if (filename.startsWith("-") || filename.startsWith(".")) + return; + filename = f.isDirectory() ? filename : filename.split("\\.")[0]; + String name = filename.startsWith("_") ? prefix + filename : filename.endsWith("_") ? filename + prefix : filename; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String file = fi.getName(); + if (file.startsWith("-") || file.startsWith(".")) + continue; + process(name, fi); + } + return; + } + String ext = f.getName().substring(filename.length()); + File ti = new File(texture + name + ext); + check(ti); + Files.copy(f, ti); + } + +} diff --git a/src/test/java/organize/sub/DataMisc.java b/src/test/java/organize/sub/DataMisc.java new file mode 100644 index 0000000..1de2eca --- /dev/null +++ b/src/test/java/organize/sub/DataMisc.java @@ -0,0 +1,32 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class DataMisc extends ResourceOrganizer { + + public DataMisc() { + super(Type.DATA, "data", ""); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) + process(fi, getResourceFolder(true) + type + "/"); + } + + private void process(File f, String pre) throws Exception { + if (f.getName().startsWith(".")) + return; + if (f.isDirectory()) { + for (File fi : f.listFiles()) + process(fi, pre + f.getName() + "/"); + } else { + File t = new File(pre + f.getName()); + check(t); + Files.copy(f, t); + } + } +} diff --git a/src/test/java/organize/sub/GeckoMisc.java b/src/test/java/organize/sub/GeckoMisc.java new file mode 100644 index 0000000..216cc29 --- /dev/null +++ b/src/test/java/organize/sub/GeckoMisc.java @@ -0,0 +1,40 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class GeckoMisc extends ResourceOrganizer { + + public GeckoMisc() { + super(Type.ASSETS, "gecko", ""); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) + process(fi); + } + + private void process(File f) throws Exception { + if (f.getName().startsWith(".")) + return; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + process(fi); + } + } else { + String name = f.getName(); + String path = getTargetFolder(); + if (name.endsWith("animation.json")) + path += "animations/"; + else if (name.endsWith("geo.json")) + path += "geo/"; + else path += "textures/gecko/"; + File t = new File(path + f.getName()); + check(t); + Files.copy(f, t); + } + } +} diff --git a/src/test/java/organize/sub/ItemFileOrganizer.java b/src/test/java/organize/sub/ItemFileOrganizer.java new file mode 100644 index 0000000..d64230c --- /dev/null +++ b/src/test/java/organize/sub/ItemFileOrganizer.java @@ -0,0 +1,43 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class ItemFileOrganizer extends ResourceOrganizer { + + public String texture; + + public ItemFileOrganizer() { + super(Type.ASSETS, "items", ""); + } + + + @Override + public void organize(File f) throws Exception { + texture = getTargetFolder() + "textures/item/"; + process("", f); + } + + private void process(String prefix, File f) throws Exception { + String filename = f.getName(); + if (filename.startsWith("-") || filename.startsWith(".")) + return; + filename = f.isDirectory() ? filename : filename.split("\\.")[0]; + String name = filename.startsWith("_") ? prefix + filename : filename.endsWith("_") ? filename + prefix : filename; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String file = fi.getName(); + if (file.startsWith("-") || file.startsWith(".")) + continue; + process(name, fi); + } + return; + } + File ti = new File(texture + name + ".png"); + check(ti); + Files.copy(f, ti); + } + +} diff --git a/src/test/java/organize/sub/LangFileOrganizer.java b/src/test/java/organize/sub/LangFileOrganizer.java new file mode 100644 index 0000000..8998afe --- /dev/null +++ b/src/test/java/organize/sub/LangFileOrganizer.java @@ -0,0 +1,82 @@ +package organize.sub; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.datafixers.util.Pair; +import organize.ResourceOrganizer; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class LangFileOrganizer extends ResourceOrganizer { + + public LangFileOrganizer() { + super(Type.ASSETS, "lang", "lang/"); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) { + if (!fi.isDirectory()) + continue; + String name = fi.getName(); + File target = new File(getTargetFolder() + name + ".json"); + check(target); + JsonObject dst_json = new JsonObject(); + for (File fj : fi.listFiles()) { + if (!fj.getName().endsWith(".json")) continue; + JsonObject json = new JsonParser().parse(new FileReader(fj.getPath(), StandardCharsets.UTF_8)).getAsJsonObject(); + inject("", json, dst_json); + if (json.has("-cartesian")) { + JsonObject block_list = json.get("-cartesian").getAsJsonObject(); + block_list.entrySet().forEach(ent0 -> { + JsonObject block = ent0.getValue().getAsJsonObject(); + String path = block.get("path").getAsString(); + boolean reverse = block.has("reverse") && block.get("reverse").getAsBoolean(); + boolean dot = block.has("use_dot") && block.get("use_dot").getAsBoolean(); + String con = dot ? "." : "_"; + List> map = new ArrayList<>(); + for (JsonElement vector : block.get("list").getAsJsonArray()) { + if (map.isEmpty()) { + List> finalMap = map; + vector.getAsJsonObject().entrySet().forEach(ent1 -> + finalMap.add(Pair.of(ent1.getKey(), ent1.getValue().getAsString()))); + } else { + map = map.stream().flatMap(ent1 -> vector.getAsJsonObject().entrySet().stream() + .map(ent2 -> Pair.of(ent1.getFirst() + con + ent2.getKey(), + reverse ? ent2.getValue().getAsString() + ent1.getSecond() : + ent1.getSecond() + ent2.getValue().getAsString()))) + .collect(Collectors.toList()); + } + } + for (Pair pair : map) { + dst_json.addProperty(path + "." + pair.getFirst(), pair.getSecond()); + } + + }); + } + } + FileWriter w = new FileWriter(target, StandardCharsets.UTF_8); + w.write(GSON.toJson(dst_json)); + w.close(); + } + } + + private void inject(String path, JsonObject src, JsonObject dst) { + for (Map.Entry ent : src.entrySet()) { + if (ent.getKey().startsWith("-")) continue; + if (ent.getValue().isJsonObject()) { + inject(path + ent.getKey() + ".", ent.getValue().getAsJsonObject(), dst); + } else { + dst.add(path + ent.getKey(), ent.getValue()); + } + } + } +} diff --git a/src/test/java/organize/sub/RecipeFileOrganizer.java b/src/test/java/organize/sub/RecipeFileOrganizer.java new file mode 100644 index 0000000..d12cd57 --- /dev/null +++ b/src/test/java/organize/sub/RecipeFileOrganizer.java @@ -0,0 +1,118 @@ +package organize.sub; + +import com.google.common.io.Files; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import organize.ResourceOrganizer; + +import java.io.File; +import java.io.FileReader; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RecipeFileOrganizer extends ResourceOrganizer { + + public RecipeFileOrganizer() { + super(Type.DATA, "recipes", "recipes/"); + } + + @Override + public void organize(File f) throws Exception { + generate(new File(f.getPath() + "/-template")); + process("", "", f, (name, file) -> { + String fs = getTargetFolder() + name; + File ti = new File(fs + ".json"); + check(ti); + Files.copy(file, ti); + }, false); + } + + private void generate(File file) throws Exception { + if (!file.exists()) + return; + File info = new File(file.getPath() + "/-info.json"); + if (!info.exists()) + return; + Map map = new HashMap<>(); + process("", "", file, (name, f) -> { + map.put(name, readFile(f.getPath())); + }, false); + JsonElement elem = new JsonParser().parse(new FileReader(info)); + for (Map.Entry layer_0 : elem.getAsJsonObject().entrySet()) { + List list = new ArrayList<>(); + if (layer_0.getKey().startsWith("-")) { + JsonArray arr = layer_0.getValue().getAsJsonObject().get("-list").getAsJsonArray(); + for (JsonElement e : arr) { + list.add(new Pair(e.getAsString(), map)); + } + } else { + list.add(new Pair(layer_0.getKey(), map)); + } + for (Map.Entry layer_1 : layer_0.getValue().getAsJsonObject().entrySet()) { + String _name = layer_1.getKey(); + if (_name.startsWith("-")) + continue; + for (Pair pair : list) { + String name = _name; + if (name.endsWith("_")) + name = name + pair.name; + else if (name.startsWith("_")) + name = pair.name + name; + else name = name + "_" + pair.name; + File dst = new File(getTargetFolder() + name + ".json"); + check(dst); + String ans = pair.template; + for (Map.Entry layer_2 : layer_1.getValue().getAsJsonObject().entrySet()) { + ans = ans.replaceAll("\\^" + layer_2.getKey(), layer_2.getValue().getAsString()); + } + ans = ans.replaceAll("\\^m", MODID); + ans = ans.replaceAll("\\^n", _name); + PrintStream ps = new PrintStream(dst); + ps.println(ans); + ps.close(); + } + } + } + } + + private void process(String folder, String prefix, File f, ExcCons cons, boolean skip_dash) throws Exception { + String filename = f.getName(); + if (skip_dash && filename.startsWith("-") || filename.startsWith(".")) + return; + filename = f.isDirectory() ? filename : filename.split("\\.")[0]; + String name = filename.startsWith("_") ? prefix + filename : filename.endsWith("_") ? filename + prefix : filename; + String subfolder = skip_dash && !prefix.startsWith("-") && name.equals(filename) ? folder.length() == 0 ? prefix : folder + "/" + prefix : folder; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String file = fi.getName(); + if (file.startsWith("-") || file.startsWith(".")) + continue; + process(subfolder, skip_dash ? name : "", fi, cons, true); + } + return; + } + cons.accept(subfolder.length() == 0 ? name : subfolder + "/" + name, f); + } + + private static class Pair { + + private final String name; + private final String template; + + private Pair(String name, Map map) { + this.name = name; + this.template = map.get(name); + } + } + + private interface ExcCons { + + void accept(String name, File file) throws Exception; + + } + +} diff --git a/src/test/java/util/ScalePic.java b/src/test/java/util/ScalePic.java new file mode 100644 index 0000000..b811d78 --- /dev/null +++ b/src/test/java/util/ScalePic.java @@ -0,0 +1,65 @@ + +package util; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class ScalePic { + + public static void main(String[] args) throws IOException { + //scale(4, "logo1", "logo1"); + //scale(18, "run_bow"); + //scale(16, "sonic_shooter"); + + //resize(64, 0,0,"iron"); + scale(25, "curse_of_spell", "curse_of_spell"); + //resize(18, 1,1,"moonwalk"); + } + + private static void scale(String name) throws IOException { + scale(2, name, name); + scale(8, name, name + "_large"); + } + + private static void resize(int size, int x0, int y0, String name) throws IOException { + File in = new File("./temp/in/" + name + ".png"); + File out = new File("./temp/out/" + name + ".png"); + BufferedImage img = ImageIO.read(in); + int sx = img.getWidth(); + int sy = img.getHeight(); + BufferedImage ans = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < sx; x++) + for (int y = 0; y < sy; y++) { + int col = img.getRGB(x, y); + ans.setRGB(x0 + x, y0 + y, col); + } + if (!out.exists()) { + out.createNewFile(); + } + ImageIO.write(ans, "PNG", out); + } + + private static void scale(int scale, String name, String out_name) throws IOException { + File in = new File("./temp/in/" + name + ".png"); + File out = new File("./temp/out/" + out_name + ".png"); + BufferedImage img = ImageIO.read(in); + int sx = img.getWidth(); + int sy = img.getHeight(); + BufferedImage ans = new BufferedImage(sx * scale, sy * scale, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < sx; x++) + for (int y = 0; y < sy; y++) { + int col = img.getRGB(x, y); + for (int i = 0; i < scale; i++) + for (int j = 0; j < scale; j++) { + ans.setRGB(x * scale + i, y * scale + j, col); + } + } + if (!out.exists()) { + out.createNewFile(); + } + ImageIO.write(ans, "PNG", out); + } + +} diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_3.png b/src/test/resources/l2library/assets/textures/gui/container/curios_3.png new file mode 100644 index 0000000000000000000000000000000000000000..c492f177d326a599c176fdd53a0c989e6bc9c3fd GIT binary patch literal 2100 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJFt-<~dxAr*0NZ#d@3xC^)h z-tSVsxUxb&*4s_YX49NiS_j@l7_9u>xPPXr&Z14phV_5H>f4`C{P*PT+qcJq@87@w zfAUZDPs}r%?!JCq5HJ5h`2Wd2$(di7=FfhAfB%0OM&=K5?w?Y%-+X^xZS=LbQVq|4 z{IK+5o^X_*U<#W;H{$~hUI#Je1A$TvI&2M#O&B717~C@%Hc2q_U1CTvWRO|Pcp{0x zf`EpfuNn5(Jy`yw|B?Trbk4mQpNxOKX84kP?Ei6U+Cs9kMm4}9@2Letetv#`ZGHX! zx7+(Qp1%t}_p%%qJTdGGe_PVBE9ufw#^PwQS>VLoN4{XFmDdteHPRDIv& zXS-k|D5$eN5d7rM#Ny@mfoZ--XP4NYm8C3yM?(vHq8JS=QgR4dXwCZ5gSEW1CcYnzZ^! z?8TMrQ$~V<_3Ra0bF7Qrec27HtNL6g&kw|?%MAC~)iF#etRYz=H<;6Zds0w78c~*d&+KkWd5GLd;eCv`u+QN zecHdyKMczPzW=hibDjOq5g>o#mak0lv)|v}|G%e!!RGnlqLb_2Y|p8MY67gw^IJYQp3-d#7HX+o-Q<@Og>m}eDVo3q<>zc=HFP1E*7 zm%L&;YngrS?jn#Sk>2-ot-dxqn`8Ps@8Wx4LS3W!UTkiaL-Dy;#oJ$$1C#i*N#)&s zwhIP>GwN&)1fSWNd~PntZ%=o;(E1Vln&Vgg{EKsT@BcgpEGX4b)u1jmG`vHXQ2q6l z!Qb$CpJh2X#XMfL`hovi`JKfCi1 zlmYrcg)z1=U4}W}|0BDt|2STa1}EMUh=}0)`1K>V!!-WWs{419ZvQ*sW2K$Wj6bWc p?fu4ltT?V4C-(d+~0Wc^lZJ;XZL`t^mO%eS?83{1OTiSji3Mk literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_5.png b/src/test/resources/l2library/assets/textures/gui/container/curios_5.png new file mode 100644 index 0000000000000000000000000000000000000000..1e47df4cf9dffebee5bf43a03e8f50f4576dc243 GIT binary patch literal 2262 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJEaxt=bLAr*0NZ#d?~xbv_* zyyK<*ab<~q{AosM$>R%mOLyGcFYt7I!>uz-+-C8*&(`n%_mti8$oxHf_x`PT_51hl z`m}$Ye;AepeE(&2=Q{hJBS8MfEnk`D&whV@|Nou_2Ak*GpH7;8GyncR-PdKT4=nBK z`hplXaWJ@RGDNa8EEZ|dVLA}V12N|8Fq0`jRu!}jglkMG;R|Npn! z?k3OQh0nYE9+-+_m;?Af962;+^Rqj;zhb!@rfq$4cTq5Z#<|GicVd6_SQSo3eagGI zlHKI_8q4zTy6H?4Qgti0zqrCYtN7ZS-LCt+8Bc7QwkNve72{dU>~nV)-3KQ4Nbh^P zR$m*Q%`ttRcM)XC8rAn=bE_PR&&?{{{-PWhz}F^~cl+5cuuMO@Gy2PJU>S0$UW$69h9ygm_V$v zJrI0mXY#qZpak-C#|y0=!LK=f<;!23vpc>L5|F$Owg-f{hei^hOCbKOYFIv_`1l-a za4svn_#{09OQkX`nc-FZo#of$9Y>QO-r@u;31)mU)FVdQ&MBb@0Q{Dy AM*si- literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_6.png b/src/test/resources/l2library/assets/textures/gui/container/curios_6.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe91c5976d025848342efc9405db5c5ac667934 GIT binary patch literal 2340 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|GzJEa*PbqpAr*0NZ#d?~xbv_* zyyK<*ab<~q{AosM$>R%mNq5}aFYtDK!>uz-+-C8*&(`n%_mti8$oxHf_x`PT_51hl z`m}$Ye;AepeE(&2=Q{hJBS8MfEnk`9XTQI{|9?*dgU$29MJLz4*`9xYTIp`44>I!h z?wSmdEDeiA8g!Tr1adiuF+R{>Rp@3Yn8Gx{mBGS`@q`kC%ue<_AlDcwm{zapFI@}C($jV%Bd8oBR3#rm_K~`_U*>^@89|N z*SZzI`**JRS1gyqv}}e~jPoL96knUO+jYM;3vVv z>TAQZIi}C^F1`n5oHeTN#pYHy6rY<_y!}NvFw0$=RNn1pyTCI2?9S*fyMehc`{Zud z<#HG1Y<_k}7i`JaCwCVG^JkokEPf~USC3WUbkwK3i!0epp0BYi@2;E9G$B>Da{G%b z%(DiAGp;k+9GzqR?9NM&-}<`OHQ8_NTk!vzRs_EbDbd%@r-rIl$cd{*zd(hcBToMTtLbcBJK=7HJ$>-*R zGQiUv8K1PZuvA~C4H>@bzYBidzhE>t@s>bD1gAv2{C$C1=+b7sO-x~30H8-&JLOKw5oUd_~?`G-S>jy!aJzf1=);T3K0RTj0##8_R literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_3.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_3.json new file mode 100644 index 0000000..e5c1e15 --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_3.json @@ -0,0 +1,21 @@ +{ + "height": 166, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 3 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_4.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_4.json new file mode 100644 index 0000000..9909d1a --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_4.json @@ -0,0 +1,21 @@ +{ + "height": 184, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 4 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_5.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_5.json new file mode 100644 index 0000000..4312008 --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_5.json @@ -0,0 +1,21 @@ +{ + "height": 202, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 5 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_6.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_6.json new file mode 100644 index 0000000..f4d143b --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_6.json @@ -0,0 +1,21 @@ +{ + "height": 220, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 6 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_3.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_3.json new file mode 100644 index 0000000..e5c1e15 --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_3.json @@ -0,0 +1,21 @@ +{ + "height": 166, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 3 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_4.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_4.json new file mode 100644 index 0000000..9909d1a --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_4.json @@ -0,0 +1,21 @@ +{ + "height": 184, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 4 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_5.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_5.json new file mode 100644 index 0000000..4312008 --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_5.json @@ -0,0 +1,21 @@ +{ + "height": 202, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 5 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_6.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_6.json new file mode 100644 index 0000000..f4d143b --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_6.json @@ -0,0 +1,21 @@ +{ + "height": 220, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 6 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json new file mode 100644 index 0000000..e617bb3 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 3 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json new file mode 100644 index 0000000..92ddafe --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 4 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json new file mode 100644 index 0000000..f515bb4 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 5 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json new file mode 100644 index 0000000..c270943 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 6 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/info.json b/src/test/resources/l2library/gui/-templates/info.json new file mode 100644 index 0000000..52fb6c3 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/info.json @@ -0,0 +1,148 @@ +{ + "toggle_slot": { + "ids": [ + "_0", + "_1", + "_2" + ], + "w": 18, + "h": 18, + "dx": 1, + "dy": 1 + }, + "altas": { + "ids": [ + "_boost_main", + "_boost_sub", + "_stat_container", + "_head", + "_necklace", + "_body", + "_bracelet", + "_belt", + "_disabled" + ], + "w": 16, + "h": 16 + }, + "button": { + "ids": [ + "_1", + "_1p", + "_2", + "_2p" + ], + "w": 8, + "h": 8 + }, + "sort": { + "ids": [ + "_1", + "_1p" + ], + "w": 9, + "h": 11 + }, + "delete": { + "ids": [ + "_on", + "_off" + ], + "w": 18, + "h": 18 + }, + "upgrade": { + "ids": [ + "_on", + "_off" + ], + "w": 18, + "h": 18 + }, + "slider/_top": { + "w": 14, + "h": 1, + "dx": 1, + "dy": 1 + }, + "slider/_bottom": { + "w": 14, + "h": 1, + "dx": 1, + "dy": -1 + }, + "slider/_middle": { + "w": 14, + "h": 1, + "dx": 1, + "dy": 0 + }, + "slider": { + "ids": [ + "_light", + "_dark" + ], + "w": 12, + "h": 15 + }, + "exchange": { + "ids": [ + "_in", + "_out" + ], + "w": 31, + "h": 14 + }, + "slot": { + "w": 18, + "h": 18, + "dx": 1, + "dy": 1 + }, + "empty_slot": { + "w": 18, + "h": 18, + "dx": 1, + "dy": 1 + }, + "result_slot": { + "w": 26, + "h": 26, + "dx": 5, + "dy": 5 + }, + "arrow": { + "ids": [ + "_0", + "_1", + "_2", + "_3" + ], + "w": 22, + "h": 16 + }, + "fire": { + "ids": [ + "_0", + "_1" + ], + "w": 14, + "h": 14 + }, + "top": { + "w": 176, + "h": 16 + }, + "bottom": { + "w": 176, + "h": 96 + }, + "bottom_screen": { + "w": 176, + "h": 7 + }, + "middle": { + "w": 176, + "h": 1 + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_belt.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_belt.png new file mode 100644 index 0000000000000000000000000000000000000000..3ab24bb6b3a3bdcf740720ea3b4535d48920ef9f GIT binary patch literal 118 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E0C5{Q2JKlQU+u(mIV0) zGdMiEkp|?*dAc};RNP8-VE$w;Arc|Mv_s;Ly`hBx<4pxY6$XZc?<^tjT>tn1RWf+G L`njxgN@xNAEpr=j literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_body.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_body.png new file mode 100644 index 0000000000000000000000000000000000000000..ad9755f0d9bb12b90a82bafe2a911167ecc6f3a8 GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E0C5{Q2JKlQU+u(mIV0) zGdMiEkp|?bc)B=-RNP8#V31>GQ{-n;6=yrH#>TEDuxUc#0|5txZ|nlC46*x|?`KTu Q0~y2M>FVdQ&MBb@0GG-f1^@s6 literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_main.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_main.png new file mode 100644 index 0000000000000000000000000000000000000000..c6dc5838cb633235bd93498371e44b71bfcf687c GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf5DjpW2`K`$I}@uwoQ1; k6w{^YrOV?~QL!fyKp00i_>zopr02xmzTmS$7 literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_sub.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_sub.png new file mode 100644 index 0000000000000000000000000000000000000000..4e498dcdb30f8674c7a5ce134688e07da6d9a303 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD3OsG;hE;^%b*2hb1<+l zvN13NS&R%!Ktc%0W(2Y|5aR8b3@l(Z3=DQant_3N0V6`?0w%buzyf9j8>DdCsxK!Q z7?>VqhD4M^`1)8S=jZArrsOB3>Q&?xfD|y;R9FF-xv3?I3Kh9IdBs*0wn|_XRzNmL zSYJs2tfVB{Rw=?aK*2e`C{@8s&p^*W$&O1wLBXadCCw_x#SN+*$g@?-C@Cqh($_C9 zFV`zK*2^zS*Eh7ZwA42+(l;{F1**_3uFNY*tkBIXR)!b?Gsh*hIJqdZpd>RtPXT0Z zVp4u-iLH_n$Rap^xU(cP4PjGWG1OZ?59)(t^bPe4^s#A6t;oco4I~562KE=kIvbE- zR?hjk1x2aF#Xx7-8QOp?KoLVY528LOwKzYg6y%H0yv!0iBd8WMVRT*oMVYCr>N^lVQT#}Es_rTyKU4F)`{p)o6>OKhm(mSyq=3d>IJzC6$J`STxv z({BWI+3uE`Z|>-IGD0`ZmF*ZylY~Hr%bOEg!K_vr6jn$c?cw?Cwm^}Q;p~f>nQpZ# zQ34_s%~N?<<1T%-`YpxvW7DC1-#7dUU|6vF$LlWz8XpV}_+0wq$g*A~)Sx+CNchbG o<&$e!%T=~i{jS-2h)e2iys^B$F$a%PDJbiCy85}Sb4q9e0I^wCX8-^I literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_head.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_head.png new file mode 100644 index 0000000000000000000000000000000000000000..e8be58da2a80abb7ad1f4daa388bc64a2a00100b GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E0C5{Q2JKlQU+u(mIV0) zGdMiEkp|@Gdb&7wc)B=-RNP8#VED$)Bg4e=NMFLlWr2rIb7L_Rb2Jn4WM&RSGY1hSh88JS U)6^h^5TG6gPgg&ebxsLQ00vPT3;+NC literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_stat_container.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_stat_container.png new file mode 100644 index 0000000000000000000000000000000000000000..8c9df8ae826bf242c072562860c8719f6ef49523 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf{h}UWU_cgy5eO8w-r$$(!{|LIn^KK1$Q#9*6LcdYg+bNwOdilu6F008jpmX$&lK! lNb>_9?_n{iCVvfvnm?;-R{fgdHXCR^gQu&X%Q~loCIB6HLs|d; literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_0r.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_0r.png new file mode 100644 index 0000000000000000000000000000000000000000..119e81165e913d2616d19cda13e930ac3528e263 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw!3HFS-u9~jDVAa<&kznEsNqQI0P@Q`T^vI! zdbeKLDCnTb!F=I?R^LNCzUO*r-02rziT`KeKDedGf0lxZde4`8fnFQu^?Ryc5f=Tn zNK2+MuR!EivvMGVz$9ZS)`l-ktPLi|Dww!W-i&fS7b&~rZ^XW5kvn!*ZoT&UZC`=O mK2v?J@O)R6yf}0HeGJohZENall^y|Iz~JfX=d#Wzp$P!>??A@@ literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_1.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_1.png new file mode 100644 index 0000000000000000000000000000000000000000..25a3f8a0e16dea7fe494bd44a0b31c212862eb0b GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw!3HFS-u9~jDVAa<&kznEsNqQI0P;&bT^vI! zdbeIW$jP9{!*by3j%7J&`N0!qXKpH5&n);@x$`$K&*@1<#{w*ZvRgZ5d_UfN?uY1) zmi9z$|HkqPsXdQeLu2C$yE^VPHa^k(XJG7=efH{(?|F+{TBdh6y)Dz2U~#l(*+K1D lOz$rk+k7y-#%i5Yl&ikl{5`$%BNqN|O#U#zu;S9^O?kd0 z;haU`oh-NcxD3`k>fLlOehTZlg@+5cWt*xs816i(G@UY8@y!mPgBUzr{an^LB{Ts5 D6x2(} literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_3.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_3.png new file mode 100644 index 0000000000000000000000000000000000000000..a1dea1cb494f9f77eaaa97e08bcf12298d8e5c1d GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gw!3HFS-u9~jDVAa<&kznEsNqQI0P>4HT^vI! zdbeKQ$lGAR<8o2{h|rozteeb%o|g{0yqlvr$?|^M z1+o9E-#($_;#jb=`T6;uZ%1bn@{t?u;*H2HLugYu5)bpDIIMyt3J^Vn9 kx6%EDQO-i|4ed;O?w{25Qur<13bdZV)78&qol`;+0NI*D5C8xG literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/bottom.png b/src/test/resources/l2library/gui/-templates/sprites/bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..50b4f84395a1cd688fa0db5b6952f58b45bfb8a7 GIT binary patch literal 549 zcmeAS@N?(olHy`uVBq!ia0vp^8-O^0gAGXT?fg*)q*#ibJVQ8upoSx*gMoqZj;D)b zNX4ADcP?f!1&X*{{M{6qDSf%apf=)!k8ISnC)o}>l>(|i`qq4veE8zH8&5@U=+@lZ zVWFYhZ?8=#nSOfds#VicSFShTFILgn(5aTNh;v2;liDmS%=UZD&p-cssy<`c%4?-t zlb^qwd->&;*N^Wud~Xat^Eu}Au3H;FfAM=CJ;}~V!1sWQu)#$Z9}`Ale9T?<+2<`= z`K+?;r{|j+KlRsI*{^@4bod;Hb-%decL@7~wVu(B2<$FG7kviQ`@z~)&*%-${>Ms( z{l4^d^Vm(p;uUm_KyU9nvBCI{pZ|9yX19|1i4T=oAg*8g+kejL6l>)-HgT%=i>KzK z|BwwoS)+sBTQGlv)z6g+KY65Z>Y3vLpa7Zj_wnI(0ikQRZi`)iU7r8Yi*Mx*%53EB v|8jbu4~&OZS#KY{zbzfdZL>Huw3gYkaQ6Ava#LmiDLsS+C#C9DzC zHb_`sNdc^+B->Ug!Z$#{Ilm}X!A#FU&p^qJOF==wrYI%ND#*nRsvXF)RmvzSDX`Ml zFE20GD>v55FG|-pw6wI;H!#vSGSUUA&@HaaD@m--%_~-h7y>iLCAB!YD6^m>Ge1uO zWNuEQYQ=AS1sd9~ebNnaQ4cC8$t;k+tpYMDQgb3)GILY&Aigv= zv(d*YgRav#KPSH^xF9h(7365J79=5b)nI!f5%!oP+kzwx)n}s*N(D%%0+QswqQEp~ z$7Q1rPrr6tH#E;n0F!j1r;B4q1n1ishC&AvI9U39;-0G4cKkn(E}7KUCbBJDrd1>0 z&YGUZQVpVFd^3vk);qGy;QVrL`yN9!y|jgw)R@$nvK|O4K3&PVqH*PR_eWZdd+bvF z^mVrCmb?3=P29t=LFk0=ji~H3QcJyA>R9fv$IOU4S#w`db;VK+0ihdve>1okh~(Y* SqW21v!8~34T-G@yGywq6E=P9& literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_1.png b/src/test/resources/l2library/gui/-templates/sprites/button/_1.png new file mode 100644 index 0000000000000000000000000000000000000000..bc148182033dae793a79050280a89a2fd9c56693 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#VfQf2FzKOUBc5&Wd u;O=Q$bGhfO<}#)dPKy`s-u>(6XJF91Z!}?ISM^h%!3>_RelF{r5}E+1^)N;N literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_1p.png b/src/test/resources/l2library/gui/-templates/sprites/button/_1p.png new file mode 100644 index 0000000000000000000000000000000000000000..39abeae0aab2db2d47102c767a32e0864043a666 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#Vf`?^1Wvv=kE=tbX&dNG^_n7(C5ALa*x>?hW_2wdJ|k?AGWMobmZU+-sEj6 zQzOa@OE#)3iRk*qPZ)^TW)=2?ZUw4b2)|KHgTe~DWM4f`2{ks literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_2.png b/src/test/resources/l2library/gui/-templates/sprites/button/_2.png new file mode 100644 index 0000000000000000000000000000000000000000..569b121d5477e5e4fcd9b77f115a84c084606aa6 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqEX7WqAsj$Z!;#VffsgwX+p>Eoo`&Y;0_NxO0_O^OLu?H6|JG z>KHhtxSm*M_;8cFGvk?vlGigXyggD7TweaYzJ`IJ#mlCYEk5!W&6&5CJ)pjKLgbllH6={t8TS8R1-`r(z>A(Lq4T&>Q; zBe?0N;5x^sXK}agKkELkz2>&Gc>c3q?yX_rSJji4x+~Q^H=1AX1zN%2>FVdQ&MBb@ E0HBsJf&c&j literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/delete/_off.png b/src/test/resources/l2library/gui/-templates/sprites/delete/_off.png new file mode 100644 index 0000000000000000000000000000000000000000..8535ffedf7e2b0463e62bf885a0afac3cc7e9138 GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|mSQK*5Dp-y;YjHK@~3;cIEGmC zF1>t^_pkv+>qGmnX)hRN>K?w4{pDF|(K!a$=6On{d=decx1LS@%F^AzQ6HAwEBavX zdxn~Ax~zN72{+Wqh`d#5jtMya1NX6WC1-r*&{;&S;-8;i`c& zzwoJPoRx{~3k)Zp$gok+j^H;AKf)ayJCWm2qH6OQgJM@Bi335|Q}0zvY`_2GY|X*(EKXm0kfO54$=Yox?%z!u^m@aD#~hen! zq3$xv0kb~E2fNoXoH^|@Nl)oSMVo=MHFv_~H!f`QMuawCQ9wvtP<;=;@^5%Wf z7*_oqLTnA&nnjzJ$i;NNH{E8s`T374F^qYIUm2Lsut?gTnKuocggL`W#cTPSlo;q{ELMJ@N>$x2W-3wR$4s&V^=0!ZOM`f6fUA z5EkxD`7Ja5;gMzXT}`sHRhOv5bUl{2X%tka)S%xT9$+asNpI->HhQZU- K&t;ucLK6VN0X60T literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/fire/_0.png b/src/test/resources/l2library/gui/-templates/sprites/fire/_0.png new file mode 100644 index 0000000000000000000000000000000000000000..33ba616254b3d6bb8d2d6a0f2ed9e577449e7910 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK@?$++978lF zuAS)3$DqjJ?7r^m=fhi%iFUeixuq`sRJqj5`1k&10s9rN!kVvp28IhW>Rfrj_dw9_ zmSJ$AdrDv0d*O@X>4wi%UU?P2+$}9&%5>SziyJP5s#%trN?&7?-eh1>z$_ LtDnm{r-UW|`T#a% literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/fire/_1.png b/src/test/resources/l2library/gui/-templates/sprites/fire/_1.png new file mode 100644 index 0000000000000000000000000000000000000000..ab4d6ee806efeaaa4dc0cb117f83c5cfea147a06 GIT binary patch literal 318 zcmV-E0m1%>P)Nklz1r@oY$RepjL&MQ90mljqkb*l> zQN)=cRAvE=4WO{4Sb+m|@|V7zY)8hJ*KohZW3$7*2=b;=US*8vtN6(o=n&B#B1tN7I~sQV_cV_Un!vg;i7}IOj;amp6^z z_3V0ajc&X}dtCOWSx@4Eb-r0*v0S9GxHB80%_h?5-mfzw-mRFfH5x3+MJn0sIaeyR znHR(72oRZq&zOR`uV&GYqBtmma3vCi@K(=vDHaN^^GcC1<`w=JM*Da62I3ZQ7k0N` QAOHXW07*qoM6N<$f}O~Oi~s-t literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/middle.png b/src/test/resources/l2library/gui/-templates/sprites/middle.png new file mode 100644 index 0000000000000000000000000000000000000000..53c6ef8ad2b7c3dcba5eb4e42478077e56c33113 GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^8-SRRgAGX5DXR1VDVAa<&kznEsNqQI0P@v5T^vI+ zCcZsr$jD&8!Mx$j|3-lph1Hu6I)0j|3B}LEFdIw=m1ml1)(WZrWhDpyuzkpkacl|>teCqC=)otNH^)lyLbQm`573# W-N?@PnD_@|4TGnvpUXO@geCyvuPLSg literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_bottom.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..76f36aba328db406d42b50eae4190455f9617833 GIT binary patch literal 88 zcmeAS@N?(olHy`uVBq!ia0vp^d_c^|!3HGX$<1d0QY^(zo*^7SP{WbZ0p#<0x;Tb# i%uIgq?%h9spz7BxX1O~qzkCXmV(@hJb6Mw<&;$VM(-uJh literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_dark.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..61dd4b47a3b515118f39f7e70facdae7cb723e27 GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CP!3HERJk;|9QY^(zo*^7SP{WbZ0pweGx;Tbt zOicds|NsAbHYG=Ye|~1}9!DOF(pMrb%stKgGiJ^-G%zqwFgd`&(HJQ0*28OSYs=#6 b&c;x7M|qXc#4Rs?rZ9NA`njxgN@xNApYtMU literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_light.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_light.png new file mode 100644 index 0000000000000000000000000000000000000000..ede330c463e779cc5a659eb33c4894d2035041a1 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CP!3HERJk;|9QY^(zo*^7SP{WbZ0pz=Qx;Tbt zOnlqBk@J89&k>OmVR0X?8MQ^9inmdWePkC{>2c@c-LnFoa~MKRxpLk!C>YB>?O@o^ moL%v_{>PbSm7M;R2G(s~RJOid9JmK)9)qW=pUXO@geCxkKrXTX literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_middle.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_middle.png new file mode 100644 index 0000000000000000000000000000000000000000..e718f5394087bb65b8c0c80916e21b1b34e152c6 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^d_c^|!3HGX$<1d0QY^(zo*^7SP{WbZ0pyE&x;Tb# q%uF^hH~%j$By`AuOG8sLk%6J_2lGGiFEvg;B@CXfelF{r5}E)qB^25K literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_top.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_top.png new file mode 100644 index 0000000000000000000000000000000000000000..0524056f4674a84cdba673e597e7e90964169372 GIT binary patch literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^d_c^|!3HGX$<1d0QY^(zo*^7SP{WbZ0pyE#x;Tb# o%uF^hH~)X$L5YKtGm(KIdI{5_+s`Mg1Ijaay85}Sb4q9e00V*)tN;K2 literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/slot.png b/src/test/resources/l2library/gui/-templates/sprites/slot.png new file mode 100644 index 0000000000000000000000000000000000000000..95d25daaade8ce227e734a65615db9e8841f71ee GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|mSQK*5Dp-y;YjHK@{K%Q978O6 zlTFOc|DShIlY&ofsH4?NslZZ}g}DsExtX L)z4*}Q$iB}jXNF2 literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/sort/_1p.png b/src/test/resources/l2library/gui/-templates/sprites/sort/_1p.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc6ff4e4cd5bd1968742f6e34645b0e871fd479 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRY!3HGPMlP2IQY^(zo*^7SP{WbZ0p#m@x;TbN zOiljt|NsAbHk$>@m$x^v9y`E#ZB69i11;x8BqTzQ9JqA(^5G3>XQlRZF?y1CYAsI literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_0.png b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_0.png new file mode 100644 index 0000000000000000000000000000000000000000..95d25daaade8ce227e734a65615db9e8841f71ee GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|mSQK*5Dp-y;YjHK@{K%Q978O6 zlTFOc|DShItW?szJD`k>(!Nas;;qKkP?M)dN Y0&Bf(92!NVfo3pxy85}Sb4q9e0Dapd5dZ)H literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_2.png b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_2.png new file mode 100644 index 0000000000000000000000000000000000000000..a51e75eec3a2cc140a6ad0ea317dcfefb949aefe GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|mSQK*5Dp-y;YjHK@+~}F978O6 zlTFOc|DShItW?szJD`k>(!Nas;;qKkP?M;C; Xob%SZ=$=srG=ss@)z4*}Q$iB}m&+t0 literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/top.png b/src/test/resources/l2library/gui/-templates/sprites/top.png new file mode 100644 index 0000000000000000000000000000000000000000..962d4900ff8c8e5df156314f967e4479adb4e30f GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^8-Q4VgAGV-Tm2QpVJUX<4B-HR8jh3>Aivzx#W5tK z@$I#ZoCgeeTrN)j&d&SD_RYpA$0j^bdpW21zgA=DiXW}c+l3w|ep|Teg}9uOQ`B2C zLH#p5hD*LCbL29fw>%%iS~F$lq$T%u-}>BG@o?GR^$h=A4{UJ5jmj90zm(X&#w%?Z P&P7NpFU;e*B*E*!-}-OzkFT1K)Rde&85b0>OBx!#4Gdg8 zH|nk88P-e(mf1~f-1yU)_O#73ZwPyrc(vttTxg|Qf!yVjx2kg&%YSjb@WDOm-hCbg eh9hTQ_UhF>lt06m`>_>hHG`+CpUXO@geCy+4nh(D literal 0 HcmV?d00001 diff --git a/src/test/resources/l2library/gui/-templates/sprites/upgrade/_on.png b/src/test/resources/l2library/gui/-templates/sprites/upgrade/_on.png new file mode 100644 index 0000000000000000000000000000000000000000..2c972b88b2462a1326714284e3b7bbfad4f0c392 GIT binary patch literal 179 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|mSQK*5Dp-y;YjHK^0Pc$978O6 zlTFOc|DShImdKI;Vst0B3?Yu>b%7 literal 0 HcmV?d00001