Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ dependencies {
implementation("curse.maven:balm-531761:${balm_file_id}")
implementation("curse.maven:waystones-245755:${waystones_file_id}")
implementation("curse.maven:lightmans-currency-472521:${lightmanscurrency_file_id}")
compileOnly("curse.maven:quality-food-973932:${qualityfood_file_id}")
compileOnly("curse.maven:applied-energistics-2-223794:${ae2_file_id}")
compileOnly("curse.maven:extendedae-892005:${extendedae_file_id}")
compileOnly("dev.latvian.mods:kubejs-neoforge:${kubejs_version}")

// Example mod dependency with JEI
Expand Down
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ createoreexcavation_file_id=7199305
balm_file_id=7420963
waystones_file_id=7966157
lightmanscurrency_file_id=7697352
qualityfood_file_id=8242923
ae2_file_id=7027323
extendedae_file_id=8025439
kubejs_version=2101.7.2-build.369
## Mod Properties
# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/io/github/jasonsimpart/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ public final class Config {
)
.define("waystonesMoneyChain", "main");

public static final ModConfigSpec.ConfigValue<String> QUALITY_FOOD_MONEY_CHAIN = BUILDER
.comment(
"Quality Food 品质兑换使用的 Lightman's Currency coin chain。",
"Lightman's Currency coin chain used when converting Quality Food quality into money."
)
.define("qualityFoodMoneyChain", "main");

public static final ModConfigSpec.IntValue WAYSTONES_TELEPORT_COST_PER_LEVEL = BUILDER
.comment(
"每 1 点 Waystones 经验成本换算成多少 Lightman's Currency 基础币值。旧 Core 默认值为 45。",
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/io/github/jasonsimpart/ModCommonEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public static void register(IEventBus modEventBus) {
NeoForge.EVENT_BUS.addListener(DropReportEvents::onServerTick);
DisabledContentEvents.register(modEventBus);
registerWaystonesMoneyTeleport();
registerQualityFoodCurrencyCompat();
}

private static void registerWaystonesMoneyTeleport() {
Expand All @@ -62,6 +63,20 @@ private static void registerWaystonesMoneyTeleport() {
}
}

private static void registerQualityFoodCurrencyCompat() {
if (!ModList.get().isLoaded("quality_food") || !ModList.get().isLoaded("lightmanscurrency")) {
return;
}

try {
Class.forName("io.github.jasonsimpart.compat.qualityfood.QualityFoodCurrencyCompat")
.getMethod("register")
.invoke(null);
} catch (ReflectiveOperationException | LinkageError exception) {
CreateDelightCore.LOGGER.warn("Failed to register createdelightcore Quality Food currency compat", exception);
}
}

private static void modifyDefaultComponents(ModifyDefaultComponentsEvent event) {
if (!Config.ENABLE_BLAZE_CAKE_FOOD_PATCH.get()) {
return;
Expand Down
37 changes: 36 additions & 1 deletion src/main/java/io/github/jasonsimpart/client/ClientModEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.simibubi.create.foundation.block.connected.SimpleCTBehaviour;
import com.simibubi.create.foundation.data.CreateRegistrate;
import io.github.jasonsimpart.CreateDelightCore;
import io.github.jasonsimpart.client.tetra.TetraEnergyBarRenderer;
import io.github.jasonsimpart.registry.ModBlocks;
import io.github.jasonsimpart.registry.ModFluids;
import io.github.jasonsimpart.registry.ModItems;
Expand All @@ -14,11 +16,12 @@
import net.minecraft.world.level.material.FluidState;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModList;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent;
import net.neoforged.neoforge.client.event.RegisterItemDecorationsEvent;
import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;

public final class ClientModEvents {
private static final int LUSH_CONFITURE_COLOR = 0xF0612E;
Expand All @@ -31,6 +34,7 @@ public static void register(IEventBus modEventBus) {
modEventBus.addListener(ClientModEvents::registerClientExtensions);
modEventBus.addListener(ClientModEvents::registerBlockColors);
modEventBus.addListener(ClientModEvents::registerItemColors);
modEventBus.addListener(ClientModEvents::registerItemDecorations);
NeoForge.EVENT_BUS.addListener(QuickReloadKeyHandler::onKeyInput);
}

Expand All @@ -45,9 +49,26 @@ private static void clientSetup(FMLClientSetupEvent event) {
ItemBlockRenderTypes.setRenderLayer(fluid.flowing().get(), RenderType.solid());
});
registerWaystonesMoneyRenderer();
registerEclipticSeasonsGrowthDetectorParticles();
});
}

private static void registerItemDecorations(RegisterItemDecorationsEvent event) {
TetraEnergyBarRenderer.register(event);

if (!ModList.get().isLoaded("extendedae") || !ModList.get().isLoaded("ae2")) {
return;
}

try {
Class.forName("io.github.jasonsimpart.client.extendedae.ExtendedAeInfinityCellRenderer")
.getMethod("register", RegisterItemDecorationsEvent.class)
.invoke(null, event);
} catch (ReflectiveOperationException | LinkageError exception) {
CreateDelightCore.LOGGER.warn("Failed to register createdelightcore ExtendedAE infinity cell renderer", exception);
}
}

private static void registerWaystonesMoneyRenderer() {
if (!ModList.get().isLoaded("waystones") || !ModList.get().isLoaded("lightmanscurrency")) {
return;
Expand All @@ -62,6 +83,20 @@ private static void registerWaystonesMoneyRenderer() {
}
}

private static void registerEclipticSeasonsGrowthDetectorParticles() {
if (!ModList.get().isLoaded("eclipticseasons")) {
return;
}

try {
Class.forName("io.github.jasonsimpart.client.eclipticseasons.EclipticSeasonsGrowthDetectorParticles")
.getMethod("register")
.invoke(null);
} catch (ReflectiveOperationException | LinkageError exception) {
CreateDelightCore.LOGGER.warn("Failed to register createdelightcore Ecliptic Seasons growth detector particles", exception);
}
}

private static void registerConnectedTextures(net.minecraft.world.level.block.Block block, com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry shift) {
CreateRegistrate.connectedTextures(() -> new SimpleCTBehaviour(shift)).accept(block);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package io.github.jasonsimpart.client.eclipticseasons;

import io.github.jasonsimpart.CreateDelightCore;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.common.NeoForge;
import org.joml.Vector3f;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public final class EclipticSeasonsGrowthDetectorParticles {
private static final String ECLIPTIC_SEASONS_MODID = "eclipticseasons";
private static final String ECLIPTIC_SEASONS_CROP_TAG_PREFIX = "crops/";
private static final ResourceLocation GROWTH_DETECTOR_ID = ResourceLocation.fromNamespaceAndPath(ECLIPTIC_SEASONS_MODID, "growth_detector");
private static final MethodHandle GET_GROW_CHANCE = getGrowChanceMethod();
private static final int INTERVAL_TICKS = 60;
private static final int HORIZONTAL_RADIUS = 5;
private static final int VERTICAL_RADIUS = 2;
private static final int PARTICLE_COUNT = 4;
private static final int MAX_PARTICLE_BLOCKS = 32;
private static final int MAX_GROWTH_EVALUATIONS = 64;
private static final float PARTICLE_SCALE = 1.0F;
private static boolean growthChanceFailureLogged;
private static boolean growthChanceUnavailable;
private static final int FAILED_POSITION_RETRY_TICKS = 600;
private static final int TRANSIENT_FAILURE_RETRY_TICKS = INTERVAL_TICKS * 2;
private static final int MAX_FAILED_POSITIONS = 64;
private static final Map<BlockPos, FailedGrowthPosition> failedGrowthPositions = new HashMap<>();
private static ClientLevel lastProcessedLevel;
private static long lastProcessedGameTime = Long.MIN_VALUE;

private EclipticSeasonsGrowthDetectorParticles() {
}

public static void register() {
if (GET_GROW_CHANCE != null) {
NeoForge.EVENT_BUS.addListener(EclipticSeasonsGrowthDetectorParticles::onClientTick);
}
}

private static void onClientTick(ClientTickEvent.Post event) {
Minecraft minecraft = Minecraft.getInstance();
ClientLevel level = minecraft.level;
LocalPlayer player = minecraft.player;
if (lastProcessedLevel != level) {
lastProcessedLevel = level;
lastProcessedGameTime = Long.MIN_VALUE;
growthChanceUnavailable = false;
growthChanceFailureLogged = false;
failedGrowthPositions.clear();
}
if (growthChanceUnavailable || level == null || player == null) {
return;
}

long gameTime = level.getGameTime();
if (lastProcessedGameTime != Long.MIN_VALUE && gameTime < lastProcessedGameTime) {
failedGrowthPositions.clear();
lastProcessedGameTime = Long.MIN_VALUE;
}
if (gameTime % INTERVAL_TICKS != 0 || gameTime == lastProcessedGameTime) {
return;
}

ItemStack mainHand = player.getMainHandItem();
if (!isGrowthDetector(mainHand)) {
return;
}

lastProcessedLevel = level;
lastProcessedGameTime = gameTime;

BlockPos playerPos = player.blockPosition();
BlockPos min = playerPos.offset(-HORIZONTAL_RADIUS, -VERTICAL_RADIUS, -HORIZONTAL_RADIUS);
BlockPos max = playerPos.offset(HORIZONTAL_RADIUS, VERTICAL_RADIUS, HORIZONTAL_RADIUS);
int particleBlocks = 0;
int growthEvaluations = 0;
List<BlockPos> positions = new ArrayList<>();
for (BlockPos pos : BlockPos.betweenClosed(min, max)) {
positions.add(pos.immutable());
}
int scanStart = level.random.nextInt(positions.size());
for (int index = 0; index < positions.size(); index++) {
BlockPos pos = positions.get((scanStart + index) % positions.size());
BlockState state = level.getBlockState(pos);
FailedGrowthPosition failure = failedGrowthPositions.get(pos);
if (failure != null) {
if (!failure.state().equals(state)) {
failedGrowthPositions.remove(pos);
} else if (gameTime < failure.retryAt()) {
continue;
}
}
if (hasEclipticSeasonsCropTag(state)) {
if (growthEvaluations++ >= MAX_GROWTH_EVALUATIONS) {
break;
}
Float chance = growthChance(level, pos, state);
if (growthChanceUnavailable) {
break;
}
if (chance == null) {
rememberGrowthFailure(pos, state, gameTime, TRANSIENT_FAILURE_RETRY_TICKS);
continue;
}
if (!Float.isFinite(chance)) {
if (!growthChanceFailureLogged) {
growthChanceFailureLogged = true;
CreateDelightCore.LOGGER.warn("Ecliptic Seasons growth detector API returned a non-finite growth chance; skipping this crop");
}
rememberGrowthFailure(pos, state, gameTime, FAILED_POSITION_RETRY_TICKS);
continue;
}
failedGrowthPositions.remove(pos);
spawnGrowthChanceParticles(level, pos, chance);
if (++particleBlocks >= MAX_PARTICLE_BLOCKS) {
break;
}
}
}
}

private static boolean isGrowthDetector(ItemStack stack) {
return BuiltInRegistries.ITEM.getKey(stack.getItem()).equals(GROWTH_DETECTOR_ID);
}

private static boolean hasEclipticSeasonsCropTag(BlockState state) {
return state.getBlock().builtInRegistryHolder()
.tags()
.anyMatch(tag -> ECLIPTIC_SEASONS_MODID.equals(tag.location().getNamespace())
&& tag.location().getPath().startsWith(ECLIPTIC_SEASONS_CROP_TAG_PREFIX));
}

private static Float growthChance(ClientLevel level, BlockPos pos, BlockState state) {
try {
return (float) GET_GROW_CHANCE.invokeExact((net.minecraft.world.level.Level) level, pos, state);
} catch (Throwable throwable) {
if (throwable instanceof LinkageError error) {
growthChanceUnavailable = true;
if (!growthChanceFailureLogged) {
growthChanceFailureLogged = true;
CreateDelightCore.LOGGER.warn("Ecliptic Seasons growth detector API became unavailable", error);
}
return null;
}
if (throwable instanceof Error error) {
throw error;
}
if (!growthChanceFailureLogged) {
growthChanceFailureLogged = true;
CreateDelightCore.LOGGER.warn("Ecliptic Seasons growth detector API failed for a crop; skipping that position", throwable);
}
return null;
}
}

private static void rememberGrowthFailure(BlockPos pos, BlockState state, long gameTime, int retryTicks) {
failedGrowthPositions.entrySet().removeIf(entry -> gameTime >= entry.getValue().retryAt());
if (failedGrowthPositions.size() >= MAX_FAILED_POSITIONS && !failedGrowthPositions.containsKey(pos)) {
BlockPos oldest = failedGrowthPositions.entrySet().stream()
.min(Map.Entry.comparingByValue((left, right) -> Long.compare(left.retryAt(), right.retryAt())))
.orElseThrow()
.getKey();
failedGrowthPositions.remove(oldest);
}
failedGrowthPositions.put(pos.immutable(), new FailedGrowthPosition(state, gameTime + retryTicks));
}

private static void spawnGrowthChanceParticles(ClientLevel level, BlockPos pos, float chance) {
float clampedChance = Math.clamp(chance, 0.0F, 1.0F);
float red;
float green;
if (clampedChance <= 0.5F) {
red = 1.0F;
green = clampedChance / 0.5F;
} else {
red = 1.0F - (clampedChance - 0.5F) / 0.5F;
green = 1.0F;
}

DustParticleOptions particles = new DustParticleOptions(new Vector3f(red, green, 0.0F), PARTICLE_SCALE);
for (int i = 0; i < PARTICLE_COUNT; i++) {
level.addParticle(
particles,
pos.getX() + 0.5D,
pos.getY() + 0.5D,
pos.getZ() + 0.5D,
(level.random.nextDouble() - 0.5D) * 0.1D,
level.random.nextDouble() * -0.05D,
(level.random.nextDouble() - 0.5D) * 0.1D
);
}
}

private static MethodHandle getGrowChanceMethod() {
try {
Class<?> growthDetectorItem = Class.forName("com.teamtea.eclipticseasons.common.item.GrowthDetectorItem");
return MethodHandles.publicLookup().findStatic(
growthDetectorItem,
"getGrowChance",
MethodType.methodType(float.class, net.minecraft.world.level.Level.class, BlockPos.class, BlockState.class)
);
} catch (ReflectiveOperationException | LinkageError exception) {
CreateDelightCore.LOGGER.warn("Failed to bind Ecliptic Seasons growth detector API", exception);
return null;
}
}

private record FailedGrowthPosition(BlockState state, long retryAt) {
}
}
Loading
Loading