diff --git a/src/main/java/com/minelittlepony/unicopia/UTags.java b/src/main/java/com/minelittlepony/unicopia/UTags.java index 70de0898..cab336b1 100644 --- a/src/main/java/com/minelittlepony/unicopia/UTags.java +++ b/src/main/java/com/minelittlepony/unicopia/UTags.java @@ -18,12 +18,16 @@ public interface UTags { TagKey SHADES = item("shades"); + TagKey POLEARMS = item("polearms"); + TagKey FRAGILE = block("fragile"); TagKey INTERESTING = block("interesting"); TagKey CRYSTAL_HEART_BASE = block("crystal_heart_base"); TagKey CRYSTAL_HEART_ORNAMENT = block("crystal_heart_ornament"); + TagKey POLEARM_MINEABLE = block("mineable/polearm"); + TagKey> TRANSFORMABLE_ENTITIES = entity("transformable"); static TagKey item(String name) { diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index 1a3a6ec2..1e67cff6 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -17,7 +17,9 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitLoader; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.block.UBlocks; +import com.minelittlepony.unicopia.block.UTreeGen; import com.minelittlepony.unicopia.block.data.BlockDestructionManager; +import com.minelittlepony.unicopia.block.data.ZapAppleStageStore; import com.minelittlepony.unicopia.block.state.StateMapLoader; import com.minelittlepony.unicopia.command.Commands; import com.minelittlepony.unicopia.container.SpellbookChapterLoader; @@ -59,6 +61,7 @@ public class Unicopia implements ModInitializer { ServerTickEvents.END_WORLD_TICK.register(w -> { ((BlockDestructionManager.Source)w).getDestructionManager().tick(); + ZapAppleStageStore.get(w).tick(); if (SpellbookChapterLoader.DEBUG) { SpellbookChapterLoader.INSTANCE.sendUpdate(w.getServer()); } @@ -78,6 +81,7 @@ public class Unicopia implements ModInitializer { SpellType.bootstrap(); Abilities.bootstrap(); UScreenHandlers.bootstrap(); + UTreeGen.bootstrap(); } public interface SidedAccess { diff --git a/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java b/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java new file mode 100644 index 00000000..65b4fdba --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/FruitBlock.java @@ -0,0 +1,57 @@ +package com.minelittlepony.unicopia.block; + +import net.minecraft.block.*; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.WorldView; + +public class FruitBlock extends Block { + public static final int DEFAULT_FRUIT_SIZE = 8; + public static final VoxelShape DEFAULT_SHAPE = createFruitShape(DEFAULT_FRUIT_SIZE); + + private final Direction attachmentFace; + private final Block stem; + private final VoxelShape shape; + + public static VoxelShape createFruitShape(int fruitSize) { + int min = (16 - fruitSize) / 2; + int max = 16 - min; + + return VoxelShapes.cuboid(min / 16D, (max - fruitSize) / 16D, min / 16D, max / 16D, 1, max / 16D); + } + + public FruitBlock(Settings settings, Direction attachmentFace, Block stem, VoxelShape shape) { + super(settings.nonOpaque().suffocates(UBlocks::never).blockVision(UBlocks::never)); + this.attachmentFace = attachmentFace; + this.stem = stem; + this.shape = shape; + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return shape; + } + + @Override + public boolean canPlaceAt(BlockState state, WorldView world, BlockPos pos) { + BlockPos attachedPos = pos.offset(attachmentFace.getOpposite()); + BlockState attachedState = world.getBlockState(attachedPos); + return canAttachTo(attachedState) && attachedState.isSideSolidFullSquare(world, attachedPos, attachmentFace); + } + + @Override + public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + if (!state.canPlaceAt(world, pos)) { + world.breakBlock(pos, true); + } + } + + protected boolean canAttachTo(BlockState state) { + return state.isOf(stem); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java index f9efda90..53b3987e 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java +++ b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java @@ -4,11 +4,17 @@ import com.minelittlepony.unicopia.Unicopia; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.FabricMaterialBuilder; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.block.MapColor; +import net.minecraft.block.*; +import net.minecraft.block.sapling.SaplingGenerator; +import net.minecraft.entity.EntityType; import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryEntry; +import net.minecraft.world.BlockView; +import net.minecraft.world.gen.feature.ConfiguredFeature; public interface UBlocks { Block ROCKS = register("rocks", new RockCropBlock(FabricBlockSettings.of( @@ -21,9 +27,30 @@ public interface UBlocks { Block FROSTED_OBSIDIAN = register("frosted_obsidian", new FrostedObsidianBlock(FabricBlockSettings.copy(Blocks.OBSIDIAN).ticksRandomly())); + Block ZAPLING = register("zapling", new SaplingBlock(new SaplingGenerator() { + @Override + protected RegistryEntry> getTreeFeature(Random rng, boolean flowersNearby) { + return UTreeGen.ZAP_APPLE_TREE; + } + }, FabricBlockSettings.copy(Blocks.OAK_SAPLING))); + + Block ZAP_LOG = register("zap_log", new ZapAppleLogBlock(MapColor.GRAY, MapColor.DEEPSLATE_GRAY)); + Block ZAP_LEAVES = register("zap_leaves", new ZapAppleLeavesBlock()); + Block ZAP_BULB = register("zap_bulb", new FruitBlock(FabricBlockSettings.of(Material.GOURD, MapColor.GRAY).strength(500, 1200).sounds(BlockSoundGroup.AZALEA_LEAVES), Direction.DOWN, ZAP_LEAVES, FruitBlock.DEFAULT_SHAPE)); + Block ZAP_APPLE = register("zap_apple", new FruitBlock(FabricBlockSettings.of(Material.GOURD, MapColor.GRAY).sounds(BlockSoundGroup.AZALEA_LEAVES), Direction.DOWN, ZAP_LEAVES, FruitBlock.DEFAULT_SHAPE)); + private static T register(String name, T item) { return Registry.register(Registry.BLOCK, Unicopia.id(name), item); } static void bootstrap() {} + + + static boolean never(BlockState state, BlockView world, BlockPos pos) { + return false; + } + + static Boolean canSpawnOnLeaves(BlockState state, BlockView world, BlockPos pos, EntityType type) { + return type == EntityType.OCELOT || type == EntityType.PARROT; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/block/UTreeGen.java b/src/main/java/com/minelittlepony/unicopia/block/UTreeGen.java new file mode 100644 index 00000000..13b021a9 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/UTreeGen.java @@ -0,0 +1,41 @@ +package com.minelittlepony.unicopia.block; + +import net.fabricmc.fabric.api.biome.v1.*; +import net.minecraft.tag.BiomeTags; +import net.minecraft.tag.BlockTags; +import net.minecraft.util.math.intprovider.ConstantIntProvider; +import net.minecraft.util.math.intprovider.UniformIntProvider; +import net.minecraft.util.registry.*; +import net.minecraft.world.gen.GenerationStep; +import net.minecraft.world.gen.feature.*; +import net.minecraft.world.gen.feature.size.TwoLayersFeatureSize; +import net.minecraft.world.gen.foliage.JungleFoliagePlacer; +import net.minecraft.world.gen.stateprovider.BlockStateProvider; +import net.minecraft.world.gen.trunk.UpwardsBranchingTrunkPlacer; + +public interface UTreeGen { + RegistryEntry> ZAP_APPLE_TREE = ConfiguredFeatures.register("unicopia:zap_apple_tree", Feature.TREE, new TreeFeatureConfig.Builder( + BlockStateProvider.of(UBlocks.ZAP_LOG), + new UpwardsBranchingTrunkPlacer(7, 2, 3, + UniformIntProvider.create(3, 6), 0.3f, + UniformIntProvider.create(1, 3), + Registry.BLOCK.getOrCreateEntryList(BlockTags.MANGROVE_LOGS_CAN_GROW_THROUGH) + ), + BlockStateProvider.of(UBlocks.ZAP_LEAVES), + new JungleFoliagePlacer( + ConstantIntProvider.create(3), + ConstantIntProvider.create(2), + 3 + ), + new TwoLayersFeatureSize(6, 0, 16) + ).forceDirt() + .build() + ); + RegistryEntry TREES_ZAP = PlacedFeatures.register("unicopia:trees_zap", ZAP_APPLE_TREE, + VegetationPlacedFeatures.modifiersWithWouldSurvive(PlacedFeatures.createCountExtraModifier(0, 0.01F, 1), UBlocks.ZAPLING) + ); + + static void bootstrap() { + BiomeModifications.addFeature(BiomeSelectors.foundInOverworld().and(BiomeSelectors.tag(BiomeTags.IS_FOREST)), GenerationStep.Feature.VEGETAL_DECORATION, TREES_ZAP.getKey().get()); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java new file mode 100644 index 00000000..df76bcf0 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLeavesBlock.java @@ -0,0 +1,188 @@ +package com.minelittlepony.unicopia.block; + +import com.minelittlepony.unicopia.block.data.ZapAppleStageStore; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.particle.ParticleUtils; +import com.minelittlepony.unicopia.particle.UParticles; + +import net.minecraft.block.*; +import net.minecraft.entity.*; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.fluid.Fluid; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.util.math.*; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import net.minecraft.world.event.GameEvent; + +public class ZapAppleLeavesBlock extends LeavesBlock { + public static final EnumProperty STAGE = EnumProperty.of("stage", ZapAppleStageStore.Stage.class); + + ZapAppleLeavesBlock() { + super(Settings.of(Material.LEAVES) + .strength(500, 1200) + .ticksRandomly() + .sounds(BlockSoundGroup.AZALEA_LEAVES) + .nonOpaque() + .allowsSpawning(UBlocks::canSpawnOnLeaves) + .suffocates(UBlocks::never) + .blockVision(UBlocks::never) + ); + setDefaultState(getDefaultState().with(STAGE, ZapAppleStageStore.Stage.HIBERNATING)); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + super.appendProperties(builder); + builder.add(STAGE); + } + + @Override + public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + super.randomTick(state, world, pos, random); + + ZapAppleStageStore store = ZapAppleStageStore.get(world); + ZapAppleStageStore.Stage newStage = store.getStage(); + if (!world.isDay() && state.get(STAGE).mustChangeInto(newStage)) { + world.setBlockState(pos, state.with(STAGE, newStage)); + onStageChanged(store, newStage, world, state, pos, random); + } + } + + @Override + public void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + super.scheduledTick(state, world, pos, random); + + ZapAppleStageStore store = ZapAppleStageStore.get(world); + ZapAppleStageStore.Stage newStage = store.getStage(); + if (!world.isDay() && state.get(STAGE).mustChangeIntoInstantly(newStage)) { + world.setBlockState(pos, state.with(STAGE, newStage)); + onStageChanged(store, newStage, world, state, pos, random); + } + + world.createAndScheduleBlockTick(pos, this, 1); + } + + @Override + protected boolean shouldDecay(BlockState state) { + return false; + } + + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + if (!ctx.getWorld().isClient) { + ctx.getWorld().createAndScheduleBlockTick(ctx.getBlockPos(), this, 1); + return super.getPlacementState(ctx).with(STAGE, ZapAppleStageStore.get(ctx.getWorld()).getStage()); + } + return super.getPlacementState(ctx); + } + + private void onStageChanged(ZapAppleStageStore store, ZapAppleStageStore.Stage stage, ServerWorld world, BlockState state, BlockPos pos, Random random) { + boolean mustFruit = Random.create(state.getRenderingSeed(pos)).nextInt(5) < 2; + BlockState below = world.getBlockState(pos.down()); + + if (world.isAir(pos.down())) { + if (stage == ZapAppleStageStore.Stage.FRUITING && mustFruit) { + world.setBlockState(pos.down(), UBlocks.ZAP_BULB.getDefaultState(), Block.NOTIFY_ALL); + store.triggerLightningStrike(pos); + } + } + + if (stage != ZapAppleStageStore.Stage.HIBERNATING && world.getRandom().nextInt(10) == 0) { + store.triggerLightningStrike(pos); + } + + if (stage == ZapAppleStageStore.Stage.RIPE) { + store.playMoonEffect(pos); + + if (below.isOf(UBlocks.ZAP_BULB)) { + world.setBlockState(pos.down(), UBlocks.ZAP_APPLE.getDefaultState(), Block.NOTIFY_ALL); + store.triggerLightningStrike(pos); + } + } + + if (mustFruit && stage == ZapAppleStageStore.Stage.HIBERNATING) { + if (below.isOf(UBlocks.ZAP_APPLE) || below.isOf(UBlocks.ZAP_BULB)) { + world.setBlockState(pos.down(), Blocks.AIR.getDefaultState()); + } + } + } + + @Override + public void onBlockBreakStart(BlockState state, World world, BlockPos pos, PlayerEntity player) { + triggerLightning(state, world, pos, player); + } + + @Deprecated + @Override + public BlockRenderType getRenderType(BlockState state) { + return isAir(state) ? BlockRenderType.INVISIBLE : super.getRenderType(state); + } + + @Deprecated + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return isAir(state) ? VoxelShapes.empty() : super.getOutlineShape(state, world, pos, context); + } + + @Deprecated + @Override + public boolean canReplace(BlockState state, ItemPlacementContext context) { + return isAir(state) || super.canReplace(state, context); + } + + @Deprecated + @Override + public boolean canBucketPlace(BlockState state, Fluid fluid) { + return isAir(state) || super.canBucketPlace(state, fluid); + } + + protected boolean isAir(BlockState state) { + return state.get(STAGE) == ZapAppleStageStore.Stage.HIBERNATING; + } + + @Deprecated + @Override + public float calcBlockBreakingDelta(BlockState state, PlayerEntity player, BlockView world, BlockPos pos) { + float delta = super.calcBlockBreakingDelta(state, player, world, pos); + + if (Pony.of(player).getSpecies().canUseEarth()) { + delta *= 50; + } + + if (state.get(STAGE) == ZapAppleStageStore.Stage.RIPE) { + delta *= 5; + } + + return MathHelper.clamp(delta, 0, 0.9F); + } + + public static void triggerLightning(BlockState state, World world, BlockPos pos, PlayerEntity player) { + if (world instanceof ServerWorld serverWorld) { + Vec3d center = Vec3d.ofCenter(pos); + LightningEntity lightning = EntityType.LIGHTNING_BOLT.create(world); + world.getOtherEntities(null, Box.from(center).expand(7)).forEach(other -> { + float dist = (float)other.getPos().distanceTo(center); + if (dist < 4) { + other.onStruckByLightning(serverWorld, lightning); + } else { + float damage = 3 / dist; + if (damage > 1) { + other.damage(DamageSource.LIGHTNING_BOLT, damage); + } + } + }); + } + world.emitGameEvent(GameEvent.LIGHTNING_STRIKE, pos, GameEvent.Emitter.of(state)); + ParticleUtils.spawnParticle(world, UParticles.LIGHTNING_BOLT, Vec3d.ofCenter(pos), Vec3d.ZERO); + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLogBlock.java b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLogBlock.java new file mode 100644 index 00000000..25f16e79 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/ZapAppleLogBlock.java @@ -0,0 +1,34 @@ +package com.minelittlepony.unicopia.block; + +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.block.*; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.util.math.*; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; + +public class ZapAppleLogBlock extends PillarBlock { + ZapAppleLogBlock(MapColor topMapColor, MapColor sideMapColor) { + super(AbstractBlock.Settings.of(Material.WOOD, state -> state.get(PillarBlock.AXIS) == Direction.Axis.Y ? topMapColor : sideMapColor).strength(2.0f).sounds(BlockSoundGroup.WOOD).strength(500, 1200)); + } + + @Deprecated + @Override + public void onBlockBreakStart(BlockState state, World world, BlockPos pos, PlayerEntity player) { + ZapAppleLeavesBlock.triggerLightning(state, world, pos, player); + } + + @Deprecated + @Override + public float calcBlockBreakingDelta(BlockState state, PlayerEntity player, BlockView world, BlockPos pos) { + float delta = super.calcBlockBreakingDelta(state, player, world, pos); + + if (Pony.of(player).getSpecies().canUseEarth()) { + delta *= 50; + } + + return MathHelper.clamp(delta, 0, 0.9F); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/data/BlockDestructionManager.java b/src/main/java/com/minelittlepony/unicopia/block/data/BlockDestructionManager.java index 5b68ba64..f05db351 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/data/BlockDestructionManager.java +++ b/src/main/java/com/minelittlepony/unicopia/block/data/BlockDestructionManager.java @@ -8,6 +8,8 @@ import com.google.common.base.Suppliers; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgBlockDestruction; +import com.minelittlepony.unicopia.util.Tickable; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.block.BlockState; @@ -17,7 +19,7 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -public class BlockDestructionManager { +public class BlockDestructionManager implements Tickable { private static final Identifier ID = Unicopia.id("destruction_manager"); public static final int DESTRUCTION_COOLDOWN = 50; @@ -63,6 +65,7 @@ public class BlockDestructionManager { } } + @Override public void tick() { chunks.tick(); } diff --git a/src/main/java/com/minelittlepony/unicopia/block/data/WorldOverlay.java b/src/main/java/com/minelittlepony/unicopia/block/data/WorldOverlay.java index d5464117..7b34aed9 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/data/WorldOverlay.java +++ b/src/main/java/com/minelittlepony/unicopia/block/data/WorldOverlay.java @@ -6,6 +6,7 @@ import java.util.function.*; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.util.NbtSerialisable; +import com.minelittlepony.unicopia.util.Tickable; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -19,7 +20,7 @@ import net.minecraft.util.math.ChunkPos; import net.minecraft.world.PersistentState; import net.minecraft.world.World; -public class WorldOverlay extends PersistentState { +public class WorldOverlay extends PersistentState implements Tickable { private final World world; private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); @@ -98,6 +99,7 @@ public class WorldOverlay extends PersistentState } } + @Override public void tick() { synchronized (locker) { chunks.long2ObjectEntrySet().removeIf(entry -> entry.getValue().tick()); diff --git a/src/main/java/com/minelittlepony/unicopia/block/data/ZapAppleStageStore.java b/src/main/java/com/minelittlepony/unicopia/block/data/ZapAppleStageStore.java new file mode 100644 index 00000000..33b16fd0 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/data/ZapAppleStageStore.java @@ -0,0 +1,164 @@ +package com.minelittlepony.unicopia.block.data; + +import java.util.Locale; +import java.util.stream.StreamSupport; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.particle.ParticleUtils; +import com.minelittlepony.unicopia.particle.UParticles; +import com.minelittlepony.unicopia.util.Tickable; + +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LightningEntity; +import net.minecraft.nbt.*; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Identifier; +import net.minecraft.util.StringIdentifiable; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.PersistentState; +import net.minecraft.world.World; +import net.minecraft.world.event.GameEvent; + +public class ZapAppleStageStore extends PersistentState implements Tickable { + private static final Identifier ID = Unicopia.id("zap_apple_stage"); + + public static ZapAppleStageStore get(World world) { + return WorldOverlay.getPersistableStorage(world, ID, ZapAppleStageStore::new, ZapAppleStageStore::new); + } + + private final World world; + + private Stage lastStage = Stage.HIBERNATING; + private int countdown; + private boolean stageChanged; + private boolean playedMoonEffect; + private int nextLightningEvent = 1200; + + ZapAppleStageStore(World world, NbtCompound compound) { + this(world); + lastStage = Stage.VALUES[Math.max(0, compound.getInt("stage")) % Stage.VALUES.length]; + stageChanged = compound.getBoolean("stageChanged"); + countdown = compound.getInt("countdown"); + playedMoonEffect = compound.getBoolean("playedMoonEffect"); + nextLightningEvent = compound.getInt("nextLightningEvent"); + } + + ZapAppleStageStore(World world) { + this.world = world; + } + + @Override + public void tick() { + if (!world.isDay()) { + if (nextLightningEvent > 0) { + nextLightningEvent--; + markDirty(); + } + + if (!stageChanged && (lastStage != Stage.HIBERNATING || (world.getMoonPhase() == 0))) { + stageChanged = true; + if (countDay()) { + lastStage = lastStage.getNext(); + countdown = 1; + playedMoonEffect = false; + markDirty(); + onStageChanged(); + } + } + } else if (stageChanged) { + stageChanged = false; + markDirty(); + } + } + + private boolean countDay() { + markDirty(); + return countdown-- <= 0; + } + + protected void onStageChanged() { + world.setRainGradient(0.5F); + } + + public void playMoonEffect(BlockPos pos) { + if (!playedMoonEffect) { + playedMoonEffect = true; + markDirty(); + world.playSound(pos.getX(), pos.getY(), pos.getZ(), SoundEvents.ENTITY_WOLF_HOWL, SoundCategory.BLOCKS, 1.5F, 0.9F, true); + } + } + + public void triggerLightningStrike(BlockPos pos) { + world.emitGameEvent(GameEvent.LIGHTNING_STRIKE, pos, GameEvent.Emitter.of(world.getBlockState(pos))); + ParticleUtils.spawnParticle(world, UParticles.LIGHTNING_BOLT, Vec3d.ofCenter(pos), Vec3d.ZERO); + + if (nextLightningEvent <= 0) { + StreamSupport.stream(BlockPos.iterateRandomly(world.random, 20, pos, 10).spliterator(), false) + .filter(p -> world.isAir(p) && !world.isAir(p.down()) && world.isSkyVisible(p)) + .findFirst().ifPresent(p -> { + LightningEntity bolt = EntityType.LIGHTNING_BOLT.create(world); + bolt.refreshPositionAfterTeleport(Vec3d.ofBottomCenter(pos)); + bolt.setCosmetic(true); + world.spawnEntity(bolt); + nextLightningEvent = world.getRandom().nextBetween(1200, 8000); + markDirty(); + }); + } + } + + /** + * Returns true during nights that the zap apples must change their states. + * @return + */ + public boolean hasStageChanged() { + return stageChanged; + } + + /** + * Returns the current zap apple ripening stage. + */ + public Stage getStage() { + return lastStage; + } + + @Override + public NbtCompound writeNbt(NbtCompound compound) { + compound.putInt("stage", lastStage.ordinal()); + compound.putBoolean("stageChanged", stageChanged); + compound.putInt("countdown", countdown); + compound.putBoolean("playedMoonEffect", playedMoonEffect); + compound.putInt("nextLightningEvent", nextLightningEvent); + return compound; + } + + public enum Stage implements StringIdentifiable { + HIBERNATING, + GREENING, + FLOWERING, + FRUITING, + RIPE; + + static final long DAY_LENGTH = 24000; + static final Stage[] VALUES = values(); + + public Stage getNext() { + return VALUES[(ordinal() + 1) % VALUES.length]; + } + + public boolean mustChangeInto(Stage to) { + return this != to && (getNext() == to || this == HIBERNATING || to == HIBERNATING); + } + + public boolean mustChangeIntoInstantly(Stage to) { + return this != to && (this == HIBERNATING || to == HIBERNATING); + } + + @Override + public String asString() { + return name().toLowerCase(Locale.ROOT); + } + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index d4da7abe..78cb633a 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia.client; +import com.minelittlepony.unicopia.block.UBlocks; import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle; import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle; import com.minelittlepony.unicopia.client.particle.DiskParticle; @@ -27,6 +28,8 @@ import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; import net.fabricmc.fabric.api.client.rendering.v1.BuiltinItemRendererRegistry; import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.color.world.BiomeColors; +import net.minecraft.client.color.world.FoliageColors; import net.minecraft.client.item.ModelPredicateProviderRegistry; import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.SpriteProvider; @@ -117,9 +120,20 @@ public interface URenderers { ColorProviderRegistry.ITEM.register((stack, i) -> { return i > 0 || !GemstoneItem.isEnchanted(stack) ? -1 : GemstoneItem.getSpellKey(stack).getColor(); }, UItems.GEMSTONE); + ColorProviderRegistry.BLOCK.register((state, view, pos, color) -> { + if (view == null || pos == null) { + color = FoliageColors.getDefaultColor(); + } else { + color = BiomeColors.getFoliageColor(view, pos); + } + + return (color << 2) | ((color >> 4) & 0xFF); + }, UBlocks.ZAP_LEAVES); // for lava boats BlockRenderLayerMap.INSTANCE.putFluids(RenderLayers.getTranslucent(), Fluids.LAVA, Fluids.FLOWING_LAVA); + + BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayers.getTranslucent(), UBlocks.ZAP_BULB, UBlocks.ZAP_APPLE, UBlocks.ZAPLING); } static PendingParticleFactory createFactory(ParticleSupplier supplier) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java index 6739b66a..ef2bbd2e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java @@ -198,10 +198,10 @@ public class FairyEntity extends PathAwareEntity implements DynamicLightSource, @Override public boolean handleAttack(Entity attacker) { - if (world instanceof ServerWorld) { + if (world instanceof ServerWorld serverWorld) { LightningEntity lightning = EntityType.LIGHTNING_BOLT.create(world); lightning.refreshPositionAfterTeleport(getX(), getY(), getZ()); - attacker.onStruckByLightning((ServerWorld)world, lightning); + attacker.onStruckByLightning(serverWorld, lightning); } emitGameEvent(GameEvent.LIGHTNING_STRIKE); ParticleUtils.spawnParticle(world, UParticles.LIGHTNING_BOLT, getPos(), Vec3d.ZERO); diff --git a/src/main/java/com/minelittlepony/unicopia/item/PolearmItem.java b/src/main/java/com/minelittlepony/unicopia/item/PolearmItem.java index 31ac019a..031c3e21 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/PolearmItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/PolearmItem.java @@ -4,6 +4,7 @@ import java.util.UUID; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; +import com.minelittlepony.unicopia.UTags; import com.minelittlepony.unicopia.entity.UEntityAttributes; import net.minecraft.block.*; @@ -40,7 +41,7 @@ public class PolearmItem extends SwordItem { @Override public boolean isSuitableFor(BlockState state) { - return false; + return state.isIn(UTags.POLEARM_MINEABLE); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/item/UItems.java b/src/main/java/com/minelittlepony/unicopia/item/UItems.java index 367e053f..ab2b18f8 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/UItems.java +++ b/src/main/java/com/minelittlepony/unicopia/item/UItems.java @@ -9,6 +9,8 @@ import com.minelittlepony.unicopia.entity.UEntities; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.item.toxin.UFoodComponents; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; import net.minecraft.item.*; import net.minecraft.item.Item.Settings; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; @@ -26,6 +28,14 @@ public interface UItems { Item SOUR_APPLE = register("sour_apple", AppleItem.registerTickCallback(new Item(new Item.Settings().group(ItemGroup.FOOD).food(FoodComponents.APPLE)))); ZapAppleItem ZAP_APPLE = register("zap_apple", AppleItem.registerTickCallback(new ZapAppleItem(new Item.Settings().group(ItemGroup.FOOD).food(UFoodComponents.ZAP_APPLE)))); + Item ZAP_BULB = register("zap_bulb", new Item(new Item.Settings().group(ItemGroup.FOOD).food(new FoodComponent.Builder() + .hunger(-2) + .saturationModifier(-0.8f) + .alwaysEdible() + .statusEffect(new StatusEffectInstance(StatusEffects.POISON, 100, 0), 0.6F) + .statusEffect(new StatusEffectInstance(StatusEffects.BLINDNESS, 100, 0), 0.6F) + .statusEffect(new StatusEffectInstance(StatusEffects.BAD_OMEN, 100, 0), 0.6F) + .build()))); Item ROTTEN_APPLE = register("rotten_apple", new RottenAppleItem(new Item.Settings().group(ItemGroup.FOOD).food(FoodComponents.APPLE))); Item COOKED_ZAP_APPLE = register("cooked_zap_apple", new Item(new Item.Settings().group(ItemGroup.FOOD).food(FoodComponents.APPLE))); @@ -90,6 +100,8 @@ public interface UItems { Item SPELLBOOK = register("spellbook", new SpellbookItem(new Item.Settings().maxCount(1).rarity(Rarity.UNCOMMON).group(ItemGroup.TOOLS))); + Item ZAPLING = register("zapling", new BlockItem(UBlocks.ZAPLING, new Item.Settings().group(ItemGroup.DECORATIONS))); + AmuletItem PEGASUS_AMULET = register("pegasus_amulet", new AmuletItem(new FabricItemSettings() .maxCount(1) .maxDamage(890) diff --git a/src/main/resources/assets/unicopia/blockstates/zap_apple.json b/src/main/resources/assets/unicopia/blockstates/zap_apple.json new file mode 100644 index 00000000..26404112 --- /dev/null +++ b/src/main/resources/assets/unicopia/blockstates/zap_apple.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "unicopia:block/zap_apple" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/blockstates/zap_bulb.json b/src/main/resources/assets/unicopia/blockstates/zap_bulb.json new file mode 100644 index 00000000..65df6348 --- /dev/null +++ b/src/main/resources/assets/unicopia/blockstates/zap_bulb.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "unicopia:block/zap_bulb" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/blockstates/zap_leaves.json b/src/main/resources/assets/unicopia/blockstates/zap_leaves.json new file mode 100644 index 00000000..6d93be93 --- /dev/null +++ b/src/main/resources/assets/unicopia/blockstates/zap_leaves.json @@ -0,0 +1,19 @@ +{ + "variants": { + "stage=hibernating": { + "model": "unicopia:block/zap_leaves" + }, + "stage=greening": { + "model": "unicopia:block/zap_leaves" + }, + "stage=flowering": { + "model": "unicopia:block/flowering_zap_leaves" + }, + "stage=fruiting": { + "model": "unicopia:block/zap_leaves" + }, + "stage=ripe": { + "model": "unicopia:block/zap_leaves" + } + } +} diff --git a/src/main/resources/assets/unicopia/blockstates/zap_log.json b/src/main/resources/assets/unicopia/blockstates/zap_log.json new file mode 100644 index 00000000..c3b64c19 --- /dev/null +++ b/src/main/resources/assets/unicopia/blockstates/zap_log.json @@ -0,0 +1,16 @@ +{ + "variants": { + "axis=x": { + "model": "unicopia:block/zap_log_horizontal", + "x": 90, + "y": 90 + }, + "axis=y": { + "model": "unicopia:block/zap_log" + }, + "axis=z": { + "model": "unicopia:block/zap_log_horizontal", + "x": 90 + } + } +} diff --git a/src/main/resources/assets/unicopia/blockstates/zapling.json b/src/main/resources/assets/unicopia/blockstates/zapling.json new file mode 100644 index 00000000..02ed0968 --- /dev/null +++ b/src/main/resources/assets/unicopia/blockstates/zapling.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "unicopia:block/zapling" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index b9f22726..2484bc1c 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -26,6 +26,7 @@ "item.unicopia.rotten_apple": "Rotten Apple", "item.unicopia.cooked_zap_apple": "Cooked Zap Apple", "item.unicopia.zap_apple": "Zap Apple", + "item.unicopia.zap_bulb": "Unripened Zap Apple", "item.unicopia.empty_jar": "Glass Jar", "item.unicopia.filled_jar": "%s in a Jar", @@ -87,6 +88,11 @@ "item.unicopia.music_disc_funk.desc": "funk, just funk", "block.unicopia.rocks": "Rocks", + "block.unicopia.zapling": "Zapling", + "block.unicopia.zap_log": "Zap Log", + "block.unicopia.zap_leaves": "Zap Leaves", + "block.unicopia.zap_apple": "Zap Apple", + "block.unicopia.zap_bulb": "Unripened Zap Apple", "entity.unicopia.butterfly": "Butterfly", "entity.unicopia.twittermite": "Twittermite", diff --git a/src/main/resources/assets/unicopia/models/block/flowering_zap_leaves.json b/src/main/resources/assets/unicopia/models/block/flowering_zap_leaves.json new file mode 100644 index 00000000..d4d79822 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/flowering_zap_leaves.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/leaves", + "textures": { + "all": "unicopia:block/flowering_zap_leaves" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/block/zap_apple.json b/src/main/resources/assets/unicopia/models/block/zap_apple.json new file mode 100644 index 00000000..c5b0e7a4 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/zap_apple.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cross", + "textures": { + "cross": "unicopia:item/zap_apple" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/block/zap_bulb.json b/src/main/resources/assets/unicopia/models/block/zap_bulb.json new file mode 100644 index 00000000..2ada77fe --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/zap_bulb.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cross", + "textures": { + "cross": "unicopia:item/zap_bulb" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/block/zap_leaves.json b/src/main/resources/assets/unicopia/models/block/zap_leaves.json new file mode 100644 index 00000000..e8066248 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/zap_leaves.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/leaves", + "textures": { + "all": "unicopia:block/zap_leaves" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/block/zap_log.json b/src/main/resources/assets/unicopia/models/block/zap_log.json new file mode 100644 index 00000000..e787c248 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/zap_log.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/cube_column", + "textures": { + "end": "unicopia:block/zap_log_top", + "side": "unicopia:block/zap_log" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/block/zap_log_horizontal.json b/src/main/resources/assets/unicopia/models/block/zap_log_horizontal.json new file mode 100644 index 00000000..67f2f50c --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/zap_log_horizontal.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/cube_column_horizontal", + "textures": { + "end": "unicopia:block/zap_log_top", + "side": "unicopia:block/zap_log" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/block/zapling.json b/src/main/resources/assets/unicopia/models/block/zapling.json new file mode 100644 index 00000000..e630495b --- /dev/null +++ b/src/main/resources/assets/unicopia/models/block/zapling.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cross", + "textures": { + "cross": "unicopia:item/zapling" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/unicopia/models/item/zap_bulb.json b/src/main/resources/assets/unicopia/models/item/zap_bulb.json new file mode 100644 index 00000000..0fce5058 --- /dev/null +++ b/src/main/resources/assets/unicopia/models/item/zap_bulb.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "unicopia:item/zap_bulb" + } +} diff --git a/src/main/resources/assets/unicopia/models/item/zapling.json b/src/main/resources/assets/unicopia/models/item/zapling.json new file mode 100644 index 00000000..3c4bffea --- /dev/null +++ b/src/main/resources/assets/unicopia/models/item/zapling.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "unicopia:item/zapling" + } +} diff --git a/src/main/resources/assets/unicopia/textures/block/flowering_zap_leaves.png b/src/main/resources/assets/unicopia/textures/block/flowering_zap_leaves.png new file mode 100644 index 00000000..ca5c516d Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/block/flowering_zap_leaves.png differ diff --git a/src/main/resources/assets/unicopia/textures/block/zap_leaves.png b/src/main/resources/assets/unicopia/textures/block/zap_leaves.png new file mode 100644 index 00000000..02e8b865 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/block/zap_leaves.png differ diff --git a/src/main/resources/assets/unicopia/textures/block/zap_log.png b/src/main/resources/assets/unicopia/textures/block/zap_log.png new file mode 100644 index 00000000..12f9c0aa Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/block/zap_log.png differ diff --git a/src/main/resources/assets/unicopia/textures/block/zap_log_top.png b/src/main/resources/assets/unicopia/textures/block/zap_log_top.png new file mode 100644 index 00000000..baa390f4 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/block/zap_log_top.png differ diff --git a/src/main/resources/assets/unicopia/textures/item/zap_bulb.png b/src/main/resources/assets/unicopia/textures/item/zap_bulb.png new file mode 100644 index 00000000..679a55ad Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/item/zap_bulb.png differ diff --git a/src/main/resources/assets/unicopia/textures/item/zapling.png b/src/main/resources/assets/unicopia/textures/item/zapling.png new file mode 100644 index 00000000..46669898 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/item/zapling.png differ diff --git a/src/main/resources/data/unicopia/loot_tables/blocks/zap_apple.json b/src/main/resources/data/unicopia/loot_tables/blocks/zap_apple.json new file mode 100644 index 00000000..3400bb32 --- /dev/null +++ b/src/main/resources/data/unicopia/loot_tables/blocks/zap_apple.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1.0, + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "unicopia:zap_apple" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/unicopia/loot_tables/blocks/zap_bulb.json b/src/main/resources/data/unicopia/loot_tables/blocks/zap_bulb.json new file mode 100644 index 00000000..2944030f --- /dev/null +++ b/src/main/resources/data/unicopia/loot_tables/blocks/zap_bulb.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1.0, + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "unicopia:zap_bulb" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/unicopia/loot_tables/blocks/zapling.json b/src/main/resources/data/unicopia/loot_tables/blocks/zapling.json new file mode 100644 index 00000000..508f8ba3 --- /dev/null +++ b/src/main/resources/data/unicopia/loot_tables/blocks/zapling.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1.0, + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:item", + "name": "unicopia:zapling" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/data/unicopia/tags/blocks/mineable/polearm.json b/src/main/resources/data/unicopia/tags/blocks/mineable/polearm.json new file mode 100644 index 00000000..11c84d4c --- /dev/null +++ b/src/main/resources/data/unicopia/tags/blocks/mineable/polearm.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "unicopia:zap_leaves", + "unicopia:zap_log" + ] +} diff --git a/src/main/resources/data/unicopia/tags/items/polearms.json b/src/main/resources/data/unicopia/tags/items/polearms.json new file mode 100644 index 00000000..54736807 --- /dev/null +++ b/src/main/resources/data/unicopia/tags/items/polearms.json @@ -0,0 +1,11 @@ +{ + "replace": false, + "values": [ + "unicopia:wooden_polearm", + "unicopia:stone_polearm", + "unicopia:iron_polearm", + "unicopia:golden_polearm", + "unicopia:diamond_polearm", + "unicopia:netherite_polearm" + ] +}