diff --git a/.gitignore b/.gitignore index 61d705679..bd1ae000f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .gradle/ /.settings/ build/ -/bin/ +bin/ /.project .idea -*.log \ No newline at end of file +*.log +run_*/ \ No newline at end of file diff --git a/arclight-common/build.gradle b/arclight-common/build.gradle index 82f8d9d62..cf15ed79d 100644 --- a/arclight-common/build.gradle +++ b/arclight-common/build.gradle @@ -8,7 +8,7 @@ apply plugin: io.izzel.arclight.gradle.ArclightGradlePlugin architectury { minecraft = minecraftVersion - common("forge") + common(project.ext.supportedPlatforms) } loom { @@ -24,18 +24,6 @@ arclight { extraMapping = project(':arclight-common').file('extra_mapping.tsrg') } -java.toolchain.languageVersion = JavaLanguageVersion.of(17) - -repositories { - maven { url = 'https://repo.spongepowered.org/maven' } - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' } - maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } - maven { url = 'https://files.minecraftforge.net/maven/' } - maven { url = 'https://maven.izzel.io/releases' } - maven { url = 'https://jitpack.io/' } - mavenCentral() -} - dependencies { minecraft "com.mojang:minecraft:$minecraftVersion" mappings loom.officialMojangMappings() diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/ItemBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/ItemBridge.java index 15cc18733..4420ba734 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/ItemBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/ItemBridge.java @@ -19,7 +19,7 @@ public interface ItemBridge { return charge; } - default AbstractArrow bridge$forge$customArrow(BowItem bowItem, AbstractArrow arrow) { + default AbstractArrow bridge$forge$customArrow(BowItem bowItem, ItemStack itemStack, AbstractArrow arrow) { return arrow; } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/crafting/RecipeManagerBridge.java b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/crafting/RecipeManagerBridge.java index ab33920e9..f5abf1153 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/crafting/RecipeManagerBridge.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/bridge/core/world/item/crafting/RecipeManagerBridge.java @@ -1,6 +1,7 @@ package io.izzel.arclight.common.bridge.core.world.item.crafting; import com.google.gson.JsonElement; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.crafting.RecipeHolder; public interface RecipeManagerBridge { @@ -9,7 +10,5 @@ public interface RecipeManagerBridge { void bridge$clearRecipes(); - default boolean bridge$forge$conditionNotMet(JsonElement element) { - return false; - } + RecipeHolder bridge$platform$loadRecipe(ResourceLocation key, JsonElement element); } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ServerEntityMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ServerEntityMixin.java index 76b39a68f..8c61596a4 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ServerEntityMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/server/level/ServerEntityMixin.java @@ -249,67 +249,6 @@ public void sendChanges() { } } - /** - * @author IzzelAliz - * @reason - */ - @Overwrite - public void sendPairingData(ServerPlayer player, final Consumer> consumer) { - Mob entityinsentient; - if (this.entity.isRemoved()) { - return; - } - Packet packet = this.entity.getAddEntityPacket(); - this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0f / 360.0f); - consumer.accept(packet); - if (this.trackedDataValues != null) { - consumer.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); - } - boolean flag = this.trackDelta; - if (this.entity instanceof LivingEntity livingEntity) { - Collection collection = livingEntity.getAttributes().getSyncableAttributes(); - if (this.entity.getId() == player.getId()) { - ((ServerPlayerEntityBridge) this.entity).bridge$getBukkitEntity().injectScaledMaxHealth(collection, false); - } - if (!collection.isEmpty()) { - consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection)); - } - if (livingEntity.isFallFlying()) { - flag = true; - } - } - this.ap = this.entity.getDeltaMovement(); - if (flag && !(this.entity instanceof LivingEntity)) { - consumer.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.ap)); - } - if (this.entity instanceof LivingEntity) { - ArrayList> list = Lists.newArrayList(); - for (EquipmentSlot enumitemslot : EquipmentSlot.values()) { - ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); - if (itemstack.isEmpty()) continue; - list.add(Pair.of(enumitemslot, itemstack.copy())); - } - if (!list.isEmpty()) { - consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); - } - ((LivingEntity) this.entity).detectEquipmentUpdates(); - } - // CraftBukkit start - MC-109346: Fix for nonsensical head yaw - if (this.entity instanceof ServerPlayer) { - consumer.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F))); - } - // CraftBukkit end - if (!this.entity.getPassengers().isEmpty()) { - consumer.accept(new ClientboundSetPassengersPacket(this.entity)); - } - if (this.entity.isPassenger()) { - consumer.accept(new ClientboundSetPassengersPacket(this.entity.getVehicle())); - } - if (this.entity instanceof Mob && (entityinsentient = (Mob) this.entity).isLeashed()) { - consumer.accept(new ClientboundSetEntityLinkPacket(entityinsentient, entityinsentient.getLeashHolder())); - } - } - @Inject(method = "sendDirtyEntityData", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", ordinal = 1, target = "Lnet/minecraft/server/level/ServerEntity;broadcastAndSend(Lnet/minecraft/network/protocol/Packet;)V")) private void arclight$sendScaledHealth(CallbackInfo ci, SynchedEntityData entitydatamanager, List> list, Set set) { if (this.entity instanceof ServerPlayerEntityBridge player) { diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/animal/frog/TadpoleMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/animal/frog/TadpoleMixin.java index 4c007b140..df15883aa 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/animal/frog/TadpoleMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/animal/frog/TadpoleMixin.java @@ -1,32 +1,8 @@ package io.izzel.arclight.common.mixin.core.world.entity.animal.frog; -import io.izzel.arclight.common.bridge.core.world.server.ServerWorldBridge; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.animal.frog.Frog; import net.minecraft.world.entity.animal.frog.Tadpole; -import org.bukkit.craftbukkit.v.event.CraftEventFactory; -import org.bukkit.event.entity.CreatureSpawnEvent; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @Mixin(Tadpole.class) public abstract class TadpoleMixin { - - // @formatter:off - @Shadow protected abstract void setAge(int p_218711_); - // @formatter:on - - @Inject(method = "ageUp()V", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/frog/Tadpole;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V")) - private void arclight$transform(CallbackInfo ci, ServerLevel serverLevel, Frog frog) { - if (CraftEventFactory.callEntityTransformEvent((Tadpole) (Object) this, frog, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS).isCancelled()) { - this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled - ci.cancel(); - } else { - ((ServerWorldBridge) serverLevel).bridge$pushAddEntityReason(CreatureSpawnEvent.SpawnReason.METAMORPHOSIS); - } - } } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/monster/SpellcastingIllager_UseSpellGoalMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/monster/SpellcastingIllager_UseSpellGoalMixin.java index a66f966a5..d8de6ccdf 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/monster/SpellcastingIllager_UseSpellGoalMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/monster/SpellcastingIllager_UseSpellGoalMixin.java @@ -14,7 +14,7 @@ public abstract class SpellcastingIllager_UseSpellGoalMixin { @Shadow(aliases = {"this$0", "f_33776_", "field_7386"}, remap = false) private SpellcasterIllager outerThis; - @Shadow(aliases = "m_7269_") + @Shadow protected abstract SpellcasterIllager.IllagerSpell getSpell(); @Inject(method = "tick", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/SpellcasterIllager$SpellcasterUseSpellGoal;performSpellCasting()V")) diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/player/ServerPlayerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/player/ServerPlayerMixin.java index d316d1c29..44bf55efd 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/player/ServerPlayerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/entity/player/ServerPlayerMixin.java @@ -617,29 +617,6 @@ private Either getBedResult(BlockPos blockposit return this.containerCounter; } - @Redirect(method = "openMenu", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;closeContainer()V")) - private void arclight$skipSwitch(ServerPlayer serverPlayer) { - } - - @Inject(method = "openMenu", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/MenuProvider;createMenu(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/entity/player/Player;)Lnet/minecraft/world/inventory/AbstractContainerMenu;")) - private void arclight$invOpen(MenuProvider itileinventory, CallbackInfoReturnable cir, AbstractContainerMenu container) { - if (container != null) { - ((ContainerBridge) container).bridge$setTitle(itileinventory.getDisplayName()); - boolean cancelled = false; - ArclightCaptures.captureContainerOwner((ServerPlayer) (Object) this); - container = CraftEventFactory.callInventoryOpenEvent((ServerPlayer) (Object) this, container, cancelled); - ArclightCaptures.resetContainerOwner(); - if (container == null && !cancelled) { - if (itileinventory instanceof Container) { - ((Container) itileinventory).stopOpen((ServerPlayer) (Object) this); - } else if (ChestBlockDoubleInventoryHacks.isInstance(itileinventory)) { - ChestBlockDoubleInventoryHacks.get(itileinventory).stopOpen((ServerPlayer) (Object) this); - } - cir.setReturnValue(OptionalInt.empty()); - } - } - } - /** * @author IzzelAliz * @reason diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/BowItemMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/BowItemMixin.java index 1a69c9a97..14d32dff2 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/BowItemMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/BowItemMixin.java @@ -54,7 +54,7 @@ public void releaseUsing(ItemStack stack, Level worldIn, LivingEntity entityLivi if (!worldIn.isClientSide) { ArrowItem arrowitem = (ArrowItem) (itemstack.getItem() instanceof ArrowItem ? itemstack.getItem() : Items.ARROW); AbstractArrow abstractarrowentity = arrowitem.createArrow(worldIn, itemstack, playerentity); - abstractarrowentity = this.bridge$forge$customArrow((BowItem) (Object) this, abstractarrowentity); + abstractarrowentity = this.bridge$forge$customArrow((BowItem) (Object) this, itemstack, abstractarrowentity); abstractarrowentity.shootFromRotation(playerentity, playerentity.getXRot(), playerentity.getYRot(), 0.0F, f * 3.0F, 1.0F); if (f == 1.0F) { abstractarrowentity.setCritArrow(true); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/crafting/RecipeManagerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/crafting/RecipeManagerMixin.java index d68ac95e5..e72df5ea5 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/crafting/RecipeManagerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/item/crafting/RecipeManagerMixin.java @@ -41,6 +41,11 @@ public abstract class RecipeManagerMixin implements RecipeManagerBridge { @Shadow protected abstract > Map> byType(RecipeType p_44055_); // @formatter:on + @Override + public RecipeHolder bridge$platform$loadRecipe(ResourceLocation key, JsonElement element) { + return fromJson(key, GsonHelper.convertToJsonObject(element, "top element")); + } + /** * @author IzzelAluz * @reason @@ -62,13 +67,9 @@ protected void apply(Map objectIn, ResourceManage continue; //Forge: filter anything beginning with "_" as it's used for metadata. try { - if (this.bridge$forge$conditionNotMet(entry.getValue())) { - LOGGER.debug("Skipping loading recipe {} as it's conditions were not met", resourcelocation); - continue; - } - RecipeHolder irecipe = fromJson(resourcelocation, GsonHelper.convertToJsonObject(entry.getValue(), "top element")); + RecipeHolder irecipe = this.bridge$platform$loadRecipe(resourcelocation, entry.getValue()); if (irecipe == null) { - LOGGER.info("Skipping loading recipe {} as it's serializer returned null", resourcelocation); + LOGGER.debug("Skipping loading recipe {} as it's conditions were not met", resourcelocation); continue; } map.computeIfAbsent(irecipe.value().getType(), (recipeType) -> new Object2ObjectLinkedOpenHashMap<>()) diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/ChestBlock2_1Mixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/ChestBlock2_1Mixin.java index 7551c1bc8..1300f9388 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/ChestBlock2_1Mixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/ChestBlock2_1Mixin.java @@ -8,7 +8,7 @@ @Mixin(targets = "net/minecraft/world/level/block/ChestBlock$2$1") public class ChestBlock2_1Mixin { - @Shadow(aliases = {"f_51614_", "val$container"}) private Container container; + @Shadow(aliases = {"f_51614_", "val$container", "field_17360"}) private Container container; public CompoundContainer inventorylargechest = (CompoundContainer) container; } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/FireBlockMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/FireBlockMixin.java index 197b25008..f4069a03d 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/FireBlockMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/level/block/FireBlockMixin.java @@ -60,21 +60,6 @@ public abstract class FireBlockMixin extends BaseFireBlockMixin implements FireB return false; } - @Inject(method = "checkBurnOut", require = 0, cancellable = true, at = @At(value = "INVOKE", ordinal = 1, target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;")) - private void arclight$blockBurn(Level worldIn, BlockPos pos, int chance, RandomSource random, int age, CallbackInfo ci) { - Block theBlock = CraftBlock.at(worldIn, pos); - Block sourceBlock = CraftBlock.at(worldIn, ArclightCaptures.getTickingPosition()); - BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock); - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - ci.cancel(); - return; - } - if (worldIn.getBlockState(pos).getBlock() instanceof TntBlock && !CraftEventFactory.callTNTPrimeEvent(worldIn, pos, TNTPrimeEvent.PrimeCause.FIRE, null, ArclightCaptures.getTickingPosition())) { - ci.cancel(); - } - } - @Redirect(method = "updateShape", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;defaultBlockState()Lnet/minecraft/world/level/block/state/BlockState;")) public BlockState arclight$blockFade(net.minecraft.world.level.block.Block block, BlockState stateIn, Direction facing, BlockState facingState, LevelAccessor worldIn, BlockPos currentPos, BlockPos facingPos) { if (!(worldIn instanceof Level)) { diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/vanilla/server/level/ServerEntityMixin_Vanilla.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/vanilla/server/level/ServerEntityMixin_Vanilla.java new file mode 100644 index 000000000..a550ef1a6 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/vanilla/server/level/ServerEntityMixin_Vanilla.java @@ -0,0 +1,101 @@ +package io.izzel.arclight.common.mixin.vanilla.server.level; + +import com.google.common.collect.Lists; +import com.mojang.datafixers.util.Pair; +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +@Mixin(ServerEntity.class) +public abstract class ServerEntityMixin_Vanilla { + + // @formatter:off + @Shadow @Final private Entity entity; + @Shadow private int yHeadRotp; + @Shadow @Nullable private List> trackedDataValues; + @Shadow @Final private boolean trackDelta; + @Shadow private Vec3 ap; + // @formatter:on + + /** + * @author IzzelAliz + * @reason + */ + @Overwrite + public void sendPairingData(ServerPlayer player, final Consumer> consumer) { + Mob entityinsentient; + if (this.entity.isRemoved()) { + return; + } + Packet packet = this.entity.getAddEntityPacket(); + this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0f / 360.0f); + consumer.accept(packet); + if (this.trackedDataValues != null) { + consumer.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); + } + boolean flag = this.trackDelta; + if (this.entity instanceof LivingEntity livingEntity) { + Collection collection = livingEntity.getAttributes().getSyncableAttributes(); + if (this.entity.getId() == player.getId()) { + ((ServerPlayerEntityBridge) this.entity).bridge$getBukkitEntity().injectScaledMaxHealth(collection, false); + } + if (!collection.isEmpty()) { + consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection)); + } + if (livingEntity.isFallFlying()) { + flag = true; + } + } + this.ap = this.entity.getDeltaMovement(); + if (flag && !(this.entity instanceof LivingEntity)) { + consumer.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.ap)); + } + if (this.entity instanceof LivingEntity) { + ArrayList> list = Lists.newArrayList(); + for (EquipmentSlot enumitemslot : EquipmentSlot.values()) { + ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); + if (itemstack.isEmpty()) continue; + list.add(Pair.of(enumitemslot, itemstack.copy())); + } + if (!list.isEmpty()) { + consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); + } + ((LivingEntity) this.entity).detectEquipmentUpdates(); + } + // CraftBukkit start - MC-109346: Fix for nonsensical head yaw + if (this.entity instanceof ServerPlayer) { + consumer.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F))); + } + // CraftBukkit end + if (!this.entity.getPassengers().isEmpty()) { + consumer.accept(new ClientboundSetPassengersPacket(this.entity)); + } + if (this.entity.isPassenger()) { + consumer.accept(new ClientboundSetPassengersPacket(this.entity.getVehicle())); + } + if (this.entity instanceof Mob && (entityinsentient = (Mob) this.entity).isLeashed()) { + consumer.accept(new ClientboundSetEntityLinkPacket(entityinsentient, entityinsentient.getLeashHolder())); + } + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/vanilla/world/entity/animal/frog/TadpoleMixin_Vanilla.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/vanilla/world/entity/animal/frog/TadpoleMixin_Vanilla.java new file mode 100644 index 000000000..bf95540f5 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/vanilla/world/entity/animal/frog/TadpoleMixin_Vanilla.java @@ -0,0 +1,33 @@ +package io.izzel.arclight.common.mixin.vanilla.world.entity.animal.frog; + +import io.izzel.arclight.common.bridge.core.world.server.ServerWorldBridge; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.animal.frog.Frog; +import net.minecraft.world.entity.animal.frog.Tadpole; +import net.minecraft.world.level.Level; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(Tadpole.class) +public abstract class TadpoleMixin_Vanilla { + + // @formatter:off + @Shadow protected abstract void setAge(int p_218711_); + // @formatter:on + + @Inject(method = "ageUp()V", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/frog/Tadpole;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V")) + private void arclight$transform(CallbackInfo ci, Level level, ServerLevel serverLevel, Frog frog) { + if (CraftEventFactory.callEntityTransformEvent((Tadpole) (Object) this, frog, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS).isCancelled()) { + this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled + ci.cancel(); + } else { + ((ServerWorldBridge) serverLevel).bridge$pushAddEntityReason(CreatureSpawnEvent.SpawnReason.METAMORPHOSIS); + } + } +} \ No newline at end of file diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/ArclightConnector.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/ArclightConnector.java index 124e8c031..84e592baa 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/ArclightConnector.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/ArclightConnector.java @@ -1,5 +1,6 @@ package io.izzel.arclight.common.mod; +import io.izzel.arclight.api.ArclightPlatform; import io.izzel.arclight.common.mod.util.log.ArclightI18nLogger; import io.izzel.arclight.mixin.injector.EjectorInfo; import org.apache.logging.log4j.Logger; @@ -16,7 +17,11 @@ public void connect() { InjectionInfo.register(EjectorInfo.class); Mixins.addConfiguration("mixins.arclight.core.json"); Mixins.addConfiguration("mixins.arclight.bukkit.json"); - Mixins.addConfiguration("mixins.arclight.forge.json"); + switch (ArclightPlatform.current()) { + case VANILLA -> Mixins.addConfiguration("mixins.arclight.vanilla.json"); + case FORGE -> Mixins.addConfiguration("mixins.arclight.forge.json"); + case NEOFORGE -> Mixins.addConfiguration("mixins.arclight.neoforge.json"); + } LOGGER.info("mixin-load.core"); Mixins.addConfiguration("mixins.arclight.impl.optimization.json"); LOGGER.info("mixin-load.optimization"); diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/block/ChestBlockDoubleInventoryHacks.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/block/ChestBlockDoubleInventoryHacks.java index 6acc41bd9..c0e28a38b 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/block/ChestBlockDoubleInventoryHacks.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/server/block/ChestBlockDoubleInventoryHacks.java @@ -1,6 +1,7 @@ package io.izzel.arclight.common.mod.server.block; import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper; import net.minecraft.world.CompoundContainer; import java.lang.reflect.Field; @@ -12,7 +13,8 @@ public class ChestBlockDoubleInventoryHacks { static { try { - cl = Class.forName("net.minecraft.world.level.block.ChestBlock$2$1"); + var className = ArclightRemapper.getNmsMapper().mapType("net/minecraft/world/level/block/BlockChest$2$1").replace('/', '.'); + cl = Class.forName(className); Field field = cl.getDeclaredField("inventorylargechest"); offset = Unsafe.objectFieldOffset(field); } catch (Exception e) { diff --git a/arclight-common/src/main/resources/mixins.arclight.core.json b/arclight-common/src/main/resources/mixins.arclight.core.json index d33ef941a..8a7314014 100644 --- a/arclight-common/src/main/resources/mixins.arclight.core.json +++ b/arclight-common/src/main/resources/mixins.arclight.core.json @@ -92,7 +92,6 @@ "world.effect.HealOrHarmMobEffectMixin", "world.effect.HungerMobEffectMixin", "world.effect.MobEffectUtilMixin", - "world.effect.PoisonMobEffectMixin", "world.effect.RegenerationMobEffectMixin", "world.effect.SaturationMobEffectMixin", "world.entity.AgeableMobMixin", diff --git a/arclight-common/src/main/resources/mixins.arclight.vanilla.json b/arclight-common/src/main/resources/mixins.arclight.vanilla.json index dbb09ce96..8da495727 100644 --- a/arclight-common/src/main/resources/mixins.arclight.vanilla.json +++ b/arclight-common/src/main/resources/mixins.arclight.vanilla.json @@ -16,9 +16,11 @@ "mixinPriority": 500, "compatibilityLevel": "JAVA_17", "mixins": [ + "server.level.ServerEntityMixin_Vanilla", "world.entity.EntityMixin_Vanilla", "world.entity.LivingEntityMixin_Vanilla", "world.entity.animal.MushroomCowMixin_Vanilla", + "world.entity.animal.frog.TadpoleMixin_Vanilla", "world.entity.monster.ZombieMixin_Vanilla", "world.entity.player.PlayerMixin_Vanilla", "world.entity.player.ServerPlayerMixin_Vanilla", diff --git a/arclight-forge-bootstrap/src/applaunch/java/io/izzel/arclight/server/Launcher.java b/arclight-forge-bootstrap/src/applaunch/java/io/izzel/arclight/server/Launcher.java deleted file mode 100644 index c687d048d..000000000 --- a/arclight-forge-bootstrap/src/applaunch/java/io/izzel/arclight/server/Launcher.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.izzel.arclight.server; - -import io.izzel.arclight.boot.application.Main_Forge; - -public class Launcher { - - private static final int MIN_CLASS_VERSION = 61; - private static final int MIN_JAVA_VERSION = 17; - - public static void main(String[] args) throws Throwable { - int javaVersion = (int) Float.parseFloat(System.getProperty("java.class.version")); - if (javaVersion < MIN_CLASS_VERSION) { - System.err.println("Arclight requires Java " + MIN_JAVA_VERSION); - System.err.println("Current: " + System.getProperty("java.version")); - System.exit(-1); - return; - } - Main_Forge.main(args); - } -} diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/Implementer.java b/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/Implementer.java deleted file mode 100644 index d52da30fe..000000000 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/Implementer.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.izzel.arclight.boot.asm; - -import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; -import org.objectweb.asm.tree.ClassNode; - -public interface Implementer { - - boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader); -} diff --git a/arclight-forge-bootstrap/src/main/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService b/arclight-forge-bootstrap/src/main/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService deleted file mode 100644 index 56d77f489..000000000 --- a/arclight-forge-bootstrap/src/main/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService +++ /dev/null @@ -1 +0,0 @@ -io.izzel.arclight.boot.mod.ArclightLaunchHandler \ No newline at end of file diff --git a/arclight-forge-bootstrap/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService b/arclight-forge-bootstrap/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService deleted file mode 100644 index 7adee4a3f..000000000 --- a/arclight-forge-bootstrap/src/main/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService +++ /dev/null @@ -1 +0,0 @@ -io.izzel.arclight.boot.asm.ArclightImplementer \ No newline at end of file diff --git a/arclight-forge-bootstrap/src/main/resources/META-INF/services/net.minecraftforge.bootstrap.api.BootstrapEntryPoint b/arclight-forge-bootstrap/src/main/resources/META-INF/services/net.minecraftforge.bootstrap.api.BootstrapEntryPoint deleted file mode 100644 index a19f82ddc..000000000 --- a/arclight-forge-bootstrap/src/main/resources/META-INF/services/net.minecraftforge.bootstrap.api.BootstrapEntryPoint +++ /dev/null @@ -1 +0,0 @@ -io.izzel.arclight.boot.mod.ModuleBootstrap \ No newline at end of file diff --git a/arclight-forge-bootstrap/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator b/arclight-forge-bootstrap/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator deleted file mode 100644 index 589a95ed4..000000000 --- a/arclight-forge-bootstrap/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator +++ /dev/null @@ -1 +0,0 @@ -io.izzel.arclight.boot.mod.ArclightLocator_Forge \ No newline at end of file diff --git a/arclight-forge-bootstrap/src/main/resources/async_catcher.json b/arclight-forge-bootstrap/src/main/resources/async_catcher.json deleted file mode 100644 index 71310db4f..000000000 --- a/arclight-forge-bootstrap/src/main/resources/async_catcher.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "net/minecraft/server/level/ServerLevel": { - "m_8872_(Lnet/minecraft/world/entity/Entity;)Z": "entity add", - "m_142646_()Lnet/minecraft/world/level/entity/LevelEntityGetter;": "chunk entity get" - }, - "net/minecraft/server/level/ServerLevel$EntityCallbacks": { - "m_141985_(Lnet/minecraft/world/entity/Entity;)V": "entity register", - "m_141981_(Lnet/minecraft/world/entity/Entity;)V": "entity unregister" - }, - "net/minecraft/server/level/ChunkMap$TrackedEntity": { - "m_140485_(Lnet/minecraft/server/level/ServerPlayer;)V": "player tracker clear", - "m_140497_(Lnet/minecraft/server/level/ServerPlayer;)V": "player tracker update" - }, - "net/minecraft/world/level/block/state/BlockBehaviour": { - "m_6807_(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Z)V": "block place", - "m_6810_(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Z)V": "block remove" - }, - "net/minecraft/world/entity/LivingEntity": { - "m_147207_(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z": "effect add" - }, - "net/minecraft/server/level/ChunkMap": { - "m_140199_(Lnet/minecraft/world/entity/Entity;)V": "entity track", - "m_140331_(Lnet/minecraft/world/entity/Entity;)V": "entity untrack" - }, - "net/minecraft/world/level/storage/loot/LootTable": { - "m_79123_(Lnet/minecraft/world/Container;Lnet/minecraft/world/level/storage/loot/LootContext;)V": "loot generate" - }, - "net/minecraftforge/network/NetworkHooks": { - "openGui(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/MenuProvider;Ljava/util/function/Consumer;)V": "open gui" - } -} \ No newline at end of file diff --git a/arclight-forge/build.gradle b/arclight-forge/build.gradle index e14037847..4ebd0a0dd 100644 --- a/arclight-forge/build.gradle +++ b/arclight-forge/build.gradle @@ -1,10 +1,8 @@ import io.izzel.arclight.gradle.tasks.RenameJarTask plugins { - id "architectury-plugin" version "3.4-SNAPSHOT" - id "com.github.johnrengelman.shadow" version "7.1.2" - id 'java' - id 'idea' + id 'architectury-plugin' version "${architect_plugin_version}" + id 'com.github.johnrengelman.shadow' version "${shadow_plugin_version}" } apply plugin: io.izzel.arclight.gradle.ArclightGradlePlugin @@ -42,16 +40,6 @@ configurations { developmentForge.extendsFrom common } -repositories { - maven { url = 'https://repo.spongepowered.org/maven' } - maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' } - maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } - maven { url = 'https://files.minecraftforge.net/maven/' } - maven { url = 'https://maven.izzel.io/releases' } - maven { url = 'https://jitpack.io/' } - mavenCentral() -} - dependencies { minecraft "com.mojang:minecraft:$minecraftVersion" mappings loom.officialMojangMappings() diff --git a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/server/level/ServerEntityMixin_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/server/level/ServerEntityMixin_Forge.java new file mode 100644 index 000000000..181bd037a --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/server/level/ServerEntityMixin_Forge.java @@ -0,0 +1,101 @@ +package io.izzel.arclight.forge.mixin.core.server.level; + +import com.google.common.collect.Lists; +import com.mojang.datafixers.util.Pair; +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +@Mixin(ServerEntity.class) +public abstract class ServerEntityMixin_Forge { + + // @formatter:off + @Shadow @Final private Entity entity; + @Shadow private int yHeadRotp; + @Shadow @Nullable private List> trackedDataValues; + @Shadow @Final private boolean trackDelta; + @Shadow private Vec3 ap; + // @formatter:on + + /** + * @author IzzelAliz + * @reason + */ + @Overwrite + public void sendPairingData(ServerPlayer player, final Consumer> consumer) { + Mob entityinsentient; + if (this.entity.isRemoved()) { + return; + } + Packet packet = this.entity.getAddEntityPacket(); + this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0f / 360.0f); + consumer.accept(packet); + if (this.trackedDataValues != null) { + consumer.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); + } + boolean flag = this.trackDelta; + if (this.entity instanceof LivingEntity livingEntity) { + Collection collection = livingEntity.getAttributes().getSyncableAttributes(); + if (this.entity.getId() == player.getId()) { + ((ServerPlayerEntityBridge) this.entity).bridge$getBukkitEntity().injectScaledMaxHealth(collection, false); + } + if (!collection.isEmpty()) { + consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection)); + } + if (livingEntity.isFallFlying()) { + flag = true; + } + } + this.ap = this.entity.getDeltaMovement(); + if (flag && !(this.entity instanceof LivingEntity)) { + consumer.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.ap)); + } + if (this.entity instanceof LivingEntity) { + ArrayList> list = Lists.newArrayList(); + for (EquipmentSlot enumitemslot : EquipmentSlot.values()) { + ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); + if (itemstack.isEmpty()) continue; + list.add(Pair.of(enumitemslot, itemstack.copy())); + } + if (!list.isEmpty()) { + consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); + } + ((LivingEntity) this.entity).detectEquipmentUpdates(); + } + // CraftBukkit start - MC-109346: Fix for nonsensical head yaw + if (this.entity instanceof ServerPlayer) { + consumer.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F))); + } + // CraftBukkit end + if (!this.entity.getPassengers().isEmpty()) { + consumer.accept(new ClientboundSetPassengersPacket(this.entity)); + } + if (this.entity.isPassenger()) { + consumer.accept(new ClientboundSetPassengersPacket(this.entity.getVehicle())); + } + if (this.entity instanceof Mob && (entityinsentient = (Mob) this.entity).isLeashed()) { + consumer.accept(new ClientboundSetEntityLinkPacket(entityinsentient, entityinsentient.getLeashHolder())); + } + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/effect/PoisonMobEffectMixin.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/effect/PoisonMobEffectMixin_Forge.java similarity index 88% rename from arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/effect/PoisonMobEffectMixin.java rename to arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/effect/PoisonMobEffectMixin_Forge.java index ce7bbf799..7b996b1dd 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/core/world/effect/PoisonMobEffectMixin.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/effect/PoisonMobEffectMixin_Forge.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.common.mixin.core.world.effect; +package io.izzel.arclight.forge.mixin.core.world.effect; import io.izzel.arclight.common.bridge.core.util.DamageSourcesBridge; import net.minecraft.world.damagesource.DamageSource; @@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(targets = "net.minecraft.world.effect.PoisonMobEffect") -public class PoisonMobEffectMixin { +public class PoisonMobEffectMixin_Forge { @Redirect(method = "applyEffectTick", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/world/damagesource/DamageSources;magic()Lnet/minecraft/world/damagesource/DamageSource;")) private DamageSource arclight$redirectPoison(DamageSources instance) { diff --git a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/animal/frog/TadpoleMixin_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/animal/frog/TadpoleMixin_Forge.java new file mode 100644 index 000000000..edaeb6121 --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/animal/frog/TadpoleMixin_Forge.java @@ -0,0 +1,32 @@ +package io.izzel.arclight.forge.mixin.core.world.entity.animal.frog; + +import io.izzel.arclight.common.bridge.core.world.server.ServerWorldBridge; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.animal.frog.Frog; +import net.minecraft.world.entity.animal.frog.Tadpole; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(Tadpole.class) +public abstract class TadpoleMixin_Forge { + + // @formatter:off + @Shadow protected abstract void setAge(int p_218711_); + // @formatter:on + + @Inject(method = "ageUp()V", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/frog/Tadpole;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V")) + private void arclight$transform(CallbackInfo ci, ServerLevel serverLevel, Frog frog) { + if (CraftEventFactory.callEntityTransformEvent((Tadpole) (Object) this, frog, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS).isCancelled()) { + this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled + ci.cancel(); + } else { + ((ServerWorldBridge) serverLevel).bridge$pushAddEntityReason(CreatureSpawnEvent.SpawnReason.METAMORPHOSIS); + } + } +} \ No newline at end of file diff --git a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/player/ServerPlayerMixin_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/player/ServerPlayerMixin_Forge.java index 5547635cd..39b7c252c 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/player/ServerPlayerMixin_Forge.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/entity/player/ServerPlayerMixin_Forge.java @@ -1,9 +1,12 @@ package io.izzel.arclight.forge.mixin.core.world.entity.player; import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.bridge.core.inventory.container.ContainerBridge; import io.izzel.arclight.common.bridge.core.network.play.ServerPlayNetHandlerBridge; import io.izzel.arclight.common.bridge.core.world.WorldBridge; import io.izzel.arclight.common.bridge.core.world.level.block.PortalInfoBridge; +import io.izzel.arclight.common.mod.server.block.ChestBlockDoubleInventoryHacks; +import io.izzel.arclight.common.mod.util.ArclightCaptures; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket; import net.minecraft.network.protocol.game.ClientboundGameEventPacket; @@ -20,10 +23,12 @@ import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.server.players.PlayerList; import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.animal.horse.AbstractHorse; +import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.level.Level; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.portal.PortalInfo; @@ -36,6 +41,7 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.craftbukkit.v.CraftWorld; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerTeleportEvent; import org.spongepowered.asm.mixin.Final; @@ -44,9 +50,13 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import javax.annotation.Nullable; +import java.util.OptionalInt; @Mixin(ServerPlayer.class) public abstract class ServerPlayerMixin_Forge extends PlayerMixin_Forge implements ServerPlayerEntityBridge { @@ -211,4 +221,27 @@ public Entity changeDimension(ServerLevel server, ITeleporter teleporter) { return (ServerPlayer) (Object) this; } } + + @Redirect(method = "openMenu", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;closeContainer()V")) + private void arclight$skipSwitch(ServerPlayer serverPlayer) { + } + + @Inject(method = "openMenu", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/MenuProvider;createMenu(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/entity/player/Player;)Lnet/minecraft/world/inventory/AbstractContainerMenu;")) + private void arclight$invOpen(MenuProvider iTileInventory, CallbackInfoReturnable cir, AbstractContainerMenu container) { + if (container != null) { + ((ContainerBridge) container).bridge$setTitle(iTileInventory.getDisplayName()); + boolean cancelled = false; + ArclightCaptures.captureContainerOwner((ServerPlayer) (Object) this); + container = CraftEventFactory.callInventoryOpenEvent((ServerPlayer) (Object) this, container, cancelled); + ArclightCaptures.resetContainerOwner(); + if (container == null && !cancelled) { + if (iTileInventory instanceof Container) { + ((Container) iTileInventory).stopOpen((ServerPlayer) (Object) this); + } else if (ChestBlockDoubleInventoryHacks.isInstance(iTileInventory)) { + ChestBlockDoubleInventoryHacks.get(iTileInventory).stopOpen((ServerPlayer) (Object) this); + } + cir.setReturnValue(OptionalInt.empty()); + } + } + } } diff --git a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/ItemMixin_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/ItemMixin_Forge.java index 959c7eb9b..cb13bfdbf 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/ItemMixin_Forge.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/ItemMixin_Forge.java @@ -19,7 +19,7 @@ public class ItemMixin_Forge implements ItemBridge { @Override - public AbstractArrow bridge$forge$customArrow(BowItem bowItem, AbstractArrow arrow) { + public AbstractArrow bridge$forge$customArrow(BowItem bowItem, ItemStack itemStack, AbstractArrow arrow) { return bowItem.customArrow(arrow); } diff --git a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/crafting/RecipeManagerMixin_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/crafting/RecipeManagerMixin_Forge.java index d05a90f94..f7bbf38b0 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/crafting/RecipeManagerMixin_Forge.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mixin/core/world/item/crafting/RecipeManagerMixin_Forge.java @@ -1,7 +1,11 @@ package io.izzel.arclight.forge.mixin.core.world.item.crafting; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.izzel.arclight.common.bridge.core.world.item.crafting.RecipeManagerBridge; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeManager; import net.minecraftforge.common.crafting.conditions.ICondition; import org.spongepowered.asm.mixin.Final; @@ -11,10 +15,16 @@ @Mixin(RecipeManager.class) public abstract class RecipeManagerMixin_Forge implements RecipeManagerBridge { + // @formatter:off @Shadow(remap = false) @Final private ICondition.IContext context; + @Shadow protected static RecipeHolder fromJson(ResourceLocation p_44046_, JsonObject p_44047_) { return null; } + // @formatter:on @Override - public boolean bridge$forge$conditionNotMet(JsonElement element) { - return element.isJsonObject() && !net.minecraftforge.common.ForgeHooks.readAndTestCondition(this.context, element.getAsJsonObject()); + public RecipeHolder bridge$platform$loadRecipe(ResourceLocation key, JsonElement element) { + if (element.isJsonObject() && !net.minecraftforge.common.ForgeHooks.readAndTestCondition(this.context, element.getAsJsonObject())) { + return null; + } + return fromJson(key, GsonHelper.convertToJsonObject(element, "top element")); } } diff --git a/arclight-forge/src/main/java/io/izzel/arclight/forge/mod/ForgeArclightServer.java b/arclight-forge/src/main/java/io/izzel/arclight/forge/mod/ForgeArclightServer.java index 154a7b4cf..b9d627be1 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/forge/mod/ForgeArclightServer.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/forge/mod/ForgeArclightServer.java @@ -1,8 +1,8 @@ package io.izzel.arclight.forge.mod; +import io.izzel.arclight.api.ArclightPlatform; import io.izzel.arclight.api.ArclightServer; import io.izzel.arclight.api.TickingTracker; -import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.common.mod.server.api.DefaultTickingTracker; import io.izzel.arclight.forge.mod.util.PluginEventHandler; import net.minecraftforge.eventbus.EventBus; @@ -15,14 +15,21 @@ public class ForgeArclightServer implements ArclightServer { @Override public void registerForgeEvent(Plugin plugin, IEventBus bus, Object target) { + registerModEvent(plugin, bus, target); + } + + @Override + public void registerModEvent(Plugin plugin, Object bus, Object target) { try { - if (bus instanceof EventBus) { - PluginEventHandler.register(plugin, (EventBus) bus, target); + if (bus instanceof EventBus eventBus) { + PluginEventHandler.register(plugin, eventBus, target); + } else if (bus instanceof IEventBus eventBus) { + eventBus.register(target); } else { - bus.register(target); + throw new IllegalArgumentException("Unknown bus type " + bus + " on platform " + ArclightPlatform.current()); } } catch (Throwable t) { - Unsafe.throwException(t); + throw new RuntimeException(t); } } diff --git a/arclight-forge/src/main/resources/mixins.arclight.forge.json b/arclight-forge/src/main/resources/mixins.arclight.forge.json index 6581803ad..247abbf32 100644 --- a/arclight-forge/src/main/resources/mixins.arclight.forge.json +++ b/arclight-forge/src/main/resources/mixins.arclight.forge.json @@ -30,12 +30,15 @@ "core.server.MinecraftServerMixin_Forge", "core.server.dedicated.DedicatedServerMixin_Forge", "core.server.level.DistanceManagerMixin_Forge", + "core.server.level.ServerEntityMixin_Forge", "core.server.management.ServerPlayerGameModeMixin_Forge", + "core.world.effect.PoisonMobEffectMixin_Forge", "core.world.entity.EntityMixin_Forge", "core.world.entity.LivingEntityMixin_Forge", "core.world.entity.MobMixin_Forge", "core.world.entity.ai.behavior.HarvestFarmlandMixin_Forge", "core.world.entity.animal.MushroomCowMixin_Forge", + "core.world.entity.animal.frog.TadpoleMixin_Forge", "core.world.entity.item.ItemEntityMixin_Forge", "core.world.entity.monster.ZombieMixin_Forge", "core.world.entity.monster.piglin.PiglinAiMixin_Forge", diff --git a/arclight-neoforge/build.gradle b/arclight-neoforge/build.gradle new file mode 100644 index 000000000..50a253ce5 --- /dev/null +++ b/arclight-neoforge/build.gradle @@ -0,0 +1,141 @@ +import io.izzel.arclight.gradle.tasks.RenameJarTask + +plugins { + id 'architectury-plugin' version "${architect_plugin_version}" + id 'com.github.johnrengelman.shadow' version "${shadow_plugin_version}" +} + +apply plugin: io.izzel.arclight.gradle.ArclightGradlePlugin + +architectury { + minecraft = minecraftVersion + platformSetupLoomIde() + neoForge() +} + +loom { + silentMojangMappingsLicense() + accessWidenerPath = project(":arclight-common").loom.accessWidenerPath + + neoForge { + } +} + +arclight { + mcVersion = minecraftVersion + bukkitVersion = 'v1_20_R3' + accessTransformer = project(':arclight-common').file('bukkit.at') + extraMapping = project(':arclight-common').file('extra_mapping.tsrg') +} + +configurations { + common + shadowCommon + compileClasspath.extendsFrom common + runtimeClasspath.extendsFrom common + developmentNeoForge.extendsFrom common +} + +dependencies { + minecraft "com.mojang:minecraft:$minecraftVersion" + mappings loom.officialMojangMappings() + neoForge "net.neoforged:neoforge:$neoForgeVersion" + + implementation("org.spigotmc:spigot-api:$minecraftVersion-R0.1-SNAPSHOT") { transitive = false } + implementation "io.izzel.arclight.generated:spigot:$minecraftVersion:deobf" + implementation 'net.md-5:bungeecord-chat:1.16-R0.4' + + implementation('io.izzel.arclight:mixin-tools:1.1.0') { transitive = false } + + shadowCommon("org.spigotmc:spigot-api:$minecraftVersion-R0.1-SNAPSHOT") { transitive = false } + shadowCommon "io.izzel.arclight.generated:spigot:$minecraftVersion:deobf" + shadowCommon('net.md-5:bungeecord-chat:1.20-R0.1') { transitive = false } + + implementation "io.izzel:tools:$toolsVersion" + implementation "io.izzel.arclight:arclight-api:$apiVersion" + compileOnly("net.minecraftforge:eventbus:4.0.0") { transitive = false } + implementation project(':i18n-config') + + common(project(path: ':arclight-common', configuration: 'namedElements')) { transitive = false } + shadowCommon(project(path: ':arclight-common', configuration: 'transformProductionNeoForge')) { transitive = false } +} + +java { + withSourcesJar() +} + +jar { + manifest.attributes 'MixinConnector': 'io.izzel.arclight.common.mod.ArclightConnector' + manifest.attributes 'Implementation-Title': 'Arclight' + manifest.attributes 'Implementation-Version': "arclight-$minecraftVersion-${project.version}-$gitHash" + manifest.attributes 'Implementation-Vendor': 'Arclight Team' + manifest.attributes 'Implementation-Timestamp': new Date().format("yyyy-MM-dd HH:mm:ss") +} + +tasks.register('neoforgeMappings', Copy) { + destinationDir = file('build/neoforge_mappings') + from(arclight.mappingsConfiguration.bukkitToNeoForge) { + rename { name -> 'bukkit_srg.srg' } + } + from arclight.mappingsConfiguration.bukkitToForgeInheritance +} + +project.sourceSets.main.output.dir file('build/neoforge_mappings'), builtBy: tasks.neoforgeMappings + +processResources { + inputs.property 'version', project.version + + filesMatching('META-INF/mods.toml') { + expand 'version': project.version + } +} + +shadowJar { + exclude 'fabric.mod.json' + exclude 'architectury.common.json' + + configurations = [project.configurations.shadowCommon] + archiveClassifier = 'dev-shadow' +} + +remapJar { + inputFile.set shadowJar.archiveFile + dependsOn shadowJar + atAccessWideners.add(loom.accessWidenerPath.get().asFile.getName()) +} + +tasks.register('reobfJar', RenameJarTask) { + inputJar.set remapJar.archiveFile + archiveClassifier.set 'reobf' + mappings = arclight.mappingsConfiguration.reobfBukkitPackage +} + +tasks.register('srgJar', Jar) { + from(tasks.remapJar.outputs.files.collect { it.isDirectory() ? it : zipTree(it) }) { + include 'io/izzel/**' + exclude 'io/izzel/arclight/common/mixin/**' + exclude 'io/izzel/arclight/forge/mixin/**' + } + archiveClassifier.set('srg') + dependsOn remapJar +} + +tasks.register('spigotJar', RenameJarTask) { + inputJar.set tasks.srgJar.archiveFile + mappings = arclight.mappingsConfiguration.bukkitToForge + reverse = true + archiveClassifier.set('spigot') + dependsOn tasks.srgJar +} + +sourcesJar { + def commonSources = project(':arclight-common').sourcesJar + dependsOn commonSources + from commonSources.archiveFile.map { zipTree(it) } +} + +components.java { + withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { + skip() + } +} diff --git a/arclight-neoforge/gradle.properties b/arclight-neoforge/gradle.properties new file mode 100644 index 000000000..2914393db --- /dev/null +++ b/arclight-neoforge/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge \ No newline at end of file diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/ArclightMod.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/ArclightMod.java new file mode 100644 index 000000000..43ab4b3e9 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/ArclightMod.java @@ -0,0 +1,49 @@ +package io.izzel.arclight.neoforge; + +import io.izzel.arclight.api.Arclight; +import io.izzel.arclight.common.mod.server.ArclightServer; +import io.izzel.arclight.neoforge.mod.NeoForgeArclightServer; +import io.izzel.arclight.neoforge.mod.event.ArclightEventDispatcherRegistry; +import net.neoforged.fml.common.Mod; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.OutputStream; +import java.io.PrintStream; + +@Mod("arclight") +public class ArclightMod { + + public ArclightMod() { + ArclightServer.LOGGER.info("mod-load"); + Arclight.setServer(new NeoForgeArclightServer()); + System.setOut(new LoggingPrintStream("STDOUT", System.out, Level.INFO)); + System.setErr(new LoggingPrintStream("STDERR", System.err, Level.ERROR)); + ArclightEventDispatcherRegistry.registerAllEventDispatchers(); + } + + private static class LoggingPrintStream extends PrintStream { + + private final Logger logger; + private final Level level; + + public LoggingPrintStream(String name, @NotNull OutputStream out, Level level) { + super(out); + this.logger = LogManager.getLogger(name); + this.level = level; + } + + @Override + public void println(@Nullable String x) { + logger.log(level, x); + } + + @Override + public void println(@Nullable Object x) { + logger.log(level, String.valueOf(x)); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftHumanEntityMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftHumanEntityMixin_NeoForge.java new file mode 100644 index 000000000..46865349c --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftHumanEntityMixin_NeoForge.java @@ -0,0 +1,23 @@ +package io.izzel.arclight.neoforge.mixin.bukkit; + +import io.izzel.arclight.i18n.ArclightConfig; +import io.izzel.arclight.neoforge.mod.permission.ArclightNeoForgePermissible; +import org.bukkit.craftbukkit.v.entity.CraftHumanEntity; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.ServerOperator; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = CraftHumanEntity.class, remap = false) +public abstract class CraftHumanEntityMixin_NeoForge { + + @Redirect(method = "", at = @At(value = "NEW", target = "(Lorg/bukkit/permissions/ServerOperator;)Lorg/bukkit/permissions/PermissibleBase;")) + private PermissibleBase arclight$forge$forwardPerm(ServerOperator opable) { + if (ArclightConfig.spec().getCompat().isForwardPermissionReverse()) { + return new ArclightNeoForgePermissible(opable); + } else { + return new PermissibleBase(opable); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftServerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftServerMixin_NeoForge.java new file mode 100644 index 000000000..70e40c959 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftServerMixin_NeoForge.java @@ -0,0 +1,48 @@ +package io.izzel.arclight.neoforge.mixin.bukkit; + +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.StringReader; +import io.izzel.arclight.common.mod.server.ArclightServer; +import net.minecraft.commands.CommandSourceStack; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.CommandEvent; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.v.CraftServer; +import org.bukkit.craftbukkit.v.command.CraftBlockCommandSender; +import org.bukkit.craftbukkit.v.entity.CraftEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(value = CraftServer.class, remap = false) +public abstract class CraftServerMixin_NeoForge { + @ModifyVariable(method = "dispatchCommand", remap = false, index = 2, at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lorg/spigotmc/AsyncCatcher;catchOp(Ljava/lang/String;)V")) + private String arclight$forge$forgeCommandEvent(String commandLine, CommandSender sender) { + CommandSourceStack commandSource; + if (sender instanceof CraftEntity) { + commandSource = ((CraftEntity) sender).getHandle().createCommandSourceStack(); + } else if (sender == Bukkit.getConsoleSender()) { + commandSource = ArclightServer.getMinecraftServer().createCommandSourceStack(); + } else if (sender instanceof CraftBlockCommandSender) { + commandSource = ((CraftBlockCommandSender) sender).getWrapper(); + } else { + return commandLine; + } + StringReader stringreader = new StringReader("/" + commandLine); + if (stringreader.canRead() && stringreader.peek() == '/') { + stringreader.skip(); + } + ParseResults parse = ArclightServer.getMinecraftServer().getCommands() + .getDispatcher().parse(stringreader, commandSource); + CommandEvent event = new CommandEvent(parse); + if (NeoForge.EVENT_BUS.post(event).isCanceled()) { + return null; + } else if (event.getException() != null) { + return null; + } else { + String s = event.getParseResults().getReader().getString(); + return s.startsWith("/") ? s.substring(1) : s; + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftWorldMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftWorldMixin_NeoForge.java new file mode 100644 index 000000000..4a7cebaec --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/CraftWorldMixin_NeoForge.java @@ -0,0 +1,16 @@ +package io.izzel.arclight.neoforge.mixin.bukkit; + +import net.minecraft.world.level.biome.Biome; +import org.bukkit.craftbukkit.v.CraftWorld; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = CraftWorld.class, remap = false) +public abstract class CraftWorldMixin_NeoForge { + + @Redirect(method = "getHumidity(III)D", at = @At(value = "FIELD", remap = true, target = "Lnet/minecraft/world/level/biome/Biome;climateSettings:Lnet/minecraft/world/level/biome/Biome$ClimateSettings;")) + private Biome.ClimateSettings arclight$useForgeSetting(Biome instance) { + return instance.getModifiedClimateSettings(); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/MaterialMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/MaterialMixin_NeoForge.java new file mode 100644 index 000000000..728193a09 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/bukkit/MaterialMixin_NeoForge.java @@ -0,0 +1,26 @@ +package io.izzel.arclight.neoforge.mixin.bukkit; + +import io.izzel.arclight.common.bridge.bukkit.MaterialBridge; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Material; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(value = Material.class, remap = false) +public abstract class MaterialMixin_NeoForge implements MaterialBridge { + + @Override + public int bridge$forge$getMaxStackSize(Item item) { + return item.getMaxStackSize(new ItemStack(item)); + } + + @Override + public int bridge$forge$getDurability(Item item) { + return item.getMaxDamage(new ItemStack(item)); + } + + @Override + public int bridge$forge$getBurnTime(Item item) { + return new ItemStack(item).getBurnTime(null); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/command/CommandsMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/command/CommandsMixin_NeoForge.java new file mode 100644 index 000000000..5026cd93d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/command/CommandsMixin_NeoForge.java @@ -0,0 +1,23 @@ +package io.izzel.arclight.neoforge.mixin.core.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.CommandNode; +import io.izzel.arclight.common.bridge.core.command.CommandsBridge; +import net.minecraft.commands.Commands; +import net.neoforged.neoforge.server.command.CommandHelper; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.Map; +import java.util.function.Function; + +@Mixin(Commands.class) +public abstract class CommandsMixin_NeoForge implements CommandsBridge { + @Override + public void bridge$forge$mergeNode(CommandNode sourceNode, CommandNode resultNode, + Map, CommandNode> sourceToResult, + S canUse, Command execute, + Function, SuggestionProvider> sourceToResultSuggestion) { + CommandHelper.mergeCommandNode(sourceNode, resultNode, sourceToResult, canUse, execute, sourceToResultSuggestion); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/fluid/LavaFluidMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/fluid/LavaFluidMixin_NeoForge.java new file mode 100644 index 000000000..7bdf4e32d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/fluid/LavaFluidMixin_NeoForge.java @@ -0,0 +1,30 @@ +package io.izzel.arclight.neoforge.mixin.core.fluid; + +import io.izzel.arclight.common.bridge.core.fluid.LavaFluidBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.LavaFluid; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(LavaFluid.class) +public abstract class LavaFluidMixin_NeoForge implements LavaFluidBridge { + + // @formatter:off + @Shadow(remap = false) protected abstract boolean isFlammable(LevelReader level, BlockPos pos, Direction face); + // @formatter:on + + @Override + public BlockState bridge$forge$fireFluidPlaceBlockEvent(LevelAccessor level, BlockPos pos, BlockPos liquidPos, BlockState state) { + return EventHooks.fireFluidPlaceBlockEvent(level, pos, liquidPos, state); + } + + @Override + public boolean bridge$forge$isFlammable(LevelReader level, BlockPos pos, Direction face) { + return this.isFlammable(level, pos, face); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerCommonPacketListenerImplMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerCommonPacketListenerImplMixin_NeoForge.java new file mode 100644 index 000000000..ed875837d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerCommonPacketListenerImplMixin_NeoForge.java @@ -0,0 +1,17 @@ +package io.izzel.arclight.neoforge.mixin.core.network; + +import io.izzel.arclight.common.bridge.core.network.common.ServerCommonPacketListenerBridge; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.DiscardedPayload; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ServerCommonPacketListenerImpl.class) +public abstract class ServerCommonPacketListenerImplMixin_NeoForge implements ServerCommonPacketListenerBridge { + + @Override + public FriendlyByteBuf bridge$getDiscardedData(DiscardedPayload payload) { + // Todo: Payload data. + return null; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerLoginNetHandlerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerLoginNetHandlerMixin_NeoForge.java new file mode 100644 index 000000000..32eed8f08 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerLoginNetHandlerMixin_NeoForge.java @@ -0,0 +1,23 @@ +package io.izzel.arclight.neoforge.mixin.core.network; + +import io.izzel.arclight.common.bridge.core.network.login.ServerLoginNetHandlerBridge; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import net.neoforged.fml.util.thread.SidedThreadGroups; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ServerLoginPacketListenerImpl.class) +public abstract class ServerLoginNetHandlerMixin_NeoForge implements ServerLoginNetHandlerBridge { + + @Override + public Thread bridge$newHandleThread(String name, Runnable runnable) { + return new Thread(SidedThreadGroups.SERVER, runnable, name); + } + + @Override + public FriendlyByteBuf bridge$getDiscardedQueryAnswerData(DiscardedQueryAnswerPayload payload) { + // Todo: Payload data. + return null; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerPlayNetHandlerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerPlayNetHandlerMixin_NeoForge.java new file mode 100644 index 000000000..354e5d3c7 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerPlayNetHandlerMixin_NeoForge.java @@ -0,0 +1,39 @@ +package io.izzel.arclight.neoforge.mixin.core.network; + +import io.izzel.arclight.common.bridge.core.network.play.ServerPlayNetHandlerBridge; +import io.izzel.tools.product.Product; +import io.izzel.tools.product.Product3; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.CommonHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(ServerGamePacketListenerImpl.class) +public abstract class ServerPlayNetHandlerMixin_NeoForge extends ServerCommonPacketListenerImplMixin_NeoForge implements ServerPlayNetHandlerBridge { + + @Shadow public ServerPlayer player; + + @Override + public Product3 bridge$platform$canSwapHandItems(LivingEntity entity) { + var event = CommonHooks.onLivingSwapHandItems(this.player); + return Product.of(event.isCanceled(), event.getItemSwappedToMainHand(), event.getItemSwappedToOffHand()); + } + + @Override + public Component bridge$platform$onServerChatSubmitted(ServerPlayer player, Component message) { + return CommonHooks.onServerChatSubmittedEvent(player, message.getString(), message); + } + + @Override + public InteractionResult bridge$platform$onInteractEntityAt(ServerPlayer player, Entity entity, Vec3 vec, InteractionHand interactionHand) { + return CommonHooks.onInteractEntityAt(player, entity, vec, interactionHand); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerStatusNetHandlerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerStatusNetHandlerMixin_NeoForge.java new file mode 100644 index 000000000..2c93a7385 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/ServerStatusNetHandlerMixin_NeoForge.java @@ -0,0 +1,17 @@ +package io.izzel.arclight.neoforge.mixin.core.network; + +import io.izzel.arclight.common.bridge.core.network.ServerStatusPacketListenerBridge; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.status.ServerStatus; +import net.minecraft.server.network.ServerStatusPacketListenerImpl; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.Optional; + +@Mixin(ServerStatusPacketListenerImpl.class) +public abstract class ServerStatusNetHandlerMixin_NeoForge implements ServerStatusPacketListenerBridge { + @Override + public ServerStatus bridge$platform$createServerStatus(Component description, Optional players, Optional version, Optional favicon, boolean enforcesSecureChat) { + return new ServerStatus(description, players, version, favicon, enforcesSecureChat, true); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/protocol/status/ServerStatusMixin.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/protocol/status/ServerStatusMixin.java new file mode 100644 index 000000000..984d367d9 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/network/protocol/status/ServerStatusMixin.java @@ -0,0 +1,19 @@ +package io.izzel.arclight.neoforge.mixin.core.network.protocol.status; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.status.ServerStatus; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.Optional; + +@Mixin(ServerStatus.class) +public abstract class ServerStatusMixin { + + public void arclight$forge$constructor(Component description, Optional players, Optional version, Optional favicon, boolean enforcesSecureChat, boolean isModded) { + throw new RuntimeException(); + } + + public void arclight$forge$constructor(Component description, Optional players, Optional version, Optional favicon, boolean enforcesSecureChat) { + arclight$forge$constructor(description, players, version, favicon, enforcesSecureChat, true); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/MinecraftServerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/MinecraftServerMixin_NeoForge.java new file mode 100644 index 000000000..6d0fd0b65 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/MinecraftServerMixin_NeoForge.java @@ -0,0 +1,61 @@ +package io.izzel.arclight.neoforge.mixin.core.server; + +import io.izzel.arclight.common.bridge.core.server.MinecraftServerBridge; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ForcedChunksSavedData; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.common.world.chunk.ForcedChunkManager; +import net.neoforged.neoforge.event.level.LevelEvent; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServerMixin_NeoForge implements MinecraftServerBridge { + + // @formatter:off + @Shadow(remap = false) public abstract void markWorldsDirty(); + // @formatter:on + + @Override + public void bridge$platform$loadLevel(Level level) { + NeoForge.EVENT_BUS.post(new LevelEvent.Load(level)); + } + + @Override + public void bridge$platform$unloadLevel(Level level) { + NeoForge.EVENT_BUS.post(new LevelEvent.Unload(level)); + } + + @Override + public void bridge$forge$markLevelsDirty() { + this.markWorldsDirty(); + } + + @Override + public void bridge$platform$serverStarted() { + ServerLifecycleHooks.handleServerStarted((MinecraftServer) (Object) this); + } + + @Override + public void bridge$platform$serverStopping() { + ServerLifecycleHooks.handleServerStopping((MinecraftServer) (Object) this); + } + + @Override + public void bridge$forge$expectServerStopped() { + ServerLifecycleHooks.expectServerStopped(); + } + + @Override + public void bridge$platform$serverStopped() { + ServerLifecycleHooks.handleServerStopped((MinecraftServer) (Object) this); + } + + @Override + public void bridge$forge$reinstatePersistentChunks(ServerLevel level, ForcedChunksSavedData savedData) { + ForcedChunkManager.reinstatePersistentChunks(level, savedData); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/dedicated/DedicatedServerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/dedicated/DedicatedServerMixin_NeoForge.java new file mode 100644 index 000000000..43c9d9cfc --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/dedicated/DedicatedServerMixin_NeoForge.java @@ -0,0 +1,21 @@ +package io.izzel.arclight.neoforge.mixin.core.server.dedicated; + +import io.izzel.arclight.common.bridge.core.server.dedicated.DedicatedServerBridge; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecrell.terminalconsole.TerminalConsoleAppender; +import org.spongepowered.asm.mixin.Mixin; + +import java.io.IOException; + +@Mixin(DedicatedServer.class) +public abstract class DedicatedServerMixin_NeoForge implements DedicatedServerBridge { + + @Override + public void bridge$platform$exitNow() { + try { + TerminalConsoleAppender.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/level/DistanceManagerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/level/DistanceManagerMixin_NeoForge.java new file mode 100644 index 000000000..812e862c3 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/level/DistanceManagerMixin_NeoForge.java @@ -0,0 +1,37 @@ +package io.izzel.arclight.neoforge.mixin.core.server.level; + +import io.izzel.arclight.common.bridge.core.world.server.TicketManagerBridge; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.server.level.DistanceManager; +import net.minecraft.server.level.Ticket; +import net.minecraft.util.SortedArraySet; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(DistanceManager.class) +public abstract class DistanceManagerMixin_NeoForge implements TicketManagerBridge { + + // @formatter:off + @Shadow(remap = false) @Final private Long2ObjectOpenHashMap>> forcedTickets; + // @formatter:on + + @Override + public boolean bridge$platform$isTicketForceTick(Ticket ticket) { + return ticket.isForceTicks(); + } + + @Override + public void bridge$forge$addForcedTicket(long chunkPosIn, Ticket ticketIn) { + SortedArraySet> tickets = this.forcedTickets.computeIfAbsent(chunkPosIn, e -> SortedArraySet.create(4)); + tickets.addOrGet(ticketIn); + } + + @Override + public void bridge$forge$removeForcedTicket(long chunkPosIn, Ticket ticketIn) { + SortedArraySet> tickets = this.forcedTickets.get(chunkPosIn); + if (tickets != null) { + tickets.remove(ticketIn); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/level/ServerEntityMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/level/ServerEntityMixin_NeoForge.java new file mode 100644 index 000000000..592842d94 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/level/ServerEntityMixin_NeoForge.java @@ -0,0 +1,100 @@ +package io.izzel.arclight.neoforge.mixin.core.server.level; + +import com.google.common.collect.Lists; +import com.mojang.datafixers.util.Pair; +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.network.protocol.game.*; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.network.bundle.PacketAndPayloadAcceptor; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Mixin(ServerEntity.class) +public abstract class ServerEntityMixin_NeoForge { + + // @formatter:off + @Shadow @Final private Entity entity; + @Shadow private int yHeadRotp; + @Shadow @Nullable private List> trackedDataValues; + @Shadow @Final private boolean trackDelta; + @Shadow private Vec3 ap; + // @formatter:on + + /** + * @author qyl27 + * @reason For bukkit. + */ + @Overwrite + public void sendPairingData(ServerPlayer player, final PacketAndPayloadAcceptor packetAndPayloadAcceptor) { + Mob entityinsentient; + if (this.entity.isRemoved()) { + return; + } + var packet = this.entity.getAddEntityPacket(); + this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0f / 360.0f); + packetAndPayloadAcceptor.accept(packet); + if (this.trackedDataValues != null) { + packetAndPayloadAcceptor.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); + } + boolean flag = this.trackDelta; + if (this.entity instanceof LivingEntity livingEntity) { + Collection collection = livingEntity.getAttributes().getSyncableAttributes(); + if (this.entity.getId() == player.getId()) { + ((ServerPlayerEntityBridge) this.entity).bridge$getBukkitEntity().injectScaledMaxHealth(collection, false); + } + if (!collection.isEmpty()) { + packetAndPayloadAcceptor.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection)); + } + if (livingEntity.isFallFlying()) { + flag = true; + } + } + this.ap = this.entity.getDeltaMovement(); + if (flag && !(this.entity instanceof LivingEntity)) { + packetAndPayloadAcceptor.accept(new ClientboundSetEntityMotionPacket(this.entity.getId(), this.ap)); + } + if (this.entity instanceof LivingEntity) { + ArrayList> list = Lists.newArrayList(); + for (EquipmentSlot enumitemslot : EquipmentSlot.values()) { + ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); + if (itemstack.isEmpty()) continue; + list.add(Pair.of(enumitemslot, itemstack.copy())); + } + if (!list.isEmpty()) { + packetAndPayloadAcceptor.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); + } + ((LivingEntity) this.entity).detectEquipmentUpdates(); + } + // CraftBukkit start - MC-109346: Fix for nonsensical head yaw + if (this.entity instanceof ServerPlayer) { + packetAndPayloadAcceptor.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F))); + } + // CraftBukkit end + if (!this.entity.getPassengers().isEmpty()) { + packetAndPayloadAcceptor.accept(new ClientboundSetPassengersPacket(this.entity)); + } + if (this.entity.isPassenger()) { + packetAndPayloadAcceptor.accept(new ClientboundSetPassengersPacket(this.entity.getVehicle())); + } + if (this.entity instanceof Mob && (entityinsentient = (Mob) this.entity).isLeashed()) { + packetAndPayloadAcceptor.accept(new ClientboundSetEntityLinkPacket(entityinsentient, entityinsentient.getLeashHolder())); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/management/ServerPlayerGameModeMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/management/ServerPlayerGameModeMixin_NeoForge.java new file mode 100644 index 000000000..617471cd3 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/server/management/ServerPlayerGameModeMixin_NeoForge.java @@ -0,0 +1,30 @@ +package io.izzel.arclight.neoforge.mixin.core.server.management; + +import io.izzel.arclight.common.bridge.core.server.management.PlayerInteractionManagerBridge; +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayerGameMode; +import org.bukkit.block.Block; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ServerPlayerGameMode.class) +public abstract class ServerPlayerGameModeMixin_NeoForge implements PlayerInteractionManagerBridge { + + @Inject(method = "destroyBlock", remap = true, at = @At(value = "INVOKE", remap = false, target = "Lnet/neoforged/neoforge/common/CommonHooks;onBlockBreakEvent(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/GameType;Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)I")) + public void arclight$beforePrimaryEventFired(BlockPos pos, CallbackInfoReturnable cir) { + ArclightCaptures.captureNextBlockBreakEventAsPrimaryEvent(); + } + + @Inject(method = "destroyBlock", remap = true, at = @At(value = "INVOKE_ASSIGN", remap = false, target = "Lnet/neoforged/neoforge/common/CommonHooks;onBlockBreakEvent(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/GameType;Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/core/BlockPos;)I")) + public void arclight$handleSecondaryBlockBreakEvents(BlockPos pos, CallbackInfoReturnable cir) { + ArclightCaptures.BlockBreakEventContext breakEventContext = ArclightCaptures.popSecondaryBlockBreakEvent(); + while (breakEventContext != null) { + Block block = breakEventContext.getEvent().getBlock(); + bridge$handleBlockDrop(breakEventContext, new BlockPos(block.getX(), block.getY(), block.getZ())); + breakEventContext = ArclightCaptures.popSecondaryBlockBreakEvent(); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/effect/PoisonMobEffectMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/effect/PoisonMobEffectMixin_NeoForge.java new file mode 100644 index 000000000..572d51b41 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/effect/PoisonMobEffectMixin_NeoForge.java @@ -0,0 +1,19 @@ +package io.izzel.arclight.neoforge.mixin.core.world.effect; + +import io.izzel.arclight.common.bridge.core.util.DamageSourcesBridge; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.damagesource.DamageSources; +import net.minecraft.world.damagesource.DamageType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(targets = "net.minecraft.world.effect.PoisonMobEffect") +public class PoisonMobEffectMixin_NeoForge { + + @Redirect(method = "applyEffectTick", at = @At(value = "INVOKE", ordinal = 0, target = "Lnet/minecraft/world/damagesource/DamageSources;source(Lnet/minecraft/resources/ResourceKey;)Lnet/minecraft/world/damagesource/DamageSource;")) + private DamageSource arclight$redirectPoison(DamageSources instance, ResourceKey source) { + return ((DamageSourcesBridge) instance).bridge$poison(); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/EntityMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/EntityMixin_NeoForge.java new file mode 100644 index 000000000..ec2985af8 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/EntityMixin_NeoForge.java @@ -0,0 +1,178 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity; + +import io.izzel.arclight.common.bridge.core.entity.EntityBridge; +import io.izzel.arclight.common.bridge.core.entity.LivingEntityBridge; +import io.izzel.arclight.common.bridge.core.world.WorldBridge; +import io.izzel.arclight.common.bridge.core.world.level.block.PortalInfoBridge; +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import io.izzel.tools.product.Product; +import io.izzel.tools.product.Product4; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.portal.PortalInfo; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.extensions.IEntityExtension; +import net.neoforged.neoforge.common.util.ITeleporter; +import net.neoforged.neoforge.entity.PartEntity; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import javax.annotation.Nullable; +import java.util.Collection; + +@Mixin(Entity.class) +public abstract class EntityMixin_NeoForge implements EntityBridge, IEntityExtension { + + // @formatter:off + @Shadow public abstract Level level(); + @Shadow public abstract boolean isRemoved(); + @Shadow @org.jetbrains.annotations.Nullable protected abstract PortalInfo findDimensionEntryPoint(ServerLevel serverLevel); + @Shadow private float yRot; + @Shadow private float xRot; + @Shadow public abstract float getXRot(); + @Shadow public abstract void moveTo(double d, double e, double f, float g, float h); + @Shadow public abstract void setDeltaMovement(Vec3 vec3); + @Shadow public abstract void unRide(); + @Shadow public abstract float getYRot(); + @Shadow public abstract EntityType getType(); + @Shadow protected abstract void removeAfterChangingDimensions(); + @Shadow(remap = false) public abstract Collection captureDrops(Collection par1); + @Shadow(remap = false) public abstract void revive(); + @Shadow public abstract Vec3 position(); + @Shadow public abstract int getId(); + @Shadow public abstract void discard(); + @Shadow public abstract double getX(); + @Shadow public abstract double getY(double d); + @Shadow public abstract double getZ(); + @Shadow(remap = false) public abstract boolean canUpdate(); + // @formatter:on + + @Override + public void bridge$revive() { + this.revive(); + } + + @Redirect(method = "updateFluidHeightAndDoFluidPushing()V", remap = false, at = @At(value = "INVOKE", remap = true, target="Lnet/minecraft/world/level/material/FluidState;getFlow(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 arclight$setLava(FluidState instance, BlockGetter level, BlockPos pos) { + if (instance.getType().is(FluidTags.LAVA)) { + this.bridge$setLastLavaContact(pos.immutable()); + } + return instance.getFlow(level, pos); + } + + @Redirect(method = "spawnAtLocation(Lnet/minecraft/world/item/ItemStack;F)Lnet/minecraft/world/entity/item/ItemEntity;", at = @At(value = "INVOKE", remap = false, ordinal = 0, target = "Lnet/minecraft/world/entity/Entity;captureDrops()Ljava/util/Collection;")) + public Collection arclight$forceDrops(Entity entity) { + Collection drops = entity.captureDrops(); + if (this instanceof LivingEntityBridge && ((LivingEntityBridge) this).bridge$isForceDrops()) { + drops = null; + } + return drops; + } + + /** + * @author IzzelAliz + * @reason + */ + @Overwrite + @Nullable + public Entity changeDimension(ServerLevel arg) { + return this.changeDimension(arg, arg.getPortalForcer()); + } + + /** + * @author IzzelAliz + * @reason + */ + @Overwrite(remap = false) + @Nullable + public Entity changeDimension(ServerLevel server, ITeleporter teleporter) { + if (CommonHooks.onTravelToDimension((Entity) (Object) this, server.dimension())) + return null; + if (this.level() instanceof ServerLevel && !this.isRemoved()) { + this.level().getProfiler().push("changeDimension"); + if (server == null) { + return null; + } + this.level().getProfiler().push("reposition"); + var bukkitPos = bridge$getLastTpPos(); + PortalInfo portalinfo = bukkitPos == null ? teleporter.getPortalInfo((Entity) (Object) this, server, this::findDimensionEntryPoint) + : new PortalInfo(new Vec3(bukkitPos.x(), bukkitPos.y(), bukkitPos.z()), Vec3.ZERO, this.yRot, this.xRot); + if (portalinfo == null) { + return null; + } else { + ServerLevel world = ((PortalInfoBridge) portalinfo).bridge$getWorld() == null ? server : ((PortalInfoBridge) portalinfo).bridge$getWorld(); + if (world == this.level()) { + this.moveTo(portalinfo.pos.x, portalinfo.pos.y, portalinfo.pos.z, portalinfo.yRot, this.getXRot()); + this.setDeltaMovement(portalinfo.speed); + return (Entity) (Object) this; + } + this.unRide(); + Entity transportedEntity = teleporter.placeEntity((Entity) (Object) this, (ServerLevel) this.level(), world, this.getYRot(), spawnPortal -> { //Forge: Start vanilla logic + this.level().getProfiler().popPush("reloading"); + Entity entity = this.getType().create(world); + if (entity != null) { + entity.restoreFrom((Entity) (Object) this); + entity.moveTo(portalinfo.pos.x, portalinfo.pos.y, portalinfo.pos.z, portalinfo.yRot, entity.getXRot()); + entity.setDeltaMovement(portalinfo.speed); + if (this.bridge$isInWorld()) { + world.addDuringTeleport(entity); + if (((WorldBridge) world).bridge$getTypeKey() == LevelStem.END) { + ArclightCaptures.captureEndPortalEntity((Entity) (Object) this, spawnPortal); + ServerLevel.makeObsidianPlatform(world); + } + } + } + return entity; + }); //Forge: End vanilla logic + + this.removeAfterChangingDimensions(); + this.level().getProfiler().pop(); + ((ServerLevel) this.level()).resetEmptyTime(); + world.resetEmptyTime(); + this.level().getProfiler().pop(); + return transportedEntity; + } + } else { + return null; + } + } + + @Override + public boolean bridge$forge$isPartEntity() { + return (Object) this instanceof PartEntity; + } + + @Override + public Entity bridge$forge$getParent() { + return ((PartEntity) (Object) this).getParent(); + } + + @Override + public Entity[] bridge$forge$getParts() { + return this.getParts(); + } + + @Override + public Product4 bridge$onEntityTeleportCommand(double x, double y, double z) { + var event = EventHooks.onEntityTeleportCommand((Entity) (Object) this, x, y, z); + return Product.of(event.isCanceled(), event.getTargetX(), event.getTargetY(), event.getTargetZ()); + } + + @Override + public boolean bridge$forge$canUpdate() { + return this.canUpdate(); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/LivingEntityMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/LivingEntityMixin_NeoForge.java new file mode 100644 index 000000000..08d903f22 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/LivingEntityMixin_NeoForge.java @@ -0,0 +1,158 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity; + +import io.izzel.arclight.common.bridge.core.entity.LivingEntityBridge; +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.tools.product.Product; +import io.izzel.tools.product.Product3; +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.EventHooks; +import net.neoforged.neoforge.event.entity.living.LivingChangeTargetEvent; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; +import java.util.function.Consumer; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin_NeoForge extends EntityMixin_NeoForge implements LivingEntityBridge { + + // @formatter:off + @Shadow public abstract boolean isSleeping(); + @Shadow public abstract Collection getActiveEffects(); + @Shadow protected abstract void dropExperience(); + // @formatter:on + + @Inject(method = "hurt", cancellable = true, at = @At("HEAD")) + private void arclight$livingHurt(DamageSource source, float amount, CallbackInfoReturnable cir) { + if (!CommonHooks.onLivingAttack((LivingEntity) (Object) this, source, amount)) { + cir.setReturnValue(false); + } + } + + @Redirect(method = "dropAllDeathLoot", at = @At(value = "INVOKE", ordinal = 0, remap = false, target = "Lnet/minecraft/world/entity/LivingEntity;captureDrops(Ljava/util/Collection;)Ljava/util/Collection;")) + private Collection arclight$captureIfNeed(LivingEntity + livingEntity, Collection value) { + Collection drops = livingEntity.captureDrops(); + // todo this instanceof ArmorStandEntity + return drops == null ? livingEntity.captureDrops(value) : drops; + } + + @Redirect(method = "dropAllDeathLoot", at = @At(value = "INVOKE", remap = false, target = "Ljava/util/Collection;forEach(Ljava/util/function/Consumer;)V")) + private void arclight$cancelEvent(Collection collection, Consumer action) { + if (this instanceof ServerPlayerEntityBridge) { + // recapture for ServerPlayerEntityMixin#onDeath + this.captureDrops(collection); + } else { + collection.forEach(action); + } + } + + @Redirect(method = "dropAllDeathLoot", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;dropExperience()V")) + private void arclight$dropLater(LivingEntity livingEntity) { + } + + @Inject(method = "dropAllDeathLoot", at = @At("RETURN")) + private void arclight$dropLast(DamageSource damageSourceIn, CallbackInfo ci) { + this.dropExperience(); + } + + @Override + public boolean bridge$forge$mobEffectExpired(MobEffectInstance effect) { + return !NeoForge.EVENT_BUS.post(new MobEffectEvent.Expired((LivingEntity) (Object) this, effect)).isCanceled(); + } + + @Override + public boolean bridge$forge$mobEffectAdded(MobEffectInstance old, MobEffectInstance effect, Entity entity) { + NeoForge.EVENT_BUS.post(new MobEffectEvent.Added((LivingEntity) (Object) this, old, effect, entity)); + return false; + } + + @Override + public float bridge$forge$onLivingHurt(LivingEntity entity, DamageSource src, float amount) { + return CommonHooks.onLivingHurt(entity, src, amount); + } + + @Override + public Product3 bridge$forge$onShieldBlock(LivingEntity blocker, DamageSource source, float blocked) { + var event = CommonHooks.onShieldBlock(blocker, source, blocked); + return Product.of(event.isCanceled(), event.getBlockedDamage(), event.shieldTakesDamage()); + } + + @Override + public float bridge$forge$onLivingDamage(LivingEntity entity, DamageSource src, float amount) { + return CommonHooks.onLivingDamage(entity, src, amount); + } + + @Override + public boolean bridge$forge$onLivingUseTotem(LivingEntity entity, DamageSource damageSource, ItemStack totem, InteractionHand hand) { + return CommonHooks.onLivingUseTotem(entity, damageSource, totem, hand); + } + + @Nullable + @Override + public LivingEntity bridge$forge$onLivingChangeTarget(LivingEntity entity, LivingEntity originalTarget, LivingTargetType targetType) { + var event = CommonHooks.onLivingChangeTarget(entity, originalTarget, LivingChangeTargetEvent.LivingTargetType.valueOf(targetType.name())); + return event.isCanceled() ? null : event.getNewTarget(); + } + + @Override + public BlockPos bridge$forge$onEnderTeleport(LivingEntity entity, double targetX, double targetY, double targetZ) { + var event = EventHooks.onEnderTeleport(entity, targetX, targetY, targetZ); + return event.isCanceled() ? null : BlockPos.containing(event.getTarget()); + } + + @Override + public void bridge$forge$onLivingConvert(LivingEntity entity, LivingEntity outcome) { + EventHooks.onLivingConvert(entity, outcome); + } + + @Override + public boolean bridge$forge$canEntityDestroy(Level level, BlockPos pos, LivingEntity entity) { + return CommonHooks.canEntityDestroy(level, pos, entity); + } + + @Override + public boolean bridge$forge$onEntityDestroyBlock(LivingEntity entity, BlockPos pos, BlockState state) { + return EventHooks.onEntityDestroyBlock(entity, pos, state); + } + + @Override + public void bridge$common$startCaptureDrops() { + } + + @Override + public boolean bridge$common$isCapturingDrops() { + return false; + } + + @Override + public void bridge$common$captureDrop(ItemEntity itemEntity) { + } + + @Override + public Collection bridge$common$getCapturedDrops() { + return this.captureDrops(null); + } + + @Override + public void bridge$common$finishCaptureAndFireEvent() { + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/MobMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/MobMixin_NeoForge.java new file mode 100644 index 000000000..918eb2537 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/MobMixin_NeoForge.java @@ -0,0 +1,26 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity; + +import io.izzel.arclight.common.bridge.core.entity.MobEntityBridge; +import net.minecraft.world.entity.AgeableMob; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.living.BabyEntitySpawnEvent; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Mob.class) +public abstract class MobMixin_NeoForge extends LivingEntityMixin_NeoForge implements MobEntityBridge { + + @Override + public AgeableMob bridge$forge$onBabyEntitySpawn(Mob partner, @Nullable AgeableMob proposedChild) { + var event = new BabyEntitySpawnEvent((Mob) (Object) this, partner, proposedChild); + var cancelled = NeoForge.EVENT_BUS.post(event).isCanceled(); + return cancelled ? null : event.getChild(); + } + + @Override + public boolean bridge$common$animalTameEvent(Player player) { + return true; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/ai/behavior/HarvestFarmlandMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/ai/behavior/HarvestFarmlandMixin_NeoForge.java new file mode 100644 index 000000000..9e1f92567 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/ai/behavior/HarvestFarmlandMixin_NeoForge.java @@ -0,0 +1,19 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.ai.behavior; + +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.ai.behavior.HarvestFarmland; +import net.minecraft.world.entity.npc.Villager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(HarvestFarmland.class) +public abstract class HarvestFarmlandMixin_NeoForge { + + @Inject(method = "tick(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;J)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Z")) + private void onSetBlock(ServerLevel worldIn, Villager owner, long gameTime, CallbackInfo ci) { + ArclightCaptures.captureEntityChangeBlock(owner); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/animal/MushroomCowMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/animal/MushroomCowMixin_NeoForge.java new file mode 100644 index 000000000..594b9345d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/animal/MushroomCowMixin_NeoForge.java @@ -0,0 +1,53 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.animal; + +import io.izzel.arclight.common.bridge.core.world.WorldBridge; +import io.izzel.arclight.neoforge.mixin.core.world.entity.MobMixin_NeoForge; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.animal.Cow; +import net.minecraft.world.entity.animal.MushroomCow; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDropItemEvent; +import org.bukkit.event.entity.EntityTransformEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(MushroomCow.class) +public abstract class MushroomCowMixin_NeoForge extends MobMixin_NeoForge { + + // @formatter:off + // @formatter:on + + @Redirect(method = "shear", remap = false, at = @At(value = "INVOKE", remap = true, target = "Lnet/minecraft/world/entity/animal/MushroomCow;discard()V")) + private void arclight$animalTransformPre(MushroomCow mushroomCow) { + } + + @Inject(method = "shear", remap = false, cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", remap = true, target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z")) + private void arclight$animalTransform(SoundSource p_28924_, CallbackInfo ci, Cow cowEntity) { + if (CraftEventFactory.callEntityTransformEvent((MushroomCow) (Object) this, cowEntity, EntityTransformEvent.TransformReason.SHEARED).isCancelled()) { + ci.cancel(); + } else { + ((WorldBridge) this.level()).bridge$pushAddEntityReason(CreatureSpawnEvent.SpawnReason.SHEARED); + this.discard(); + } + } + + @Redirect(method = "shear", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/MushroomCow;spawnAtLocation(Lnet/minecraft/world/item/ItemStack;F)Lnet/minecraft/world/entity/item/ItemEntity;")) + private ItemEntity arclight$onShearDrop(MushroomCow cow, ItemStack stack, float v) { + var itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), stack); + EntityDropItemEvent event = new EntityDropItemEvent(this.bridge$getBukkitEntity(), (org.bukkit.entity.Item) itemEntity.bridge$getBukkitEntity()); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return null; + } + this.level().addFreshEntity(itemEntity); + return itemEntity; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/animal/frog/TadpoleMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/animal/frog/TadpoleMixin_NeoForge.java new file mode 100644 index 000000000..aca3669fe --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/animal/frog/TadpoleMixin_NeoForge.java @@ -0,0 +1,33 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.animal.frog; + +import io.izzel.arclight.common.bridge.core.world.server.ServerWorldBridge; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.animal.frog.Frog; +import net.minecraft.world.entity.animal.frog.Tadpole; +import net.minecraft.world.level.Level; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(Tadpole.class) +public abstract class TadpoleMixin_NeoForge { + + // @formatter:off + @Shadow protected abstract void setAge(int p_218711_); + // @formatter:on + + @Inject(method = "ageUp()V", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/frog/Tadpole;playSound(Lnet/minecraft/sounds/SoundEvent;FF)V")) + private void arclight$transform(CallbackInfo ci, Level level, ServerLevel serverLevel, Frog frog) { + if (CraftEventFactory.callEntityTransformEvent((Tadpole) (Object) this, frog, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS).isCancelled()) { + this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled + ci.cancel(); + } else { + ((ServerWorldBridge) serverLevel).bridge$pushAddEntityReason(CreatureSpawnEvent.SpawnReason.METAMORPHOSIS); + } + } +} \ No newline at end of file diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/item/ItemEntityMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/item/ItemEntityMixin_NeoForge.java new file mode 100644 index 000000000..b44057ba7 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/item/ItemEntityMixin_NeoForge.java @@ -0,0 +1,27 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.item; + +import io.izzel.arclight.common.bridge.core.entity.item.ItemEntityBridge; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ItemEntity.class) +public abstract class ItemEntityMixin_NeoForge implements ItemEntityBridge { + + @Override + public int bridge$forge$onItemPickup(Player player) { + return EventHooks.onItemPickup((ItemEntity) (Object) this, player); + } + + @Override + public void bridge$forge$firePlayerItemPickupEvent(Player player, ItemStack clone) { + EventHooks.firePlayerItemPickupEvent(player, (ItemEntity) (Object) this, clone); + } + + @Override + public boolean bridge$common$itemDespawnEvent() { + return true; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/ZombieMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/ZombieMixin_NeoForge.java new file mode 100644 index 000000000..b2e837558 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/ZombieMixin_NeoForge.java @@ -0,0 +1,28 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.monster; + +import io.izzel.arclight.common.bridge.core.entity.MobEntityBridge; +import io.izzel.arclight.common.bridge.core.world.WorldBridge; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.monster.Zombie; +import net.neoforged.neoforge.event.entity.living.ZombieEvent; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityTargetEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(Zombie.class) +public abstract class ZombieMixin_NeoForge { + + @Inject(method = "hurt", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/monster/Zombie;setTarget(Lnet/minecraft/world/entity/LivingEntity;)V")) + private void arclight$spawnWithReasonForge(DamageSource source, float amount, CallbackInfoReturnable cir, ServerLevel world, LivingEntity livingEntity, int i, int j, int k, ZombieEvent.SummonAidEvent event, Zombie zombieEntity) { + ((WorldBridge) world).bridge$pushAddEntityReason(CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); + if (livingEntity != null) { + ((MobEntityBridge) zombieEntity).bridge$pushGoalTargetReason(EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/piglin/PiglinAiMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/piglin/PiglinAiMixin_NeoForge.java new file mode 100644 index 000000000..86980378b --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/piglin/PiglinAiMixin_NeoForge.java @@ -0,0 +1,33 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.monster.piglin; + +import io.izzel.arclight.common.bridge.core.entity.monster.piglin.PiglinBridge; +import net.minecraft.world.entity.monster.piglin.Piglin; +import net.minecraft.world.entity.monster.piglin.PiglinAi; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(PiglinAi.class) +public abstract class PiglinAiMixin_NeoForge { + + // @formatter:off + @Shadow private static boolean isBarterCurrency(ItemStack arg) { return false; } + // @formatter:on + + @Redirect(method = "stopHoldingOffHandItem", at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/item/ItemStack;isPiglinCurrency()Z")) + private static boolean arclight$customBarter(ItemStack stack, Piglin piglin) { + return isBarterCurrency(stack) || ((PiglinBridge) piglin).bridge$getAllowedBarterItems().contains(stack.getItem()); + } + + @Redirect(method = "wantsToPickup", at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/item/ItemStack;isPiglinCurrency()Z")) + private static boolean arclight$customBanter2(ItemStack stack, Piglin piglin) { + return isBarterCurrency(stack) || ((PiglinBridge) piglin).bridge$getAllowedBarterItems().contains(stack.getItem()); + } + + @Redirect(method = "canAdmire", at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/item/ItemStack;isPiglinCurrency()Z")) + private static boolean arclight$customBanter3(ItemStack stack, Piglin piglin) { + return isBarterCurrency(stack) || ((PiglinBridge) piglin).bridge$getAllowedBarterItems().contains(stack.getItem()); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/piglin/PiglinMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/piglin/PiglinMixin_NeoForge.java new file mode 100644 index 000000000..0fda8b82f --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/monster/piglin/PiglinMixin_NeoForge.java @@ -0,0 +1,18 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.monster.piglin; + +import io.izzel.arclight.common.bridge.core.entity.monster.piglin.PiglinBridge; +import io.izzel.arclight.neoforge.mixin.core.world.entity.LivingEntityMixin_NeoForge; +import net.minecraft.world.entity.monster.piglin.Piglin; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(Piglin.class) +public abstract class PiglinMixin_NeoForge extends LivingEntityMixin_NeoForge implements PiglinBridge { + + @Redirect(method = "holdInOffHand", at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/item/ItemStack;isPiglinCurrency()Z")) + private boolean arclight$customBarter(ItemStack itemStack) { + return itemStack.isPiglinCurrency() || bridge$getAllowedBarterItems().contains(itemStack.getItem()); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/player/PlayerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/player/PlayerMixin_NeoForge.java new file mode 100644 index 000000000..3c623b8d0 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/player/PlayerMixin_NeoForge.java @@ -0,0 +1,78 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.player; + +import io.izzel.arclight.common.bridge.core.entity.player.PlayerEntityBridge; +import io.izzel.arclight.neoforge.mixin.core.world.entity.LivingEntityMixin_NeoForge; +import io.izzel.tools.product.Product; +import io.izzel.tools.product.Product3; +import io.izzel.tools.product.Product6; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Abilities; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.phys.BlockHitResult; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.extensions.IPlayerExtension; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Player.class) +public abstract class PlayerMixin_NeoForge extends LivingEntityMixin_NeoForge implements PlayerEntityBridge, IPlayerExtension { + + // @formatter:off + @Shadow public abstract Abilities getAbilities(); + @Shadow public AbstractContainerMenu containerMenu; + // @formatter:on + + @Shadow public abstract boolean isCreative(); + + @Inject(method = "hurt", cancellable = true, at = @At("HEAD")) + private void arclight$onPlayerAttack(DamageSource source, float amount, CallbackInfoReturnable cir) { + if (!CommonHooks.onPlayerAttack((Player) (Object) this, source, amount)) { + cir.setReturnValue(false); + } + } + + @Inject(method = "attack", cancellable = true, at = @At("HEAD")) + private void arclight$onPlayerAttackTarget(Entity entity, CallbackInfo ci) { + if (!CommonHooks.onPlayerAttackTarget((Player) (Object) this, entity)) { + ci.cancel(); + } + } + + @Override + public Float bridge$forge$getCriticalHit(Player player, Entity target, boolean vanillaCritical, float damageModifier) { + var hit = CommonHooks.getCriticalHit(player, target, vanillaCritical, damageModifier); + return hit == null ? null : hit.getDamageModifier(); + } + + @Override + public double bridge$forge$getEntityReach() { + return this.getEntityReach(); + } + + @Override + public Product3 bridge$platform$onLeftClickBlock(BlockPos pos, Direction direction, ServerboundPlayerActionPacket.Action action) { + var event = CommonHooks.onLeftClickBlock((Player) (Object) this, pos, direction, action); + return Product.of(event.isCanceled(), event.getUseItem() == Event.Result.DENY, event.getUseBlock() == Event.Result.DENY); + } + + @Override + public Product6 bridge$platform$onRightClickBlock(InteractionHand hand, BlockPos pos, BlockHitResult hitResult) { + var event = CommonHooks.onRightClickBlock((Player) (Object) this, hand, pos, hitResult); + return Product.of(event.isCanceled(), + event.getUseItem() == Event.Result.ALLOW, event.getUseItem() == Event.Result.DENY, + event.getUseBlock() == Event.Result.ALLOW, event.getUseBlock() == Event.Result.DENY, + event.getCancellationResult()); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/player/ServerPlayerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/player/ServerPlayerMixin_NeoForge.java new file mode 100644 index 000000000..c8744d1ba --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/player/ServerPlayerMixin_NeoForge.java @@ -0,0 +1,244 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.player; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.bridge.core.inventory.container.ContainerBridge; +import io.izzel.arclight.common.bridge.core.network.play.ServerPlayNetHandlerBridge; +import io.izzel.arclight.common.bridge.core.world.WorldBridge; +import io.izzel.arclight.common.bridge.core.world.level.block.PortalInfoBridge; +import io.izzel.arclight.common.mod.server.block.ChestBlockDoubleInventoryHacks; +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.*; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.Container; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.animal.horse.AbstractHorse; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.portal.PortalInfo; +import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.common.util.ITeleporter; +import net.neoforged.neoforge.event.EventHooks; +import net.neoforged.neoforge.event.entity.player.PlayerContainerEvent; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v.CraftWorld; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import javax.annotation.Nullable; +import java.util.OptionalInt; +import java.util.function.Consumer; + +@Mixin(ServerPlayer.class) +public abstract class ServerPlayerMixin_NeoForge extends PlayerMixin_NeoForge implements ServerPlayerEntityBridge { + + // @formatter:off + @Shadow public abstract ServerLevel serverLevel(); + @Shadow public boolean isChangingDimension; + @Shadow public boolean wonGame; + @Shadow public ServerGamePacketListenerImpl connection; + @Shadow private boolean seenCredits; + @Shadow public abstract CommonPlayerSpawnInfo createCommonSpawnInfo(ServerLevel arg); + @Shadow @Final public MinecraftServer server; + @Shadow @Nullable private Vec3 enteredNetherPosition; + @Shadow protected abstract void createEndPlatform(ServerLevel arg, BlockPos arg2); + @Shadow public abstract void setServerLevel(ServerLevel arg); + @Shadow public abstract void triggerDimensionChangeTriggers(ServerLevel arg); + @Shadow @Final public ServerPlayerGameMode gameMode; + @Shadow public int lastSentExp; + @Shadow private float lastSentHealth; + @Shadow private int lastSentFood; + // @formatter:on + + @Inject(method = "die", cancellable = true, at = @At("HEAD")) + private void arclight$onDeath(DamageSource source, CallbackInfo ci) { + if (CommonHooks.onLivingDeath((ServerPlayer) (Object) this, source)) { + ci.cancel(); + } + } + + @Inject(method = "openHorseInventory", at = @At("TAIL")) + private void arclight$openHorstContainer(AbstractHorse arg, Container arg2, CallbackInfo ci) { + NeoForge.EVENT_BUS.post(new PlayerContainerEvent.Open((ServerPlayer) (Object) this, this.containerMenu)); + } + + @Inject(method = "setRespawnPosition", cancellable = true, at = @At("HEAD")) + private void arclight$forgeRespawnPos(ResourceKey arg, BlockPos arg2, float f, boolean bl, boolean bl2, CallbackInfo ci) { + if (EventHooks.onPlayerSpawnSet((ServerPlayer) (Object) this, arg2 == null ? Level.OVERWORLD : arg, arg2, bl)) { + ci.cancel(); + } + } + + /** + * @author IzzelAliz + * @reason + */ + @Override + @Nullable + public Entity changeDimension(ServerLevel arg) { + return this.changeDimension(arg, arg.getPortalForcer()); + } + + /** + * @author IzzelAliz + * @reason + */ + @Overwrite(remap = false) + @Nullable + public Entity changeDimension(ServerLevel server, ITeleporter teleporter) { + PlayerTeleportEvent.TeleportCause cause = bridge$getTeleportCause().orElse(PlayerTeleportEvent.TeleportCause.UNKNOWN); + if (this.isSleeping()) { + return (ServerPlayer) (Object) this; + } + if (CommonHooks.onTravelToDimension((ServerPlayer) (Object) this, server.dimension())) return null; + + // this.invulnerableDimensionChange = true; + ServerLevel serverworld = this.serverLevel(); + ResourceKey registrykey = ((WorldBridge) serverworld).bridge$getTypeKey(); + if (registrykey == LevelStem.END && ((WorldBridge) server).bridge$getTypeKey() == LevelStem.OVERWORLD && teleporter.isVanilla()) { //Forge: Fix non-vanilla teleporters triggering end credits + this.isChangingDimension = true; + this.unRide(); + this.serverLevel().removePlayerImmediately((ServerPlayer) (Object) this, Entity.RemovalReason.CHANGED_DIMENSION); + if (!this.wonGame) { + this.wonGame = true; + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, this.seenCredits ? 0.0F : 1.0F)); + this.seenCredits = true; + } + + return (ServerPlayer) (Object) this; + } else { + PortalInfo portalinfo = teleporter.getPortalInfo((ServerPlayer) (Object) this, server, this::findDimensionEntryPoint); + if (portalinfo != null) { + if (((PortalInfoBridge) portalinfo).bridge$getWorld() != null) { + server = ((PortalInfoBridge) portalinfo).bridge$getWorld(); + } + ServerLevel[] exitWorld = new ServerLevel[]{server}; + LevelData iworldinfo = server.getLevelData(); + this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(server), (byte) 3)); + this.connection.send(new ClientboundChangeDifficultyPacket(iworldinfo.getDifficulty(), iworldinfo.isDifficultyLocked())); + PlayerList playerlist = this.server.getPlayerList(); + playerlist.sendPlayerPermissionLevel((ServerPlayer) (Object) this); + this.serverLevel().removePlayerImmediately((ServerPlayer) (Object) this, Entity.RemovalReason.CHANGED_DIMENSION); + this.revive(); + Entity e = teleporter.placeEntity((ServerPlayer) (Object) this, serverworld, exitWorld[0], this.getYRot(), spawnPortal -> {//Forge: Start vanilla logic + serverworld.getProfiler().push("moving"); + if (exitWorld[0] != null) { + if (registrykey == LevelStem.OVERWORLD && ((WorldBridge) exitWorld[0]).bridge$getTypeKey() == LevelStem.NETHER) { + this.enteredNetherPosition = this.position(); + } else if (spawnPortal && ((WorldBridge) exitWorld[0]).bridge$getTypeKey() == LevelStem.END + && (((PortalInfoBridge) portalinfo).bridge$getPortalEventInfo() == null || ((PortalInfoBridge) portalinfo).bridge$getPortalEventInfo().getCanCreatePortal())) { + this.createEndPlatform(exitWorld[0], BlockPos.containing(portalinfo.pos)); + } + } + + Location enter = this.bridge$getBukkitEntity().getLocation(); + Location exit = (exitWorld[0] == null) ? null : new Location(exitWorld[0].bridge$getWorld(), portalinfo.pos.x, portalinfo.pos.y, portalinfo.pos.z, portalinfo.yRot, portalinfo.xRot); + PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.bridge$getBukkitEntity(), enter, exit, cause); + Bukkit.getServer().getPluginManager().callEvent(tpEvent); + if (tpEvent.isCancelled() || tpEvent.getTo() == null) { + return null; + } + exit = tpEvent.getTo(); + + serverworld.getProfiler().pop(); + serverworld.getProfiler().push("placing"); + + this.isChangingDimension = true; + ServerLevel newWorld = ((CraftWorld) exit.getWorld()).getHandle(); + if (newWorld != exitWorld[0]) { + exitWorld[0] = newWorld; + LevelData newWorldInfo = exitWorld[0].getLevelData(); + this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(newWorld), (byte) 3)); + this.connection.send(new ClientboundChangeDifficultyPacket(newWorldInfo.getDifficulty(), newWorldInfo.isDifficultyLocked())); + } + + this.setServerLevel(exitWorld[0]); + exitWorld[0].addDuringPortalTeleport((ServerPlayer) (Object) this); + + ((ServerPlayNetHandlerBridge) this.connection).bridge$teleport(exit); + this.connection.resetPosition(); + + serverworld.getProfiler().pop(); + this.triggerDimensionChangeTriggers(exitWorld[0]); + return (ServerPlayer) (Object) this;//forge: this is part of the ITeleporter patch + });//Forge: End vanilla logic + if (e == null) { + serverworld.addDuringPortalTeleport((ServerPlayer) (Object) this); + return (ServerPlayer) (Object) this; + } else if (e != (Object) this) { + throw new IllegalArgumentException(String.format("Teleporter %s returned not the player entity but instead %s, expected PlayerEntity %s", teleporter, e, this)); + } + + this.gameMode.setLevel(exitWorld[0]); + this.connection.send(new ClientboundPlayerAbilitiesPacket(this.getAbilities())); + playerlist.sendLevelInfo((ServerPlayer) (Object) this, exitWorld[0]); + playerlist.sendAllPlayerInfo((ServerPlayer) (Object) this); + + for (MobEffectInstance effectinstance : this.getActiveEffects()) { + this.connection.send(new ClientboundUpdateMobEffectPacket(this.getId(), effectinstance)); + } + + if (teleporter.playTeleportSound((ServerPlayer) (Object) this, serverworld, exitWorld[0])) { + this.connection.send(new ClientboundLevelEventPacket(1032, BlockPos.ZERO, 0, false)); + } + this.lastSentExp = -1; + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; + EventHooks.firePlayerChangedDimensionEvent((ServerPlayer) (Object) this, serverworld.dimension(), exitWorld[0].dimension()); + PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.bridge$getBukkitEntity(), serverworld.bridge$getWorld()); + Bukkit.getPluginManager().callEvent(changeEvent); + } + + return (ServerPlayer) (Object) this; + } + } + + @Redirect(method = "openMenu(Lnet/minecraft/world/MenuProvider;Ljava/util/function/Consumer;)Ljava/util/OptionalInt;", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;closeContainer()V")) + private void arclight$skipSwitch(ServerPlayer serverPlayer) { + } + + @Inject(method = "openMenu(Lnet/minecraft/world/MenuProvider;Ljava/util/function/Consumer;)Ljava/util/OptionalInt;", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/MenuProvider;createMenu(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/entity/player/Player;)Lnet/minecraft/world/inventory/AbstractContainerMenu;")) + private void arclight$invOpen(MenuProvider iTileInventory, Consumer extraDataWriter, CallbackInfoReturnable cir, AbstractContainerMenu container) { + if (container != null) { + ((ContainerBridge) container).bridge$setTitle(iTileInventory.getDisplayName()); + boolean cancelled = false; + ArclightCaptures.captureContainerOwner((ServerPlayer) (Object) this); + container = CraftEventFactory.callInventoryOpenEvent((ServerPlayer) (Object) this, container, cancelled); + ArclightCaptures.resetContainerOwner(); + if (container == null && !cancelled) { + if (iTileInventory instanceof Container) { + ((Container) iTileInventory).stopOpen((ServerPlayer) (Object) this); + } else if (ChestBlockDoubleInventoryHacks.isInstance(iTileInventory)) { + ChestBlockDoubleInventoryHacks.get(iTileInventory).stopOpen((ServerPlayer) (Object) this); + } + cir.setReturnValue(OptionalInt.empty()); + } + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/projectile/FishingHookMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/projectile/FishingHookMixin_NeoForge.java new file mode 100644 index 000000000..fe3aafcf6 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/projectile/FishingHookMixin_NeoForge.java @@ -0,0 +1,22 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.projectile; + +import io.izzel.arclight.common.bridge.core.entity.projectile.FishingHookBridge; +import io.izzel.tools.product.Product; +import io.izzel.tools.product.Product2; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.ItemFishedEvent; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.List; + +@Mixin(FishingHook.class) +public abstract class FishingHookMixin_NeoForge implements FishingHookBridge { + + @Override + public Product2 bridge$forge$onItemFished(List stacks, int rodDamage, FishingHook hook) { + var event = NeoForge.EVENT_BUS.post(new ItemFishedEvent(stacks, rodDamage, hook)); + return Product.of(event.isCanceled(), event.getRodDamage()); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/vehicle/AbstractMinecartMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/vehicle/AbstractMinecartMixin_NeoForge.java new file mode 100644 index 000000000..7ca05eb87 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/entity/vehicle/AbstractMinecartMixin_NeoForge.java @@ -0,0 +1,19 @@ +package io.izzel.arclight.neoforge.mixin.core.world.entity.vehicle; + +import io.izzel.arclight.common.bridge.core.entity.vehicle.AbstractMinecartBridge; +import net.minecraft.world.entity.vehicle.AbstractMinecart; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(AbstractMinecart.class) +public abstract class AbstractMinecartMixin_NeoForge implements AbstractMinecartBridge { + + // @formatter:off + @Shadow(remap = false) public abstract boolean canUseRail(); + // @formatter:on + + @Override + public boolean bridge$forge$canUseRail() { + return this.canUseRail(); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/food/FoodDataMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/food/FoodDataMixin_NeoForge.java new file mode 100644 index 000000000..87eeb8e46 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/food/FoodDataMixin_NeoForge.java @@ -0,0 +1,51 @@ +package io.izzel.arclight.neoforge.mixin.core.world.food; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.bridge.core.util.FoodStatsBridge; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.food.FoodData; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import javax.annotation.Nullable; + +@Mixin(FoodData.class) +public abstract class FoodDataMixin_NeoForge implements FoodStatsBridge { + + // @formatter:off + @Shadow public int foodLevel; + @Shadow public abstract void eat(int i, float f); + // @formatter:on + + @Redirect(method = "eat(Lnet/minecraft/world/item/Item;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/entity/LivingEntity;)V", remap = false, + at = @At(value = "INVOKE", remap = true, target = "Lnet/minecraft/world/food/FoodData;eat(IF)V")) + private void arclight$foodLevelChangeForge(FoodData foodStats, int foodLevelIn, float foodSaturationModifier, Item maybeFood, ItemStack stack, @Nullable LivingEntity entity) { + var player = this.bridge$getEntityHuman() != null ? this.bridge$getEntityHuman() : (entity instanceof Player p ? p : null); + if (player == null) { + foodStats.eat(foodLevelIn, foodSaturationModifier); + return; + } else if (this.bridge$getEntityHuman() == null) { + this.bridge$setEntityHuman(player); + } + FoodProperties food = maybeFood.getFoodProperties(stack, entity); + int oldFoodLevel = this.foodLevel; + FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(player, food.getNutrition() + oldFoodLevel, stack); + if (!event.isCancelled()) { + this.eat(event.getFoodLevel() - oldFoodLevel, food.getSaturationModifier()); + } + ((ServerPlayerEntityBridge) player).bridge$getBukkitEntity().sendHealthUpdate(); + } + + @Override + public FoodProperties bridge$forge$getFoodProperties(ItemStack stack, LivingEntity entity) { + return stack.getFoodProperties(entity); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/AbstractContainerMenuMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/AbstractContainerMenuMixin_NeoForge.java new file mode 100644 index 000000000..d6e0538ba --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/AbstractContainerMenuMixin_NeoForge.java @@ -0,0 +1,20 @@ +package io.izzel.arclight.neoforge.mixin.core.world.inventory; + +import io.izzel.arclight.common.bridge.core.inventory.container.ContainerBridge; +import net.minecraft.world.entity.SlotAccess; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ClickAction; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.CommonHooks; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(AbstractContainerMenu.class) +public abstract class AbstractContainerMenuMixin_NeoForge implements ContainerBridge { + + @Override + public boolean bridge$forge$onItemStackedOn(ItemStack carriedItem, ItemStack stackedOnItem, Slot slot, ClickAction action, Player player, SlotAccess carriedSlotAccess) { + return CommonHooks.onItemStackedOn(carriedItem, stackedOnItem, slot, action, player, carriedSlotAccess); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/AnvilMenuMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/AnvilMenuMixin_NeoForge.java new file mode 100644 index 000000000..092549d81 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/AnvilMenuMixin_NeoForge.java @@ -0,0 +1,24 @@ +package io.izzel.arclight.neoforge.mixin.core.world.inventory; + +import io.izzel.arclight.common.bridge.core.inventory.AnvilMenuBridge; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.CommonHooks; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(AnvilMenu.class) +public abstract class AnvilMenuMixin_NeoForge implements AnvilMenuBridge { + + @Override + public boolean bridge$forge$onAnvilChange(AnvilMenu container, @NotNull ItemStack left, @NotNull ItemStack right, Container outputSlot, String name, int baseCost, Player player) { + return CommonHooks.onAnvilChange(container, left, right, outputSlot, name, baseCost, player); + } + + @Override + public boolean bridge$forge$isBookEnchantable(ItemStack a, ItemStack b) { + return a.isBookEnchantable(b); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/EnchantmentMenuMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/EnchantmentMenuMixin_NeoForge.java new file mode 100644 index 000000000..32fa133fe --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/inventory/EnchantmentMenuMixin_NeoForge.java @@ -0,0 +1,25 @@ +package io.izzel.arclight.neoforge.mixin.core.world.inventory; + +import io.izzel.arclight.common.bridge.core.inventory.EnchantmentMenuBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.world.inventory.EnchantmentMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(EnchantmentMenu.class) +public abstract class EnchantmentMenuMixin_NeoForge implements EnchantmentMenuBridge { + + @Override + public float bridge$forge$getEnchantPowerBonus(BlockState state, LevelReader level, BlockPos pos) { + return state.getBlock().getEnchantPowerBonus(state, level, pos); + } + + @Override + public int bridge$forge$onEnchantmentLevelSet(Level level, BlockPos pos, int enchantRow, int power, ItemStack itemStack, int enchantmentLevel) { + return EventHooks.onEnchantmentLevelSet(level, pos, enchantRow, power, itemStack, enchantmentLevel); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/BucketItemMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/BucketItemMixin_NeoForge.java new file mode 100644 index 000000000..95210afa0 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/BucketItemMixin_NeoForge.java @@ -0,0 +1,69 @@ +package io.izzel.arclight.neoforge.mixin.core.world.item; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.mod.util.DistValidate; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BucketItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(BucketItem.class) +public abstract class BucketItemMixin_NeoForge { + + // @formatter:off + @Shadow(remap = false) public abstract boolean emptyContents(@Nullable Player p_150716_, Level p_150717_, BlockPos p_150718_, @Nullable BlockHitResult p_150719_, @Nullable ItemStack container); + // @formatter:on + + @Inject(method = "use", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/item/BucketItem;emptyContents(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/phys/BlockHitResult;Lnet/minecraft/world/item/ItemStack;)Z")) + private void arclight$capture(Level worldIn, Player playerIn, InteractionHand handIn, CallbackInfoReturnable> cir, ItemStack stack, BlockHitResult result) { + arclight$direction = result.getDirection(); + arclight$click = result.getBlockPos(); + arclight$hand = handIn; + } + + public boolean emptyContents(Player entity, Level world, BlockPos pos, @Nullable BlockHitResult result, Direction direction, BlockPos clicked, ItemStack itemstack, InteractionHand hand) { + arclight$direction = direction; + arclight$click = clicked; + arclight$hand = hand; + try { + return this.emptyContents(entity, world, pos, result, itemstack); + } finally { + arclight$direction = null; + arclight$click = null; + } + } + + private transient Direction arclight$direction; + private transient BlockPos arclight$click; + private transient InteractionHand arclight$hand; + + @Inject(method = "emptyContents(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/phys/BlockHitResult;Lnet/minecraft/world/item/ItemStack;)Z", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/dimension/DimensionType;ultraWarm()Z")) + private void arclight$bucketEmpty(Player player, Level worldIn, BlockPos posIn, BlockHitResult rayTrace, ItemStack stack, CallbackInfoReturnable cir) { + if (!DistValidate.isValid(worldIn)) return; + if (player != null && stack != null) { + PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) worldIn, player, posIn, arclight$click, arclight$direction, stack, arclight$hand == null ? InteractionHand.MAIN_HAND : arclight$hand); + if (event.isCancelled()) { + ((ServerPlayer) player).connection.send(new ClientboundBlockUpdatePacket(worldIn, posIn)); + ((ServerPlayerEntityBridge) player).bridge$getBukkitEntity().updateInventory(); + cir.setReturnValue(false); + } + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ItemMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ItemMixin_NeoForge.java new file mode 100644 index 000000000..4030da02a --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ItemMixin_NeoForge.java @@ -0,0 +1,45 @@ +package io.izzel.arclight.neoforge.mixin.core.world.item; + +import io.izzel.arclight.common.bridge.core.world.item.ItemBridge; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.AbstractArrow; +import net.minecraft.world.item.ArrowItem; +import net.minecraft.world.item.BowItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Item.class) +public abstract class ItemMixin_NeoForge implements ItemBridge { + + @Override + public AbstractArrow bridge$forge$customArrow(BowItem bowItem, ItemStack itemStack, AbstractArrow arrow) { + return bowItem.customArrow(arrow, itemStack); + } + + @Override + public int bridge$forge$onArrowLoose(ItemStack stack, Level level, Player player, int charge, boolean hasAmmo) { + return EventHooks.onArrowLoose(stack, level, player, charge, hasAmmo); + } + + @Override + public boolean bridge$forge$isInfinite(ArrowItem item, ItemStack stack, ItemStack bow, Player player) { + return item.isInfinite(stack, bow, player); + } + + @Override + public boolean bridge$forge$onChorusFruitTeleport(LivingEntity entity, double targetX, double targetY, double targetZ) { + return EventHooks.onChorusFruitTeleport(entity, targetX, targetY, targetZ).isCanceled(); + } + + @Override + public void bridge$forge$onPlayerDestroyItem(Player player, @NotNull ItemStack stack, @Nullable InteractionHand hand) { + EventHooks.onPlayerDestroyItem(player, stack, hand); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ItemStackMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ItemStackMixin_NeoForge.java new file mode 100644 index 000000000..574b16d6d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ItemStackMixin_NeoForge.java @@ -0,0 +1,79 @@ +package io.izzel.arclight.neoforge.mixin.core.world.item; + +import io.izzel.arclight.common.bridge.core.world.item.ItemStackBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.phys.AABB; +import net.neoforged.neoforge.attachment.AttachmentHolder; +import net.neoforged.neoforge.common.extensions.IItemStackExtension; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +import javax.annotation.Nullable; +import java.util.Locale; + +@SuppressWarnings("MixinSuperClass") +@Mixin(ItemStack.class) +public abstract class ItemStackMixin_NeoForge extends AttachmentHolder implements ItemStackBridge, IItemStackExtension { + + // @formatter:off + @Mutable @Shadow @Final @Deprecated @Nullable private Item item; + // @formatter:on + + @Override + public CompoundTag bridge$getForgeCaps() { + return this.serializeAttachments(); + } + + @Override + public void bridge$setForgeCaps(CompoundTag caps) { + if (caps != null) { + this.deserializeAttachments(caps); + } + } + + @Deprecated + public void setItem(Item item) { + this.item = item; + } + + @Override + public boolean bridge$forge$hasCraftingRemainingItem() { + return this.hasCraftingRemainingItem(); + } + + @Override + public ItemStack bridge$forge$getCraftingRemainingItem() { + return this.getCraftingRemainingItem(); + } + + @Override + public boolean bridge$forge$canPerformAction(ToolAction action) { + return this.canPerformAction(net.neoforged.neoforge.common.ToolAction.get(action.name().toLowerCase(Locale.ROOT))); + } + + @Override + public AABB bridge$forge$getSweepHitBox(@NotNull Player player, @NotNull Entity target) { + return this.getSweepHitBox(player, target); + } + + @Override + public InteractionResult bridge$forge$onItemUseFirst(UseOnContext context) { + return onItemUseFirst(context); + } + + @Override + public boolean bridge$forge$doesSneakBypassUse(LevelReader level, BlockPos pos, Player player) { + return doesSneakBypassUse(level, pos, player); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/MilkBucketItemMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/MilkBucketItemMixin_NeoForge.java new file mode 100644 index 000000000..6ba610db0 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/MilkBucketItemMixin_NeoForge.java @@ -0,0 +1,21 @@ +package io.izzel.arclight.neoforge.mixin.core.world.item; + +import io.izzel.arclight.common.bridge.core.entity.LivingEntityBridge; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.MilkBucketItem; +import net.minecraft.world.level.Level; +import org.bukkit.event.entity.EntityPotionEffectEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(MilkBucketItem.class) +public abstract class MilkBucketItemMixin_NeoForge { + + @Inject(method = "finishUsingItem", at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/entity/LivingEntity;removeEffectsCuredBy(Lnet/neoforged/neoforge/common/EffectCure;)Z")) + private void arclight$cureReason(ItemStack stack, Level worldIn, LivingEntity entityLiving, CallbackInfoReturnable cir) { + ((LivingEntityBridge) entityLiving).bridge$pushEffectCause(EntityPotionEffectEvent.Cause.MILK); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ShearsItemMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ShearsItemMixin_NeoForge.java new file mode 100644 index 000000000..085dc3073 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/ShearsItemMixin_NeoForge.java @@ -0,0 +1,24 @@ +package io.izzel.arclight.neoforge.mixin.core.world.item; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ShearsItem; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ShearsItem.class) +public abstract class ShearsItemMixin_NeoForge { + + @Inject(method = "interactLivingEntity", cancellable = true, at = @At(value = "INVOKE", remap = false, target = "Lnet/neoforged/neoforge/common/IShearable;isShearable(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Z")) + private void arclight$onShear(ItemStack stack, Player playerIn, LivingEntity entity, InteractionHand hand, CallbackInfoReturnable cir) { + if (!CraftEventFactory.handlePlayerShearEntityEvent(playerIn, entity, stack, hand)) { + cir.setReturnValue(InteractionResult.PASS); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/crafting/RecipeManagerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/crafting/RecipeManagerMixin_NeoForge.java new file mode 100644 index 000000000..e944621c7 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/item/crafting/RecipeManagerMixin_NeoForge.java @@ -0,0 +1,36 @@ +package io.izzel.arclight.neoforge.mixin.core.world.item.crafting; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import io.izzel.arclight.common.bridge.core.world.item.crafting.RecipeManagerBridge; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeManager; +import net.neoforged.neoforge.common.conditions.ConditionalOps; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Optional; + +@Mixin(RecipeManager.class) +public abstract class RecipeManagerMixin_NeoForge extends SimpleJsonResourceReloadListener implements RecipeManagerBridge { + + // @formatter:off + @Shadow public static Optional> fromJson(ResourceLocation arg, JsonObject jsonObject, DynamicOps jsonElementOps) { return Optional.empty(); } + // @formatter:on + + public RecipeManagerMixin_NeoForge(Gson gson, String string) { + super(gson, string); + } + + @Override + public RecipeHolder bridge$platform$loadRecipe(ResourceLocation key, JsonElement element) { + return fromJson(key, GsonHelper.convertToJsonObject(element, "top element"), ConditionalOps.create(RegistryOps.create(JsonOps.INSTANCE, this.registryAccess), this.conditionContext)).orElse(null); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/BaseSpawnerMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/BaseSpawnerMixin_NeoForge.java new file mode 100644 index 000000000..9887ae1c0 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/BaseSpawnerMixin_NeoForge.java @@ -0,0 +1,31 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level; + +import io.izzel.arclight.common.bridge.core.world.spawner.BaseSpawnerBridge; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.SpawnGroupData; +import net.minecraft.world.level.BaseSpawner; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.SpawnData; +import net.neoforged.neoforge.event.EventHooks; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BaseSpawner.class) +public abstract class BaseSpawnerMixin_NeoForge implements BaseSpawnerBridge { + + @Override + public boolean bridge$forge$checkSpawnRules(Mob mob, ServerLevelAccessor level, MobSpawnType spawnType, SpawnData spawnData) { + return !EventHooks.checkSpawnPositionSpawner(mob, level, MobSpawnType.SPAWNER, spawnData, (BaseSpawner) (Object) this); + } + + @Override + public void bridge$forge$finalizeSpawnerSpawn(Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag) { + var event = EventHooks.onFinalizeSpawnSpawner(mob, level, difficulty, spawnData, spawnTag, (BaseSpawner) (Object) this); + if (event != null) { + mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData(), event.getSpawnTag()); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/ExplosionMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/ExplosionMixin_NeoForge.java new file mode 100644 index 000000000..3acaa7485 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/ExplosionMixin_NeoForge.java @@ -0,0 +1,19 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level; + +import io.izzel.arclight.common.bridge.core.world.ExplosionBridge; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; + +import java.util.List; + +@Mixin(Explosion.class) +public abstract class ExplosionMixin_NeoForge implements ExplosionBridge { + + @Override + public void bridge$forge$onExplosionDetonate(Level level, Explosion explosion, List list, double diameter) { + EventHooks.onExplosionDetonate(level, explosion, list, diameter); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/LevelMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/LevelMixin_NeoForge.java new file mode 100644 index 000000000..cf218330c --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/LevelMixin_NeoForge.java @@ -0,0 +1,89 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level; + +import io.izzel.arclight.common.bridge.core.world.WorldBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.neoforged.neoforge.common.brewing.BrewingRecipeRegistry; +import net.neoforged.neoforge.common.util.BlockSnapshot; +import net.neoforged.neoforge.event.EventHooks; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v.block.CraftBlock; +import org.bukkit.craftbukkit.v.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Level.class) +public abstract class LevelMixin_NeoForge implements WorldBridge { + + // @formatter:off + @Shadow(remap = false) public abstract void markAndNotifyBlock(BlockPos arg, @Nullable LevelChunk arg2, BlockState j, BlockState k, int j2, int k2); + @Shadow public abstract ResourceKey dimension(); + @Shadow(remap = false) public boolean restoringBlockSnapshots; + // @formatter:on + + @Inject(method = "markAndNotifyBlock", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;updateNeighbourShapes(Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;II)V")) + private void arclight$callBlockPhysics(BlockPos pos, LevelChunk chunk, BlockState blockstate, BlockState state, int flags, int recursionLeft, CallbackInfo ci) { + try { + if (this.bridge$getWorld() != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent(CraftBlock.at((LevelAccessor) this, pos), CraftBlockData.fromData(state)); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + ci.cancel(); + } + } + } catch (StackOverflowError e) { + bridge$setLastPhysicsProblem(pos); + } + } + + @Inject(method = "markAndNotifyBlock", cancellable = true, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;onBlockStateChange(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;)V")) + private void arclight$preventPoiUpdate(BlockPos p_46605_, LevelChunk levelchunk, BlockState blockstate, BlockState p_46606_, int p_46607_, int p_46608_, CallbackInfo ci) { + if (this.bridge$preventPoiUpdated()) { + ci.cancel(); + } + } + + @Override + public void bridge$forge$notifyAndUpdatePhysics(BlockPos pos, LevelChunk chunk, BlockState oldBlock, BlockState newBlock, int i, int j) { + this.markAndNotifyBlock(pos, chunk, oldBlock, newBlock, i, j); + } + + @Override + public boolean bridge$forge$onBlockPlace(BlockPos pos, LivingEntity livingEntity, Direction direction) { + return EventHooks.onBlockPlace(livingEntity, BlockSnapshot.create(this.dimension(), (Level) (Object) this, pos), direction); + } + + @Override + public boolean bridge$forge$mobGriefing(Entity entity) { + return EventHooks.getMobGriefingEvent((Level) (Object) this, entity); + } + + @Override + public ItemStack bridge$forge$potionBrewMix(ItemStack a, ItemStack b) { + return BrewingRecipeRegistry.getOutput(a, b); + } + + @Override + public void bridge$forge$onPotionBrewed(NonNullList stacks) { + EventHooks.onPotionBrewed(stacks); + } + + @Override + public boolean bridge$forge$restoringBlockSnapshots() { + return this.restoringBlockSnapshots; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/BlockMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/BlockMixin_NeoForge.java new file mode 100644 index 000000000..fff721484 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/BlockMixin_NeoForge.java @@ -0,0 +1,63 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.block; + +import io.izzel.arclight.common.bridge.core.world.level.block.BlockBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.PoweredRailBlock; +import net.minecraft.world.level.block.entity.DispenserBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.extensions.IBlockExtension; +import net.neoforged.neoforge.items.VanillaInventoryCodeHooks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Block.class) +public abstract class BlockMixin_NeoForge implements BlockBridge, IBlockExtension { + + @Override + public int bridge$getExpDrop(BlockState blockState, ServerLevel world, BlockPos blockPos, ItemStack itemStack) { + int silkTouch = itemStack.getEnchantmentLevel(Enchantments.SILK_TOUCH); + int fortune = itemStack.getEnchantmentLevel(Enchantments.BLOCK_FORTUNE); + return this.getExpDrop(blockState, world, world.random, blockPos, fortune, silkTouch); + } + + @Override + public boolean bridge$forge$onCropsGrowPre(Level level, BlockPos pos, BlockState state, boolean def) { + return CommonHooks.onCropsGrowPre(level, pos, state, def); + } + + @Override + public void bridge$forge$onCropsGrowPost(Level level, BlockPos pos, BlockState state) { + CommonHooks.onCropsGrowPost(level, pos, state); + } + + @Override + public boolean bridge$forge$dropperInsertHook(Level level, BlockPos pos, DispenserBlockEntity dropper, int slot, @NotNull ItemStack stack) { + return VanillaInventoryCodeHooks.dropperInsertHook(level, pos, dropper, slot, stack); + } + + @Override + public void bridge$forge$onCaughtFire(BlockState state, Level level, BlockPos pos, @Nullable Direction direction, @Nullable LivingEntity igniter) { + this.onCaughtFire(state, level, pos, direction, igniter); + } + + @Override + public boolean bridge$forge$isActivatorRail(BlockState state) { + return state.getBlock() instanceof PoweredRailBlock block && block.isActivatorRail(); + } + + @Override + public boolean bridge$forge$canEntityDestroy(BlockState state, BlockGetter level, BlockPos pos, Entity entity) { + return this.canEntityDestroy(state, level, pos, entity); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/CropBlockMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/CropBlockMixin_NeoForge.java new file mode 100644 index 000000000..017f9d67f --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/CropBlockMixin_NeoForge.java @@ -0,0 +1,23 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.CropBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.event.EventHooks; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(CropBlock.class) +public abstract class CropBlockMixin_NeoForge { + + @Redirect(method = "entityInside", at = @At(value = "INVOKE", remap = false, target = "Lnet/neoforged/neoforge/event/EventHooks;getMobGriefingEvent(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/Entity;)Z")) + public boolean arclight$entityChangeBlock(Level world, Entity entity, BlockState state, Level worldIn, BlockPos pos) { + boolean result = EventHooks.getMobGriefingEvent(world, entity); + return !CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state, result); + } + +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/FireBlockMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/FireBlockMixin_NeoForge.java new file mode 100644 index 000000000..254a9b7e0 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/FireBlockMixin_NeoForge.java @@ -0,0 +1,37 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.FireBlock; +import net.minecraft.world.level.block.TntBlock; +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v.block.CraftBlock; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.TNTPrimeEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FireBlock.class) +public class FireBlockMixin_NeoForge { + + @Inject(method = "checkBurnOut", cancellable = true, at = @At(value = "INVOKE", ordinal = 1, target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;")) + private void arclight$blockBurn(Level worldIn, BlockPos pos, int chance, RandomSource random, int age, Direction face, CallbackInfo ci) { + Block theBlock = CraftBlock.at(worldIn, pos); + Block sourceBlock = CraftBlock.at(worldIn, pos.relative(face)); + BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + ci.cancel(); + return; + } + if (worldIn.getBlockState(pos).getBlock() instanceof TntBlock && !CraftEventFactory.callTNTPrimeEvent(worldIn, pos, TNTPrimeEvent.PrimeCause.FIRE, null, pos.relative(face))) { + ci.cancel(); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/TntBlockMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/TntBlockMixin_NeoForge.java new file mode 100644 index 000000000..e7d2ab15b --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/TntBlockMixin_NeoForge.java @@ -0,0 +1,49 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.TntBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.block.TNTPrimeEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(TntBlock.class) +public abstract class TntBlockMixin_NeoForge extends BlockMixin_NeoForge { + + @Redirect(method = "playerWillDestroy", at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/level/block/TntBlock;onCaughtFire(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;Lnet/minecraft/world/entity/LivingEntity;)V")) + private void arclight$playerBreak(TntBlock instance, BlockState state, Level world, BlockPos pos, Direction face, LivingEntity igniter, + Level p_57445_, BlockPos p_57446_, BlockState p_57447_, Player player) { + if (CraftEventFactory.callTNTPrimeEvent(world, pos, TNTPrimeEvent.PrimeCause.BLOCK_BREAK, player, null)) { + this.bridge$forge$onCaughtFire(state, world, pos, face, igniter); + } + } + + @Inject(method = "use", cancellable = true, at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/level/block/TntBlock;onCaughtFire(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;Lnet/minecraft/world/entity/LivingEntity;)V")) + private void arclight$player(BlockState p_57450_, Level level, BlockPos pos, Player player, InteractionHand p_57454_, BlockHitResult p_57455_, CallbackInfoReturnable cir) { + if (!CraftEventFactory.callTNTPrimeEvent(level, pos, TNTPrimeEvent.PrimeCause.PLAYER, player, null)) { + cir.setReturnValue(InteractionResult.CONSUME); + } + } + + @Inject(method = "onProjectileHit", cancellable = true, at = @At(value = "INVOKE", remap = false, target = "Lnet/minecraft/world/level/block/TntBlock;onCaughtFire(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/Direction;Lnet/minecraft/world/entity/LivingEntity;)V")) + private void arclight$entityChangeBlock(Level worldIn, BlockState state, BlockHitResult hit, Projectile projectile, CallbackInfo ci) { + if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, hit.getBlockPos(), Blocks.AIR.defaultBlockState()) + || !CraftEventFactory.callTNTPrimeEvent(worldIn, hit.getBlockPos(), TNTPrimeEvent.PrimeCause.PROJECTILE, projectile, null)) { + ci.cancel(); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/entity/BrewingStandBlockEntityMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/entity/BrewingStandBlockEntityMixin_NeoForge.java new file mode 100644 index 000000000..81f947a30 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/entity/BrewingStandBlockEntityMixin_NeoForge.java @@ -0,0 +1,23 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BrewingStandBlockEntity; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BrewingStandBlockEntity.class) +public class BrewingStandBlockEntityMixin_NeoForge { + + @Inject(method = "doBrew", cancellable = true, at = @At("HEAD")) + private static void arclight$forgeBrew(Level arg, BlockPos arg2, NonNullList stacks, CallbackInfo ci) { + if (EventHooks.onPotionAttemptBrew(stacks)) { + ci.cancel(); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/state/BlockBehaviourMixin_Forge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/state/BlockBehaviourMixin_Forge.java new file mode 100644 index 000000000..f9eba2035 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/block/state/BlockBehaviourMixin_Forge.java @@ -0,0 +1,24 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.block.state; + +import io.izzel.arclight.common.bridge.core.world.level.block.state.BlockBehaviourBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockBehaviour.class) +public abstract class BlockBehaviourMixin_Forge implements BlockBehaviourBridge { + + @Override + public boolean bridge$forge$canDropFromExplosion(BlockState state, BlockGetter level, BlockPos pos, Explosion explosion) { + return state.getBlock().canDropFromExplosion(state, level, pos, explosion); + } + + @Override + public void bridge$forge$onBlockExploded(BlockState state, Level level, BlockPos pos, Explosion explosion) { + state.getBlock().onBlockExploded(state, level, pos, explosion); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/levelgen/structure/templatesystem/StructureTemplateMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/levelgen/structure/templatesystem/StructureTemplateMixin_NeoForge.java new file mode 100644 index 000000000..38e12b729 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/levelgen/structure/templatesystem/StructureTemplateMixin_NeoForge.java @@ -0,0 +1,34 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.levelgen.structure.templatesystem; + +import io.izzel.arclight.common.bridge.core.world.level.levelgen.structure.templatesystem.StructureTemplateBridge; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; + +@Mixin(StructureTemplate.class) +public abstract class StructureTemplateMixin_NeoForge implements StructureTemplateBridge { + + // @formatter:off + @Shadow(remap = false) public static List processBlockInfos(ServerLevelAccessor p_278297_, BlockPos p_74519_, BlockPos p_74520_, StructurePlaceSettings p_74521_, List p_74522_, @Nullable StructureTemplate template) { return null; } + @Shadow(remap = false) protected abstract void addEntitiesToWorld(ServerLevelAccessor par1, BlockPos par2, StructurePlaceSettings par3); + // @formatter:on + + @Override + public List bridge$platform$processBlockInfos(ServerLevelAccessor arg, BlockPos arg2, BlockPos arg3, StructurePlaceSettings arg4, List list2, @Nullable StructureTemplate template) { + return processBlockInfos(arg, arg2, arg3, arg4, list2, template); + } + + @Override + public void bridge$platform$placeEntities(ServerLevelAccessor serverLevelAccessor, BlockPos blockPos, Mirror mirror, Rotation rotation, BlockPos blockPos2, @Nullable BoundingBox boundingBox, boolean bl, StructurePlaceSettings placementIn) { + this.addEntitiesToWorld(serverLevelAccessor, blockPos, placementIn); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/storage/loot/LootContextMixin_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/storage/loot/LootContextMixin_NeoForge.java new file mode 100644 index 000000000..514564a2a --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/core/world/level/storage/loot/LootContextMixin_NeoForge.java @@ -0,0 +1,20 @@ +package io.izzel.arclight.neoforge.mixin.core.world.level.storage.loot; + +import io.izzel.arclight.common.bridge.core.world.storage.loot.LootContextBridge; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.storage.loot.LootContext; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(LootContext.class) +public abstract class LootContextMixin_NeoForge implements LootContextBridge { + + // @formatter:off + @Shadow(remap = false) public abstract int getLootingModifier(); + // @formatter:on + + @Override + public int bridge$forge$getLootingModifier(Entity entity) { + return this.getLootingModifier(); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/CommonHooksMixin.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/CommonHooksMixin.java new file mode 100644 index 000000000..5d0ed5550 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/CommonHooksMixin.java @@ -0,0 +1,25 @@ +package io.izzel.arclight.neoforge.mixin.forge; + +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.item.context.UseOnContext; +import net.neoforged.neoforge.common.CommonHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CommonHooks.class) +public abstract class CommonHooksMixin { + + @Inject(method = "onPlaceItemIntoWorld", remap = false, at = @At("HEAD")) + private static void arclight$captureHand(UseOnContext context, CallbackInfoReturnable cir) { + ArclightCaptures.capturePlaceEventHand(context.getHand()); + } + + @Inject(method = "onPlaceItemIntoWorld", remap = false, at = @At("RETURN")) + private static void arclight$removeHand(UseOnContext context, CallbackInfoReturnable cir) { + ArclightCaptures.getPlaceEventHand(InteractionHand.MAIN_HAND); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/PacketDistributorMixin.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/PacketDistributorMixin.java new file mode 100644 index 000000000..6b2e8f3b1 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/PacketDistributorMixin.java @@ -0,0 +1,26 @@ +package io.izzel.arclight.neoforge.mixin.forge; + +import net.minecraft.network.protocol.Packet; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +import java.util.function.Consumer; + +@Mixin(PacketDistributor.class) +public abstract class PacketDistributorMixin { + + /** + * @author IzzelAliz + * @reason + */ + @Overwrite(remap = false) + private Consumer> playerConsumer(ServerPlayer entity) { + return p -> { + if (entity.connection != null && entity.connection.isAcceptingMessages()) { + entity.connection.send(p); + } + }; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/PermissionAPIMixin.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/PermissionAPIMixin.java new file mode 100644 index 000000000..7a7471d91 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/forge/PermissionAPIMixin.java @@ -0,0 +1,28 @@ +package io.izzel.arclight.neoforge.mixin.forge; + +import io.izzel.arclight.common.mod.server.ArclightServer; +import io.izzel.arclight.i18n.ArclightConfig; +import io.izzel.arclight.neoforge.mod.permission.ArclightPermissionHandler; +import net.neoforged.neoforge.server.permission.PermissionAPI; +import net.neoforged.neoforge.server.permission.handler.IPermissionHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = PermissionAPI.class, remap = false) +public abstract class PermissionAPIMixin { + + @Shadow private static IPermissionHandler activeHandler; + + @Inject(method = "initializePermissionAPI", at = @At("RETURN")) + private static void arclight$init(CallbackInfo ci) { + if (!ArclightConfig.spec().getCompat().isForwardPermission()) { + return; + } + var handler = new ArclightPermissionHandler(activeHandler); + ArclightServer.LOGGER.info("Forwarding forge permission[{}] to bukkit", activeHandler.getIdentifier()); + activeHandler = handler; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/optimization/general/activationrange/entity/ItemEntityMixin_ActivationRange_NeoForge.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/optimization/general/activationrange/entity/ItemEntityMixin_ActivationRange_NeoForge.java new file mode 100644 index 000000000..90e0d75bf --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mixin/optimization/general/activationrange/entity/ItemEntityMixin_ActivationRange_NeoForge.java @@ -0,0 +1,49 @@ +package io.izzel.arclight.neoforge.mixin.optimization.general.activationrange.entity; + +import io.izzel.arclight.common.bridge.core.entity.item.ItemEntityBridge; +import io.izzel.arclight.common.bridge.core.world.WorldBridge; +import io.izzel.arclight.common.mod.util.DistValidate; +import io.izzel.arclight.neoforge.mixin.core.world.entity.EntityMixin_NeoForge; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ItemEntity.class) +public abstract class ItemEntityMixin_ActivationRange_NeoForge extends EntityMixin_NeoForge implements ItemEntityBridge { + + // @formatter:off + @Shadow(remap = false) public int lifespan; + @Shadow public int age; + @Shadow public abstract ItemStack getItem(); + // @formatter:on + + @Inject(method = "(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/level/Level;)V", at = @At("RETURN")) + private void activationRange$init(EntityType entityType, Level world, CallbackInfo ci) { + if (DistValidate.isValid(this.level())) { + this.lifespan = ((WorldBridge) this.level()).bridge$spigotConfig().itemDespawnRate; + } + } + + @Inject(method = "(Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/item/ItemStack;)V", at = @At("RETURN")) + private void activationRange$init(Level worldIn, double x, double y, double z, ItemStack stack, CallbackInfo ci) { + if (DistValidate.isValid(this.level()) && this.lifespan == 6000) { + this.lifespan = ((WorldBridge) this.level()).bridge$spigotConfig().itemDespawnRate; + } + } + + @Override + public void bridge$forge$optimization$discardItemEntity() { + if (!this.level().isClientSide && this.age >= this.lifespan) { + int hook = EventHooks.onItemExpire((ItemEntity) (Object) this, this.getItem()); + if (hook < 0) this.discard(); + else this.lifespan += hook; + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/NeoForgeArclightServer.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/NeoForgeArclightServer.java new file mode 100644 index 000000000..114c3139c --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/NeoForgeArclightServer.java @@ -0,0 +1,36 @@ +package io.izzel.arclight.neoforge.mod; + +import io.izzel.arclight.api.ArclightPlatform; +import io.izzel.arclight.api.ArclightServer; +import io.izzel.arclight.api.TickingTracker; +import io.izzel.arclight.common.mod.server.api.DefaultTickingTracker; +import net.neoforged.bus.api.IEventBus; +import org.bukkit.plugin.Plugin; + +public class NeoForgeArclightServer implements ArclightServer { + + private final TickingTracker tickingTracker = new DefaultTickingTracker(); + + @Override + public void registerForgeEvent(Plugin plugin, net.minecraftforge.eventbus.api.IEventBus eventBus, Object target) { + registerModEvent(plugin, eventBus, target); + } + + @Override + public void registerModEvent(Plugin plugin, Object bus, Object target) { + try { + if (bus instanceof IEventBus eventBus) { + eventBus.register(target); + } else { + throw new IllegalArgumentException("Unknown bus type " + bus + " on platform " + ArclightPlatform.current()); + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + @Override + public TickingTracker getTickingTracker() { + return this.tickingTracker; + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/NeoForgeCommonImpl.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/NeoForgeCommonImpl.java new file mode 100644 index 000000000..87823511d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/NeoForgeCommonImpl.java @@ -0,0 +1,39 @@ +package io.izzel.arclight.neoforge.mod; + +import cpw.mods.modlauncher.ClassTransformer; +import cpw.mods.modlauncher.TransformingClassLoader; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.common.mod.ArclightCommon; +import org.objectweb.asm.ClassReader; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class NeoForgeCommonImpl implements ArclightCommon.Api { + + private static final MethodHandle MH_TRANSFORM; + + static { + try { + ClassLoader classLoader = NeoForgeCommonImpl.class.getClassLoader(); + Field classTransformer = TransformingClassLoader.class.getDeclaredField("classTransformer"); + classTransformer.setAccessible(true); + ClassTransformer tranformer = (ClassTransformer) classTransformer.get(classLoader); + Method transform = tranformer.getClass().getDeclaredMethod("transform", byte[].class, String.class, String.class); + MH_TRANSFORM = Unsafe.lookup().unreflect(transform).bindTo(tranformer); + } catch (Throwable t) { + throw new IllegalStateException("Unknown modlauncher version", t); + } + } + + @Override + public byte[] platformRemapClass(byte[] cl) { + String className = new ClassReader(cl).getClassName(); + try { + return (byte[]) MH_TRANSFORM.invokeExact(cl, className.replace('/', '.'), "source"); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/ArclightEventDispatcherRegistry.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/ArclightEventDispatcherRegistry.java new file mode 100644 index 000000000..6c3352ebf --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/ArclightEventDispatcherRegistry.java @@ -0,0 +1,16 @@ +package io.izzel.arclight.neoforge.mod.event; + +import io.izzel.arclight.common.mod.server.ArclightServer; +import net.neoforged.neoforge.common.NeoForge; + +public abstract class ArclightEventDispatcherRegistry { + + public static void registerAllEventDispatchers() { + NeoForge.EVENT_BUS.register(new BlockBreakEventDispatcher()); + NeoForge.EVENT_BUS.register(new BlockPlaceEventDispatcher()); + NeoForge.EVENT_BUS.register(new EntityEventDispatcher()); + NeoForge.EVENT_BUS.register(new EntityTeleportEventDispatcher()); + NeoForge.EVENT_BUS.register(new ItemEntityEventDispatcher()); + ArclightServer.LOGGER.info("registry.forge-event"); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/BlockBreakEventDispatcher.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/BlockBreakEventDispatcher.java new file mode 100644 index 000000000..89c01e11d --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/BlockBreakEventDispatcher.java @@ -0,0 +1,58 @@ +package io.izzel.arclight.neoforge.mod.event; + +import io.izzel.arclight.common.bridge.core.entity.EntityBridge; +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import io.izzel.arclight.common.mod.util.DistValidate; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.Blocks; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.level.BlockEvent; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v.block.CraftBlock; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.event.Cancellable; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityInteractEvent; + +// TODO common impl +public class BlockBreakEventDispatcher { + + @SubscribeEvent(receiveCanceled = true) + public void onBreakBlock(BlockEvent.BreakEvent event) { + if (DistValidate.isValid(event.getLevel())) { + CraftBlock craftBlock = CraftBlock.at(event.getLevel(), event.getPos()); + BlockBreakEvent breakEvent = new BlockBreakEvent(craftBlock, ((ServerPlayerEntityBridge) event.getPlayer()).bridge$getBukkitEntity()); + ArclightCaptures.captureBlockBreakPlayer(breakEvent); + breakEvent.setCancelled(event.isCanceled()); + breakEvent.setExpToDrop(event.getExpToDrop()); + Bukkit.getPluginManager().callEvent(breakEvent); + event.setCanceled(breakEvent.isCancelled()); + event.setExpToDrop(breakEvent.getExpToDrop()); + } + } + + @SubscribeEvent + public void onFarmlandBreak(BlockEvent.FarmlandTrampleEvent event) { + if (!DistValidate.isValid(event.getLevel())) return; + Entity entity = event.getEntity(); + Cancellable cancellable; + if (entity instanceof Player) { + cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, Action.PHYSICAL, event.getPos(), null, null, null); + } else { + cancellable = new EntityInteractEvent(((EntityBridge) entity).bridge$getBukkitEntity(), CraftBlock.at(event.getLevel(), event.getPos())); + Bukkit.getPluginManager().callEvent((EntityInteractEvent) cancellable); + } + + if (cancellable.isCancelled()) { + event.setCanceled(true); + return; + } + + if (!CraftEventFactory.callEntityChangeBlockEvent(entity, event.getPos(), Blocks.DIRT.defaultBlockState())) { + event.setCanceled(true); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/BlockPlaceEventDispatcher.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/BlockPlaceEventDispatcher.java new file mode 100644 index 000000000..b584a6453 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/BlockPlaceEventDispatcher.java @@ -0,0 +1,95 @@ +package io.izzel.arclight.neoforge.mod.event; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import io.izzel.arclight.common.mod.util.ArclightCaptures; +import io.izzel.arclight.common.mod.util.DistValidate; +import io.izzel.arclight.neoforge.mod.util.ArclightBlockSnapshot; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.common.util.BlockSnapshot; +import net.neoforged.neoforge.event.level.BlockEvent; +import org.bukkit.Bukkit; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.v.block.CraftBlock; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockMultiPlaceEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +// TODO common impl +public class BlockPlaceEventDispatcher { + + @SubscribeEvent(receiveCanceled = true) + public void onBlockPlace(BlockEvent.EntityPlaceEvent event) { + Entity entity = event.getEntity(); + if (entity instanceof ServerPlayerEntityBridge playerEntity) { + Player player = playerEntity.bridge$getBukkitEntity(); + Direction direction = ArclightCaptures.getPlaceEventDirection(); + if (direction != null && DistValidate.isValid(event.getLevel())) { + InteractionHand hand = ArclightCaptures.getPlaceEventHand(InteractionHand.MAIN_HAND); + CraftBlock placedBlock = ArclightBlockSnapshot.fromBlockSnapshot(event.getBlockSnapshot(), true); + CraftBlock againstBlock = CraftBlock.at(event.getLevel(), event.getPos().relative(direction.getOpposite())); + ItemStack bukkitStack; + EquipmentSlot bukkitHand; + if (hand == InteractionHand.MAIN_HAND) { + bukkitStack = player.getInventory().getItemInMainHand(); + bukkitHand = EquipmentSlot.HAND; + } else { + bukkitStack = player.getInventory().getItemInOffHand(); + bukkitHand = EquipmentSlot.OFF_HAND; + } + BlockPlaceEvent placeEvent = new BlockPlaceEvent( + placedBlock, + placedBlock.getState(), + againstBlock, + bukkitStack, + player, + !event.isCanceled(), + bukkitHand + ); + placeEvent.setCancelled(event.isCanceled()); + Bukkit.getPluginManager().callEvent(placeEvent); + event.setCanceled(placeEvent.isCancelled() || !placeEvent.canBuild()); + } + } + } + + @SubscribeEvent(receiveCanceled = true) + public void onMultiPlace(BlockEvent.EntityMultiPlaceEvent event) { + Entity entity = event.getEntity(); + if (entity instanceof ServerPlayerEntityBridge playerEntity) { + Player player = playerEntity.bridge$getBukkitEntity(); + Direction direction = ArclightCaptures.getPlaceEventDirection(); + if (direction != null && DistValidate.isValid(event.getLevel())) { + InteractionHand hand = ArclightCaptures.getPlaceEventHand(InteractionHand.MAIN_HAND); + List placedBlocks = new ArrayList<>(event.getReplacedBlockSnapshots().size()); + for (BlockSnapshot snapshot : event.getReplacedBlockSnapshots()) { + placedBlocks.add(ArclightBlockSnapshot.fromBlockSnapshot(snapshot, true).getState()); + } + CraftBlock againstBlock = CraftBlock.at(event.getLevel(), event.getPos().relative(direction.getOpposite())); + ItemStack bukkitStack; + if (hand == InteractionHand.MAIN_HAND) { + bukkitStack = player.getInventory().getItemInMainHand(); + } else { + bukkitStack = player.getInventory().getItemInOffHand(); + } + BlockPlaceEvent placeEvent = new BlockMultiPlaceEvent( + placedBlocks, + againstBlock, + bukkitStack, + player, + !event.isCanceled() + ); + placeEvent.setCancelled(event.isCanceled()); + Bukkit.getPluginManager().callEvent(placeEvent); + event.setCanceled(placeEvent.isCancelled() || !placeEvent.canBuild()); + } + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/EntityEventDispatcher.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/EntityEventDispatcher.java new file mode 100644 index 000000000..d9cebac28 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/EntityEventDispatcher.java @@ -0,0 +1,51 @@ +package io.izzel.arclight.neoforge.mod.event; + +import io.izzel.arclight.common.mod.server.event.ArclightEventFactory; +import io.izzel.tools.collection.XmapList; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.item.ItemEntity; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.entity.living.AnimalTameEvent; +import net.neoforged.neoforge.event.entity.living.LivingDropsEvent; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; +import org.bukkit.craftbukkit.v.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class EntityEventDispatcher { + + @SubscribeEvent(receiveCanceled = true) + public void onLivingDeath(LivingDropsEvent event) { + if (event.getEntity() instanceof ServerPlayer) { + // handled at ServerPlayerEntityMixin#onDeath + // Cancelled at io.izzel.arclight.common.mixin.core.world.entity.LivingEntityMixin#arclight$cancelEvent + // event.setCanceled(true); + return; + } + LivingEntity livingEntity = event.getEntity(); + Collection drops = event.getDrops(); + if (!(drops instanceof ArrayList)) { + drops = new ArrayList<>(drops); + } + List itemStackList = XmapList.create((List) drops, ItemStack.class, + (ItemEntity entity) -> CraftItemStack.asCraftMirror(entity.getItem()), + itemStack -> { + ItemEntity itemEntity = new ItemEntity(livingEntity.level(), livingEntity.getX(), livingEntity.getY(), livingEntity.getZ(), CraftItemStack.asNMSCopy(itemStack)); + itemEntity.setDefaultPickUpDelay(); + return itemEntity; + }); + ArclightEventFactory.callEntityDeathEvent(livingEntity, itemStackList); + if (drops.isEmpty()) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public void onEntityTame(AnimalTameEvent event) { + event.setCanceled(CraftEventFactory.callEntityTameEvent(event.getAnimal(), event.getTamer()).isCancelled()); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/EntityTeleportEventDispatcher.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/EntityTeleportEventDispatcher.java new file mode 100644 index 000000000..e21b297ea --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/EntityTeleportEventDispatcher.java @@ -0,0 +1,36 @@ +package io.izzel.arclight.neoforge.mod.event; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.SubscribeEvent; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.craftbukkit.v.entity.CraftEntity; +import org.bukkit.craftbukkit.v.entity.CraftPlayer; +import org.bukkit.event.entity.EntityTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent; + +// TODO common impl +public class EntityTeleportEventDispatcher { + + @SubscribeEvent(receiveCanceled = true) + public void onTeleport(net.neoforged.neoforge.event.entity.EntityTeleportEvent.EnderEntity event) { + if (event.getEntity() instanceof ServerPlayer) { + CraftPlayer player = ((ServerPlayerEntityBridge) event.getEntity()).bridge$getBukkitEntity(); + PlayerTeleportEvent bukkitEvent = new PlayerTeleportEvent(player, player.getLocation(), new Location(player.getWorld(), event.getTargetX(), event.getTargetY(), event.getTargetZ()), PlayerTeleportEvent.TeleportCause.ENDER_PEARL); + Bukkit.getPluginManager().callEvent(bukkitEvent); + event.setCanceled(bukkitEvent.isCancelled()); + event.setTargetX(bukkitEvent.getTo().getX()); + event.setTargetY(bukkitEvent.getTo().getY()); + event.setTargetZ(bukkitEvent.getTo().getZ()); + } else { + CraftEntity entity = event.getEntity().bridge$getBukkitEntity(); + EntityTeleportEvent bukkitEvent = new EntityTeleportEvent(entity, entity.getLocation(), new Location(entity.getWorld(), event.getTargetX(), event.getTargetY(), event.getTargetZ())); + Bukkit.getPluginManager().callEvent(bukkitEvent); + event.setCanceled(bukkitEvent.isCancelled()); + event.setTargetX(bukkitEvent.getTo().getX()); + event.setTargetY(bukkitEvent.getTo().getY()); + event.setTargetZ(bukkitEvent.getTo().getZ()); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/ItemEntityEventDispatcher.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/ItemEntityEventDispatcher.java new file mode 100644 index 000000000..c818e9e3e --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/event/ItemEntityEventDispatcher.java @@ -0,0 +1,13 @@ +package io.izzel.arclight.neoforge.mod.event; + +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.event.entity.item.ItemExpireEvent; +import org.bukkit.craftbukkit.v.event.CraftEventFactory; + +public class ItemEntityEventDispatcher { + + @SubscribeEvent(receiveCanceled = true) + public void onExpire(ItemExpireEvent event) { + event.setCanceled(CraftEventFactory.callItemDespawnEvent(event.getEntity()).isCancelled()); + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/permission/ArclightNeoForgePermissible.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/permission/ArclightNeoForgePermissible.java new file mode 100644 index 000000000..0c81144dd --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/permission/ArclightNeoForgePermissible.java @@ -0,0 +1,77 @@ +package io.izzel.arclight.neoforge.mod.permission; + +import io.izzel.arclight.api.Unsafe; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.server.permission.PermissionAPI; +import net.neoforged.neoforge.server.permission.handler.IPermissionHandler; +import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContextKey; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionType; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; +import org.bukkit.craftbukkit.v.entity.CraftHumanEntity; +import org.bukkit.permissions.PermissibleBase; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.ServerOperator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + +public class ArclightNeoForgePermissible extends PermissibleBase { + + private final CraftHumanEntity player; + + public ArclightNeoForgePermissible(@Nullable ServerOperator opable) { + super(opable); + this.player = (CraftHumanEntity) opable; + } + + @Override + public boolean hasPermission(@NotNull String inName) { + var node = newNode(inName, (player, playerUUID, context) -> super.hasPermission(inName)); + if (player.getHandle() instanceof ServerPlayer player) { + return getHandler().getPermission(player, node); + } else { + return getHandler().getOfflinePermission(player.getUniqueId(), node); + } + } + + @Override + public boolean hasPermission(@NotNull Permission perm) { + var node = newNode(perm.getName(), (player, playerUUID, context) -> super.hasPermission(perm)); + if (player.getHandle() instanceof ServerPlayer player) { + return getHandler().getPermission(player, node); + } else { + return getHandler().getOfflinePermission(player.getUniqueId(), node); + } + } + + private static final MethodHandle H_handler, H_newNode; + + private static IPermissionHandler getHandler() { + try { + return (IPermissionHandler) H_handler.invokeExact(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private static PermissionNode newNode(String nodeName, PermissionNode.PermissionResolver defaultResolver, PermissionDynamicContextKey... dynamics) { + try { + return (PermissionNode) H_newNode.invokeExact(nodeName, PermissionTypes.BOOLEAN, defaultResolver, dynamics); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + static { + try { + H_handler = Unsafe.lookup().findStaticGetter(PermissionAPI.class, "activeHandler", IPermissionHandler.class); + H_newNode = Unsafe.lookup().findConstructor(PermissionNode.class, MethodType.methodType(void.class, String.class, PermissionType.class, PermissionNode.PermissionResolver.class, PermissionDynamicContextKey[].class)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/permission/ArclightPermissionHandler.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/permission/ArclightPermissionHandler.java new file mode 100644 index 000000000..065c96a82 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/permission/ArclightPermissionHandler.java @@ -0,0 +1,54 @@ +package io.izzel.arclight.neoforge.mod.permission; + +import io.izzel.arclight.common.bridge.core.entity.player.ServerPlayerEntityBridge; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.server.permission.handler.IPermissionHandler; +import net.neoforged.neoforge.server.permission.nodes.PermissionDynamicContext; +import net.neoforged.neoforge.server.permission.nodes.PermissionNode; +import net.neoforged.neoforge.server.permission.nodes.PermissionTypes; +import org.bukkit.Bukkit; + +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +public final class ArclightPermissionHandler implements IPermissionHandler { + + private final IPermissionHandler delegate; + + public ArclightPermissionHandler(IPermissionHandler delegate) { + Objects.requireNonNull(delegate, "permission handler"); + this.delegate = delegate; + } + + @Override + public ResourceLocation getIdentifier() { + return new ResourceLocation("arclight", "permission"); + } + + @Override + public Set> getRegisteredNodes() { + return delegate.getRegisteredNodes(); + } + + @SuppressWarnings("unchecked") + @Override + public T getPermission(ServerPlayer player, PermissionNode node, PermissionDynamicContext... context) { + if (node.getType() == PermissionTypes.BOOLEAN) { + return (T) (Object) ((ServerPlayerEntityBridge) player).bridge$getBukkitEntity().hasPermission(node.getNodeName()); + } else { + return delegate.getPermission(player, node, context); + } + } + + @Override + public T getOfflinePermission(UUID uuid, PermissionNode node, PermissionDynamicContext... context) { + var player = Bukkit.getPlayer(uuid); + if (player != null && node.getType() == PermissionTypes.BOOLEAN) { + return (T) (Object) player.hasPermission(node.getNodeName()); + } else { + return delegate.getOfflinePermission(uuid, node, context); + } + } +} diff --git a/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/util/ArclightBlockSnapshot.java b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/util/ArclightBlockSnapshot.java new file mode 100644 index 000000000..3ad5974b0 --- /dev/null +++ b/arclight-neoforge/src/main/java/io/izzel/arclight/neoforge/mod/util/ArclightBlockSnapshot.java @@ -0,0 +1,24 @@ +package io.izzel.arclight.neoforge.mod.util; + +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.common.util.BlockSnapshot; +import org.bukkit.craftbukkit.v.block.CraftBlock; + +public class ArclightBlockSnapshot extends CraftBlock { + + private final BlockState blockState; + + public ArclightBlockSnapshot(BlockSnapshot blockSnapshot, boolean current) { + super(blockSnapshot.getLevel(), blockSnapshot.getPos()); + this.blockState = current ? blockSnapshot.getCurrentBlock() : blockSnapshot.getReplacedBlock(); + } + + @Override + public BlockState getNMS() { + return blockState; + } + + public static ArclightBlockSnapshot fromBlockSnapshot(BlockSnapshot blockSnapshot, boolean current) { + return new ArclightBlockSnapshot(blockSnapshot, current); + } +} diff --git a/arclight-neoforge/src/main/resources/META-INF/mods.toml b/arclight-neoforge/src/main/resources/META-INF/mods.toml new file mode 100644 index 000000000..89db02f4b --- /dev/null +++ b/arclight-neoforge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,14 @@ +modLoader="javafml" +loaderVersion="[2,)" +license="GNU GENERAL PUBLIC LICENSE Version 3" + +[[mods]] +modId="arclight" +version="${version}" +displayName="Arclight Mod" +displayTest="IGNORE_SERVER_VERSION" +credits="lona,夜幕" +authors="IzzelAliz" +description=''' + Arclight bukkit layer for MinecraftForge + ''' diff --git a/arclight-neoforge/src/main/resources/arclight_neoforge.accesswidener b/arclight-neoforge/src/main/resources/arclight_neoforge.accesswidener new file mode 100644 index 000000000..f0bf0e67b --- /dev/null +++ b/arclight-neoforge/src/main/resources/arclight_neoforge.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named +accessible method net/neoforged/neoforge/registries/BaseMappedRegistry unfreeze ()V +accessible method net/neoforged/neoforge/attachment/AttachmentHolder deserializeAttachments (Lnet/minecraft/nbt/CompoundTag;)V diff --git a/arclight-neoforge/src/main/resources/mixins.arclight.neoforge.json b/arclight-neoforge/src/main/resources/mixins.arclight.neoforge.json new file mode 100644 index 000000000..cb67b2063 --- /dev/null +++ b/arclight-neoforge/src/main/resources/mixins.arclight.neoforge.json @@ -0,0 +1,75 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "io.izzel.arclight.neoforge.mixin", + "target": "@env(DEFAULT)", + "plugin": "io.izzel.arclight.common.mod.ArclightMixinPlugin", + "setSourceFile": true, + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "conformVisibility": true, + "requireAnnotations": false + }, + "mixinPriority": 555, + "compatibilityLevel": "JAVA_17", + "mixins": [ + "bukkit.CraftHumanEntityMixin_NeoForge", + "bukkit.CraftServerMixin_NeoForge", + "bukkit.CraftWorldMixin_NeoForge", + "bukkit.MaterialMixin_NeoForge", + "core.command.CommandsMixin_NeoForge", + "core.fluid.LavaFluidMixin_NeoForge", + "core.network.ServerCommonPacketListenerImplMixin_NeoForge", + "core.network.ServerLoginNetHandlerMixin_NeoForge", + "core.network.ServerPlayNetHandlerMixin_NeoForge", + "core.network.ServerStatusNetHandlerMixin_NeoForge", + "core.network.protocol.status.ServerStatusMixin", + "core.server.MinecraftServerMixin_NeoForge", + "core.server.dedicated.DedicatedServerMixin_NeoForge", + "core.server.level.DistanceManagerMixin_NeoForge", + "core.server.level.ServerEntityMixin_NeoForge", + "core.server.management.ServerPlayerGameModeMixin_NeoForge", + "core.world.effect.PoisonMobEffectMixin_NeoForge", + "core.world.entity.EntityMixin_NeoForge", + "core.world.entity.LivingEntityMixin_NeoForge", + "core.world.entity.MobMixin_NeoForge", + "core.world.entity.ai.behavior.HarvestFarmlandMixin_NeoForge", + "core.world.entity.animal.MushroomCowMixin_NeoForge", + "core.world.entity.animal.frog.TadpoleMixin_NeoForge", + "core.world.entity.item.ItemEntityMixin_NeoForge", + "core.world.entity.monster.ZombieMixin_NeoForge", + "core.world.entity.monster.piglin.PiglinAiMixin_NeoForge", + "core.world.entity.monster.piglin.PiglinMixin_NeoForge", + "core.world.entity.player.PlayerMixin_NeoForge", + "core.world.entity.player.ServerPlayerMixin_NeoForge", + "core.world.entity.projectile.FishingHookMixin_NeoForge", + "core.world.entity.vehicle.AbstractMinecartMixin_NeoForge", + "core.world.food.FoodDataMixin_NeoForge", + "core.world.inventory.AbstractContainerMenuMixin_NeoForge", + "core.world.inventory.AnvilMenuMixin_NeoForge", + "core.world.inventory.EnchantmentMenuMixin_NeoForge", + "core.world.item.BucketItemMixin_NeoForge", + "core.world.item.ItemMixin_NeoForge", + "core.world.item.ItemStackMixin_NeoForge", + "core.world.item.MilkBucketItemMixin_NeoForge", + "core.world.item.ShearsItemMixin_NeoForge", + "core.world.item.crafting.RecipeManagerMixin_NeoForge", + "core.world.level.BaseSpawnerMixin_NeoForge", + "core.world.level.ExplosionMixin_NeoForge", + "core.world.level.LevelMixin_NeoForge", + "core.world.level.block.BlockMixin_NeoForge", + "core.world.level.block.CropBlockMixin_NeoForge", + "core.world.level.block.FireBlockMixin_NeoForge", + "core.world.level.block.TntBlockMixin_NeoForge", + "core.world.level.block.entity.BrewingStandBlockEntityMixin_NeoForge", + "core.world.level.block.state.BlockBehaviourMixin_Forge", + "core.world.level.levelgen.structure.templatesystem.StructureTemplateMixin_NeoForge", + "core.world.level.storage.loot.LootContextMixin_NeoForge", + "forge.CommonHooksMixin", + "forge.PacketDistributorMixin", + "forge.PermissionAPIMixin", + "optimization.general.activationrange.entity.ItemEntityMixin_ActivationRange_NeoForge" + ] +} \ No newline at end of file diff --git a/arclight-forge-bootstrap/.gitignore b/bootstrap/.gitignore similarity index 100% rename from arclight-forge-bootstrap/.gitignore rename to bootstrap/.gitignore diff --git a/bootstrap/async_catcher.json b/bootstrap/async_catcher.json new file mode 100644 index 000000000..8deb302d7 --- /dev/null +++ b/bootstrap/async_catcher.json @@ -0,0 +1,31 @@ +{ + "net/minecraft/server/level/ServerLevel": { + "addEntity(Lnet/minecraft/world/entity/Entity;)Z": "entity add", + "getEntities()Lnet/minecraft/world/level/entity/LevelEntityGetter;": "chunk entity get" + }, + "net/minecraft/server/level/ServerLevel$EntityCallbacks": { + "onTrackingStart(Lnet/minecraft/world/entity/Entity;)V": "entity register", + "onTrackingEnd(Lnet/minecraft/world/entity/Entity;)V": "entity unregister" + }, + "net/minecraft/server/level/ChunkMap$TrackedEntity": { + "removePlayer(Lnet/minecraft/server/level/ServerPlayer;)V": "player tracker clear", + "updatePlayer(Lnet/minecraft/server/level/ServerPlayer;)V": "player tracker update" + }, + "net/minecraft/world/level/block/state/BlockBehaviour": { + "onPlace(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Z)V": "block place", + "onRemove(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Z)V": "block remove" + }, + "net/minecraft/world/entity/LivingEntity": { + "addEffect(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z": "effect add" + }, + "net/minecraft/server/level/ChunkMap": { + "addEntity(Lnet/minecraft/world/entity/Entity;)V": "entity track", + "removeEntity(Lnet/minecraft/world/entity/Entity;)V": "entity untrack" + }, + "net/minecraft/world/level/storage/loot/LootTable": { + "fill(Lnet/minecraft/world/Container;Lnet/minecraft/world/level/storage/loot/LootParams;J)V": "loot generate" + }, + "net/minecraftforge/network/NetworkHooks": { + "openGui(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/MenuProvider;Ljava/util/function/Consumer;)V": "open gui" + } +} \ No newline at end of file diff --git a/arclight-forge-bootstrap/build.gradle b/bootstrap/build.gradle similarity index 62% rename from arclight-forge-bootstrap/build.gradle rename to bootstrap/build.gradle index 64fde6bc2..36895de96 100644 --- a/arclight-forge-bootstrap/build.gradle +++ b/bootstrap/build.gradle @@ -1,19 +1,26 @@ import io.izzel.arclight.gradle.tasks.GenerateInstallerInfo - -plugins { - id 'com.github.johnrengelman.shadow' version '7.1.2' apply false -} +import io.izzel.arclight.gradle.tasks.RenameAsyncCatcherTask apply plugin: 'java' apply plugin: 'idea' apply plugin: 'maven-publish' -apply plugin: 'com.github.johnrengelman.shadow' sourceSets { - applaunch { + applaunch {} + forge { + java { + compileClasspath += main.output + compileClasspath += main.compileClasspath + runtimeClasspath += main.output + runtimeClasspath += main.runtimeClasspath + } + } + neoforge { java { compileClasspath += main.output + compileClasspath += main.compileClasspath runtimeClasspath += main.output + runtimeClasspath += main.runtimeClasspath } } } @@ -35,6 +42,7 @@ repositories { maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url = 'https://files.minecraftforge.net/maven/' } + maven { url = 'https://maven.neoforged.net/releases/' } maven { url = 'https://maven.izzel.io/releases' } maven { url = 'https://jitpack.io/' } mavenCentral() @@ -64,30 +72,32 @@ dependencies { implementation 'org.ow2.asm:asm:9.5' implementation 'org.ow2.asm:asm-tree:9.5' implementation 'net.minecraftforge:modlauncher:10.1.2' - implementation 'net.minecraftforge:securemodules:2.2.7' - implementation 'net.minecraftforge:forgespi:7.1.0' - implementation 'net.minecraftforge:bootstrap:2.0.0' - implementation 'net.minecraftforge:bootstrap-api:2.0.0' - gson 'com.google.code.gson:gson:2.9.0' implementation 'org.apache.logging.log4j:log4j-api:2.17.2' implementation 'org.apache.logging.log4j:log4j-core:2.17.2' implementation 'org.jetbrains:annotations:23.0.0' implementation 'org.spongepowered:mixin:0.8.5' implementation 'org.apache.logging.log4j:log4j-jul:2.17.2' - implementation "net.minecraftforge:fmlloader:${minecraftVersion}-${forgeVersion}" + + forgeImplementation "net.minecraftforge:fmlloader:${minecraftVersion}-${forgeVersion}" + forgeImplementation 'net.minecraftforge:securemodules:2.2.7' + forgeImplementation 'net.minecraftforge:forgespi:7.1.0' + forgeImplementation 'net.minecraftforge:bootstrap:2.0.0' + forgeImplementation 'net.minecraftforge:bootstrap-api:2.0.0' + + neoforgeImplementation 'net.neoforged.fancymodloader:spi:2.0.7' + neoforgeImplementation 'net.neoforged.fancymodloader:loader:2.0.7' + neoforgeImplementation 'cpw.mods:securejarhandler:2.1.24' + for (def lib : embedLibs) { installer lib } - embed 'io.izzel.arclight:mixin-tools:1.0.1' + gson 'com.google.code.gson:gson:2.9.0' embed "io.izzel:tools:$toolsVersion" embed "io.izzel.arclight:arclight-api:$apiVersion" embed 'commons-lang:commons-lang:2.6@jar' - embed(project(':i18n-config')) { - transitive = false - } - embed(project(':forge-installer')) { - transitive = false - } + embed('io.izzel.arclight:mixin-tools:1.1.0') { transitive = false } + embed(project(':i18n-config')) { transitive = false } + embed(project(':installer')) { transitive = false } } jar { @@ -98,6 +108,7 @@ jar { manifest.attributes 'Implementation-Vendor': 'Arclight Team' manifest.attributes 'Implementation-Timestamp': new Date().format("yyyy-MM-dd HH:mm:ss") manifest.attributes 'Automatic-Module-Name': 'arclight.boot' + manifest.attributes 'Arclight-Target': 'io.izzel.arclight.boot.forge.application.Main_Forge' from(configurations.embed.collect { it.isDirectory() ? it : zipTree(it) }) { exclude "META-INF/MANIFEST.MF" exclude "META-INF/*.SF" @@ -115,13 +126,48 @@ jar { it.from(configurations.gson.collect()) it.rename { name -> 'gson.jar' } } - from sourceSets.applaunch.output.classesDirs + from sourceSets.applaunch.output + from sourceSets.forge.output duplicatesStrategy = DuplicatesStrategy.EXCLUDE dependsOn(project(':arclight-forge').tasks.reobfJar) } +tasks.register('neoforgeJar', Jar) { + archiveBaseName.set 'arclight-neoforge-' + minecraftVersion + manifest.attributes 'Main-Class': 'io.izzel.arclight.server.Launcher' + manifest.attributes 'Implementation-Title': 'Arclight' + manifest.attributes 'Implementation-Version': "arclight-$minecraftVersion-${project.version}-$gitHash" + manifest.attributes 'Implementation-Vendor': 'Arclight Team' + manifest.attributes 'Implementation-Timestamp': new Date().format("yyyy-MM-dd HH:mm:ss") + manifest.attributes 'Automatic-Module-Name': 'arclight.boot' + manifest.attributes 'Arclight-Target': 'io.izzel.arclight.boot.neoforge.application.Main_Neoforge' + from(configurations.embed.collect { it.isDirectory() ? it : zipTree(it) }) { + exclude "META-INF/MANIFEST.MF" + exclude "META-INF/*.SF" + exclude "META-INF/*.DSA" + exclude "META-INF/*.RSA" + exclude "LICENSE.txt" + exclude "META-INF/services/**" + exclude "org/apache/commons/lang/enum/**" + } + into('/') { + it.from(project(':arclight-neoforge').tasks.reobfJar.outputs.files.collect()) + it.rename { name -> 'common.jar' } + } + into('/') { + it.from(configurations.gson.collect()) + it.rename { name -> 'gson.jar' } + } + from sourceSets.applaunch.output + from sourceSets.main.output + from sourceSets.neoforge.output + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn(project(':arclight-neoforge').tasks.reobfJar) +} + tasks.register('generateInstallerInfo', GenerateInstallerInfo) { forgeVersion = rootProject.ext.forgeVersion + neoforgeVersion = rootProject.ext.neoForgeVersion minecraftVersion = rootProject.ext.minecraftVersion def installer = file("build/arclight_installer/META-INF/installer.json") outputs.file(installer) @@ -130,6 +176,18 @@ tasks.register('generateInstallerInfo', GenerateInstallerInfo) { project.sourceSets.main.output.dir file("build/arclight_installer"), builtBy: tasks.generateInstallerInfo +project.sourceSets.forge.output.dir file("build/forge_async_catcher"), builtBy: tasks.register('forgeAsyncCatcher', RenameAsyncCatcherTask) { + inputFile.set project.file("async_catcher.json") + mapping.set "srg" + outputs.file("build/forge_async_catcher/async_catcher.json") +} + +project.sourceSets.neoforge.output.dir file("build/neoforge_async_catcher"), builtBy: tasks.register('neoforgeAsyncCatcher', RenameAsyncCatcherTask) { + inputFile.set project.file("async_catcher.json") + mapping.set "named" + outputs.file("build/neoforge_async_catcher/async_catcher.json") +} + compileJava { sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17 options.compilerArgs << '-XDignore.symbol.file' << '-XDenableSunApiLintControl' @@ -148,7 +206,25 @@ tasks.register('runProdServer', JavaExec) { systemProperties 'mixin.dumpTargetOnFailure': 'true' systemProperties 'arclight.alwaysExtract': 'true' systemProperties 'arclight.remapper.dump': './.mixin.out/plugin_classes' - workingDir System.env.ARCLIGHT_PROD_DIR ?: file('run_prod') + workingDir System.env.ARCLIGHT_PROD_DIR ?: file('run_prod/forge') + maxHeapSize '4G' + args 'nogui' + standardInput System.in + javaLauncher.convention(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(17) + }) + dependsOn project.tasks.build +} + +tasks.register('runProdNeoforge', JavaExec) { + classpath = files(tasks.neoforgeJar) + // systemProperties 'bsl.debug': 'true' + systemProperties 'terminal.ansi': 'true' + systemProperties 'mixin.debug.export': 'true' + systemProperties 'mixin.dumpTargetOnFailure': 'true' + systemProperties 'arclight.alwaysExtract': 'true' + systemProperties 'arclight.remapper.dump': './.mixin.out/plugin_classes' + workingDir System.env.ARCLIGHT_PROD_DIR ?: file('run_prod/neoforge') maxHeapSize '4G' args 'nogui' standardInput System.in diff --git a/bootstrap/src/applaunch/java/io/izzel/arclight/server/Launcher.java b/bootstrap/src/applaunch/java/io/izzel/arclight/server/Launcher.java new file mode 100644 index 000000000..ed038da58 --- /dev/null +++ b/bootstrap/src/applaunch/java/io/izzel/arclight/server/Launcher.java @@ -0,0 +1,30 @@ +package io.izzel.arclight.server; + +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.jar.Manifest; + +public class Launcher { + + private static final int MIN_CLASS_VERSION = 61; + private static final int MIN_JAVA_VERSION = 17; + + public static void main(String[] args) throws Throwable { + int javaVersion = (int) Float.parseFloat(System.getProperty("java.class.version")); + if (javaVersion < MIN_CLASS_VERSION) { + System.err.println("Arclight requires Java " + MIN_JAVA_VERSION); + System.err.println("Current: " + System.getProperty("java.version")); + System.exit(-1); + return; + } + + try (InputStream input = Launcher.class.getResourceAsStream("/META-INF/MANIFEST.MF")) { + Manifest manifest = new Manifest(input); + String target = manifest.getMainAttributes().getValue("Arclight-Target"); + MethodHandle main = MethodHandles.lookup().findStatic(Class.forName(target), "main", MethodType.methodType(void.class, String[].class)); + main.invoke((Object) args); + } + } +} diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/application/ApplicationBootstrap.java similarity index 94% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/application/ApplicationBootstrap.java index afcbf7138..ce3022070 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/application/ApplicationBootstrap.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot.application; +package io.izzel.arclight.boot.forge.application; import cpw.mods.jarhandling.SecureJar; import cpw.mods.jarhandling.impl.SimpleJarMetadata; @@ -25,7 +25,7 @@ protected void start(String... args) throws Exception { var classPath = getClassPath(); var boot = selectBootModules(classPath); var arclight = classPath.stream().filter(it -> it.moduleDataProvider().name().equals("arclight.boot")).findAny().orElseThrow(); - var jar = SecureJar.from(it -> new SimpleJarMetadata("arclight.launch", "1.0", Set.of("io.izzel.arclight.boot.application"), List.of()), arclight.getPrimaryPath()); + var jar = SecureJar.from(it -> new SimpleJarMetadata("arclight.launch", "1.0", Set.of("io.izzel.arclight.boot.forge.application"), List.of()), arclight.getPrimaryPath()); boot.add(jar); // First we need to get ourselves onto a module layer, so that we can be the parent of the actual runtime layer @@ -57,7 +57,7 @@ public void moduleMain(String... args) throws Exception { secure.add(mavenMerged); var arclight = classPath.stream().filter(it -> it.moduleDataProvider().name().equals("arclight.boot")).findAny().orElseThrow(); secure.add(SecureJar.from(it -> new SimpleJarMetadata(arclight.name(), arclight.moduleDataProvider().descriptor().rawVersion().orElse("1.0"), - arclight.getPackages().stream().filter(p -> !p.equals("io.izzel.arclight.boot.application")).collect(Collectors.toSet()), arclight.getProviders()), arclight.getPrimaryPath())); + arclight.getPackages().stream().filter(p -> !p.equals("io.izzel.arclight.boot.forge.application")).collect(Collectors.toSet()), arclight.getProviders()), arclight.getPrimaryPath())); // Now lets build a layer that has all the non-Bootstrap/SecureModule libraries on it. var finder = SecureModuleFinder.of(secure.toArray(SecureJar[]::new)); diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/application/Main_Forge.java similarity index 92% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/application/Main_Forge.java index 2fe463362..a644a5f93 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/application/Main_Forge.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot.application; +package io.izzel.arclight.boot.forge.application; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -36,7 +36,7 @@ private static Map.Entry> forgeInstall() throws Throwable { Files.copy(Objects.requireNonNull(Main_Forge.class.getResourceAsStream("/gson.jar")), path); } try (var loader = new URLClassLoader(new URL[]{path.toUri().toURL(), Main_Forge.class.getProtectionDomain().getCodeSource().getLocation()}, ClassLoader.getPlatformClassLoader())) { - var cl = loader.loadClass("io.izzel.arclight.forgeinstaller.ForgeInstaller"); + var cl = loader.loadClass("io.izzel.arclight.installer.ForgeInstaller"); var handle = MethodHandles.lookup().findStatic(cl, "applicationInstall", MethodType.methodType(Map.Entry.class)); return (Map.Entry>) handle.invoke(); } diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightImplementer.java similarity index 79% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightImplementer.java index 5ce7c67bd..6529a531e 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightImplementer.java @@ -1,22 +1,13 @@ -package io.izzel.arclight.boot.asm; +package io.izzel.arclight.boot.forge.mod; import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import io.izzel.arclight.boot.asm.*; import io.izzel.arclight.boot.log.ArclightI18nLogger; -import io.izzel.arclight.boot.mod.ModBootstrap; import org.apache.logging.log4j.Logger; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.VarInsnNode; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; + +import java.util.*; import java.util.function.Consumer; public class ArclightImplementer implements ILaunchPluginService { @@ -93,7 +84,7 @@ public boolean processClass(Phase phase, ClassNode classNode, Type classType, St for (Map.Entry entry : implementers.entrySet()) { String key = entry.getKey(); Implementer implementer = entry.getValue(); - if (implementer.processClass(classNode, transformerLoader)) { + if (implementer.processClass(classNode)) { trails.add(key); } } @@ -107,15 +98,4 @@ public boolean processClass(Phase phase, ClassNode classNode, Type classType, St public boolean processClass(Phase phase, ClassNode classNode, Type classType) { throw new IllegalStateException("Outdated ModLauncher"); } - - public static void loadArgs(InsnList list, MethodNode methodNode, Type[] types, int i) { - if (!Modifier.isStatic(methodNode.access)) { - list.add(new VarInsnNode(Opcodes.ALOAD, i)); - i += 1; - } - for (Type type : types) { - list.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), i)); - i += type.getSize(); - } - } } diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightJarInJarAdaptor.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightJarInJarAdaptor.java similarity index 98% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightJarInJarAdaptor.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightJarInJarAdaptor.java index 421f5a5ef..8f8cbe1ba 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightJarInJarAdaptor.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightJarInJarAdaptor.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot.mod; +package io.izzel.arclight.boot.forge.mod; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator; diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightLaunchHandler.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightLaunchHandler.java similarity index 76% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightLaunchHandler.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightLaunchHandler.java index debb0125d..e5b747c0d 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightLaunchHandler.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightLaunchHandler.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot.mod; +package io.izzel.arclight.boot.forge.mod; import net.minecraftforge.fml.loading.targets.CommonLaunchHandler; @@ -9,7 +9,7 @@ public class ArclightLaunchHandler extends CommonLaunchHandler { public ArclightLaunchHandler() { - super(SERVER, "arclight_"); + super(CommonLaunchHandler.SERVER, "arclight_"); } @Override @@ -24,7 +24,7 @@ public boolean isProduction() { @Override public List getMinecraftPaths() { - return List.of(getPathFromResource("net/minecraft/server/MinecraftServer.class")); + return List.of(CommonLaunchHandler.getPathFromResource("net/minecraft/server/MinecraftServer.class")); } @Override diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightLocator_Forge.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightLocator_Forge.java similarity index 90% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightLocator_Forge.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightLocator_Forge.java index f72b6db85..bc930745c 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ArclightLocator_Forge.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ArclightLocator_Forge.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot.mod; +package io.izzel.arclight.boot.forge.mod; import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; @@ -82,18 +82,13 @@ protected IModFile loadJar() { throw new RuntimeException(e); } }; - return (IModFile) handle.invoke(SecureJar.from(it -> excludePackages(it, version), path), this, parser); + return (IModFile) handle.invoke(SecureJar.from(it -> versionMetadata(it, version), path), this, parser); } catch (Throwable e) { throw new RuntimeException(e); } } - private static final Set EXCLUDES = Set.of( - "net.minecraft.world.level.block" - ); - - private JarMetadata excludePackages(SecureJar secureJar, String version) { - secureJar.getPackages().removeIf(it -> EXCLUDES.stream().anyMatch(it::startsWith)); + private JarMetadata versionMetadata(SecureJar secureJar, String version) { return new SimpleJarMetadata("arclight", version.substring(version.indexOf('-') + 1), secureJar.getPackages(), List.of()); } } diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ModBootstrap.java similarity index 95% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ModBootstrap.java index 407212a66..8104dd191 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ModBootstrap.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot.mod; +package io.izzel.arclight.boot.forge.mod; import cpw.mods.jarhandling.SecureJar; import cpw.mods.jarhandling.impl.Jar; @@ -6,10 +6,11 @@ import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; import cpw.mods.util.LambdaExceptionUtils; +import io.izzel.arclight.api.ArclightPlatform; import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.boot.AbstractBootstrap; -import io.izzel.arclight.boot.asm.ArclightImplementer; -import io.izzel.arclight.forgeinstaller.ForgeInstaller; +import io.izzel.arclight.installer.ForgeInstaller; +import io.izzel.arclight.installer.MinecraftProvider; import net.minecraftforge.securemodules.SecureModuleClassLoader; import net.minecraftforge.securemodules.SecureModuleFinder; import org.apache.logging.log4j.LogManager; @@ -45,7 +46,7 @@ static void run() { var logger = LogManager.getLogger("Arclight"); var marker = MarkerManager.getMarker("INSTALL"); try { - var paths = ForgeInstaller.modInstall(s -> logger.info(marker, s)); + var paths = MinecraftProvider.modInstall(s -> logger.info(marker, s)); load(paths.toArray(new Path[0])); new ModBootstrap().inject(); } catch (Throwable e) { @@ -76,7 +77,7 @@ public static void postRun() { private void inject() throws Throwable { dirtyHacks(); - setupMod(); + setupMod(ArclightPlatform.FORGE); injectClassPath(); injectLaunchPlugin(); } diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ModuleBootstrap.java b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ModuleBootstrap.java similarity index 94% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ModuleBootstrap.java rename to bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ModuleBootstrap.java index ded9982b9..e8f038716 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/mod/ModuleBootstrap.java +++ b/bootstrap/src/forge/java/io/izzel/arclight/boot/forge/mod/ModuleBootstrap.java @@ -1,5 +1,6 @@ -package io.izzel.arclight.boot.mod; +package io.izzel.arclight.boot.forge.mod; +import io.izzel.arclight.api.ArclightPlatform; import io.izzel.arclight.api.EnumHelper; import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.boot.AbstractBootstrap; @@ -34,7 +35,7 @@ public void main(String[] args) { return; } try { - this.setupMod(); + this.setupMod(ArclightPlatform.FORGE); this.dirtyHacks(); int targetIndex = Arrays.asList(args).indexOf("--launchTarget"); if (targetIndex >= 0 && targetIndex < args.length - 1) { diff --git a/bootstrap/src/forge/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService b/bootstrap/src/forge/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService new file mode 100644 index 000000000..0f7f7b2cb --- /dev/null +++ b/bootstrap/src/forge/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService @@ -0,0 +1 @@ +io.izzel.arclight.boot.forge.mod.ArclightLaunchHandler \ No newline at end of file diff --git a/bootstrap/src/forge/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService b/bootstrap/src/forge/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService new file mode 100644 index 000000000..8f4d9ac19 --- /dev/null +++ b/bootstrap/src/forge/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService @@ -0,0 +1 @@ +io.izzel.arclight.boot.forge.mod.ArclightImplementer \ No newline at end of file diff --git a/bootstrap/src/forge/resources/META-INF/services/net.minecraftforge.bootstrap.api.BootstrapEntryPoint b/bootstrap/src/forge/resources/META-INF/services/net.minecraftforge.bootstrap.api.BootstrapEntryPoint new file mode 100644 index 000000000..34fd7ca15 --- /dev/null +++ b/bootstrap/src/forge/resources/META-INF/services/net.minecraftforge.bootstrap.api.BootstrapEntryPoint @@ -0,0 +1 @@ +io.izzel.arclight.boot.forge.mod.ModuleBootstrap \ No newline at end of file diff --git a/bootstrap/src/forge/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator b/bootstrap/src/forge/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator new file mode 100644 index 000000000..c254b054a --- /dev/null +++ b/bootstrap/src/forge/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator @@ -0,0 +1 @@ +io.izzel.arclight.boot.forge.mod.ArclightLocator_Forge \ No newline at end of file diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java b/bootstrap/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java similarity index 95% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java index 8a7e48c41..71eaa4810 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java @@ -6,7 +6,6 @@ import io.izzel.arclight.api.ArclightVersion; import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.i18n.ArclightLocale; -import net.minecraftforge.forgespi.locating.IModLocator; import org.apache.logging.log4j.LogManager; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; @@ -86,13 +85,13 @@ default void dirtyHacks() throws Exception { var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); node.accept(cw); byte[] bytes = cw.toByteArray(); - Unsafe.defineClass("com.mojang.brigadier.tree.CommandNode", bytes, 0, bytes.length, IModLocator.class.getClassLoader() /* MC-BOOTSTRAP */, getClass().getProtectionDomain()); + Unsafe.defineClass("com.mojang.brigadier.tree.CommandNode", bytes, 0, bytes.length, getClass().getClassLoader() /* MC-BOOTSTRAP */, getClass().getProtectionDomain()); } } - default void setupMod() throws Exception { + default void setupMod(ArclightPlatform platform) throws Exception { ArclightVersion.setVersion(ArclightVersion.WHISPER); - ArclightPlatform.setPlatform(ArclightPlatform.FORGE); + ArclightPlatform.setPlatform(platform); try (InputStream stream = getClass().getModule().getResourceAsStream("/META-INF/MANIFEST.MF")) { Manifest manifest = new Manifest(stream); Attributes attributes = manifest.getMainAttributes(); diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/EnumTypeFactory.java b/bootstrap/src/main/java/io/izzel/arclight/boot/EnumTypeFactory.java similarity index 100% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/EnumTypeFactory.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/EnumTypeFactory.java diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/AsyncCatcher.java b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/AsyncCatcher.java similarity index 93% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/AsyncCatcher.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/asm/AsyncCatcher.java index 4f194cbcb..4eac9b951 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/AsyncCatcher.java +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/AsyncCatcher.java @@ -3,7 +3,6 @@ import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; import io.izzel.arclight.api.Unsafe; import io.izzel.arclight.i18n.ArclightConfig; import io.izzel.arclight.i18n.conf.AsyncCatcherSpec; @@ -68,7 +67,7 @@ public AsyncCatcher() { } @Override - public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + public boolean processClass(ClassNode node) { Map map = reasons.get(node.name); if (map != null) { boolean found = false; @@ -87,7 +86,7 @@ public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoa } private void injectCheck(ClassNode node, MethodNode methodNode, String reason) { - ArclightImplementer.LOGGER.debug(MARKER, "Injecting {}/{}{} for reason {}", node.name, methodNode.name, methodNode.desc, reason); + Implementer.LOGGER.debug(MARKER, "Injecting {}/{}{} for reason {}", node.name, methodNode.name, methodNode.desc, reason); AsyncCatcherSpec.Operation operation = ArclightConfig.spec().getAsyncCatcher().getOverrides().getOrDefault(reason, defaultOp); InsnList insnList = new InsnList(); LabelNode labelNode = new LabelNode(new Label()); @@ -135,7 +134,7 @@ private void instantiateCallback(ClassNode node, MethodNode methodNode, InsnList insnList.add(new TypeInsnNode(Opcodes.NEW, classNode.name)); insnList.add(new InsnNode(Opcodes.DUP)); Type methodType = Type.getMethodType(methodNode.desc); - ArclightImplementer.loadArgs(insnList, methodNode, methodType.getArgumentTypes(), 0); + Implementer.loadArgs(insnList, methodNode, methodType.getArgumentTypes(), 0); insnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.name, "", desc)); } @@ -192,7 +191,7 @@ private String createImplType(ClassNode node, MethodNode methodNode, ClassNode c classNode.accept(writer); byte[] bytes = writer.toByteArray(); Unsafe.defineClass(Type.getObjectType(classNode.name).getClassName(), bytes, 0, bytes.length, this.classLoader, AsyncCatcher.class.getProtectionDomain()); - ArclightImplementer.LOGGER.debug(MARKER, "Defined impl callback class {}", classNode.name); + Implementer.LOGGER.debug(MARKER, "Defined impl callback class {}", classNode.name); return init.desc; } @@ -205,12 +204,12 @@ private MethodNode createBridge(ClassNode node, MethodNode methodNode) { ret.access = ret.access | Opcodes.ACC_STATIC; } Type methodType = Type.getMethodType(methodNode.desc); - ArclightImplementer.loadArgs(ret.instructions, methodNode, methodType.getArgumentTypes(), 0); + Implementer.loadArgs(ret.instructions, methodNode, methodType.getArgumentTypes(), 0); int invokeCode = Modifier.isStatic(methodNode.access) ? Opcodes.INVOKESTATIC : Opcodes.INVOKESPECIAL; ret.instructions.add(new MethodInsnNode(invokeCode, node.name, methodNode.name, methodNode.desc)); ret.instructions.add(new InsnNode(methodType.getReturnType().getOpcode(Opcodes.IRETURN))); node.methods.add(ret); - ArclightImplementer.LOGGER.debug(MARKER, "Bridge method {}/{}{} created", node.name, ret.name, ret.desc); + Implementer.LOGGER.debug(MARKER, "Bridge method {}/{}{} created", node.name, ret.name, ret.desc); return ret; } @@ -231,11 +230,11 @@ static String getReturnDescriptor(org.objectweb.asm.Type returnType) { @SuppressWarnings("unchecked") public static CallbackInfoReturnable checkOp(Supplier method, AsyncCatcherSpec.Operation operation, String reason, Executor executor) throws Throwable { if (INSTANCE.warn) { - ArclightImplementer.LOGGER.warn(MARKER, "Async " + reason); + Implementer.LOGGER.warn(MARKER, "Async " + reason); } IllegalStateException exception = new IllegalStateException("Asynchronous " + reason + "!"); if (INSTANCE.dump) { - ArclightImplementer.LOGGER.debug(MARKER, "Async " + reason, exception); + Implementer.LOGGER.debug(MARKER, "Async " + reason, exception); } switch (operation) { case NONE: return (CallbackInfoReturnable) NOOP; @@ -249,7 +248,7 @@ public static CallbackInfoReturnable checkOp(Supplier method, AsyncCat var thread = ((Supplier) executor).get(); var ex = new Exception("Server thread"); ex.setStackTrace(thread.getStackTrace()); - ArclightImplementer.LOGGER.error(MARKER, "Async catcher timeout", ex); + Implementer.LOGGER.error(MARKER, "Async catcher timeout", ex); throw e; } return cir; diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/EnumDefinalizer.java b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/EnumDefinalizer.java similarity index 85% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/EnumDefinalizer.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/asm/EnumDefinalizer.java index b09350b83..c0a8eb38f 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/EnumDefinalizer.java +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/EnumDefinalizer.java @@ -1,6 +1,5 @@ package io.izzel.arclight.boot.asm; -import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; @@ -28,14 +27,14 @@ public class EnumDefinalizer implements Implementer { ); @Override - public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + public boolean processClass(ClassNode node) { if (ENUM.contains(node.name)) { var find = false; for (FieldNode field : node.fields) { if (Modifier.isStatic(field.access) && Modifier.isFinal(field.access) && field.name.equals("ENUM$VALUES")) { field.access &= ~Opcodes.ACC_FINAL; - ArclightImplementer.LOGGER.debug("Definalize enum class {} values field {}", node.name, field.name); + Implementer.LOGGER.debug("Definalize enum class {} values field {}", node.name, field.name); if (find) { throw new IllegalStateException("Duplicate static final field found for " + node.name + ": " + field.name); } else { diff --git a/bootstrap/src/main/java/io/izzel/arclight/boot/asm/Implementer.java b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/Implementer.java new file mode 100644 index 000000000..e025c4575 --- /dev/null +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/Implementer.java @@ -0,0 +1,30 @@ +package io.izzel.arclight.boot.asm; + +import io.izzel.arclight.boot.log.ArclightI18nLogger; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.lang.reflect.Modifier; + +public interface Implementer { + + Logger LOGGER = ArclightI18nLogger.getLogger("Implementer"); + + boolean processClass(ClassNode node); + + public static void loadArgs(InsnList list, MethodNode methodNode, Type[] types, int i) { + if (!Modifier.isStatic(methodNode.access)) { + list.add(new VarInsnNode(Opcodes.ALOAD, i)); + i += 1; + } + for (Type type : types) { + list.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), i)); + i += type.getSize(); + } + } +} diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/InventoryImplementer.java b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/InventoryImplementer.java similarity index 84% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/InventoryImplementer.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/asm/InventoryImplementer.java index 20c8d3882..bae7873d0 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/InventoryImplementer.java +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/InventoryImplementer.java @@ -1,6 +1,6 @@ package io.izzel.arclight.boot.asm; -import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import io.izzel.arclight.api.ArclightPlatform; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.objectweb.asm.Opcodes; @@ -23,11 +23,18 @@ public class InventoryImplementer implements Implementer { private static final Marker MARKER = MarkerManager.getMarker("INVENTORY"); private static final String BRIDGE_TYPE = "io/izzel/arclight/common/bridge/core/inventory/IInventoryBridge"; + private final String maxStackSizeMethodName; + public InventoryImplementer() { + this.maxStackSizeMethodName = switch (ArclightPlatform.current()) { + case VANILLA, NEOFORGE -> "getMaxStackSize"; + case FORGE -> "m_6893_"; + // case FABRIC -> "method_5444"; + }; } @Override - public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + public boolean processClass(ClassNode node) { if (Modifier.isInterface(node.access) || node.interfaces.contains(BRIDGE_TYPE)) { return false; } @@ -37,7 +44,7 @@ public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoa private boolean tryImplement(ClassNode node) { MethodNode stackLimitMethod = null; for (MethodNode method : node.methods) { - if (!Modifier.isAbstract(method.access) && method.name.equals("m_6893_") && method.desc.equals("()I")) { // getMaxStackSize + if (!Modifier.isAbstract(method.access) && method.name.equals(this.maxStackSizeMethodName) && method.desc.equals("()I")) { stackLimitMethod = method; break; } @@ -47,12 +54,12 @@ private boolean tryImplement(ClassNode node) { } else { for (MethodNode method : node.methods) { if (method.name.equals("setMaxStackSize") && method.desc.equals("(I)V")) { - ArclightImplementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name); + Implementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name); return false; } } - ArclightImplementer.LOGGER.debug(MARKER, "Implementing inventory for class {}", node.name); + Implementer.LOGGER.debug(MARKER, "Implementing inventory for class {}", node.name); FieldNode maxStack = new FieldNode(Opcodes.ACC_PRIVATE, "arclight$maxStack", Type.getType(Integer.class).getDescriptor(), null, null); node.fields.add(maxStack); node.interfaces.add(BRIDGE_TYPE); diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java similarity index 91% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java index e7387ca40..850f83ded 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java @@ -14,7 +14,7 @@ public class LoggerTransformer implements Implementer { private static final LogManager JUL_MANAGER = new LogManager(); @Override - public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + public boolean processClass(ClassNode node) { var transform = false; for (var mn : node.methods) { for (var insn : mn.instructions) { diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/SwitchTableFixer.java b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/SwitchTableFixer.java similarity index 87% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/SwitchTableFixer.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/asm/SwitchTableFixer.java index 585e0c78b..ec1b35b6f 100644 --- a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/asm/SwitchTableFixer.java +++ b/bootstrap/src/main/java/io/izzel/arclight/boot/asm/SwitchTableFixer.java @@ -1,6 +1,5 @@ package io.izzel.arclight.boot.asm; -import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.objectweb.asm.ClassReader; @@ -32,14 +31,14 @@ public class SwitchTableFixer implements Implementer, Function { public byte[] apply(byte[] bytes) { ClassNode node = new ClassNode(); new ClassReader(bytes).accept(node, 0); - processClass(node, null); + processClass(node); ClassWriter writer = new ClassWriter(0); node.accept(writer); return writer.toByteArray(); } @Override - public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + public boolean processClass(ClassNode node) { boolean success = false; for (MethodNode method : node.methods) { // There are two variants of switch map @@ -61,7 +60,7 @@ private boolean inject1(ClassNode node, MethodNode method) { } else return false; } if (!foundTryCatch) return false; - ArclightImplementer.LOGGER.debug(MARKER, "Candidate switch enum method {} class {}", method.name + method.desc, node.name); + Implementer.LOGGER.debug(MARKER, "Candidate switch enum method {} class {}", method.name + method.desc, node.name); FieldInsnNode fieldInsnNode = null; String enumType = null; for (AbstractInsnNode insnNode : method.instructions) { @@ -90,7 +89,7 @@ private boolean inject1(ClassNode node, MethodNode method) { } } if (fieldInsnNode != null && enumType != null) { - ArclightImplementer.LOGGER.debug(MARKER, "Find switch(enum {}) table method {} in class {}", enumType, method.name + method.desc, node.name); + Implementer.LOGGER.debug(MARKER, "Find switch(enum {}) table method {} in class {}", enumType, method.name + method.desc, node.name); AbstractInsnNode last = method.instructions.getLast(); while (last != null && last.getOpcode() != Opcodes.ARETURN) { last = last.getPrevious(); @@ -102,7 +101,7 @@ private boolean inject1(ClassNode node, MethodNode method) { list.add(new InsnNode(Opcodes.DUP)); list.add(new FieldInsnNode(Opcodes.PUTSTATIC, fieldInsnNode.owner, fieldInsnNode.name, fieldInsnNode.desc)); method.instructions.insertBefore(last, list); - ArclightImplementer.LOGGER.debug(MARKER, "Inject method in method {}:{}, switch table field is {}", node.name, method.name + method.desc, fieldInsnNode.name + fieldInsnNode.desc); + Implementer.LOGGER.debug(MARKER, "Inject method in method {}:{}, switch table field is {}", node.name, method.name + method.desc, fieldInsnNode.name + fieldInsnNode.desc); return true; } } @@ -111,7 +110,7 @@ private boolean inject1(ClassNode node, MethodNode method) { @SuppressWarnings("unused") public static int[] fillSwitchTable1(int[] arr, Class> cl) { - ArclightImplementer.LOGGER.debug(MARKER, "Filling switch table for {}", cl); + Implementer.LOGGER.debug(MARKER, "Filling switch table for {}", cl); Enum[] enums = cl.getEnumConstants(); if (arr.length < enums.length) { int[] ints = new int[enums.length]; @@ -140,7 +139,7 @@ private boolean inject2(ClassNode node, MethodNode method) { } else return false; } if (!foundTryCatch) return false; - ArclightImplementer.LOGGER.debug(MARKER, "Candidate switch enum method {} class {}", method.name + method.desc, node.name); + Implementer.LOGGER.debug(MARKER, "Candidate switch enum method {} class {}", method.name + method.desc, node.name); FieldInsnNode fieldInsnNode = null; String enumType = null; for (AbstractInsnNode insnNode : method.instructions) { @@ -167,7 +166,7 @@ private boolean inject2(ClassNode node, MethodNode method) { } } if (fieldInsnNode != null) { - ArclightImplementer.LOGGER.debug(MARKER, "Find switch(enum {}) table method {} in class {}", enumType, method.name + method.desc, node.name); + Implementer.LOGGER.debug(MARKER, "Find switch(enum {}) table method {} in class {}", enumType, method.name + method.desc, node.name); AbstractInsnNode last = method.instructions.getLast(); while (last != null && last.getOpcode() != Opcodes.RETURN) { last = last.getPrevious(); @@ -179,7 +178,7 @@ private boolean inject2(ClassNode node, MethodNode method) { list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(SwitchTableFixer.class), "fillSwitchTable2", "([ILjava/lang/Class;)[I", false)); list.add(new FieldInsnNode(Opcodes.PUTSTATIC, fieldInsnNode.owner, fieldInsnNode.name, fieldInsnNode.desc)); method.instructions.insertBefore(last, list); - ArclightImplementer.LOGGER.debug(MARKER, "Inject method in method {}:{}, switch table field is {}", node.name, method.name + method.desc, fieldInsnNode.name + fieldInsnNode.desc); + Implementer.LOGGER.debug(MARKER, "Inject method in method {}:{}, switch table field is {}", node.name, method.name + method.desc, fieldInsnNode.name + fieldInsnNode.desc); return true; } } @@ -189,7 +188,7 @@ private boolean inject2(ClassNode node, MethodNode method) { @SuppressWarnings("unused") public static int[] fillSwitchTable2(int[] arr, Class> cl) { - ArclightImplementer.LOGGER.debug(MARKER, "Filling switch table for {}", cl); + Implementer.LOGGER.debug(MARKER, "Filling switch table for {}", cl); Enum[] enums = cl.getEnumConstants(); if (arr.length < enums.length) { int[] ints = new int[enums.length]; diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightI18nLogger.java b/bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightI18nLogger.java similarity index 100% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightI18nLogger.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightI18nLogger.java diff --git a/arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightLoggerAdapter.java b/bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightLoggerAdapter.java similarity index 100% rename from arclight-forge-bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightLoggerAdapter.java rename to bootstrap/src/main/java/io/izzel/arclight/boot/log/ArclightLoggerAdapter.java diff --git a/arclight-forge-bootstrap/src/main/resources/arclight-log4j2.xml b/bootstrap/src/main/resources/arclight-log4j2.xml similarity index 100% rename from arclight-forge-bootstrap/src/main/resources/arclight-log4j2.xml rename to bootstrap/src/main/resources/arclight-log4j2.xml diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/application/ApplicationBootstrap.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/application/ApplicationBootstrap.java new file mode 100644 index 000000000..462ea9fc2 --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/application/ApplicationBootstrap.java @@ -0,0 +1,53 @@ +package io.izzel.arclight.boot.neoforge.application; + +import io.izzel.arclight.api.ArclightPlatform; +import io.izzel.arclight.api.EnumHelper; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.boot.AbstractBootstrap; +import io.izzel.arclight.i18n.ArclightConfig; +import io.izzel.arclight.i18n.ArclightLocale; + +import java.util.Arrays; +import java.util.ServiceLoader; +import java.util.function.Consumer; + +public class ApplicationBootstrap implements Consumer, AbstractBootstrap { + + private static final int MIN_DEPRECATED_VERSION = 60; + private static final int MIN_DEPRECATED_JAVA_VERSION = 16; + + @Override + @SuppressWarnings("unchecked") + public void accept(String[] args) { + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.boot.log.ArclightLoggerAdapter"); + System.setProperty("log4j.configurationFile", "arclight-log4j2.xml"); + ArclightLocale.info("i18n.using-language", ArclightConfig.spec().getLocale().getCurrent(), ArclightConfig.spec().getLocale().getFallback()); + try { + int javaVersion = (int) Float.parseFloat(System.getProperty("java.class.version")); + if (javaVersion < MIN_DEPRECATED_VERSION) { + ArclightLocale.error("java.deprecated", System.getProperty("java.version"), MIN_DEPRECATED_JAVA_VERSION); + Thread.sleep(3000); + } + Unsafe.ensureClassInitialized(EnumHelper.class); + } catch (Throwable t) { + System.err.println("Your Java is not compatible with Arclight."); + t.printStackTrace(); + return; + } + try { + this.setupMod(ArclightPlatform.NEOFORGE); + this.dirtyHacks(); + int targetIndex = Arrays.asList(args).indexOf("--launchTarget"); + if (targetIndex >= 0 && targetIndex < args.length - 1) { + args[targetIndex + 1] = "arclightserver"; + } + ServiceLoader.load(getClass().getModule().getLayer(), Consumer.class).stream() + .filter(it -> !it.type().getName().contains("arclight")) + .findFirst().orElseThrow().get().accept(args); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Fail to launch Arclight."); + } + } +} diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/application/Main_Neoforge.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/application/Main_Neoforge.java new file mode 100644 index 000000000..00c3ffff8 --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/application/Main_Neoforge.java @@ -0,0 +1,44 @@ +package io.izzel.arclight.boot.neoforge.application; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +public class Main_Neoforge { + + public static void main(String[] args) throws Throwable { + try { + Map.Entry> install = forgeInstall(); + var cl = Class.forName(install.getKey()); + var method = cl.getMethod("main", String[].class); + var target = Stream.concat(install.getValue().stream(), Arrays.stream(args)).toArray(String[]::new); + method.invoke(null, (Object) target); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Fail to launch Arclight."); + System.exit(-1); + } + } + + @SuppressWarnings("unchecked") + private static Map.Entry> forgeInstall() throws Throwable { + var path = Paths.get(".arclight", "gson.jar"); + if (!Files.exists(path)) { + Files.createDirectories(path.getParent()); + Files.copy(Objects.requireNonNull(Main_Neoforge.class.getResourceAsStream("/gson.jar")), path); + } + try (var loader = new URLClassLoader(new URL[]{path.toUri().toURL(), Main_Neoforge.class.getProtectionDomain().getCodeSource().getLocation()}, ClassLoader.getPlatformClassLoader())) { + var cl = loader.loadClass("io.izzel.arclight.installer.NeoforgeInstaller"); + var handle = MethodHandles.lookup().findStatic(cl, "applicationInstall", MethodType.methodType(Map.Entry.class)); + return (Map.Entry>) handle.invoke(); + } + } +} diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightImplementer.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightImplementer.java new file mode 100644 index 000000000..ee31e690f --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightImplementer.java @@ -0,0 +1,102 @@ +package io.izzel.arclight.boot.neoforge.mod; + +import cpw.mods.modlauncher.api.NamedPath; +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import io.izzel.arclight.boot.asm.*; +import io.izzel.arclight.boot.log.ArclightI18nLogger; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; + +import java.util.*; +import java.util.function.Consumer; + +public class ArclightImplementer implements ILaunchPluginService { + + static final Logger LOGGER = ArclightI18nLogger.getLogger("Implementer"); + + private static final EnumSet OH_YES_SIR = EnumSet.of(Phase.AFTER); + private static final EnumSet NOT_TODAY = EnumSet.noneOf(Phase.class); + + private final Map implementers = new HashMap<>(); + private volatile Consumer auditAcceptor; + private ITransformerLoader transformerLoader; + private final boolean logger; + + public ArclightImplementer() { + this(detectTransformLogger()); + } + + public ArclightImplementer(boolean logger) { + this.logger = logger; + } + + private static boolean detectTransformLogger() { + var transformLogger = !(java.util.logging.LogManager.getLogManager() instanceof org.apache.logging.log4j.jul.LogManager); + if (transformLogger && !System.getProperties().contains("log4j.jul.LoggerAdapter")) { + System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.boot.log.ArclightLoggerAdapter"); + } + return transformLogger; + } + + @Override + public String name() { + return "arclight_implementer"; + } + + @Override + public void initializeLaunch(ITransformerLoader transformerLoader, NamedPath[] specialPaths) { + // runs after TX CL built + ModBootstrap.postRun(); + this.transformerLoader = transformerLoader; + this.implementers.put("inventory", new InventoryImplementer()); + this.implementers.put("switch", SwitchTableFixer.INSTANCE); + this.implementers.put("async", AsyncCatcher.INSTANCE); + this.implementers.put("enum", new EnumDefinalizer()); + if (this.logger) { + this.implementers.put("logger", new LoggerTransformer()); + } + } + + @Override + public EnumSet handlesClass(Type classType, boolean isEmpty, String reason) { + if ("mixin".equals(reason)) { + return NOT_TODAY; + } + return isEmpty ? NOT_TODAY : OH_YES_SIR; + } + + @Override + public EnumSet handlesClass(Type classType, boolean isEmpty) { + throw new IllegalStateException("Outdated ModLauncher"); + } + + @Override + public void customAuditConsumer(String className, Consumer auditDataAcceptor) { + auditAcceptor = auditDataAcceptor; + } + + @Override + public boolean processClass(Phase phase, ClassNode classNode, Type classType, String reason) { + if ("mixin".equals(reason)) { + return false; + } + List trails = new ArrayList<>(); + for (Map.Entry entry : implementers.entrySet()) { + String key = entry.getKey(); + Implementer implementer = entry.getValue(); + if (implementer.processClass(classNode)) { + trails.add(key); + } + } + if (this.auditAcceptor != null && !trails.isEmpty()) { + this.auditAcceptor.accept(new String[]{String.join(",", trails)}); + } + return !trails.isEmpty(); + } + + @Override + public boolean processClass(Phase phase, ClassNode classNode, Type classType) { + throw new IllegalStateException("Outdated ModLauncher"); + } +} diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightJarInJarAdaptor.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightJarInJarAdaptor.java new file mode 100644 index 000000000..97e7e0e3f --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightJarInJarAdaptor.java @@ -0,0 +1,77 @@ +package io.izzel.arclight.boot.neoforge.mod; + +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.fml.loading.moddiscovery.JarInJarDependencyLocator; +import net.neoforged.fml.loading.moddiscovery.ModDiscoverer; +import net.neoforged.neoforgespi.locating.IDependencyLocator; +import net.neoforged.neoforgespi.locating.IModFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class ArclightJarInJarAdaptor implements IDependencyLocator { + + private static final Logger LOGGER = LoggerFactory.getLogger("ArclightJiJ"); + + private final IDependencyLocator delegate; + + public ArclightJarInJarAdaptor(IDependencyLocator delegate) { + this.delegate = delegate; + } + + @Override + public List scanMods(Iterable loadedMods) { + return delegate.scanMods(loadedMods).stream().filter(it -> { + var optional = getClass().getModule().getLayer().findModule(it.getModFileInfo().moduleName()); + optional.ifPresent(module -> LOGGER.info("Skip jij dependency {}@{} because Arclight has {}", + it.getModFileInfo().moduleName(), it.getModFileInfo().versionString(), module.getDescriptor().toNameAndVersion())); + return optional.isEmpty(); + }).toList(); + } + + @Override + public String name() { + return "arclight_jij"; + } + + @Override + public void scanFile(IModFile modFile, Consumer pathConsumer) { + delegate.scanFile(modFile, pathConsumer); + } + + @Override + public void initArguments(Map arguments) { + delegate.initArguments(arguments); + } + + @Override + public boolean isValid(IModFile modFile) { + return delegate.isValid(modFile); + } + + @SuppressWarnings("unchecked") + static void inject() { + try { + var field = FMLLoader.class.getDeclaredField("modDiscoverer"); + field.setAccessible(true); + var discoverer = (ModDiscoverer) field.get(null); + var locatorField = ModDiscoverer.class.getDeclaredField("dependencyLocatorList"); + locatorField.setAccessible(true); + var locatorList = (List) locatorField.get(discoverer); + var newList = locatorList.stream().map(it -> { + if (it instanceof JarInJarDependencyLocator) { + return new ArclightJarInJarAdaptor(it); + } else { + return it; + } + }).toList(); + locatorField.set(discoverer, newList); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightLaunchHandler.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightLaunchHandler.java new file mode 100644 index 000000000..cc76d1665 --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightLaunchHandler.java @@ -0,0 +1,17 @@ +package io.izzel.arclight.boot.neoforge.mod; + +import net.neoforged.fml.loading.targets.ForgeServerLaunchHandler; + +public class ArclightLaunchHandler extends ForgeServerLaunchHandler { + + @Override + public String name() { + return "arclightserver"; + } + + @Override + protected String[] preLaunch(String[] arguments, ModuleLayer layer) { + // skip the log4j configuration reloading + return arguments; + } +} diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightLocator_Neoforge.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightLocator_Neoforge.java new file mode 100644 index 000000000..00adfa5e7 --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ArclightLocator_Neoforge.java @@ -0,0 +1,92 @@ +package io.izzel.arclight.boot.neoforge.mod; + +import cpw.mods.jarhandling.JarContentsBuilder; +import cpw.mods.jarhandling.SecureJar; +import cpw.mods.jarhandling.impl.SimpleJarMetadata; +import net.neoforged.neoforgespi.language.IModFileInfo; +import net.neoforged.neoforgespi.locating.IModFile; +import net.neoforged.neoforgespi.locating.IModLocator; +import net.neoforged.neoforgespi.locating.IModProvider; +import net.neoforged.neoforgespi.locating.ModFileFactory; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.lang.Class.forName; + +public class ArclightLocator_Neoforge implements IModLocator { + + private final IModFile arclight; + + public ArclightLocator_Neoforge() { + ModBootstrap.run(); + this.arclight = loadJar(); + } + + @Override + public List scanMods() { + ArclightJarInJarAdaptor.inject(); + return List.of(new ModFileOrException(arclight, null)); + } + + @Override + public String name() { + return "arclight"; + } + + @Override + public void scanFile(IModFile file, Consumer pathConsumer) { + final Function status = p -> file.getSecureJar().verifyPath(p); + try (Stream files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { + file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2) -> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void initArguments(Map arguments) { + } + + @Override + public boolean isValid(IModFile modFile) { + return true; + } + + protected IModFile loadJar() { + try { + var cl = forName("net.neoforged.fml.loading.moddiscovery.ModFile"); + var lookup = MethodHandles.lookup(); + var handle = lookup.findConstructor(cl, MethodType.methodType(void.class, SecureJar.class, IModProvider.class, ModFileFactory.ModFileInfoParser.class)); + var version = System.getProperty("arclight.version"); + var path = Paths.get(".arclight", "mod_file", version + ".jar"); + var parserCl = forName("net.neoforged.fml.loading.moddiscovery.ModFileParser"); + var modsToml = lookup.findStatic(parserCl, "modsTomlParser", MethodType.methodType(IModFileInfo.class, IModFile.class)); + ModFileFactory.ModFileInfoParser parser = modFile -> { + try { + return (IModFileInfo) modsToml.invoke(modFile); + } catch (Throwable e) { + throw new RuntimeException(e); + } + }; + return (IModFile) handle.invoke(withVersion(path, version), this, parser); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + private SecureJar withVersion(Path path, String version) { + var contents = new JarContentsBuilder().paths(path).build(); + return SecureJar.from(contents, new SimpleJarMetadata("arclight", version.substring(version.indexOf('-') + 1), + contents::getPackages, List.of())); + } +} diff --git a/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ModBootstrap.java b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ModBootstrap.java new file mode 100644 index 000000000..daffff21b --- /dev/null +++ b/bootstrap/src/neoforge/java/io/izzel/arclight/boot/neoforge/mod/ModBootstrap.java @@ -0,0 +1,179 @@ +package io.izzel.arclight.boot.neoforge.mod; + +import cpw.mods.cl.JarModuleFinder; +import cpw.mods.cl.ModuleClassLoader; +import cpw.mods.jarhandling.JarContentsBuilder; +import cpw.mods.jarhandling.SecureJar; +import cpw.mods.jarhandling.impl.Jar; +import cpw.mods.modlauncher.LaunchPluginHandler; +import cpw.mods.modlauncher.Launcher; +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import cpw.mods.util.LambdaExceptionUtils; +import io.izzel.arclight.api.ArclightPlatform; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.boot.AbstractBootstrap; +import io.izzel.arclight.installer.ForgeInstaller; +import io.izzel.arclight.installer.MinecraftProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.MarkerManager; + +import java.io.File; +import java.io.InputStream; +import java.lang.invoke.MethodType; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ResolvedModule; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.CodeSigner; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.jar.Manifest; + +public class ModBootstrap implements AbstractBootstrap { + + public record ModBoot(Configuration configuration, ClassLoader parent) {} + + private static ModBoot modBoot; + + static void run() { + var plugin = Launcher.INSTANCE.environment().findLaunchPlugin("arclight_implementer"); + if (plugin.isPresent()) return; + var logger = LogManager.getLogger("Arclight"); + var marker = MarkerManager.getMarker("INSTALL"); + try { + var paths = MinecraftProvider.modInstall(s -> logger.info(marker, s)); + load(paths.toArray(new Path[0])); + new ModBootstrap().inject(); + } catch (Throwable e) { + logger.error("Error bootstrap Arclight", e); + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + public static void postRun() { + if (modBoot == null) return; + try { + var conf = modBoot.configuration(); + var parent = modBoot.parent(); + var classLoader = (ModuleClassLoader) Thread.currentThread().getContextClassLoader(); + var parentField = ModuleClassLoader.class.getDeclaredField("parentLoaders"); + var parentLoaders = (Map) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(parentField)); + for (var mod : conf.modules()) { + for (var pk : mod.reference().descriptor().packages()) { + parentLoaders.put(pk, parent); + } + } + modBoot = null; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + private void inject() throws Throwable { + dirtyHacks(); + setupMod(ArclightPlatform.NEOFORGE); + injectClassPath(); + injectLaunchPlugin(); + } + + private void injectClassPath() throws Throwable { + var platform = ClassLoader.getPlatformClassLoader(); + var ucpField = platform.getClass().getSuperclass().getDeclaredField("ucp"); + var ucp = Unsafe.lookup().unreflectGetter(ucpField).invoke(platform); + if (ucp == null) { + for (var module : ModuleLayer.boot().configuration().modules()) { + var optional = module.reference().location(); + if (optional.isPresent()) { + var uri = optional.get(); + if (uri.getScheme().equals("file")) { + ForgeInstaller.addToPath(new File(uri).toPath()); + } + } + } + } + } + + @SuppressWarnings("unchecked") + private void injectLaunchPlugin() throws Exception { + var instance = Launcher.INSTANCE; + var launchPlugins = Launcher.class.getDeclaredField("launchPlugins"); + launchPlugins.setAccessible(true); + var handler = (LaunchPluginHandler) launchPlugins.get(instance); + var plugins = LaunchPluginHandler.class.getDeclaredField("plugins"); + plugins.setAccessible(true); + var map = (Map) plugins.get(handler); + var plugin = new ArclightImplementer(); + map.put(plugin.name(), plugin); + } + + private static final Set EXCLUDES = Set.of("org/apache/maven/artifact/repository/metadata"); + + @SuppressWarnings("unchecked") + private static void load(Path[] file) throws Throwable { + var classLoader = (ModuleClassLoader) ModBootstrap.class.getClassLoader(); + var secureJar = SecureJar.from(new JarContentsBuilder().paths(file).pathFilter((entry, basePath) -> EXCLUDES.stream().noneMatch(entry::startsWith)).build()); + var configurationField = ModuleClassLoader.class.getDeclaredField("configuration"); + var confOffset = Unsafe.objectFieldOffset(configurationField); + var oldConf = (Configuration) Unsafe.getObject(classLoader, confOffset); + var conf = oldConf.resolveAndBind(JarModuleFinder.of(secureJar), ModuleFinder.of(), List.of(secureJar.name())); + modBoot = new ModBoot(conf, classLoader); + Unsafe.putObjectVolatile(classLoader, confOffset, conf); + var pkgField = ModuleClassLoader.class.getDeclaredField("packageLookup"); + var packageLookup = (Map) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(pkgField)); + var rootField = ModuleClassLoader.class.getDeclaredField("resolvedRoots"); + var resolvedRoots = (Map) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(rootField)); + var moduleRefCtor = Unsafe.lookup().findConstructor(Class.forName("cpw.mods.cl.JarModuleFinder$JarModuleReference"), + MethodType.methodType(void.class, SecureJar.ModuleDataProvider.class)); + for (var mod : conf.modules()) { + for (var pk : mod.reference().descriptor().packages()) { + packageLookup.put(pk, mod); + } + resolvedRoots.put(mod.name(), moduleRefCtor.invokeWithArguments(new JarModuleDataProvider((Jar) secureJar))); + } + } + + private record JarModuleDataProvider(Jar jar) implements SecureJar.ModuleDataProvider { + + @Override + public String name() { + return jar.name(); + } + + @Override + public ModuleDescriptor descriptor() { + return jar.computeDescriptor(); + } + + @Override + public URI uri() { + return jar.getURI(); + } + + @Override + public Optional findFile(final String name) { + return jar.findFile(name); + } + + @Override + public Optional open(final String name) { + return jar.findFile(name).map(Paths::get).map(LambdaExceptionUtils.rethrowFunction(Files::newInputStream)); + } + + @Override + public Manifest getManifest() { + return jar.moduleDataProvider().getManifest(); + } + + @Override + public CodeSigner[] verifyAndGetSigners(final String cname, final byte[] bytes) { + return jar.moduleDataProvider().verifyAndGetSigners(cname, bytes); + } + } +} diff --git a/bootstrap/src/neoforge/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService b/bootstrap/src/neoforge/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService new file mode 100644 index 000000000..3dbb2952f --- /dev/null +++ b/bootstrap/src/neoforge/resources/META-INF/services/cpw.mods.modlauncher.api.ILaunchHandlerService @@ -0,0 +1 @@ +io.izzel.arclight.boot.neoforge.mod.ArclightLaunchHandler \ No newline at end of file diff --git a/bootstrap/src/neoforge/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService b/bootstrap/src/neoforge/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService new file mode 100644 index 000000000..1154787cc --- /dev/null +++ b/bootstrap/src/neoforge/resources/META-INF/services/cpw.mods.modlauncher.serviceapi.ILaunchPluginService @@ -0,0 +1 @@ +io.izzel.arclight.boot.neoforge.mod.ArclightImplementer \ No newline at end of file diff --git a/bootstrap/src/neoforge/resources/META-INF/services/java.util.function.Consumer b/bootstrap/src/neoforge/resources/META-INF/services/java.util.function.Consumer new file mode 100644 index 000000000..03c25abc2 --- /dev/null +++ b/bootstrap/src/neoforge/resources/META-INF/services/java.util.function.Consumer @@ -0,0 +1 @@ +io.izzel.arclight.boot.neoforge.application.ApplicationBootstrap \ No newline at end of file diff --git a/bootstrap/src/neoforge/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModLocator b/bootstrap/src/neoforge/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModLocator new file mode 100644 index 000000000..ee3082e90 --- /dev/null +++ b/bootstrap/src/neoforge/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModLocator @@ -0,0 +1 @@ +io.izzel.arclight.boot.neoforge.mod.ArclightLocator_Neoforge \ No newline at end of file diff --git a/build.gradle b/build.gradle index b55bda6c9..943a27d38 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,6 @@ allprojects { + apply plugin: 'java' + group 'io.izzel.arclight' version '1.0.3-SNAPSHOT' @@ -13,15 +15,30 @@ allprojects { ext { minecraftVersion = '1.20.4' + supportedPlatforms = ['forge', 'neoforge'] fabricLoaderVersion = '0.15.5' forgeVersion = '49.0.22' - apiVersion = '1.6.1' + neoForgeVersion = '20.4.147-beta' + apiVersion = '1.6.2' toolsVersion = '1.3.0' mixinVersion = '0.8.5' versionName = 'whisper' gitHash = getGitHash() } + java.toolchain.languageVersion = JavaLanguageVersion.of(17) + + repositories { + maven { url = 'https://repo.spongepowered.org/maven' } + maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } + maven { url = 'https://maven.neoforged.net/releases' } + maven { url = 'https://files.minecraftforge.net/maven/' } + maven { url = 'https://maven.izzel.io/releases' } + maven { url = 'https://jitpack.io/' } + mavenCentral() + } + tasks.register('cleanBuild') { doFirst { project.file("build/libs").deleteDir() @@ -31,6 +48,6 @@ allprojects { tasks.register('collect', Copy) { destinationDir = file('build/libs') - from project(':arclight-forge-bootstrap').file('build/libs') - dependsOn { project(':arclight-forge-bootstrap').tasks.jar } + from project(':bootstrap').file('build/libs') + dependsOn (project(':bootstrap').tasks.jar, project(':bootstrap').tasks.neoforgeJar) } diff --git a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightExtension.groovy b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightExtension.groovy index 92af461b3..2b966bd17 100644 --- a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightExtension.groovy +++ b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightExtension.groovy @@ -72,6 +72,16 @@ class ArclightExtension { this.bukkitToForge = bukkitToForge } + private File bukkitToNeoForge + + File getBukkitToNeoForge() { + return bukkitToNeoForge + } + + void setBukkitToNeoForge(File bukkitToNeoForge) { + this.bukkitToNeoForge = bukkitToNeoForge + } + private File bukkitToForgeInheritance File getBukkitToForgeInheritance() { diff --git a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightGradlePlugin.groovy b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightGradlePlugin.groovy index 58796c7c8..913f2dd43 100644 --- a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightGradlePlugin.groovy +++ b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/ArclightGradlePlugin.groovy @@ -4,6 +4,7 @@ import io.izzel.arclight.gradle.tasks.BuildSpigotTask import io.izzel.arclight.gradle.tasks.DownloadBuildToolsTask import io.izzel.arclight.gradle.tasks.ProcessMappingTask import io.izzel.arclight.gradle.tasks.RemapSpigotTask +import net.fabricmc.loom.bootstrap.LoomGradlePluginBootstrap import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper import org.gradle.api.Plugin import org.gradle.api.Project @@ -12,7 +13,7 @@ class ArclightGradlePlugin implements Plugin { @Override void apply(Project project) { - project.plugins.apply(net.fabricmc.loom.bootstrap.LoomGradlePluginBootstrap) + project.plugins.apply(LoomGradlePluginBootstrap) def arclightExt = project.extensions.create('arclight', ArclightExtension, project) def arclightRepo = project.rootProject.file("${Project.DEFAULT_BUILD_DIR_NAME}/arclight_repo") project.repositories.maven { @@ -24,9 +25,11 @@ class ArclightGradlePlugin implements Plugin { def forgeMappings = new File(mappingsDir, "bukkit_srg.srg") def forgeInheritance = new File(mappingsDir, 'inheritanceMap.txt') def reobfMappings = new File(mappingsDir, 'reobf_bukkit.srg') + def neoforgeMappings = new File(mappingsDir, 'bukkit_moj.srg') arclightExt.mappingsConfiguration.bukkitToForge = forgeMappings arclightExt.mappingsConfiguration.reobfBukkitPackage = reobfMappings arclightExt.mappingsConfiguration.bukkitToForgeInheritance = forgeInheritance + arclightExt.mappingsConfiguration.bukkitToNeoForge = neoforgeMappings project.afterEvaluate { setupSpigot(project, arclightRepo) @@ -42,15 +45,13 @@ class ArclightGradlePlugin implements Plugin { def forgeMappings = new File(mappingsDir, "bukkit_srg.srg") def forgeInheritance = new File(mappingsDir, 'inheritanceMap.txt') def reobfMappings = new File(mappingsDir, 'reobf_bukkit.srg') - arclightExt.mappingsConfiguration.bukkitToForge = forgeMappings - arclightExt.mappingsConfiguration.reobfBukkitPackage = reobfMappings - arclightExt.mappingsConfiguration.bukkitToForgeInheritance = forgeInheritance + def neoforgeMappings = new File(mappingsDir, 'bukkit_moj.srg') def spigotDeps = new File(arclightRepo, "io/izzel/arclight/generated/spigot/${arclightExt.mcVersion}") def spigotMapped = new File(spigotDeps, "spigot-${arclightExt.mcVersion}-mapped.jar") def spigotDeobf = new File(spigotDeps, "spigot-${arclightExt.mcVersion}-deobf.jar") - if (forgeMappings.exists() && reobfMappings.exists() && forgeInheritance.exists() && spigotDeobf.exists()) { + if (forgeMappings.exists() && reobfMappings.exists() && forgeInheritance.exists() && neoforgeMappings.exists() && spigotDeobf.exists()) { return } project.logger.lifecycle(":step1 download build tools") diff --git a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/GenerateInstallerInfo.groovy b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/GenerateInstallerInfo.groovy index 057ec36f3..93ddf5cba 100644 --- a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/GenerateInstallerInfo.groovy +++ b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/GenerateInstallerInfo.groovy @@ -1,5 +1,7 @@ package io.izzel.arclight.gradle.tasks +import groovy.json.JsonOutput +import io.izzel.arclight.gradle.Utils import org.gradle.api.DefaultTask import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.DependencyArtifact @@ -12,7 +14,7 @@ import java.security.MessageDigest class GenerateInstallerInfo extends DefaultTask { - private String minecraftVersion, forgeVersion + private String minecraftVersion, forgeVersion, neoforgeVersion private Configuration configuration @Classpath @@ -42,6 +44,15 @@ class GenerateInstallerInfo extends DefaultTask { this.forgeVersion = forgeVersion } + @Input + String getNeoforgeVersion() { + return neoforgeVersion + } + + void setNeoforgeVersion(String neoforgeVersion) { + this.neoforgeVersion = neoforgeVersion + } + @TaskAction void run() { def libs = configuration.dependencies.collect { dep -> @@ -95,19 +106,20 @@ class GenerateInstallerInfo extends DefaultTask { } def installerUrl = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/$minecraftVersion-$forgeVersion/forge-$minecraftVersion-$forgeVersion-installer.jar" def tmpInstaller = Files.createTempFile("installer", "jar") - installerUrl.toURI().toURL().withReader { r -> - tmpInstaller.withWriter { w -> - r.transferTo(w) - } - } + Utils.download(installerUrl, tmpInstaller.toFile()) + def neoforgeUrl = "https://maven.neoforged.net/releases/net/neoforged/neoforge/$neoforgeVersion/neoforge-$neoforgeVersion-installer.jar" + def tmpNeoforge = Files.createTempFile("neoforge", "jar") + Utils.download(neoforgeUrl, tmpNeoforge.toFile()) def output = [ installer: [ - minecraft: minecraftVersion, - forge : forgeVersion, - hash : sha1(tmpInstaller.toFile()) + minecraft : minecraftVersion, + forge : forgeVersion, + forgeHash : sha1(tmpInstaller.toFile()), + neoforge : neoforgeVersion, + neoforgeHash: sha1(tmpNeoforge.toFile()), ], libraries: artifacts(libs) ] - outputs.files.singleFile.text = groovy.json.JsonOutput.toJson(output) + outputs.files.singleFile.text = JsonOutput.toJson(output) } } diff --git a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/ProcessMappingTask.groovy b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/ProcessMappingTask.groovy index e148886f3..251b5e4c4 100644 --- a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/ProcessMappingTask.groovy +++ b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/ProcessMappingTask.groovy @@ -2,6 +2,7 @@ package io.izzel.arclight.gradle.tasks import io.izzel.arclight.gradle.util.AwWriter import net.fabricmc.loom.LoomGradleExtension +import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration import net.fabricmc.lorenztiny.TinyMappingsReader import net.fabricmc.mappingio.MappingReader import net.fabricmc.mappingio.tree.MemoryMappingTree @@ -46,9 +47,13 @@ class ProcessMappingTask implements Runnable { void run() { def tree = new MemoryMappingTree() MappingReader.read(LoomGradleExtension.get(project).mappingConfiguration.tinyMappingsWithSrg, tree) - def official = new TinyMappingsReader(tree, "official", "named").read() def mcp = new TinyMappingsReader(tree, "named", "srg").read() + def mojmapTree = new MemoryMappingTree() + MappingReader.read(MappingConfiguration.getMojmapSrgFileIfPossible(project), mojmapTree) + def official = new TinyMappingsReader(mojmapTree, "official", "named").read() + def officialRev = official.reverse() + if (!outDir.isDirectory()) { outDir.mkdirs() } @@ -86,6 +91,7 @@ class ProcessMappingTask implements Runnable { } def srgRev = srg.reverse() def finalMap = srgRev.merge(csrg).reverse() + def neoforgeMap = officialRev.merge(csrg).reverse() new File(outDir, 'inheritanceMap.txt').with { it.delete() it.createNewFile() @@ -138,6 +144,35 @@ class ProcessMappingTask implements Runnable { } }.write(finalMap) } + new File(outDir, 'bukkit_moj.srg').withWriter { + new TSrgWriter(it) { + @Override + void write(final MappingSet mappings) { + mappings.getTopLevelClassMappings().stream() + .sorted(this.getConfig().getClassMappingComparator()) + .forEach(this::writeClassMapping) + } + + @Override + protected void writeClassMapping(ClassMapping mapping) { + if (!mapping.hasMappings()) { + this.writer.println(String.format("%s %s", mapping.getFullObfuscatedName(), mapping.getFullDeobfuscatedName())); + } else if (mapping.fullObfuscatedName.contains('/')) { + super.writeClassMapping(mapping) + } + } + + @Override + protected void writeFieldMapping(FieldMapping mapping) { + def cl = officialRev.getClassMapping(mapping.parent.fullDeobfuscatedName).get() + def field = cl.getFieldMapping(mapping.deobfuscatedName).get().deobfuscatedName + def nmsCl = official.getClassMapping(cl.fullDeobfuscatedName) + .get().getFieldMapping(field).get().signature.type.get() + def sig = Type.getType(csrg.deobfuscate(nmsCl).toString()).getClassName() + this.writer.println(String.format(" %s %s -> %s", sig, mapping.getDeobfuscatedName(), mapping.getObfuscatedName())) + } + }.write(neoforgeMap) + } new File(outDir, 'bukkit_at.at').withWriter { w -> new File(buildData, "mappings/bukkit-${mcVersion}.at").eachLine { l -> if (l.trim().isEmpty() || l.startsWith('#')) { diff --git a/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/RenameAsyncCatcherTask.groovy b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/RenameAsyncCatcherTask.groovy new file mode 100644 index 000000000..4d3f8c208 --- /dev/null +++ b/buildSrc/src/main/groovy/io/izzel/arclight/gradle/tasks/RenameAsyncCatcherTask.groovy @@ -0,0 +1,45 @@ +package io.izzel.arclight.gradle.tasks + +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import net.fabricmc.loom.LoomGradleExtension +import net.fabricmc.lorenztiny.TinyMappingsReader +import net.fabricmc.mappingio.MappingReader +import net.fabricmc.mappingio.tree.MemoryMappingTree +import org.cadixdev.bombe.type.signature.MethodSignature +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.TaskAction + +abstract class RenameAsyncCatcherTask extends DefaultTask { + + @InputFile + abstract RegularFileProperty getInputFile() + + @Input + abstract Property getMapping() + + @TaskAction + void run() { + def input = inputFile.get().asFile + def output = outputs.files.singleFile + def tree = new MemoryMappingTree() + MappingReader.read(LoomGradleExtension.get(project.project(":arclight-common")).mappingConfiguration.tinyMappingsWithSrg, tree) + def set = new TinyMappingsReader(tree, "named", mapping.get()).read() + def map = new JsonSlurper().parse(input) as Map> + def mapped = map.collectEntries { ent -> + def clMap = set.getOrCreateClassMapping(ent.key) + def cl = clMap.fullDeobfuscatedName + def methods = ent.value.collectEntries { + def sig = MethodSignature.of(it.key) + def mappedSig = clMap.getOrCreateMethodMapping(sig) + [(mappedSig.deobfuscatedSignature.toJvmsIdentifier()): it.value] + } + [(cl): methods] + } + output.text = JsonOutput.toJson(mapped) + } +} \ No newline at end of file diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java b/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java deleted file mode 100644 index 6c005b900..000000000 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java +++ /dev/null @@ -1,338 +0,0 @@ -package io.izzel.arclight.forgeinstaller; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.izzel.arclight.api.Unsafe; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.security.AccessControlContext; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.stream.Collectors; - -public class ForgeInstaller { - - public static List modInstall(Consumer logger) throws Throwable { - InputStream stream = ForgeInstaller.class.getModule().getResourceAsStream("/META-INF/installer.json"); - InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); - List> suppliers = checkMavenNoSource(installInfo.libraries); - if (!suppliers.isEmpty()) { - logger.accept("Downloading missing libraries ..."); - ExecutorService pool = Executors.newFixedThreadPool(8); - CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, logger)).toArray(CompletableFuture[]::new); - handleFutures(logger, array); - pool.shutdownNow(); - } - return installInfo.libraries.keySet().stream().map(it -> Paths.get("libraries").resolve(Util.mavenToPath(it))).collect(Collectors.toList()); - } - - @SuppressWarnings("unused") - public static Map.Entry> applicationInstall() throws Throwable { - InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json"); - InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); - List> suppliers = checkMavenNoSource(installInfo.libraries); - Path path = Paths.get("forge-" + installInfo.installer.minecraft + "-" + installInfo.installer.forge + "-shim.jar"); - var installForge = !Files.exists(path) || forgeClasspathMissing(path); - if (!suppliers.isEmpty() || installForge) { - System.out.println("Downloading missing libraries ..."); - ExecutorService pool = Executors.newWorkStealingPool(8); - CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, System.out::println)).toArray(CompletableFuture[]::new); - if (installForge) { - var futures = installForge(installInfo, pool, System.out::println); - handleFutures(System.out::println, futures); - System.out.println("Forge installation is starting, please wait... "); - try { - ProcessBuilder builder = new ProcessBuilder(); - File file = new File(System.getProperty("java.home"), "bin/java"); - builder.command(file.getCanonicalPath(), "-Djava.net.useSystemProxies=true", "-jar", futures[0].join().toString(), "--installServer", ".", "--debug"); - builder.inheritIO(); - Process process = builder.start(); - if (process.waitFor() > 0) { - throw new Exception("Forge installation failed"); - } - } catch (IOException e) { - try (URLClassLoader loader = new URLClassLoader( - new URL[]{new File(String.format("forge-%s-%s-installer.jar", installInfo.installer.minecraft, installInfo.installer.forge)).toURI().toURL()}, - ForgeInstaller.class.getClassLoader().getParent())) { - Method method = loader.loadClass("net.minecraftforge.installer.SimpleInstaller").getMethod("main", String[].class); - method.invoke(null, (Object) new String[]{"--installServer", ".", "--debug"}); - } - } - } - handleFutures(System.out::println, array); - pool.shutdownNow(); - } - return classpath(path, installInfo); - } - - private static Function, CompletableFuture> reportSupply(ExecutorService service, Consumer logger) { - return it -> CompletableFuture.supplyAsync(it, service).thenApply(path -> { - logger.accept("Downloaded " + path); - return path; - }); - } - - private record MinecraftData(String mirror, String serverUrl, String serverHash, String mappingUrl, - String mappingHash) { - } - - @SuppressWarnings("unchecked") - private static CompletableFuture[] installForge(InstallInfo info, ExecutorService pool, Consumer logger) { - var minecraftData = CompletableFuture.supplyAsync(() -> { - logger.accept("Downloading mc version manifest..."); - for (Map.Entry entry : Mirrors.getVersionManifest()) { - try (var stream = FileDownloader.read(entry.getValue())) { - var bytes = stream.readAllBytes(); - var element = new JsonParser().parse(new String(bytes, StandardCharsets.UTF_8)).getAsJsonObject(); - var versions = element.getAsJsonArray("versions"); - for (var version : versions) { - var id = version.getAsJsonObject().get("id").getAsString(); - if (Objects.equals(id, info.installer.minecraft)) { - var url = version.getAsJsonObject().get("url").getAsString(); - try (var versionStream = FileDownloader.read(url)) { - var object = new JsonParser().parse(new String(versionStream.readAllBytes(), StandardCharsets.UTF_8)).getAsJsonObject(); - var downloads = object.getAsJsonObject("downloads"); - var server = downloads.getAsJsonObject("server"); - var serverUrl = server.get("url").getAsString(); - var serverHash = server.get("sha1").getAsString(); - var mapping = downloads.getAsJsonObject("server_mappings"); - var mappingUrl = mapping.get("url").getAsString(); - var mappingHash = mapping.get("sha1").getAsString(); - logger.accept("Minecraft version: %s, server: %s, mappings: %s".formatted(info.installer.minecraft, serverHash, mappingHash)); - return new MinecraftData(entry.getKey(), - Mirrors.mapMojangMirror(serverUrl, entry.getKey()), serverHash, - Mirrors.mapMojangMirror(mappingUrl, entry.getKey()), mappingHash); - } - } - } - logger.accept("Version %s not available in %s".formatted(info.installer.minecraft, entry.getKey())); - } catch (Exception e) { - logger.accept("Failed to download manifest from " + entry.getKey() + "\n " + e); - } - } - return null; - }, pool); - String coord = String.format("net.minecraftforge:forge:%s-%s:installer", info.installer.minecraft, info.installer.forge); - String dist = String.format("forge-%s-%s-installer.jar", info.installer.minecraft, info.installer.forge); - MavenDownloader forge = new MavenDownloader(Mirrors.getMavenRepo(), coord, dist, info.installer.hash); - var installerFuture = reportSupply(pool, logger).apply(forge).thenCombineAsync(minecraftData, (path, data) -> { - try (var jarFile = new JarFile(path.toFile())) { - Map> map = new HashMap<>(); - var profile = jarFile.getEntry("install_profile.json"); - map.putAll(profileLibraries(new InputStreamReader(jarFile.getInputStream(profile)), info.installer.minecraft, data)); - var version = jarFile.getEntry("version.json"); - map.putAll(profileLibraries(new InputStreamReader(jarFile.getInputStream(version)), info.installer.minecraft, data)); - List> suppliers = checkMaven(map); - CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, logger)).toArray(CompletableFuture[]::new); - handleFutures(logger, array); - } catch (IOException e) { - e.printStackTrace(); - } - return stripDownloadMapping(path, logger); - }); - var serverFuture = minecraftData.thenCompose(data -> reportSupply(pool, logger).apply( - new FileDownloader(String.format(data.serverUrl, info.installer.minecraft), - String.format("libraries/net/minecraft/server/%1$s/server-%1$s-bundled.jar", info.installer.minecraft), data.serverHash) - )); - return new CompletableFuture[]{installerFuture, serverFuture}; - } - - private static Path stripDownloadMapping(Path installer, Consumer logger) { - try { - logger.accept("Processing forge installer..."); - var path = Paths.get(".arclight", "installer_stripped.jar"); - if (!Files.isDirectory(path.getParent())) { - Files.createDirectories(path.getParent()); - } - Files.deleteIfExists(path); - try (var from = new JarFile(installer.toFile()); - var to = new JarOutputStream(Files.newOutputStream(path, StandardOpenOption.CREATE))) { - var entries = from.entries(); - while (entries.hasMoreElements()) { - var entry = entries.nextElement(); - // strip signature - var name = entry.getName(); - if (name.endsWith(".SF") || name.endsWith(".DSA") || name.endsWith(".RSA")) { - continue; - } - if (name.equals("install_profile.json")) { - var element = new JsonParser().parse(new InputStreamReader(from.getInputStream(entry))); - var processors = element.getAsJsonObject().getAsJsonArray("processors"); - outer: - for (var i = 0; i < processors.size(); i++) { - var processor = processors.get(i).getAsJsonObject(); - var args = processor.getAsJsonArray("args"); - for (var arg : args) { - if (arg.getAsString().equals("DOWNLOAD_MOJMAPS")) { - processors.remove(i); - break outer; - } - } - } - to.putNextEntry(entry); - to.write(new Gson().toJson(element).getBytes(StandardCharsets.UTF_8)); - } else { - to.putNextEntry(entry); - from.getInputStream(entry).transferTo(to); - } - } - } - return path; - } catch (Exception e) { - throw new CompletionException(e); - } - } - - private static void handleFutures(Consumer logger, CompletableFuture... futures) { - for (CompletableFuture future : futures) { - try { - future.join(); - } catch (CompletionException e) { - logger.accept(e.getCause().toString()); - Unsafe.throwException(e.getCause()); - } catch (Exception e) { - e.printStackTrace(); - Unsafe.throwException(e); - } - } - } - - private static Map> profileLibraries(Reader reader, String minecraft, MinecraftData minecraftData) throws IOException { - Map> ret = new HashMap<>(); - var object = new JsonParser().parse(reader).getAsJsonObject(); - JsonArray array = object.getAsJsonArray("libraries"); - for (JsonElement element : array) { - String name = element.getAsJsonObject().get("name").getAsString(); - JsonObject artifact = element.getAsJsonObject().getAsJsonObject("downloads").getAsJsonObject("artifact"); - String hash = artifact.get("sha1").getAsString(); - String url = artifact.get("url").getAsString(); - if (url == null || url.trim().isEmpty()) continue; - ret.put(name, new AbstractMap.SimpleImmutableEntry<>(hash, url)); - } - if (object.has("data")) { - var data = object.getAsJsonObject("data"); - if (data.has("MOJMAPS")) { - var serverMapping = data.getAsJsonObject("MOJMAPS").get("server").getAsString(); - ret.put(serverMapping.substring(1, serverMapping.length() - 1), - new AbstractMap.SimpleImmutableEntry<>(minecraftData.mappingHash, minecraftData.mappingUrl)); - } - } - return ret; - } - - private static List> checkMavenNoSource(Map map) { - LinkedHashMap> hashMap = new LinkedHashMap<>(map.size()); - for (Map.Entry entry : map.entrySet()) { - hashMap.put(entry.getKey(), new AbstractMap.SimpleImmutableEntry<>(entry.getValue(), null)); - } - return checkMaven(hashMap); - } - - private static List> checkMaven(Map> map) { - List> incomplete = new ArrayList<>(); - for (Map.Entry> entry : map.entrySet()) { - String maven = entry.getKey(); - String hash = entry.getValue().getKey(); - String url = entry.getValue().getValue(); - String path = "libraries/" + Util.mavenToPath(maven); - if (new File(path).exists()) { - try { - String fileHash = Util.hash(path); - if (!fileHash.equals(hash)) { - incomplete.add(new MavenDownloader(Mirrors.getMavenRepo(), maven, path, hash, url)); - } - } catch (Exception e) { - incomplete.add(new MavenDownloader(Mirrors.getMavenRepo(), maven, path, hash, url)); - } - } else { - incomplete.add(new MavenDownloader(Mirrors.getMavenRepo(), maven, path, hash, url)); - } - } - return incomplete; - } - - private static boolean forgeClasspathMissing(Path path) throws Exception { - try (var file = new JarFile(path.toFile())) { - var entry = file.getEntry("bootstrap-shim.list"); - try (var stream = file.getInputStream(entry)) { - return new String(stream.readAllBytes()).lines().anyMatch(it -> !Files.exists(Paths.get("libraries", it.split("\t")[2]))); - } - } - } - - private static Map.Entry> classpath(Path path, InstallInfo installInfo) throws Throwable { - var libs = new ArrayList<>(installInfo.libraries.keySet()); - var classpath = new StringBuilder(System.getProperty("java.class.path")); - try (var file = new JarFile(path.toFile())) { - var entry = file.getEntry("bootstrap-shim.list"); - try (var stream = file.getInputStream(entry)) { - new String(stream.readAllBytes()).lines().forEach(it -> { - var args = it.split("\t"); - classpath.append(File.pathSeparator).append("libraries/").append(args[2]); - addToPath(Paths.get("libraries", args[2])); - }); - } - } - for (var lib : libs) { - classpath.append(File.pathSeparator).append("libraries/").append(Util.mavenToPath(lib)); - } - System.setProperty("java.class.path", classpath.toString()); - return Map.entry("io.izzel.arclight.boot.application.ApplicationBootstrap", List.of("--launchTarget", "arclight_server")); - } - - @SuppressWarnings("removal") - public static void addToPath(Path path) { - try { - ClassLoader loader = ClassLoader.getSystemClassLoader(); - Field ucpField; - try { - ucpField = loader.getClass().getDeclaredField("ucp"); - } catch (NoSuchFieldException e) { - ucpField = loader.getClass().getSuperclass().getDeclaredField("ucp"); - } - long offset = Unsafe.objectFieldOffset(ucpField); - Object ucp = Unsafe.getObject(loader, offset); - if (ucp == null) { - var cl = Class.forName("jdk.internal.loader.URLClassPath"); - var handle = Unsafe.lookup().findConstructor(cl, MethodType.methodType(void.class, URL[].class, AccessControlContext.class)); - ucp = handle.invoke(new URL[]{}, (AccessControlContext) null); - Unsafe.putObjectVolatile(loader, offset, ucp); - } - Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class); - Unsafe.lookup().unreflect(method).invoke(ucp, path.toUri().toURL()); - } catch (Throwable t) { - t.printStackTrace(); - } - } -} diff --git a/gradle.properties b/gradle.properties index 10ac8f35f..e5bf2984d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,5 @@ -org.gradle.jvmargs=-Xmx4g -Dfile.encoding=utf-8 \ No newline at end of file +org.gradle.jvmargs=-Xmx4g -Dfile.encoding=utf-8 + +architect_plugin_version=3.4-SNAPSHOT +architectury_loom_plugin_version=1.4-SNAPSHOT +shadow_plugin_version=7.1.2 diff --git a/forge-installer/.gitignore b/installer/.gitignore similarity index 100% rename from forge-installer/.gitignore rename to installer/.gitignore diff --git a/forge-installer/build.gradle b/installer/build.gradle similarity index 84% rename from forge-installer/build.gradle rename to installer/build.gradle index dbef38ca1..ca3f02665 100644 --- a/forge-installer/build.gradle +++ b/installer/build.gradle @@ -12,6 +12,6 @@ repositories { } dependencies { - implementation 'com.google.code.gson:gson:2.8.0' + implementation 'com.google.code.gson:gson:2.8.9' implementation "io.izzel.arclight:arclight-api:$apiVersion" } diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/FileDownloader.java b/installer/src/main/java/io/izzel/arclight/installer/FileDownloader.java similarity index 99% rename from forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/FileDownloader.java rename to installer/src/main/java/io/izzel/arclight/installer/FileDownloader.java index 817504522..de8982be2 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/FileDownloader.java +++ b/installer/src/main/java/io/izzel/arclight/installer/FileDownloader.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.forgeinstaller; +package io.izzel.arclight.installer; import io.izzel.arclight.api.Unsafe; diff --git a/installer/src/main/java/io/izzel/arclight/installer/ForgeInstaller.java b/installer/src/main/java/io/izzel/arclight/installer/ForgeInstaller.java new file mode 100644 index 000000000..468215213 --- /dev/null +++ b/installer/src/main/java/io/izzel/arclight/installer/ForgeInstaller.java @@ -0,0 +1,136 @@ +package io.izzel.arclight.installer; + +import com.google.gson.Gson; +import io.izzel.arclight.api.Unsafe; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.AccessControlContext; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.jar.JarFile; + +public class ForgeInstaller { + + @SuppressWarnings("unused") + public static Map.Entry> applicationInstall() throws Throwable { + InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json"); + InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); + List> suppliers = MinecraftProvider.checkMavenNoSource(installInfo.libraries); + Path path = Paths.get("forge-" + installInfo.installer.minecraft + "-" + installInfo.installer.forge + "-shim.jar"); + var installForge = !Files.exists(path) || forgeClasspathMissing(path); + if (!suppliers.isEmpty() || installForge) { + System.out.println("Downloading missing libraries ..."); + ExecutorService pool = Executors.newWorkStealingPool(8); + CompletableFuture[] array = suppliers.stream().map(MinecraftProvider.reportSupply(pool, System.out::println)).toArray(CompletableFuture[]::new); + if (installForge) { + var futures = installForge(installInfo, pool, System.out::println); + MinecraftProvider.handleFutures(System.out::println, futures); + System.out.println("Forge installation is starting, please wait... "); + try { + ProcessBuilder builder = new ProcessBuilder(); + File file = new File(System.getProperty("java.home"), "bin/java"); + builder.command(file.getCanonicalPath(), "-Djava.net.useSystemProxies=true", "-jar", futures[0].join().toString(), "--installServer", ".", "--debug"); + builder.inheritIO(); + Process process = builder.start(); + if (process.waitFor() > 0) { + throw new Exception("Forge installation failed"); + } + } catch (IOException e) { + try (URLClassLoader loader = new URLClassLoader( + new URL[]{new File(String.format("forge-%s-%s-installer.jar", installInfo.installer.minecraft, installInfo.installer.forge)).toURI().toURL()}, + ForgeInstaller.class.getClassLoader().getParent())) { + Method method = loader.loadClass("net.minecraftforge.installer.SimpleInstaller").getMethod("main", String[].class); + method.invoke(null, (Object) new String[]{"--installServer", ".", "--debug"}); + } + } + } + MinecraftProvider.handleFutures(System.out::println, array); + pool.shutdownNow(); + } + return classpath(path, installInfo); + } + + @SuppressWarnings("unchecked") + private static CompletableFuture[] installForge(InstallInfo info, ExecutorService pool, Consumer logger) { + var minecraftData = MinecraftProvider.downloadMinecraftData(info, pool, logger); + String coord = String.format("net.minecraftforge:forge:%s-%s:installer", info.installer.minecraft, info.installer.forge); + String dist = String.format("forge-%s-%s-installer.jar", info.installer.minecraft, info.installer.forge); + var installerFuture = ForgeLikeProvider.downloadInstaller(coord, dist, info.installer.forgeHash, minecraftData, info, pool, logger); + var serverFuture = minecraftData.thenCompose(data -> MinecraftProvider.reportSupply(pool, logger).apply( + new FileDownloader(String.format(data.serverUrl(), info.installer.minecraft), + String.format("libraries/net/minecraft/server/%1$s/server-%1$s-bundled.jar", info.installer.minecraft), data.serverHash()) + )); + return new CompletableFuture[]{installerFuture, serverFuture}; + } + + private static boolean forgeClasspathMissing(Path path) throws Exception { + try (var file = new JarFile(path.toFile())) { + var entry = file.getEntry("bootstrap-shim.list"); + try (var stream = file.getInputStream(entry)) { + return new String(stream.readAllBytes()).lines().anyMatch(it -> !Files.exists(Paths.get("libraries", it.split("\t")[2]))); + } + } + } + + private static Map.Entry> classpath(Path path, InstallInfo installInfo) throws Throwable { + var libs = new ArrayList<>(installInfo.libraries.keySet()); + var classpath = new StringBuilder(System.getProperty("java.class.path")); + try (var file = new JarFile(path.toFile())) { + var entry = file.getEntry("bootstrap-shim.list"); + try (var stream = file.getInputStream(entry)) { + new String(stream.readAllBytes()).lines().forEach(it -> { + var args = it.split("\t"); + classpath.append(File.pathSeparator).append("libraries/").append(args[2]); + addToPath(Paths.get("libraries", args[2])); + }); + } + } + for (var lib : libs) { + classpath.append(File.pathSeparator).append("libraries/").append(Util.mavenToPath(lib)); + } + System.setProperty("java.class.path", classpath.toString()); + return Map.entry("io.izzel.arclight.boot.forge.application.ApplicationBootstrap", List.of("--launchTarget", "arclight_server")); + } + + @SuppressWarnings("removal") + public static void addToPath(Path path) { + try { + ClassLoader loader = ClassLoader.getSystemClassLoader(); + Field ucpField; + try { + ucpField = loader.getClass().getDeclaredField("ucp"); + } catch (NoSuchFieldException e) { + ucpField = loader.getClass().getSuperclass().getDeclaredField("ucp"); + } + long offset = Unsafe.objectFieldOffset(ucpField); + Object ucp = Unsafe.getObject(loader, offset); + if (ucp == null) { + var cl = Class.forName("jdk.internal.loader.URLClassPath"); + var handle = Unsafe.lookup().findConstructor(cl, MethodType.methodType(void.class, URL[].class, AccessControlContext.class)); + ucp = handle.invoke(new URL[]{}, (AccessControlContext) null); + Unsafe.putObjectVolatile(loader, offset, ucp); + } + Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class); + Unsafe.lookup().unreflect(method).invoke(ucp, path.toUri().toURL()); + } catch (Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/installer/src/main/java/io/izzel/arclight/installer/ForgeLikeProvider.java b/installer/src/main/java/io/izzel/arclight/installer/ForgeLikeProvider.java new file mode 100644 index 000000000..a6e7e83f0 --- /dev/null +++ b/installer/src/main/java/io/izzel/arclight/installer/ForgeLikeProvider.java @@ -0,0 +1,114 @@ +package io.izzel.arclight.installer; + +import com.google.gson.*; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +public class ForgeLikeProvider { + + static CompletableFuture downloadInstaller(String coord, String dist, String hash, CompletableFuture minecraftData, InstallInfo info, ExecutorService pool, Consumer logger) { + MavenDownloader forge = new MavenDownloader(Mirrors.getMavenRepo(), coord, dist, hash); + return MinecraftProvider.reportSupply(pool, logger).apply(forge).thenCombineAsync(minecraftData, (path, data) -> { + try (var jarFile = new JarFile(path.toFile())) { + Map> map = new HashMap<>(); + var profile = jarFile.getEntry("install_profile.json"); + map.putAll(profileLibraries(new InputStreamReader(jarFile.getInputStream(profile)), info.installer.minecraft, data)); + var version = jarFile.getEntry("version.json"); + map.putAll(profileLibraries(new InputStreamReader(jarFile.getInputStream(version)), info.installer.minecraft, data)); + List> suppliers = MinecraftProvider.checkMaven(map); + CompletableFuture[] array = suppliers.stream().map(MinecraftProvider.reportSupply(pool, logger)).toArray(CompletableFuture[]::new); + MinecraftProvider.handleFutures(logger, array); + } catch (IOException e) { + e.printStackTrace(); + } + return stripDownloadMapping(path, logger); + }); + } + + static Path stripDownloadMapping(Path installer, Consumer logger) { + try { + logger.accept("Processing forge installer..."); + var path = Paths.get(".arclight", "installer_stripped.jar"); + if (!Files.isDirectory(path.getParent())) { + Files.createDirectories(path.getParent()); + } + Files.deleteIfExists(path); + try (var from = new JarFile(installer.toFile()); + var to = new JarOutputStream(Files.newOutputStream(path, StandardOpenOption.CREATE))) { + var entries = from.entries(); + while (entries.hasMoreElements()) { + var entry = entries.nextElement(); + // strip signature + var name = entry.getName(); + if (name.endsWith(".SF") || name.endsWith(".DSA") || name.endsWith(".RSA")) { + continue; + } + if (name.equals("install_profile.json")) { + var element = new JsonParser().parse(new InputStreamReader(from.getInputStream(entry))); + var processors = element.getAsJsonObject().getAsJsonArray("processors"); + outer: + for (var i = 0; i < processors.size(); i++) { + var processor = processors.get(i).getAsJsonObject(); + var args = processor.getAsJsonArray("args"); + for (var arg : args) { + if (arg.getAsString().equals("DOWNLOAD_MOJMAPS")) { + processors.remove(i); + break outer; + } + } + } + to.putNextEntry(entry); + to.write(new Gson().toJson(element).getBytes(StandardCharsets.UTF_8)); + } else { + to.putNextEntry(entry); + from.getInputStream(entry).transferTo(to); + } + } + } + return path; + } catch (Exception e) { + throw new CompletionException(e); + } + } + + static Map> profileLibraries(Reader reader, String minecraft, MinecraftProvider.MinecraftData minecraftData) throws IOException { + Map> ret = new HashMap<>(); + var object = new JsonParser().parse(reader).getAsJsonObject(); + JsonArray array = object.getAsJsonArray("libraries"); + for (JsonElement element : array) { + String name = element.getAsJsonObject().get("name").getAsString(); + JsonObject artifact = element.getAsJsonObject().getAsJsonObject("downloads").getAsJsonObject("artifact"); + String hash = artifact.get("sha1").getAsString(); + String url = artifact.get("url").getAsString(); + if (url == null || url.trim().isEmpty()) continue; + ret.put(name, new AbstractMap.SimpleImmutableEntry<>(hash, url)); + } + if (object.has("data")) { + var data = object.getAsJsonObject("data"); + if (data.has("MOJMAPS")) { + var serverMapping = data.getAsJsonObject("MOJMAPS").get("server").getAsString(); + ret.put(serverMapping.substring(1, serverMapping.length() - 1), + new AbstractMap.SimpleImmutableEntry<>(minecraftData.mappingHash(), minecraftData.mappingUrl())); + } + } + return ret; + } +} diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/InstallInfo.java b/installer/src/main/java/io/izzel/arclight/installer/InstallInfo.java similarity index 62% rename from forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/InstallInfo.java rename to installer/src/main/java/io/izzel/arclight/installer/InstallInfo.java index 7846e7544..97e3d0fc2 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/InstallInfo.java +++ b/installer/src/main/java/io/izzel/arclight/installer/InstallInfo.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.forgeinstaller; +package io.izzel.arclight.installer; import java.util.Map; @@ -11,6 +11,8 @@ public static class Installer { public String minecraft; public String forge; - public String hash; + public String forgeHash; + public String neoforge; + public String neoforgeHash; } } diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/MavenDownloader.java b/installer/src/main/java/io/izzel/arclight/installer/MavenDownloader.java similarity index 97% rename from forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/MavenDownloader.java rename to installer/src/main/java/io/izzel/arclight/installer/MavenDownloader.java index 9d1f87312..8aa06fd85 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/MavenDownloader.java +++ b/installer/src/main/java/io/izzel/arclight/installer/MavenDownloader.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.forgeinstaller; +package io.izzel.arclight.installer; import java.nio.file.Path; import java.util.ArrayList; diff --git a/installer/src/main/java/io/izzel/arclight/installer/MinecraftProvider.java b/installer/src/main/java/io/izzel/arclight/installer/MinecraftProvider.java new file mode 100644 index 000000000..8db558602 --- /dev/null +++ b/installer/src/main/java/io/izzel/arclight/installer/MinecraftProvider.java @@ -0,0 +1,127 @@ +package io.izzel.arclight.installer; + +import com.google.gson.*; +import io.izzel.arclight.api.Unsafe; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class MinecraftProvider { + static Function, CompletableFuture> reportSupply(ExecutorService service, Consumer logger) { + return it -> CompletableFuture.supplyAsync(it, service).thenApply(path -> { + logger.accept("Downloaded " + path); + return path; + }); + } + + public static List modInstall(Consumer logger) throws Throwable { + InputStream stream = ForgeInstaller.class.getModule().getResourceAsStream("/META-INF/installer.json"); + InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); + List> suppliers = checkMavenNoSource(installInfo.libraries); + if (!suppliers.isEmpty()) { + logger.accept("Downloading missing libraries ..."); + ExecutorService pool = Executors.newFixedThreadPool(8); + CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, logger)).toArray(CompletableFuture[]::new); + handleFutures(logger, array); + pool.shutdownNow(); + } + return installInfo.libraries.keySet().stream().map(it -> Paths.get("libraries").resolve(Util.mavenToPath(it))).collect(Collectors.toList()); + } + + static CompletableFuture downloadMinecraftData(InstallInfo info, ExecutorService pool, Consumer logger) { + return CompletableFuture.supplyAsync(() -> { + logger.accept("Downloading mc version manifest..."); + for (Map.Entry entry : Mirrors.getVersionManifest()) { + try (var stream = FileDownloader.read(entry.getValue())) { + var bytes = stream.readAllBytes(); + var element = new JsonParser().parse(new String(bytes, StandardCharsets.UTF_8)).getAsJsonObject(); + var versions = element.getAsJsonArray("versions"); + for (var version : versions) { + var id = version.getAsJsonObject().get("id").getAsString(); + if (Objects.equals(id, info.installer.minecraft)) { + var url = version.getAsJsonObject().get("url").getAsString(); + try (var versionStream = FileDownloader.read(url)) { + var object = new JsonParser().parse(new String(versionStream.readAllBytes(), StandardCharsets.UTF_8)).getAsJsonObject(); + var downloads = object.getAsJsonObject("downloads"); + var server = downloads.getAsJsonObject("server"); + var serverUrl = server.get("url").getAsString(); + var serverHash = server.get("sha1").getAsString(); + var mapping = downloads.getAsJsonObject("server_mappings"); + var mappingUrl = mapping.get("url").getAsString(); + var mappingHash = mapping.get("sha1").getAsString(); + logger.accept("Minecraft version: %s, server: %s, mappings: %s".formatted(info.installer.minecraft, serverHash, mappingHash)); + return new MinecraftProvider.MinecraftData(entry.getKey(), + Mirrors.mapMojangMirror(serverUrl, entry.getKey()), serverHash, + Mirrors.mapMojangMirror(mappingUrl, entry.getKey()), mappingHash); + } + } + } + logger.accept("Version %s not available in %s".formatted(info.installer.minecraft, entry.getKey())); + } catch (Exception e) { + logger.accept("Failed to download manifest from " + entry.getKey() + "\n " + e); + } + } + return null; + }, pool); + } + + static void handleFutures(Consumer logger, CompletableFuture... futures) { + for (CompletableFuture future : futures) { + try { + future.join(); + } catch (CompletionException e) { + logger.accept(e.getCause().toString()); + Unsafe.throwException(e.getCause()); + } catch (Exception e) { + e.printStackTrace(); + Unsafe.throwException(e); + } + } + } + + static List> checkMavenNoSource(Map map) { + LinkedHashMap> hashMap = new LinkedHashMap<>(map.size()); + for (Map.Entry entry : map.entrySet()) { + hashMap.put(entry.getKey(), new AbstractMap.SimpleImmutableEntry<>(entry.getValue(), null)); + } + return checkMaven(hashMap); + } + + static List> checkMaven(Map> map) { + List> incomplete = new ArrayList<>(); + for (Map.Entry> entry : map.entrySet()) { + String maven = entry.getKey(); + String hash = entry.getValue().getKey(); + String url = entry.getValue().getValue(); + String path = "libraries/" + Util.mavenToPath(maven); + if (new File(path).exists()) { + try { + String fileHash = Util.hash(path); + if (!fileHash.equals(hash)) { + incomplete.add(new MavenDownloader(Mirrors.getMavenRepo(), maven, path, hash, url)); + } + } catch (Exception e) { + incomplete.add(new MavenDownloader(Mirrors.getMavenRepo(), maven, path, hash, url)); + } + } else { + incomplete.add(new MavenDownloader(Mirrors.getMavenRepo(), maven, path, hash, url)); + } + } + return incomplete; + } + + record MinecraftData(String mirror, String serverUrl, String serverHash, String mappingUrl, + String mappingHash) { + } +} diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/Mirrors.java b/installer/src/main/java/io/izzel/arclight/installer/Mirrors.java similarity index 97% rename from forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/Mirrors.java rename to installer/src/main/java/io/izzel/arclight/installer/Mirrors.java index d33198828..c4e5ff6c0 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/Mirrors.java +++ b/installer/src/main/java/io/izzel/arclight/installer/Mirrors.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.forgeinstaller; +package io.izzel.arclight.installer; import java.util.Arrays; import java.util.List; diff --git a/installer/src/main/java/io/izzel/arclight/installer/NeoforgeInstaller.java b/installer/src/main/java/io/izzel/arclight/installer/NeoforgeInstaller.java new file mode 100644 index 000000000..99aef84ec --- /dev/null +++ b/installer/src/main/java/io/izzel/arclight/installer/NeoforgeInstaller.java @@ -0,0 +1,342 @@ +package io.izzel.arclight.installer; + +import com.google.gson.Gson; +import io.izzel.arclight.api.Unsafe; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.module.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.AccessControlContext; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class NeoforgeInstaller { + + private static final MethodHandles.Lookup IMPL_LOOKUP = Unsafe.lookup(); + + @SuppressWarnings("unused") + public static Map.Entry> applicationInstall() throws Throwable { + InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json"); + InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); + List> suppliers = MinecraftProvider.checkMavenNoSource(installInfo.libraries); + var sysType = File.pathSeparatorChar == ';' ? "win" : "unix"; + Path path = Paths.get("libraries", "net", "neoforged", "neoforge", installInfo.installer.neoforge, sysType + "_args.txt"); + var installForge = !Files.exists(path) || forgeClasspathMissing(path); + if (!suppliers.isEmpty() || installForge) { + System.out.println("Downloading missing libraries ..."); + ExecutorService pool = Executors.newWorkStealingPool(8); + CompletableFuture[] array = suppliers.stream().map(MinecraftProvider.reportSupply(pool, System.out::println)).toArray(CompletableFuture[]::new); + if (installForge) { + var futures = installForge(installInfo, pool, System.out::println); + MinecraftProvider.handleFutures(System.out::println, futures); + System.out.println("Forge installation is starting, please wait... "); + try { + ProcessBuilder builder = new ProcessBuilder(); + File file = new File(System.getProperty("java.home"), "bin/java"); + builder.command(file.getCanonicalPath(), "-Djava.net.useSystemProxies=true", "-jar", futures[0].join().toString(), "--installServer", ".", "--debug"); + builder.inheritIO(); + Process process = builder.start(); + if (process.waitFor() > 0) { + throw new Exception("Forge installation failed"); + } + } catch (IOException e) { + try (URLClassLoader loader = new URLClassLoader( + new URL[]{futures[0].join().toUri().toURL()}, + ForgeInstaller.class.getClassLoader().getParent())) { + Method method = loader.loadClass("net.minecraftforge.installer.SimpleInstaller").getMethod("main", String[].class); + method.invoke(null, (Object) new String[]{"--installServer", ".", "--debug"}); + } + } + } + MinecraftProvider.handleFutures(System.out::println, array); + pool.shutdownNow(); + } + return classpath(path, installInfo); + } + + @SuppressWarnings("unchecked") + private static CompletableFuture[] installForge(InstallInfo info, ExecutorService pool, Consumer logger) { + var minecraftData = MinecraftProvider.downloadMinecraftData(info, pool, logger); + String coord = String.format("net.neoforged:neoforge:%s:installer", info.installer.neoforge); + String dist = String.format("neoforge-%s-installer.jar", info.installer.neoforge); + var installerFuture = ForgeLikeProvider.downloadInstaller(coord, dist, info.installer.neoforgeHash, minecraftData, info, pool, logger); + var serverFuture = minecraftData.thenCompose(data -> MinecraftProvider.reportSupply(pool, logger).apply( + new FileDownloader(String.format(data.serverUrl(), info.installer.minecraft), + String.format("libraries/net/minecraft/server/%1$s/server-%1$s.jar", info.installer.minecraft), data.serverHash()) + )); + return new CompletableFuture[]{installerFuture, serverFuture}; + } + + private static boolean forgeClasspathMissing(Path path) throws Exception { + for (String arg : Files.lines(path).toList()) { + if (arg.startsWith("-p ")) { + var modules = arg.substring(2).trim(); + if (!Arrays.stream(modules.split(File.pathSeparator)).map(Paths::get).allMatch(Files::exists)) { + return true; + } + } else if (arg.startsWith("-DlegacyClassPath")) { + var classpath = arg.substring("-DlegacyClassPath=".length()).trim(); + if (!Arrays.stream(classpath.split(File.pathSeparator)).map(Paths::get).allMatch(Files::exists)) { + return true; + } + } + } + return false; + } + + private static Map.Entry> classpath(Path path, InstallInfo installInfo) throws Throwable { + boolean jvmArgs = true; + String mainClass = null; + List userArgs = new ArrayList<>(); + List opens = new ArrayList<>(); + List exports = new ArrayList<>(); + exports.add("cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED"); + List ignores = new ArrayList<>(); + List merges = new ArrayList<>(); + var self = new File(ForgeInstaller.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toPath(); + for (String arg : Files.lines(path).toList()) { + if (jvmArgs && arg.startsWith("-")) { + if (arg.startsWith("-p ")) { + addModules(arg.substring(2).trim()); + } else if (arg.startsWith("--add-opens ")) { + opens.add(arg.substring("--add-opens ".length()).trim()); + } else if (arg.startsWith("--add-exports ")) { + exports.add(arg.substring("--add-exports ".length()).trim()); + } else if (arg.startsWith("-D")) { + var split = arg.substring(2).split("=", 2); + if (split[0].equals("legacyClassPath")) { + split[1] = + Stream.concat( + Stream.concat(Stream.concat(Stream.of(self.toString()), Arrays.stream(split[1].split(File.pathSeparator))), installInfo.libraries.keySet().stream() + .map(it -> Paths.get("libraries", Util.mavenToPath(it))) + .peek(it -> { + var name = it.getFileName().toString(); + if (name.contains("maven-model")) { + merges.add(name); + } + }) + .map(Path::toString)), + Stream.empty() + //Stream.of(self) + ).sorted((a, b) -> { + // damn stupid jpms + if (a.contains("maven-repository-metadata")) { + return -1; + } else if (b.contains("maven-repository-metadata")) { + return 1; + } else { + return 0; + } + }).collect(Collectors.joining(File.pathSeparator)); + } else if (split[0].equals("ignoreList")) { + ignores.addAll(Arrays.asList(split[1].split(","))); + } + System.setProperty(split[0], split[1]); + } + } else { + if (jvmArgs) { + jvmArgs = false; + mainClass = arg; + } else { + userArgs.addAll(Arrays.asList(arg.split(" "))); + } + } + } + var merge = String.join(",", merges); + var mergeModules = System.getProperty("mergeModules"); + if (mergeModules != null) { + System.setProperty("mergeModules", mergeModules + ";" + merge); + } else { + System.setProperty("mergeModules", merge); + } + addOpens(opens); + addExports(exports); + /* + JarFile jarFile = new JarFile(path.toFile()); + Manifest manifest = jarFile.getManifest(); + String[] split = manifest.getMainAttributes().getValue("Class-Path").split(" "); + for (String s : split) { + addToPath(Paths.get(s)); + } + for (String library : installInfo.libraries.keySet()) { + addToPath(Paths.get("libraries", Util.mavenToPath(library))); + } + addToPath(path); + for (String library : installInfo.libraries.keySet()) { + addToPath(Paths.get("libraries", Util.mavenToPath(library)), false); + }*/ + return Map.entry(Objects.requireNonNull(mainClass, "No main class found"), userArgs); + } + + public static void addExports(List exports) throws Throwable { + MethodHandle implAddExportsMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddExports", MethodType.methodType(void.class, String.class, Module.class)); + MethodHandle implAddExportsToAllUnnamedMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddExportsToAllUnnamed", MethodType.methodType(void.class, String.class)); + + addExtra(exports, implAddExportsMH, implAddExportsToAllUnnamedMH); + } + + public static void addOpens(List opens) throws Throwable { + MethodHandle implAddOpensMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddOpens", MethodType.methodType(void.class, String.class, Module.class)); + MethodHandle implAddOpensToAllUnnamedMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddOpensToAllUnnamed", MethodType.methodType(void.class, String.class)); + + addExtra(opens, implAddOpensMH, implAddOpensToAllUnnamedMH); + } + + private static ParserData parseModuleExtra(String extra) { + String[] all = extra.split("=", 2); + if (all.length < 2) { + return null; + } + + String[] source = all[0].split("/", 2); + if (source.length < 2) { + return null; + } + return new ParserData(source[0], source[1], all[1]); + } + + private record ParserData(String module, String packages, String target) { + } + + private static void addExtra(List extras, MethodHandle implAddExtraMH, MethodHandle implAddExtraToAllUnnamedMH) { + extras.forEach(extra -> { + ParserData data = parseModuleExtra(extra); + if (data != null) { + ModuleLayer.boot().findModule(data.module).ifPresent(m -> { + try { + if ("ALL-UNNAMED".equals(data.target)) { + implAddExtraToAllUnnamedMH.invokeWithArguments(m, data.packages); + } else { + ModuleLayer.boot().findModule(data.target).ifPresent(tm -> { + try { + implAddExtraMH.invokeWithArguments(m, data.packages, tm); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + }); + } + + @SuppressWarnings("unchecked") + private static void addModules(String modulePath) throws Throwable { + + // Find all extra modules + ModuleFinder finder = ModuleFinder.of(Arrays.stream(modulePath.split(File.pathSeparator)).map(Paths::get).peek(NeoforgeInstaller::addToPath).toArray(Path[]::new)); + MethodHandle loadModuleMH = IMPL_LOOKUP.findVirtual(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "loadModule", MethodType.methodType(void.class, ModuleReference.class)); + + // Resolve modules to a new config + Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, finder.findAll().stream().peek(mref -> { + try { + // Load all extra modules in system class loader (unnamed modules for now) + loadModuleMH.invokeWithArguments(ClassLoader.getSystemClassLoader(), mref); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }).map(ModuleReference::descriptor).map(ModuleDescriptor::name).collect(Collectors.toList())); + + // Copy the new config graph to boot module layer config + MethodHandle graphGetter = IMPL_LOOKUP.findGetter(Configuration.class, "graph", Map.class); + HashMap> graphMap = new HashMap<>((Map>) graphGetter.invokeWithArguments(config)); + MethodHandle cfSetter = IMPL_LOOKUP.findSetter(ResolvedModule.class, "cf", Configuration.class); + // Reset all extra resolved modules config to boot module layer config + graphMap.forEach((k, v) -> { + try { + cfSetter.invokeWithArguments(k, ModuleLayer.boot().configuration()); + v.forEach(m -> { + try { + cfSetter.invokeWithArguments(m, ModuleLayer.boot().configuration()); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }); + graphMap.putAll((Map>) graphGetter.invokeWithArguments(ModuleLayer.boot().configuration())); + IMPL_LOOKUP.findSetter(Configuration.class, "graph", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(graphMap)); + + // Reset boot module layer resolved modules as new config resolved modules to prepare define modules + Set oldBootModules = ModuleLayer.boot().configuration().modules(); + MethodHandle modulesSetter = IMPL_LOOKUP.findSetter(Configuration.class, "modules", Set.class); + HashSet modulesSet = new HashSet<>(config.modules()); + modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet)); + + // Prepare to add all of the new config "nameToModule" to boot module layer config + MethodHandle nameToModuleGetter = IMPL_LOOKUP.findGetter(Configuration.class, "nameToModule", Map.class); + HashMap nameToModuleMap = new HashMap<>((Map) nameToModuleGetter.invokeWithArguments(ModuleLayer.boot().configuration())); + nameToModuleMap.putAll((Map) nameToModuleGetter.invokeWithArguments(config)); + IMPL_LOOKUP.findSetter(Configuration.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(nameToModuleMap)); + + // Define all extra modules and add all of the new config "nameToModule" to boot module layer config + ((Map) IMPL_LOOKUP.findGetter(ModuleLayer.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot())).putAll((Map) IMPL_LOOKUP.findStatic(Module.class, "defineModules", MethodType.methodType(Map.class, Configuration.class, Function.class, ModuleLayer.class)).invokeWithArguments(ModuleLayer.boot().configuration(), (Function) name -> ClassLoader.getSystemClassLoader(), ModuleLayer.boot())); + + // Add all of resolved modules + modulesSet.addAll(oldBootModules); + modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet)); + + // Reset cache of boot module layer + IMPL_LOOKUP.findSetter(ModuleLayer.class, "modules", Set.class).invokeWithArguments(ModuleLayer.boot(), null); + IMPL_LOOKUP.findSetter(ModuleLayer.class, "servicesCatalog", Class.forName("jdk.internal.module.ServicesCatalog")).invokeWithArguments(ModuleLayer.boot(), null); + + // Add reads from extra modules to jdk modules + MethodHandle implAddReadsMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddReads", MethodType.methodType(void.class, Module.class)); + config.modules().forEach(rm -> ModuleLayer.boot().findModule(rm.name()).ifPresent(m -> oldBootModules.forEach(brm -> ModuleLayer.boot().findModule(brm.name()).ifPresent(bm -> { + try { + implAddReadsMH.invokeWithArguments(m, bm); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + })))); + } + + @SuppressWarnings("removal") + public static void addToPath(Path path) { + try { + ClassLoader loader = ClassLoader.getPlatformClassLoader(); + Field ucpField; + try { + ucpField = loader.getClass().getDeclaredField("ucp"); + } catch (NoSuchFieldException e) { + ucpField = loader.getClass().getSuperclass().getDeclaredField("ucp"); + } + long offset = Unsafe.objectFieldOffset(ucpField); + Object ucp = Unsafe.getObject(loader, offset); + if (ucp == null) { + var cl = Class.forName("jdk.internal.loader.URLClassPath"); + var handle = Unsafe.lookup().findConstructor(cl, MethodType.methodType(void.class, URL[].class, AccessControlContext.class)); + ucp = handle.invoke(new URL[]{}, (AccessControlContext) null); + Unsafe.putObjectVolatile(loader, offset, ucp); + } + Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class); + Unsafe.lookup().unreflect(method).invoke(ucp, path.toUri().toURL()); + } catch (Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/Util.java b/installer/src/main/java/io/izzel/arclight/installer/Util.java similarity index 97% rename from forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/Util.java rename to installer/src/main/java/io/izzel/arclight/installer/Util.java index c577fe26b..fcb86a8fa 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/Util.java +++ b/installer/src/main/java/io/izzel/arclight/installer/Util.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.forgeinstaller; +package io.izzel.arclight.installer; import java.io.File; import java.math.BigInteger; diff --git a/settings.gradle b/settings.gradle index d636d36aa..1aaf407e6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,9 +9,12 @@ pluginManagement { rootProject.name = 'arclight' -include 'forge-installer' +include 'installer' include 'i18n-config' include 'arclight-common' + include 'arclight-forge' -include 'arclight-forge-bootstrap' +include 'arclight-neoforge' + +include 'bootstrap'