Yet further finely-tuned

- Fixed blocks not updating their neighbors when being assembled into a contraption
- Fixed Stockpile Switch not updating its redstone output when its signal is inverted
- Reduced lag caused by many active nearby soul particles
- The Wither's shield is now immune to potato projectiles
- Fixed potato projectile potion effect weirdness
- Golden Apple potato projectiles can now cure Zombie Villagers
- Potato Recovery will no longer drop Golden Apples that successfully hit an entity
This commit is contained in:
reidbhuntley 2021-07-11 21:25:00 -04:00
parent 4306d076db
commit b70608b030
9 changed files with 99 additions and 40 deletions

View file

@ -84,6 +84,7 @@ import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.INBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.network.DebugPacketSender;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.state.properties.ChestType;
import net.minecraft.state.properties.PistonType;
@ -102,6 +103,7 @@ import net.minecraft.village.PointOfInterestType;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.gen.feature.template.Template.BlockInfo;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants.BlockFlags;
@ -949,8 +951,7 @@ public abstract class Contraption {
int flags = BlockFlags.IS_MOVING | BlockFlags.NO_NEIGHBOR_DROPS | BlockFlags.UPDATE_NEIGHBORS
| BlockFlags.BLOCK_UPDATE | BlockFlags.RERENDER_MAIN_THREAD;
if (blockIn instanceof IWaterLoggable && oldState.contains(BlockStateProperties.WATERLOGGED)
&& oldState.get(BlockStateProperties.WATERLOGGED)
.booleanValue()) {
&& oldState.get(BlockStateProperties.WATERLOGGED)) {
world.setBlockState(add, Blocks.WATER.getDefaultState(), flags);
continue;
}
@ -962,8 +963,22 @@ public abstract class Contraption {
.add(offset);
// if (!shouldUpdateAfterMovement(block))
// continue;
int flags = BlockFlags.IS_MOVING | BlockFlags.DEFAULT;
world.notifyBlockUpdate(add, block.state, Blocks.AIR.getDefaultState(), flags);
// when the blockstate is set to air, the block's POI data is removed, but markAndNotifyBlock tries to
// remove it again, so to prevent an error from being logged by double-removal we add the POI data back now
// (code copied from ServerWorld.onBlockStateChange)
ServerWorld serverWorld = (ServerWorld) world;
PointOfInterestType.forState(block.state).ifPresent(poiType -> {
world.getServer().execute(() -> {
serverWorld.getPointOfInterestManager().func_219135_a(add, poiType);
DebugPacketSender.func_218799_a(serverWorld, add);
});
});
world.markAndNotifyBlock(add, world.getChunkAt(add), block.state, Blocks.AIR.getDefaultState(), flags, 512);
block.state.updateDiagonalNeighbors(world, add, flags & -2);
}
}

View file

@ -19,7 +19,7 @@ public class HauntedBellMovementBehaviour extends BellMovementBehaviour {
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
if (!context.world.isRemote && getRecharge(context) == 0) {
HauntedBellPulser.sendPulse(context.world, pos, DISTANCE, true);
HauntedBellPulser.sendPulse(context.world, pos, DISTANCE, false);
setRecharge(context, HauntedBellTileEntity.RECHARGE_TICKS);
playSound(context);
}

View file

@ -46,7 +46,7 @@ public class HauntedBellTileEntity extends AbstractBellTileEntity {
return false;
if (!world.isRemote)
HauntedBellPulser.sendPulse(world, pos, DISTANCE, true);
HauntedBellPulser.sendPulse(world, pos, DISTANCE, false);
startEffect();

View file

@ -31,7 +31,7 @@ public class SoulBaseParticle extends CustomRotationParticle {
selectSpriteLoopingWithAge(animatedSprite);
BlockPos pos = new BlockPos(posX, posY, posZ);
if (age++ >= maxAge || !SoulPulseEffect.canSpawnSoulAt(world, pos, false))
if (age++ >= maxAge || !SoulPulseEffect.isDark(world, pos))
setExpired();
}

View file

@ -73,7 +73,7 @@ public class SoulParticle extends CustomRotationParticle {
BlockPos pos = new BlockPos(posX, posY, posZ);
if (animationStage == null)
setExpired();
if (!SoulPulseEffect.canSpawnSoulAt(world, pos, false)) {
if (!SoulPulseEffect.isDark(world, pos)) {
isVisible = true;
if (!isPerimeter)
setExpired();

View file

@ -74,6 +74,10 @@ public class SoulPulseEffect {
.collect(Collectors.toList());
}
public static boolean isDark(World world, BlockPos at) {
return world.getLightLevel(LightType.BLOCK, at) < 8;
}
public static boolean canSpawnSoulAt(World world, BlockPos at, boolean ignoreLight) {
EntityType<?> dummy = EntityType.ZOMBIE;
double dummyWidth = 0.2, dummyHeight = 0.75;
@ -82,7 +86,7 @@ public class SoulPulseEffect {
return world != null
&& WorldEntitySpawner
.canCreatureTypeSpawnAtLocation(EntitySpawnPlacementRegistry.PlacementType.ON_GROUND, world, at, dummy)
&& (ignoreLight || world.getLightLevel(LightType.BLOCK, at) < 8)
&& (ignoreLight || isDark(world, at))
&& world
.getBlockCollisions(null,
new AxisAlignedBB(at.getX() + 0.5 - w2, at.getY(), at.getZ() + 0.5 - w2, at.getX() + 0.5 + w2,

View file

@ -4,7 +4,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import com.mojang.datafixers.util.Pair;
@ -17,7 +16,9 @@ import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.item.FallingBlockEntity;
import net.minecraft.entity.monster.ZombieVillagerEntity;
import net.minecraft.entity.passive.FoxEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Food;
import net.minecraft.item.Foods;
import net.minecraft.item.Item;
@ -27,6 +28,7 @@ import net.minecraft.potion.Effect;
import net.minecraft.potion.EffectInstance;
import net.minecraft.potion.Effects;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.IItemProvider;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
@ -39,7 +41,9 @@ import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.IPlantable;
import net.minecraftforge.common.util.FakePlayerFactory;
import net.minecraftforge.registries.IRegistryDelegate;
public class PotatoCannonProjectileTypes {
@ -64,7 +68,7 @@ public class PotatoCannonProjectileTypes {
.velocity(1.25f)
.knockback(0.5f)
.renderTumbling()
.onEntityHit(ray -> ray.getEntity().setFire(3))
.onEntityHit(setFire(3))
.registerAndAssign(Items.BAKED_POTATO),
CARROT = create("carrot").damage(4)
@ -107,7 +111,7 @@ public class PotatoCannonProjectileTypes {
.knockback(0.05f)
.velocity(1.25f)
.renderTumbling()
.onEntityHit(potion(Effects.POISON, 1,160))
.onEntityHit(potion(Effects.POISON, 1,160, true))
.registerAndAssign(Items.POISONOUS_POTATO),
CHORUS_FRUIT = create("chorus_fruit").damage(3)
@ -115,7 +119,7 @@ public class PotatoCannonProjectileTypes {
.velocity(1.20f)
.knockback(0.05f)
.renderTumbling()
.onEntityHitRecoveryCancelable(chorusTeleport(20))
.onEntityHit(chorusTeleport(20))
.registerAndAssign(Items.CHORUS_FRUIT),
APPLE = create("apple").damage(5)
@ -132,7 +136,7 @@ public class PotatoCannonProjectileTypes {
.knockback(0.1f)
.renderTumbling()
.soundPitch(1.1f)
.onEntityHit(potion(Effects.SLOWNESS, 2,160))
.onEntityHit(potion(Effects.SLOWNESS, 2,160, true))
.registerAndAssign(AllItems.HONEYED_APPLE.get()),
GOLDEN_APPLE = create("golden_apple").damage(1)
@ -141,7 +145,21 @@ public class PotatoCannonProjectileTypes {
.knockback(0.05f)
.renderTumbling()
.soundPitch(1.1f)
.onEntityHitRecoveryCancelable(foodEffects(Foods.GOLDEN_APPLE))
.onEntityHit(ray -> {
Entity entity = ray.getEntity();
World world = entity.world;
if (!(entity instanceof ZombieVillagerEntity)
|| !((ZombieVillagerEntity) entity).isPotionActive(Effects.WEAKNESS))
return foodEffects(Foods.GOLDEN_APPLE, false).test(ray);
if (world.isRemote)
return false;
PlayerEntity dummy = FakePlayerFactory.getMinecraft((ServerWorld) world);
dummy.setHeldItem(Hand.MAIN_HAND, new ItemStack(Items.GOLDEN_APPLE));
((ZombieVillagerEntity) entity).interactMob(dummy, Hand.MAIN_HAND);
return true;
})
.registerAndAssign(Items.GOLDEN_APPLE),
ENCHANTED_GOLDEN_APPLE = create("enchanted_golden_apple").damage(1)
@ -150,7 +168,7 @@ public class PotatoCannonProjectileTypes {
.knockback(0.05f)
.renderTumbling()
.soundPitch(1.1f)
.onEntityHitRecoveryCancelable(foodEffects(Foods.ENCHANTED_GOLDEN_APPLE))
.onEntityHit(foodEffects(Foods.ENCHANTED_GOLDEN_APPLE, false))
.registerAndAssign(Items.ENCHANTED_GOLDEN_APPLE),
BEETROOT = create("beetroot").damage(2)
@ -175,7 +193,7 @@ public class PotatoCannonProjectileTypes {
.velocity(1.45f)
.renderTumbling()
.soundPitch(1.5f)
.onEntityHit(potion(Effects.GLOWING, 1, 100))
.onEntityHit(potion(Effects.GLOWING, 1, 100, true))
.registerAndAssign(Items.GLISTERING_MELON_SLICE),
MELON_BLOCK = create("melon_block").damage(8)
@ -214,13 +232,13 @@ public class PotatoCannonProjectileTypes {
.soundPitch(1.0f)
.registerAndAssign(Items.CAKE),
BLAZE_CAKE = create("blaze_cake").damage(12)
BLAZE_CAKE = create("blaze_cake").damage(15)
.reloadTicks(20)
.knockback(0.3f)
.velocity(1.1f)
.renderTumbling()
.sticky()
.onEntityHit(ray -> ray.getEntity().setFire(12))
.onEntityHit(setFire(12))
.soundPitch(1.0f)
.registerAndAssign(AllItems.BLAZE_CAKE.get())
;
@ -304,29 +322,52 @@ public class PotatoCannonProjectileTypes {
return onBlockHit.test(world, ray);
}
private static Consumer<EntityRayTraceResult> potion(Effect effect, int level, int ticks) {
private static Predicate<EntityRayTraceResult> setFire(int seconds) {
return ray -> {
Entity entity = ray.getEntity();
if (entity instanceof LivingEntity)
((LivingEntity) entity).addPotionEffect(new EffectInstance(effect, ticks, level - 1));
ray.getEntity().setFire(seconds);
return false;
};
}
private static Predicate<EntityRayTraceResult> foodEffects(Food food) {
private static Predicate<EntityRayTraceResult> potion(Effect effect, int level, int ticks, boolean recoverable) {
return ray -> {
Entity entity = ray.getEntity();
if (entity.world.isRemote)
return true;
if (entity instanceof LivingEntity)
applyEffect((LivingEntity) entity, new EffectInstance(effect, ticks, level - 1));
return !recoverable;
};
}
private static Predicate<EntityRayTraceResult> foodEffects(Food food, boolean recoverable) {
return ray -> {
Entity entity = ray.getEntity();
if (entity.world.isRemote)
return true;
if (entity instanceof LivingEntity) {
for (Pair<EffectInstance, Float> effect : food.getEffects()) {
if (Create.RANDOM.nextFloat() < effect.getSecond())
((LivingEntity) entity).addPotionEffect(effect.getFirst());
applyEffect((LivingEntity) entity, new EffectInstance(effect.getFirst()));
}
}
return true;
return !recoverable;
};
}
public static void applyEffect(LivingEntity entity, EffectInstance effect) {
if (effect.getPotion().isInstant())
effect.getPotion().affectEntity(null, null, entity, effect.getDuration(), 1.0);
else
entity.addPotionEffect(effect);
}
private static BiPredicate<IWorld, BlockRayTraceResult> plantCrop(IRegistryDelegate<? extends Block> cropBlock) {
return (world, ray) -> {
if (world.isRemote())
return true;
BlockPos hitPos = ray.getPos();
if (!world.isAreaLoaded(hitPos, 1))
return true;
@ -348,6 +389,9 @@ public class PotatoCannonProjectileTypes {
private static BiPredicate<IWorld, BlockRayTraceResult> placeBlockOnGround(IRegistryDelegate<? extends Block> block) {
return (world, ray) -> {
if (world.isRemote())
return true;
BlockPos hitPos = ray.getPos();
if (!world.isAreaLoaded(hitPos, 1))
return true;
@ -372,7 +416,7 @@ public class PotatoCannonProjectileTypes {
falling.fallTime = 1;
world.addEntity(falling);
}
return true;
};
}
@ -397,12 +441,12 @@ public class PotatoCannonProjectileTypes {
double teleportZ = entityZ + (livingEntity.getRNG().nextDouble() - 0.5D) * teleportDiameter;
/* Usable as soon as lowest supported forge > 36.1.3 */
// EntityTeleportEvent.ChorusFruit event = ForgeEventFactory.onChorusFruitTeleport(livingEntity, teleportX, teleportY, teleportZ);
// if (event.isCanceled())
// return;
// if (livingEntity.attemptTeleport(event.getTargetX(), event.getTargetY(), event.getTargetZ(), true)) {
if (livingEntity.attemptTeleport(teleportX, teleportY, teleportZ, true)) {
if (livingEntity.isPassenger())
livingEntity.stopRiding();
@ -414,7 +458,7 @@ public class PotatoCannonProjectileTypes {
return true;
}
}
return false;
};
}
@ -489,18 +533,10 @@ public class PotatoCannonProjectileTypes {
return this;
}
public Builder onEntityHitRecoveryCancelable(Predicate<EntityRayTraceResult> callback) {
public Builder onEntityHit(Predicate<EntityRayTraceResult> callback) {
result.onEntityHit = callback;
return this;
}
public Builder onEntityHit(Consumer<EntityRayTraceResult> callback) {
result.onEntityHit = ray -> {
callback.accept(ray);
return false;
};
return this;
}
public Builder onBlockHit(BiPredicate<IWorld, BlockRayTraceResult> callback) {
result.onBlockHit = callback;

View file

@ -14,6 +14,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityClassification;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.boss.WitherEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.entity.projectile.DamagingProjectileEntity;
@ -188,6 +189,9 @@ public class PotatoProjectileEntity extends DamagingProjectileEntity implements
pop(hit);
if (target instanceof WitherEntity && ((WitherEntity) target).shouldRenderOverlay())
return;
boolean targetIsEnderman = target.getType() == EntityType.ENDERMAN;
int k = target.getFireTimer();
if (this.isBurning() && !targetIsEnderman)
@ -203,7 +207,7 @@ public class PotatoProjectileEntity extends DamagingProjectileEntity implements
if (targetIsEnderman)
return;
if (!projectileType.onEntityHit(ray))
if (!projectileType.onEntityHit(ray) && onServer)
if (rand.nextDouble() <= recoveryChance)
recoverItem();
@ -271,7 +275,7 @@ public class PotatoProjectileEntity extends DamagingProjectileEntity implements
protected void onBlockHit(BlockRayTraceResult ray) {
Vector3d hit = ray.getHitVec();
pop(hit);
if (!getProjectileType().onBlockHit(world, ray))
if (!getProjectileType().onBlockHit(world, ray) && !world.isRemote)
if (rand.nextDouble() <= recoveryChance)
recoverItem();
super.onBlockHit(ray);

View file

@ -170,6 +170,6 @@ public class StockpileSwitchTileEntity extends SmartTileEntity {
if (inverted == this.inverted)
return;
this.inverted = inverted;
world.updateNeighbors(pos, getBlockState().getBlock());
updatePowerAfterDelay();
}
}