/** * @author gabizou - February 7th, 2016 * <p>This will short circuit all other patches such that we control the entities being loaded * by chunkloading and can throw our bulk entity event. This will bypass Forge's hook for * individual entity events, but the SpongeModEventManager will still successfully throw the * appropriate event and cancel the entities otherwise contained. * @param entities The entities being loaded * @param callbackInfo The callback info */ @Final @Inject(method = "loadEntities", at = @At("HEAD"), cancellable = true) private void spongeLoadEntities( Collection<net.minecraft.entity.Entity> entities, CallbackInfo callbackInfo) { if (entities.isEmpty()) { // just return, no entities to load! callbackInfo.cancel(); return; } List<Entity> entityList = new ArrayList<>(); ImmutableList.Builder<EntitySnapshot> snapshotBuilder = ImmutableList.builder(); for (net.minecraft.entity.Entity entity : entities) { entityList.add((Entity) entity); snapshotBuilder.add(((Entity) entity).createSnapshot()); } SpawnCause cause = SpawnCause.builder().type(InternalSpawnTypes.CHUNK_LOAD).build(); List<NamedCause> causes = new ArrayList<>(); causes.add(NamedCause.source(cause)); causes.add(NamedCause.of("World", this)); SpawnEntityEvent.ChunkLoad chunkLoad = SpongeEventFactory.createSpawnEntityEventChunkLoad( Cause.of(causes), entityList, snapshotBuilder.build(), this); SpongeImpl.postEvent(chunkLoad); if (!chunkLoad.isCancelled()) { for (Entity successful : chunkLoad.getEntities()) { this.loadedEntityList.add((net.minecraft.entity.Entity) successful); this.onEntityAdded((net.minecraft.entity.Entity) successful); } } callbackInfo.cancel(); }
@Inject(method = "trySpawnGolem", at = @At("HEAD"), cancellable = true) private void checkChunkBeforeTrySpawnGolem(World world, BlockPos pos, CallbackInfo callbackInfo) { final Chunk chunk = world.getChunkFromBlockCoords(pos); if (chunk == null || chunk.isEmpty() || !chunk.isTerrainPopulated()) { callbackInfo.cancel(); } }
@Inject(method = "playSoundAtEntity", at = @At("HEAD"), cancellable = true) private void spongePlaySoundAtEntity( net.minecraft.entity.Entity entity, String name, float volume, float pitch, CallbackInfo callbackInfo) { if (((IMixinEntity) entity).isVanished()) { callbackInfo.cancel(); } }
@Inject( method = "onCollideWithPlayer", at = @At( value = "INVOKE", target = "Lnet/minecraft/entity/item/EntityItem;getEntityItem()Lnet/minecraft/item/ItemStack;"), cancellable = true) public void onPlayerItemPickup(EntityPlayer entityIn, CallbackInfo ci) { if (!SpongeCommonEventFactory.callPlayerChangeInventoryPickupEvent( entityIn, this.getEntityItem(), this.delayBeforeCanPickup)) { ci.cancel(); } }
/** * @Author Zidane * * <p>Invoke before {@code System.arraycopy(packetIn.getLines(), 0, tileentitysign.signText, 0, * 4);} (line 1156 in source) to call SignChangeEvent. * * @param packetIn Injected packet param * @param ci Info to provide mixin on how to handle the callback * @param worldserver Injected world param * @param blockpos Injected blockpos param * @param tileentity Injected tilentity param * @param tileentitysign Injected tileentitysign param */ @Inject( method = "processUpdateSign", at = @At( value = "INVOKE", target = "Lnet/minecraft/network/play/client/C12PacketUpdateSign;getLines()[Lnet/minecraft/util/IChatComponent;"), cancellable = true, locals = LocalCapture.CAPTURE_FAILSOFT) public void callSignChangeEvent( C12PacketUpdateSign packetIn, CallbackInfo ci, WorldServer worldserver, BlockPos blockpos, TileEntity tileentity, TileEntitySign tileentitysign) { ci.cancel(); final Optional<SignData> existingSignData = ((Sign) tileentitysign).getData(); if (!existingSignData.isPresent()) { // TODO Unsure if this is the best to do here... throw new RuntimeException("Critical error! Sign data not present on sign!"); } final SignData changedSignData = existingSignData.get().copy(); for (int i = 0; i < packetIn.getLines().length; i++) { changedSignData.setLine(i, SpongeTexts.toText(packetIn.getLines()[i])); } // I pass changedSignData in here twice to emulate the fact that even-though the current sign // data doesn't have the lines from the packet // applied, this is what it "is" right now. If the data shown in the world is desired, it can be // fetched from Sign.getData final SignChangeEvent event = SpongeEventFactory.createSignChange( Sponge.getGame(), new Cause(null, this.playerEntity, null), (Sign) tileentitysign, changedSignData, changedSignData); if (!Sponge.getGame().getEventManager().post(event)) { ((Sign) tileentitysign).offer(event.getNewData()); } else { // If cancelled, I set the data back that was fetched from the sign. This means that if its a // new sign, the sign will be empty else // it will be the text of the sign that was showing in the world ((Sign) tileentitysign).offer(existingSignData.get()); } tileentitysign.markDirty(); worldserver.markBlockForUpdate(blockpos); }
@Inject( method = "randomTick", at = @At(value = "HEAD"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true) public void callRandomTickEvent( World world, BlockPos pos, IBlockState state, Random rand, CallbackInfo ci) { final BlockRandomTickEvent event = SpongeEventFactory.createBlockRandomTick( Sponge.getGame(), null, new Location<org.spongepowered.api.world.World>( (org.spongepowered.api.world.World) world, VecHelper.toVector(pos))); // TODO Fix null Cause Sponge.getGame().getEventManager().post(event); if (event.isCancelled()) { ci.cancel(); } }
@Inject( method = "randomTick", at = @At(value = "HEAD"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true) public void callRandomTickEvent( net.minecraft.world.World world, BlockPos pos, IBlockState state, Random rand, CallbackInfo ci) { BlockSnapshot blockSnapshot = ((World) world).createSnapshot(VecHelper.toVector(pos)); final TickBlockEvent event = SpongeEventFactory.createTickBlockEvent(Cause.of(NamedCause.source(world)), blockSnapshot); SpongeImpl.postEvent(event); if (event.isCancelled()) { ci.cancel(); } }
@Inject( method = "processPlayer", at = @At( value = "FIELD", target = "net.minecraft.network.NetHandlerPlayServer.hasMoved:Z", ordinal = 2), cancellable = true) public void proccesPlayerMoved(C03PacketPlayer packetIn, CallbackInfo ci) { if (packetIn.isMoving() || packetIn.getRotating() && !this.playerEntity.isDead) { Player player = (Player) this.playerEntity; Vector3d fromrot = player.getRotation(); // If Sponge used the player's current location, the delta might never be triggered which // could be exploited Location from = player.getLocation(); if (this.lastMoveLocation != null) { from = this.lastMoveLocation; } Vector3d torot = new Vector3d(packetIn.getPitch(), packetIn.getYaw(), 0); Location to = new Location( player.getWorld(), packetIn.getPositionX(), packetIn.getPositionY(), packetIn.getPositionZ()); // Minecraft sends a 0, 0, 0 position when rotation only update occurs, this needs to be // recognized and corrected boolean rotationOnly = !packetIn.isMoving() && packetIn.getRotating(); if (rotationOnly) { // Correct the to location so it's not misrepresented to plugins, only when player rotates // without moving // In this case it's only a rotation update, which isn't related to the to location from = player.getLocation(); to = from; } // Minecraft does the same with rotation when it's only a positional update boolean positionOnly = packetIn.isMoving() && !packetIn.getRotating(); if (positionOnly) { // Correct the new rotation to match the old rotation torot = fromrot; } double deltaSquared = to.getPosition().distanceSquared(from.getPosition()); double deltaAngleSquared = fromrot.distanceSquared(torot); // These magic numbers are sad but help prevent excessive lag from this event. // eventually it would be nice to not have them if (deltaSquared > ((1f / 16) * (1f / 16)) || deltaAngleSquared > (.15f * .15f)) { PlayerMoveEvent event = SpongeEventFactory.createPlayerMove(Sponge.getGame(), player, from, to, torot); Sponge.getGame().getEventManager().post(event); if (event.isCancelled()) { player.setLocationAndRotation(from, fromrot); this.lastMoveLocation = from; ci.cancel(); } else if (!event.getNewLocation().equals(to)) { player.setLocationAndRotation(event.getNewLocation(), event.getRotation()); this.lastMoveLocation = event.getNewLocation(); ci.cancel(); } else if (!from.equals(player.getLocation()) && this.justTeleported) { this.lastMoveLocation = player.getLocation(); // Prevent teleports during the move event from causing odd behaviors this.justTeleported = false; ci.cancel(); } else { this.lastMoveLocation = event.getNewLocation(); } } } }
/** * @author zml * <p>Purpose: replace the logic used for command blocks to make functional * @param ci callback * @param packetIn method param */ @Inject( method = "processVanilla250Packet", at = @At( value = "INVOKE", shift = At.Shift.AFTER, target = "net/minecraft/network/PacketThreadUtil.checkThreadAndEnqueue(Lnet/minecraft/network/Packet;" + "Lnet/minecraft/network/INetHandler;Lnet/minecraft/util/IThreadListener;)V"), cancellable = true) public void processCommandBlock(C17PacketCustomPayload packetIn, CallbackInfo ci) { if ("MC|AdvCdm".equals(packetIn.getChannelName())) { PacketBuffer packetbuffer; try { if (!this.serverController.isCommandBlockEnabled()) { this.playerEntity.addChatMessage( new ChatComponentTranslation("advMode.notEnabled", new Object[0])); // Sponge: Check permissions for command block usage TODO: Maybe throw an event instead? // } else if (this.playerEntity.canCommandSenderUseCommand(2, "") && // this.playerEntity.capabilities.isCreativeMode) { } else { packetbuffer = packetIn.getBufferData(); try { byte b0 = packetbuffer.readByte(); CommandBlockLogic commandblocklogic = null; String permissionCheck = null; // Sponge if (b0 == 0) { TileEntity tileentity = this.playerEntity.worldObj.getTileEntity( new BlockPos( packetbuffer.readInt(), packetbuffer.readInt(), packetbuffer.readInt())); if (tileentity instanceof TileEntityCommandBlock) { commandblocklogic = ((TileEntityCommandBlock) tileentity).getCommandBlockLogic(); permissionCheck = "minecraft.commandblock.edit.block." + commandblocklogic.getCommandSenderName(); // Sponge } } else if (b0 == 1) { Entity entity = this.playerEntity.worldObj.getEntityByID(packetbuffer.readInt()); if (entity instanceof EntityMinecartCommandBlock) { commandblocklogic = ((EntityMinecartCommandBlock) entity).getCommandBlockLogic(); permissionCheck = "minecraft.commandblock.edit.minecart." + commandblocklogic.getCommandSenderName(); // Sponge } // Sponge begin } else { throw new IllegalArgumentException("Unknown command block type!"); } Player spongePlayer = ((Player) this.playerEntity); if (permissionCheck == null || !spongePlayer.hasPermission(permissionCheck)) { spongePlayer.sendMessage( t("You do not have permission to edit this command block!") .builder() .color(TextColors.RED) .build()); return; // Sponge end } String s1 = packetbuffer.readStringFromBuffer(packetbuffer.readableBytes()); boolean flag = packetbuffer.readBoolean(); if (commandblocklogic != null) { commandblocklogic.setCommand(s1); commandblocklogic.setTrackOutput(flag); if (!flag) { commandblocklogic.setLastOutput((IChatComponent) null); } commandblocklogic.func_145756_e(); this.playerEntity.addChatMessage( new ChatComponentTranslation("advMode.setCommand.success", new Object[] {s1})); } } catch (Exception exception1) { logger.error("Couldn\'t set command block", exception1); } finally { packetbuffer.release(); } /*} else { // Sponge: Give more accurate no permission message this.playerEntity.addChatMessage(new ChatComponentTranslation("advMode.notAllowed", new Object[0]));*/ } } finally { ci.cancel(); } } }