init
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package dev.ryanhcode.sable.neoforge;
|
||||
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.SableCommonEvents;
|
||||
import dev.ryanhcode.sable.SableConfig;
|
||||
import dev.ryanhcode.sable.command.SableCommand;
|
||||
import dev.ryanhcode.sable.command.argument.SubLevelSelectorModifiers;
|
||||
import dev.ryanhcode.sable.index.SableAttributes;
|
||||
import dev.ryanhcode.sable.physics.config.FloatingBlockMaterialDataHandler;
|
||||
import dev.ryanhcode.sable.physics.config.block_properties.PhysicsBlockPropertiesDefinitionLoader;
|
||||
import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.CrashReportCallables;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.config.ModConfig;
|
||||
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.AddReloadListenerEvent;
|
||||
import net.neoforged.neoforge.event.OnDatapackSyncEvent;
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
|
||||
@Mod(Sable.MOD_ID)
|
||||
public final class SableNeoForge {
|
||||
public SableNeoForge(final ModContainer modContainer, final IEventBus modBus) {
|
||||
Sable.init();
|
||||
|
||||
final IEventBus neoBus = NeoForge.EVENT_BUS;
|
||||
neoBus.addListener(this::registerCommand);
|
||||
neoBus.addListener(this::registerReloadListeners);
|
||||
modBus.addListener(this::serverSetup);
|
||||
neoBus.addListener(this::syncDataPack);
|
||||
|
||||
SubLevelSelectorModifiers.registerModifiers();
|
||||
|
||||
final DeferredRegister<Attribute> attributes = DeferredRegister.create(BuiltInRegistries.ATTRIBUTE, Sable.MOD_ID);
|
||||
SableAttributes.PUNCH_STRENGTH = attributes.register(SableAttributes.PUNCH_STRENGTH_NAME, () -> SableAttributes.PUNCH_STRENGTH_ATTRIBUTE);
|
||||
SableAttributes.PUNCH_COOLDOWN = attributes.register(SableAttributes.PUNCH_COOLDOWN_NAME, () -> SableAttributes.PUNCH_COOLDOWN_ATTRIBUTE);
|
||||
attributes.register(modBus);
|
||||
|
||||
modContainer.registerConfig(ModConfig.Type.COMMON, SableConfig.SPEC);
|
||||
|
||||
CrashReportCallables.registerHeader(Sable::getCrashHeader);
|
||||
}
|
||||
|
||||
public void registerReloadListeners(final AddReloadListenerEvent event) {
|
||||
event.addListener(PhysicsBlockPropertiesDefinitionLoader.INSTANCE);
|
||||
event.addListener(DimensionPhysicsData.ReloadListener.INSTANCE);
|
||||
event.addListener(FloatingBlockMaterialDataHandler.ReloadListener.INSTANCE);
|
||||
}
|
||||
|
||||
private void serverSetup(final FMLCommonSetupEvent event) {
|
||||
SableAttributes.register();
|
||||
}
|
||||
|
||||
private void registerCommand(final RegisterCommandsEvent event) {
|
||||
SableCommand.register(event.getDispatcher(), event.getBuildContext());
|
||||
}
|
||||
|
||||
private void syncDataPack(final OnDatapackSyncEvent event) {
|
||||
SableCommonEvents.syncDataPacket(packet -> event.getRelevantPlayers().forEach(player -> player.connection.send(packet)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package dev.ryanhcode.sable.neoforge;
|
||||
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.SableClient;
|
||||
import dev.ryanhcode.sable.SableClientConfig;
|
||||
import dev.ryanhcode.sable.neoforge.compatibility.flywheel.FlywheelCompatNeoForge;
|
||||
import dev.ryanhcode.sable.physics.config.FloatingBlockMaterialDataHandler;
|
||||
import dev.ryanhcode.sable.sublevel.render.dispatcher.SubLevelRenderDispatcher;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.config.ModConfig;
|
||||
import net.neoforged.fml.event.config.ModConfigEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
|
||||
@Mod(value = Sable.MOD_ID, dist = Dist.CLIENT)
|
||||
public final class SableNeoForgeClient {
|
||||
|
||||
public SableNeoForgeClient(final ModContainer modContainer, final IEventBus modBus) {
|
||||
final IEventBus neoBus = NeoForge.EVENT_BUS;
|
||||
|
||||
SableClient.init();
|
||||
|
||||
modContainer.registerConfig(ModConfig.Type.CLIENT, SableClientConfig.SPEC);
|
||||
modBus.<ModConfigEvent.Loading>addListener(event -> SableClientConfig.onUpdate(false));
|
||||
modBus.<ModConfigEvent.Reloading>addListener(event -> SableClientConfig.onUpdate(true));
|
||||
neoBus.<ClientPlayerNetworkEvent.LoggingOut>addListener(event -> {
|
||||
if (event.getPlayer() != null) { // LoggingOut may fire when logging in
|
||||
FloatingBlockMaterialDataHandler.clearMaterials();
|
||||
}
|
||||
});
|
||||
modBus.<RegisterClientReloadListenersEvent>addListener(event -> event.registerReloadListener((arg, arg2, arg3, arg4, executor, executor2) -> SubLevelRenderDispatcher.get().reload(arg, arg2, arg3, arg4, executor, executor2)));
|
||||
|
||||
if (FlywheelCompatNeoForge.FLYWHEEL_LOADED) {
|
||||
Sable.LOGGER.warn("NOTE: Sable is loaded with Flywheel. Sable contains extensive shader overrides and a full light-storage replacement. Expect this to cause compatibility issues. If issues arise, please report them to the Sable issue tracker ({}) instead of the Flywheel issue tracker.", Sable.ISSUE_TRACKER_URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package dev.ryanhcode.sable.neoforge.compatibility.flywheel;
|
||||
|
||||
import dev.engine_room.flywheel.lib.visualization.VisualizationHelper;
|
||||
import dev.ryanhcode.sable.api.sublevel.ClientSubLevelContainer;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3d;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import foundry.veil.Veil;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FlywheelCompatNeoForge {
|
||||
public static boolean FLYWHEEL_LOADED = Veil.platform().isModLoaded("flywheel");
|
||||
|
||||
private static final Long2ObjectMap<SubLevelFlwRenderState> RENDER_POSES = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
|
||||
|
||||
/**
|
||||
* Tries to add a flywheel visual for a block-entity.
|
||||
* Done for single block sub-level renderers, because we're not compiling their sections.
|
||||
*/
|
||||
public static void tryAddVisual(final BlockEntity blockEntity) {
|
||||
VisualizationHelper.tryAddBlockEntity(blockEntity);
|
||||
}
|
||||
|
||||
public static void preVisualizationFrame(final Level level, final float partialTicks) {
|
||||
final ClientSubLevelContainer container = (ClientSubLevelContainer) SubLevelContainer.getContainer(level);
|
||||
|
||||
if (container == null) {
|
||||
RENDER_POSES.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final ObjectIterator<Long2ObjectMap.Entry<SubLevelFlwRenderState>> iter = RENDER_POSES.long2ObjectEntrySet().iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
final Long2ObjectMap.Entry<SubLevelFlwRenderState> entry = iter.next();
|
||||
final long pos = entry.getLongKey();
|
||||
final SubLevelFlwRenderState poseEntry = entry.getValue();
|
||||
|
||||
final int plotX = ChunkPos.getX(pos);
|
||||
final int plotZ = ChunkPos.getZ(pos);
|
||||
|
||||
final SubLevel subLevel = container.getSubLevel(plotX, plotZ);
|
||||
|
||||
if (subLevel == null || !Objects.equals(subLevel.getUniqueId(), poseEntry.subLevelID)) {
|
||||
iter.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: some other form of removal when we don't have any more flywheel contraption visuals to worry about?
|
||||
|
||||
updateEntry(container, (ClientSubLevel) subLevel, poseEntry, partialTicks);
|
||||
}
|
||||
}
|
||||
|
||||
public static SubLevelFlwRenderState getInfo(final long plotCoord) {
|
||||
return RENDER_POSES.get(plotCoord);
|
||||
}
|
||||
|
||||
private static void updateEntry(final ClientSubLevelContainer container, final ClientSubLevel clientSubLevel, final SubLevelFlwRenderState poseEntry, final float partialTicks) {
|
||||
poseEntry.sceneID = container.getLightingSceneId(clientSubLevel);
|
||||
poseEntry.subLevelID = clientSubLevel.getUniqueId();
|
||||
poseEntry.renderPose.set(clientSubLevel.renderPose(partialTicks));
|
||||
poseEntry.latestSkyLightScale = clientSubLevel.getLatestSkyLightScale();
|
||||
poseEntry.centerChunk = clientSubLevel.getPlot().getCenterChunk();
|
||||
}
|
||||
|
||||
public static void createRenderInfo(final Level level, final SubLevel subLevel) {
|
||||
final ClientSubLevelContainer container = (ClientSubLevelContainer) SubLevelContainer.getContainer(level);
|
||||
if (container == null) return;
|
||||
|
||||
final ChunkPos plotPos = subLevel.getPlot().plotPos;
|
||||
final long plotCoord = ChunkPos.asLong(plotPos.x - container.getOrigin().x, plotPos.z - container.getOrigin().y);
|
||||
|
||||
RENDER_POSES.computeIfAbsent(plotCoord, x -> {
|
||||
final SubLevelFlwRenderState renderState = new SubLevelFlwRenderState();
|
||||
updateEntry(container, (ClientSubLevel) subLevel, renderState, 1.0f);
|
||||
return renderState;
|
||||
});
|
||||
}
|
||||
|
||||
public static class SubLevelFlwRenderState {
|
||||
public int sceneID;
|
||||
public final Pose3d renderPose = new Pose3d();
|
||||
public UUID subLevelID;
|
||||
public float latestSkyLightScale;
|
||||
public ChunkPos centerChunk;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package dev.ryanhcode.sable.neoforge.compatibility.flywheel;
|
||||
|
||||
public class SableFlywheelEmbeddingUniforms {
|
||||
public static final String SCENE_MATRIX = "_flw_lightingSceneMatrixUniform";
|
||||
public static final String SCENE = "_flw_lightingSceneUniform";
|
||||
public static final String SKY_LIGHT_SCALE = "_flw_lightingSkyLightScaleUniform";
|
||||
}
|
||||
+462
@@ -0,0 +1,462 @@
|
||||
package dev.ryanhcode.sable.neoforge.compatibility.flywheel;
|
||||
|
||||
import dev.engine_room.flywheel.api.task.Plan;
|
||||
import dev.engine_room.flywheel.api.visual.EffectVisual;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationManager;
|
||||
import dev.engine_room.flywheel.backend.BackendDebugFlags;
|
||||
import dev.engine_room.flywheel.backend.engine.LightStorage;
|
||||
import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
|
||||
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
|
||||
import dev.engine_room.flywheel.lib.task.SimplePlan;
|
||||
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
|
||||
import dev.engine_room.flywheel.lib.visual.component.HitboxComponent;
|
||||
import dev.engine_room.flywheel.lib.visual.util.InstanceRecycler;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.sublevel.ClientSubLevelContainer;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.neoforge.mixin.compatibility.flywheel.LightStorageAccessor;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.plot.LevelPlot;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
public class SableFlywheelLightStorage extends LightStorage {
|
||||
private static final int INVALID_SECTION = -1;
|
||||
public static final int STATIC_SCENE_ID = 0;
|
||||
|
||||
private final SableLightLut sableLut;
|
||||
private final Int2ObjectMap<Long2IntMap> scene2SectionArenaIndexMap;
|
||||
|
||||
private final BitSet changed = new BitSet();
|
||||
private final LongSet updatedSections = new LongOpenHashSet();
|
||||
private boolean isDebugOn = false;
|
||||
|
||||
@Nullable
|
||||
private LongSet requestedSections;
|
||||
|
||||
public SableFlywheelLightStorage(final LevelAccessor level) {
|
||||
super(level);
|
||||
this.sableLut = new SableLightLut();
|
||||
this.scene2SectionArenaIndexMap = new Int2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EffectVisual<?> visualize(final VisualizationContext ctx, final float partialTick) {
|
||||
return new SableFlywheelLightStorage.DebugVisual(ctx, partialTick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C> Plan<C> createFramePlan() {
|
||||
return SimplePlan.of(() -> {
|
||||
if (BackendDebugFlags.LIGHT_STORAGE_VIEW != this.isDebugOn) {
|
||||
final var visualizationManager = VisualizationManager.get(this.level());
|
||||
|
||||
// Really should be non-null, but just in case.
|
||||
if (visualizationManager != null) {
|
||||
if (BackendDebugFlags.LIGHT_STORAGE_VIEW) {
|
||||
visualizationManager.effects()
|
||||
.queueAdd(this);
|
||||
} else {
|
||||
visualizationManager.effects()
|
||||
.queueRemove(this);
|
||||
}
|
||||
}
|
||||
this.isDebugOn = BackendDebugFlags.LIGHT_STORAGE_VIEW;
|
||||
}
|
||||
|
||||
if (this.updatedSections.isEmpty() && this.requestedSections == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateLightSections();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the set of requested sections.
|
||||
* <p> When set, this will be processed in the next frame plan. It may not be set every frame.
|
||||
*
|
||||
* @param sections The set of sections requested by the impl.
|
||||
*/
|
||||
@Override
|
||||
public void sections(final LongSet sections) {
|
||||
this.requestedSections = sections;
|
||||
}
|
||||
|
||||
private void updateLightSections() {
|
||||
this.removeUnusedSections();
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final ClientLevel level = Minecraft.getInstance().level;
|
||||
final ClientSubLevelContainer container = SubLevelContainer.getContainer(level);
|
||||
|
||||
final Int2ObjectMap<LongSet> sectionsToCollect;
|
||||
if (this.requestedSections == null) {
|
||||
sectionsToCollect = new Int2ObjectOpenHashMap<>();
|
||||
} else {
|
||||
sectionsToCollect = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
for (final long section : this.requestedSections) {
|
||||
final SectionPos sectionPos = SectionPos.of(section);
|
||||
final SubLevel subLevel = helper.getContaining(level, sectionPos);
|
||||
|
||||
int lightingSceneId = 0;
|
||||
|
||||
if (subLevel instanceof final ClientSubLevel clientSubLevel) {
|
||||
final int subLevelLightingSceneId = container.getLightingSceneId(clientSubLevel);
|
||||
|
||||
if (subLevelLightingSceneId != -1) {
|
||||
lightingSceneId = subLevelLightingSceneId;
|
||||
}
|
||||
}
|
||||
|
||||
sectionsToCollect.computeIfAbsent(lightingSceneId, x -> new LongOpenHashSet())
|
||||
.add(section);
|
||||
}
|
||||
}
|
||||
|
||||
for (final int scene : this.scene2SectionArenaIndexMap.keySet()) {
|
||||
final Long2IntMap section2ArenaIndex = this.scene2SectionArenaIndexMap.get(scene);
|
||||
final LongSet longs = sectionsToCollect.get(scene);
|
||||
|
||||
if (longs != null) {
|
||||
longs.removeAll(section2ArenaIndex.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
for (final long updatedSection : this.updatedSections) {
|
||||
for (int x = -1; x <= 1; ++x) {
|
||||
for (int y = -1; y <= 1; ++y) {
|
||||
for (int z = -1; z <= 1; ++z) {
|
||||
final long section = SectionPos.offset(updatedSection, x, y, z);
|
||||
|
||||
final SectionPos sectionPos = SectionPos.of(section);
|
||||
final SubLevel subLevel = helper.getContaining(level, sectionPos);
|
||||
|
||||
if (subLevel instanceof final ClientSubLevel clientSubLevel) {
|
||||
final int lightingSceneId = container.getLightingSceneId(clientSubLevel);
|
||||
|
||||
if (lightingSceneId != -1) {
|
||||
final Long2IntMap map = this.scene2SectionArenaIndexMap.get(lightingSceneId);
|
||||
|
||||
if (map != null && map.containsKey(section)) {
|
||||
sectionsToCollect.computeIfAbsent(lightingSceneId, ignored -> new LongOpenHashSet()).add(section);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.scene2SectionArenaIndexMap.values().stream().anyMatch(map -> map.containsKey(section))) {
|
||||
sectionsToCollect.computeIfAbsent(0, ignored -> new LongOpenHashSet()).add(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Int2ObjectMap.Entry<LongSet> entry : sectionsToCollect.int2ObjectEntrySet()) {
|
||||
final int scene = entry.getIntKey();
|
||||
final LongSet sections = entry.getValue();
|
||||
|
||||
for (final long section : sections) {
|
||||
this.collectSection(scene, section);
|
||||
}
|
||||
}
|
||||
|
||||
this.updatedSections.clear();
|
||||
this.requestedSections = null;
|
||||
}
|
||||
|
||||
public void onLightUpdate(final long section) {
|
||||
this.updatedSections.add(section);
|
||||
}
|
||||
|
||||
public void collectSection(final int scene, final long section) {
|
||||
final int index = this.indexForSection(scene, section);
|
||||
|
||||
this.changed.set(index);
|
||||
|
||||
final long ptr = this.arena.indexToPointer(index);
|
||||
|
||||
// Zero it out first. This is basically free and makes it easier to handle missing sections later.
|
||||
MemoryUtil.memSet(ptr, 0, LightStorage.SECTION_SIZE_BYTES);
|
||||
|
||||
((LightStorageAccessor) this).getCollector().collectSection(ptr, section);
|
||||
}
|
||||
|
||||
private int indexForSection(final int scene, long section) {
|
||||
final Long2IntMap map = this.scene2SectionArenaIndexMap.get(scene);
|
||||
int out = map != null ? map.get(section) : INVALID_SECTION;
|
||||
|
||||
// Need to allocate.
|
||||
if (out == INVALID_SECTION) {
|
||||
out = this.arena.alloc();
|
||||
this.scene2SectionArenaIndexMap.computeIfAbsent(scene, (ignored) -> {
|
||||
final Long2IntOpenHashMap newMap = new Long2IntOpenHashMap();
|
||||
newMap.defaultReturnValue(INVALID_SECTION);
|
||||
return newMap;
|
||||
}).put(section, out);
|
||||
|
||||
final SectionPos sectionPos = SectionPos.of(section);
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(sectionPos);
|
||||
|
||||
if (subLevel != null) {
|
||||
final LevelPlot plot = subLevel.getPlot();
|
||||
final ChunkPos centerChunk = plot.getCenterChunk();
|
||||
section = SectionPos.asLong(
|
||||
sectionPos.x() - centerChunk.x,
|
||||
sectionPos.y(),
|
||||
sectionPos.z() - centerChunk.z
|
||||
);
|
||||
}
|
||||
|
||||
this.beginTrackingSection(scene, section, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void removeUnusedSections() {
|
||||
if (this.requestedSections == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ClientLevel world = Minecraft.getInstance().level;
|
||||
|
||||
boolean anyRemoved = false;
|
||||
|
||||
for (final Int2ObjectMap.Entry<Long2IntMap> sceneEntry : this.scene2SectionArenaIndexMap.int2ObjectEntrySet()) {
|
||||
final int sceneId = sceneEntry.getIntKey();
|
||||
final Long2IntMap section2ArenaIndex = sceneEntry.getValue();
|
||||
|
||||
final var entries = section2ArenaIndex.long2IntEntrySet();
|
||||
final var it = entries.iterator();
|
||||
while (it.hasNext()) {
|
||||
final var entry = it.next();
|
||||
final var section = entry.getLongKey();
|
||||
|
||||
if (!this.requestedSections.contains(section)) {
|
||||
this.arena.free(entry.getIntValue());
|
||||
|
||||
var localSection = section;
|
||||
final SectionPos sectionPos = SectionPos.of(section);
|
||||
final SubLevelContainer container = SubLevelContainer.getContainer(world);
|
||||
|
||||
if (container != null && container.inBounds(sectionPos.x(), sectionPos.z())) {
|
||||
final int logPlotSize = container.getLogPlotSize();
|
||||
final int plotX = (sectionPos.x() >> logPlotSize);
|
||||
final int plotZ = (sectionPos.z() >> logPlotSize);
|
||||
|
||||
localSection = SectionPos.asLong(sectionPos.x() - ((plotX << logPlotSize) + (1 << (logPlotSize - 1))),
|
||||
sectionPos.y(),
|
||||
sectionPos.z() - ((plotZ << logPlotSize) + (1 << (logPlotSize - 1))));
|
||||
}
|
||||
|
||||
this.endTrackingSection(sceneId, localSection);
|
||||
it.remove();
|
||||
anyRemoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anyRemoved) {
|
||||
this.sableLut.prune();
|
||||
((LightStorageAccessor)this).setNeedsLutRebuild(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void beginTrackingSection(final int scene, final long section, final int index) {
|
||||
this.sableLut.add(scene, section, index);
|
||||
((LightStorageAccessor)this).setNeedsLutRebuild(true);
|
||||
}
|
||||
|
||||
private void endTrackingSection(final int scene, final long section) {
|
||||
this.sableLut.remove(scene, section);
|
||||
((LightStorageAccessor)this).setNeedsLutRebuild(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadChangedSections(final StagingBuffer staging, final int dstVbo) {
|
||||
for (int i = this.changed.nextSetBit(0); i >= 0; i = this.changed.nextSetBit(i + 1)) {
|
||||
staging.enqueueCopy(this.arena.indexToPointer(i), SECTION_SIZE_BYTES, dstVbo, (long) i * SECTION_SIZE_BYTES);
|
||||
}
|
||||
this.changed.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upload(final GlBuffer buffer) {
|
||||
if (this.changed.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.upload(this.arena.indexToPointer(0), (long) this.arena.capacity() * SECTION_SIZE_BYTES);
|
||||
this.changed.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntArrayList createLut() {
|
||||
return this.sableLut.flatten();
|
||||
}
|
||||
|
||||
public class DebugVisual implements EffectVisual<LightStorage>, SimpleDynamicVisual {
|
||||
|
||||
private final InstanceRecycler<TransformedInstance> boxes;
|
||||
private final Vec3i renderOrigin;
|
||||
|
||||
public DebugVisual(final VisualizationContext ctx, final float partialTick) {
|
||||
this.renderOrigin = ctx.renderOrigin();
|
||||
this.boxes = new InstanceRecycler<>(() -> ctx.instancerProvider()
|
||||
.instancer(InstanceTypes.TRANSFORMED, HitboxComponent.BOX_MODEL)
|
||||
.createInstance());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginFrame(final Context ctx) {
|
||||
this.boxes.resetCount();
|
||||
|
||||
this.setupSectionBoxes();
|
||||
this.setupLutRangeBoxes();
|
||||
|
||||
this.boxes.discardExtra();
|
||||
}
|
||||
|
||||
private void setupSectionBoxes() {
|
||||
for (final Int2ObjectMap.Entry<Long2IntMap> entry : SableFlywheelLightStorage.this.scene2SectionArenaIndexMap.int2ObjectEntrySet()) {
|
||||
final int sceneId = entry.getIntKey();
|
||||
final Long2IntMap section2ArenaIndex = entry.getValue();
|
||||
section2ArenaIndex.keySet()
|
||||
.forEach(l -> {
|
||||
final var x = SectionPos.x(l) * 16 - this.renderOrigin.getX();
|
||||
final var y = SectionPos.y(l) * 16 - this.renderOrigin.getY();
|
||||
final var z = SectionPos.z(l) * 16 - this.renderOrigin.getZ();
|
||||
|
||||
final var instance = this.boxes.get();
|
||||
|
||||
// Slightly smaller than a full 16x16x16 section to make it obvious which sections
|
||||
// are actually represented when many are tiled next to each other.
|
||||
instance.setIdentityTransform()
|
||||
.translate(x + 1, y + 1, z + 1)
|
||||
.scale(14)
|
||||
.color(255, 255, sceneId * 64)
|
||||
.light(LightTexture.FULL_BRIGHT)
|
||||
.setChanged();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setupLutRangeBoxes() {
|
||||
final var first = SableFlywheelLightStorage.this.sableLut.indices;
|
||||
|
||||
final var base1 = first.base();
|
||||
final var size1 = first.size();
|
||||
|
||||
final float debug1 = base1 * 16 - this.renderOrigin.getY();
|
||||
|
||||
float min2 = Float.POSITIVE_INFINITY;
|
||||
float max2 = Float.NEGATIVE_INFINITY;
|
||||
|
||||
float min3 = Float.POSITIVE_INFINITY;
|
||||
float max3 = Float.NEGATIVE_INFINITY;
|
||||
|
||||
for (int y = 0; y < size1; y++) {
|
||||
final var second = first.getRaw(y);
|
||||
|
||||
if (second == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var base2 = second.base();
|
||||
final var size2 = second.size();
|
||||
|
||||
final float y2 = (base1 + y) * 16 - this.renderOrigin.getY() + 7.5f;
|
||||
|
||||
min2 = Math.min(min2, base2);
|
||||
max2 = Math.max(max2, base2 + size2);
|
||||
|
||||
float minLocal3 = Float.POSITIVE_INFINITY;
|
||||
float maxLocal3 = Float.NEGATIVE_INFINITY;
|
||||
|
||||
final float debug2 = base2 * 16 - this.renderOrigin.getX();
|
||||
|
||||
for (int x = 0; x < size2; x++) {
|
||||
final var third = second.getRaw(x);
|
||||
|
||||
if (third == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final var base3 = third.base();
|
||||
final var size3 = third.size();
|
||||
|
||||
final float x2 = (base2 + x) * 16 - this.renderOrigin.getX() + 7.5f;
|
||||
|
||||
min3 = Math.min(min3, base3);
|
||||
max3 = Math.max(max3, base3 + size3);
|
||||
|
||||
minLocal3 = Math.min(minLocal3, base3);
|
||||
maxLocal3 = Math.max(maxLocal3, base3 + size3);
|
||||
|
||||
final float debug3 = base3 * 16 - this.renderOrigin.getZ();
|
||||
|
||||
for (int z = 0; z < size3; z++) {
|
||||
this.boxes.get()
|
||||
.setIdentityTransform()
|
||||
.translate(x2, y2, debug3)
|
||||
.scale(1, 1, size3 * 16)
|
||||
.color(0, 0, 255)
|
||||
.light(LightTexture.FULL_BRIGHT)
|
||||
.setChanged();
|
||||
}
|
||||
}
|
||||
|
||||
this.boxes.get()
|
||||
.setIdentityTransform()
|
||||
.translate(debug2, y2, minLocal3 * 16 - this.renderOrigin.getZ())
|
||||
.scale(size2 * 16, 1, (maxLocal3 - minLocal3) * 16)
|
||||
.color(255, 0, 0)
|
||||
.light(LightTexture.FULL_BRIGHT)
|
||||
.setChanged();
|
||||
}
|
||||
|
||||
this.boxes.get()
|
||||
.setIdentityTransform()
|
||||
.translate(min2 * 16 - this.renderOrigin.getX(), debug1, min3 * 16 - this.renderOrigin.getZ())
|
||||
.scale((max2 - min2) * 16, size1 * 16, (max3 - min3) * 16)
|
||||
.color(0, 255, 0)
|
||||
.light(LightTexture.FULL_BRIGHT)
|
||||
.setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final float partialTick) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
this.boxes.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package dev.ryanhcode.sable.neoforge.compatibility.flywheel;
|
||||
|
||||
public class SableFlywheelMatrixBuffer {
|
||||
public static final int INFO_SIZE_BYTES = (16 + 12) * Float.BYTES +
|
||||
Float.BYTES + // sky light scale
|
||||
Integer.BYTES + // scene ID
|
||||
2 * Float.BYTES + // padding
|
||||
16 * Float.BYTES; // scene matrix
|
||||
}
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
package dev.ryanhcode.sable.neoforge.compatibility.flywheel;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A light look-up table based on the Flywheel / RogueLogix LUT scheme, with an extra layer for the lighting scene ID
|
||||
* First layer is studio, Y, then X, then Z.
|
||||
*/
|
||||
public final class SableLightLut {
|
||||
public final Layer<Layer<Layer<IntLayer>>> indices = new Layer<>();
|
||||
|
||||
public void add(final int scene, final long position, final int index) {
|
||||
final var x = SectionPos.x(position);
|
||||
final var y = SectionPos.y(position);
|
||||
final var z = SectionPos.z(position);
|
||||
|
||||
this.indices.computeIfAbsent(scene, Layer::new)
|
||||
.computeIfAbsent(y, Layer::new)
|
||||
.computeIfAbsent(x, IntLayer::new)
|
||||
.set(z, index + 1);
|
||||
}
|
||||
|
||||
public void prune() {
|
||||
// Maybe this could be done better incrementally?
|
||||
this.indices.prune((scene) -> scene.prune((middle) -> middle.prune(IntLayer::prune)));
|
||||
}
|
||||
|
||||
public void remove(final int scene, final long section) {
|
||||
final var x = SectionPos.x(section);
|
||||
final var y = SectionPos.y(section);
|
||||
final var z = SectionPos.z(section);
|
||||
|
||||
final var first = this.indices.get(scene);
|
||||
|
||||
if (first == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var second = first.get(y);
|
||||
|
||||
if (second == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var third = second.get(x);
|
||||
|
||||
if (third == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
third.clear(z);
|
||||
}
|
||||
|
||||
public IntArrayList flatten() {
|
||||
final var out = new IntArrayList();
|
||||
this.indices.fillLut(out, (sceneIndices, lut1) -> sceneIndices.fillLut(lut1,
|
||||
(yIndices, lut2) -> yIndices.fillLut(lut2, IntLayer::fillLut)
|
||||
));
|
||||
return out;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Prune<T> {
|
||||
boolean prune(T t);
|
||||
}
|
||||
|
||||
public static final class Layer<T> {
|
||||
private boolean hasBase = false;
|
||||
private int base = 0;
|
||||
private Object[] nextLayer = new Object[0];
|
||||
|
||||
public void fillLut(final IntArrayList lut, final BiConsumer<T, IntArrayList> inner) {
|
||||
lut.add(this.base);
|
||||
lut.add(this.nextLayer.length);
|
||||
|
||||
final int innerIndexBase = lut.size();
|
||||
|
||||
// Reserve space for the inner indices...
|
||||
lut.size(innerIndexBase + this.nextLayer.length);
|
||||
|
||||
for (int i = 0; i < this.nextLayer.length; i++) {
|
||||
final var innerIndices = (T) this.nextLayer[i];
|
||||
if (innerIndices == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int layerPosition = lut.size();
|
||||
|
||||
// ...so we can write in their actual positions later.
|
||||
lut.set(innerIndexBase + i, layerPosition);
|
||||
|
||||
// Append the next layer to the lut.
|
||||
inner.accept(innerIndices, lut);
|
||||
}
|
||||
}
|
||||
|
||||
public int base() {
|
||||
return this.base;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.nextLayer.length;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T getRaw(final int i) {
|
||||
if (i < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (i >= this.nextLayer.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (T) this.nextLayer[i];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T get(final int i) {
|
||||
if (!this.hasBase) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getRaw(i - this.base);
|
||||
}
|
||||
|
||||
public T computeIfAbsent(final int i, final Supplier<T> ifAbsent) {
|
||||
if (!this.hasBase) {
|
||||
// We don't want to default to base 0, so we'll use the first value we get.
|
||||
this.base = i;
|
||||
this.hasBase = true;
|
||||
}
|
||||
|
||||
if (i < this.base) {
|
||||
this.rebase(i);
|
||||
}
|
||||
|
||||
final var offset = i - this.base;
|
||||
|
||||
if (offset >= this.nextLayer.length) {
|
||||
this.resize(offset + 1);
|
||||
}
|
||||
|
||||
var out = this.nextLayer[offset];
|
||||
|
||||
if (out == null) {
|
||||
out = ifAbsent.get();
|
||||
this.nextLayer[offset] = out;
|
||||
}
|
||||
return (T) out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the layer is now empty.
|
||||
*/
|
||||
public boolean prune(final Prune<T> inner) {
|
||||
if (!this.hasBase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prune the next layer before checking for leading/trailing zeros.
|
||||
for (var i = 0; i < this.nextLayer.length; i++) {
|
||||
final var o = this.nextLayer[i];
|
||||
if (o != null && inner.prune((T) o)) {
|
||||
this.nextLayer[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
final var leadingZeros = this.getLeadingZeros();
|
||||
|
||||
if (leadingZeros == this.nextLayer.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final var trailingZeros = this.getTrailingZeros();
|
||||
|
||||
if (leadingZeros == 0 && trailingZeros == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var newIndices = new Object[this.nextLayer.length - leadingZeros - trailingZeros];
|
||||
|
||||
System.arraycopy(this.nextLayer, leadingZeros, newIndices, 0, newIndices.length);
|
||||
this.nextLayer = newIndices;
|
||||
this.base += leadingZeros;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getLeadingZeros() {
|
||||
int out = 0;
|
||||
|
||||
for (final Object index : this.nextLayer) {
|
||||
if (index == null) {
|
||||
out++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private int getTrailingZeros() {
|
||||
int out = 0;
|
||||
|
||||
for (int i = this.nextLayer.length - 1; i >= 0; i--) {
|
||||
if (this.nextLayer[i] == null) {
|
||||
out++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void resize(final int length) {
|
||||
final var newIndices = new Object[length];
|
||||
System.arraycopy(this.nextLayer, 0, newIndices, 0, this.nextLayer.length);
|
||||
this.nextLayer = newIndices;
|
||||
}
|
||||
|
||||
private void rebase(final int newBase) {
|
||||
final var growth = this.base - newBase;
|
||||
|
||||
final var newIndices = new Object[this.nextLayer.length + growth];
|
||||
// Shift the existing elements to the end of the new array to maintain their offset with the new base.
|
||||
System.arraycopy(this.nextLayer, 0, newIndices, growth, this.nextLayer.length);
|
||||
|
||||
this.nextLayer = newIndices;
|
||||
this.base = newBase;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class IntLayer {
|
||||
private boolean hasBase = false;
|
||||
private int base = 0;
|
||||
private int[] indices = new int[0];
|
||||
|
||||
public void fillLut(final IntArrayList lut) {
|
||||
lut.add(this.base);
|
||||
lut.add(this.indices.length);
|
||||
|
||||
for (final int index : this.indices) {
|
||||
lut.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
public int base() {
|
||||
return this.base;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.indices.length;
|
||||
}
|
||||
|
||||
public int getRaw(final int i) {
|
||||
if (i < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (i >= this.indices.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.indices[i];
|
||||
}
|
||||
|
||||
public int get(final int i) {
|
||||
if (!this.hasBase) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.getRaw(i - this.base);
|
||||
}
|
||||
|
||||
public void set(final int i, final int index) {
|
||||
if (!this.hasBase) {
|
||||
this.base = i;
|
||||
this.hasBase = true;
|
||||
}
|
||||
|
||||
if (i < this.base) {
|
||||
this.rebase(i);
|
||||
}
|
||||
|
||||
final var offset = i - this.base;
|
||||
|
||||
if (offset >= this.indices.length) {
|
||||
this.resize(offset + 1);
|
||||
}
|
||||
|
||||
this.indices[offset] = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the layer is now empty.
|
||||
*/
|
||||
public boolean prune() {
|
||||
if (!this.hasBase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final var leadingZeros = this.getLeadingZeros();
|
||||
|
||||
if (leadingZeros == this.indices.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final var trailingZeros = this.getTrailingZeros();
|
||||
|
||||
if (leadingZeros == 0 && trailingZeros == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var newIndices = new int[this.indices.length - leadingZeros - trailingZeros];
|
||||
|
||||
System.arraycopy(this.indices, leadingZeros, newIndices, 0, newIndices.length);
|
||||
this.indices = newIndices;
|
||||
this.base += leadingZeros;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getTrailingZeros() {
|
||||
int out = 0;
|
||||
|
||||
for (int i = this.indices.length - 1; i >= 0; i--) {
|
||||
if (this.indices[i] == 0) {
|
||||
out++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private int getLeadingZeros() {
|
||||
int out = 0;
|
||||
|
||||
for (final int index : this.indices) {
|
||||
if (index == 0) {
|
||||
out++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void clear(final int i) {
|
||||
if (!this.hasBase) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (i < this.base) {
|
||||
return;
|
||||
}
|
||||
|
||||
final var offset = i - this.base;
|
||||
|
||||
if (offset >= this.indices.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.indices[offset] = 0;
|
||||
}
|
||||
|
||||
private void resize(final int length) {
|
||||
final var newIndices = new int[length];
|
||||
System.arraycopy(this.indices, 0, newIndices, 0, this.indices.length);
|
||||
this.indices = newIndices;
|
||||
}
|
||||
|
||||
private void rebase(final int newBase) {
|
||||
final var growth = this.base - newBase;
|
||||
|
||||
final var newIndices = new int[this.indices.length + growth];
|
||||
System.arraycopy(this.indices, 0, newIndices, growth, this.indices.length);
|
||||
|
||||
this.indices = newIndices;
|
||||
this.base = newBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package dev.ryanhcode.sable.neoforge.event;
|
||||
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import net.neoforged.bus.api.Event;
|
||||
|
||||
/**
|
||||
* Fired when Sable's {@link SubLevelPhysicsSystem} is complete with a physics tick.
|
||||
* </br>
|
||||
* Note that multiple physics ticks are completed per game tick, based on the amount of configured sub-steps.
|
||||
* Logic that needs to influence the physics world should occur on the physics tick, and not the game tick
|
||||
* due to this reason.
|
||||
*/
|
||||
public class ForgeSablePostPhysicsTickEvent extends Event {
|
||||
private final SubLevelPhysicsSystem physicsSystem;
|
||||
private final double timeStep;
|
||||
|
||||
public ForgeSablePostPhysicsTickEvent(final SubLevelPhysicsSystem physicsSystem, final double timeStep) {
|
||||
this.physicsSystem = physicsSystem;
|
||||
this.timeStep = timeStep;
|
||||
}
|
||||
|
||||
public SubLevelPhysicsSystem getPhysicsSystem() {
|
||||
return this.physicsSystem;
|
||||
}
|
||||
|
||||
public double getTimeStep() {
|
||||
return this.timeStep;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package dev.ryanhcode.sable.neoforge.event;
|
||||
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import net.neoforged.bus.api.Event;
|
||||
|
||||
/**
|
||||
* Fired when Sable's {@link dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem} is ticking physics.
|
||||
* </br>
|
||||
* Note that multiple physics ticks are completed per game tick, based on the amount of configured sub-steps.
|
||||
* Logic that needs to influence the physics world should occur on the physics tick, and not the game tick
|
||||
* due to this reason.
|
||||
*/
|
||||
public class ForgeSablePrePhysicsTickEvent extends Event {
|
||||
private final SubLevelPhysicsSystem physicsSystem;
|
||||
private final double timeStep;
|
||||
|
||||
public ForgeSablePrePhysicsTickEvent(final SubLevelPhysicsSystem physicsSystem, final double timeStep) {
|
||||
this.physicsSystem = physicsSystem;
|
||||
this.timeStep = timeStep;
|
||||
}
|
||||
|
||||
public SubLevelPhysicsSystem getPhysicsSystem() {
|
||||
return this.physicsSystem;
|
||||
}
|
||||
|
||||
public double getTimeStep() {
|
||||
return this.timeStep;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package dev.ryanhcode.sable.neoforge.event;
|
||||
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.neoforged.bus.api.Event;
|
||||
|
||||
/**
|
||||
* Fired when Sable has finished initialization for a level and its sub-level container is ready to use.
|
||||
*/
|
||||
public class ForgeSableSubLevelContainerReadyEvent extends Event {
|
||||
private final Level level;
|
||||
private final SubLevelContainer container;
|
||||
|
||||
public ForgeSableSubLevelContainerReadyEvent(final Level level, final SubLevelContainer container) {
|
||||
this.level = level;
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
public SubLevelContainer getContainer() {
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package dev.ryanhcode.sable.neoforge.gametest;
|
||||
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.SubLevelAssemblyHelper;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3i;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3ic;
|
||||
import dev.ryanhcode.sable.api.sublevel.ServerSubLevelContainer;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestAssertPosException;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3i;
|
||||
import org.joml.Vector3ic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@GameTestHolder(Sable.MOD_ID)
|
||||
public final class AssemblyTest {
|
||||
|
||||
@GameTest(template = "brittlebreak")
|
||||
public static void testBrittleBreaking(final GameTestHelper helper) {
|
||||
final ServerLevel level = helper.getLevel();
|
||||
final ServerSubLevelContainer plotContainer = SubLevelContainer.getContainer(level);
|
||||
if (plotContainer == null) {
|
||||
throw new IllegalStateException("Plot container not found in level");
|
||||
}
|
||||
|
||||
final SubLevelPhysicsSystem physicsSystem = plotContainer.physicsSystem();
|
||||
if (physicsSystem == null) {
|
||||
throw new IllegalStateException("Plot container does not have physics");
|
||||
}
|
||||
|
||||
final BlockPos min = helper.absolutePos(new BlockPos(0, 1, 0));
|
||||
final BlockPos max = helper.absolutePos(new BlockPos(2, 3, 2));
|
||||
final BoundingBox3i bounds = new BoundingBox3i(
|
||||
min.getX(), min.getY(), min.getZ(),
|
||||
max.getX(), max.getY(), max.getZ()
|
||||
);
|
||||
|
||||
final List<BlockState> expectedStates = new ArrayList<>(bounds.volume());
|
||||
for (final BlockPos pos : BlockPos.betweenClosed(min, max)) {
|
||||
expectedStates.add(level.getBlockState(pos));
|
||||
}
|
||||
|
||||
final ServerSubLevel subLevel = SubLevelAssemblyHelper.assembleBlocks(level, min, BlockPos.betweenClosed(min, max), bounds);
|
||||
physicsSystem.getPipeline().teleport(subLevel,
|
||||
new Vector3d(min.getX() + (1 + max.getX() - min.getX()) / 2.0,
|
||||
min.getY() + (1 + max.getY() - min.getY()) / 2.0,
|
||||
min.getZ() + (1 + max.getZ() - min.getZ()) / 2.0),
|
||||
helper.getTestRotation().rotation().transformation().getNormalizedRotation(new Quaterniond()));
|
||||
helper.runAtTickTime(10, () -> {
|
||||
final Level plot = subLevel.getLevel();
|
||||
final BoundingBox3ic sublevelBounds = subLevel.getPlot().getBoundingBox();
|
||||
final Vector3ic actualSize = sublevelBounds.size(new Vector3i());
|
||||
final Vector3ic expectedSize = bounds.size(new Vector3i());
|
||||
if (actualSize.equals(expectedSize)) {
|
||||
int i = 0;
|
||||
for (final BlockPos pos : BlockPos.betweenClosed(sublevelBounds.minX(),
|
||||
sublevelBounds.minY(),
|
||||
sublevelBounds.minZ(),
|
||||
sublevelBounds.maxX(),
|
||||
sublevelBounds.maxY(),
|
||||
sublevelBounds.maxZ())) {
|
||||
final BlockState expected = expectedStates.get(i);
|
||||
if (!plot.getBlockState(pos).equals(expected)) {
|
||||
throw new GameTestAssertPosException("Expected %s".formatted(expected.getBlock().getName().getString()), pos, pos, helper.getTick());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
helper.succeed();
|
||||
} else {
|
||||
helper.fail("Expected %dx%dx%d region, got %dx%dx%d".formatted(
|
||||
expectedSize.x(),
|
||||
expectedSize.y(),
|
||||
expectedSize.z(),
|
||||
actualSize.x(),
|
||||
actualSize.y(),
|
||||
actualSize.z()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package dev.ryanhcode.sable.neoforge.gametest;
|
||||
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.physics.handle.RigidBodyHandle;
|
||||
import dev.ryanhcode.sable.api.sublevel.ServerSubLevelContainer;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestAssertPosException;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3dc;
|
||||
|
||||
import static dev.ryanhcode.sable.neoforge.gametest.SableTestHelper.*;
|
||||
|
||||
@GameTestHolder(Sable.MOD_ID)
|
||||
public final class PhysicsTest {
|
||||
|
||||
@GameTest(template = "continuouscollision")
|
||||
public static void testContinuousCollision(final GameTestHelper helper) {
|
||||
final ServerLevel level = helper.getLevel();
|
||||
final ServerSubLevelContainer plotContainer = SubLevelContainer.getContainer(level);
|
||||
if (plotContainer == null) {
|
||||
throw new IllegalStateException("Plot container not found in level");
|
||||
}
|
||||
|
||||
final SubLevelPhysicsSystem physicsSystem = plotContainer.physicsSystem();
|
||||
if (physicsSystem == null) {
|
||||
throw new IllegalStateException("Plot container does not have physics");
|
||||
}
|
||||
|
||||
final ServerSubLevel subLevel = spawnSingleBlockSubLevel(plotContainer, absolutePosition(helper, new Vector3d(2.5, 4, 1.5)), Blocks.GLASS.defaultBlockState());
|
||||
final RigidBodyHandle handle = physicsSystem.getPhysicsHandle(subLevel);
|
||||
final Vector3d impulse = absoluteDirection(helper, new Vector3d(0, 10, 20));
|
||||
|
||||
helper.startSequence()
|
||||
.thenExecuteAfter(10, () -> handle.applyLinearImpulse(impulse))
|
||||
.thenExecuteFor(40, () -> {
|
||||
final Vector3d globalPos = subLevel.logicalPose().position();
|
||||
final Vector3d localPos = localPosition(helper, globalPos);
|
||||
if (localPos.z >= 9 || !isInBounds(helper, globalPos)) {
|
||||
helper.fail("Sublevel passed through wall", BlockPos.containing(localPos.x, localPos.y, localPos.z));
|
||||
}
|
||||
}).thenSucceed();
|
||||
}
|
||||
|
||||
// FIXME allow manual tests to run automatically when rapier is set to 64-bit mode
|
||||
@GameTest(template = "gravity", required = false)
|
||||
public static void testGravity(final GameTestHelper helper) {
|
||||
final ServerLevel level = helper.getLevel();
|
||||
final ServerSubLevelContainer plotContainer = SubLevelContainer.getContainer(level);
|
||||
if (plotContainer == null) {
|
||||
throw new IllegalStateException("Plot container not found in level");
|
||||
}
|
||||
|
||||
final SubLevelPhysicsSystem physicsSystem = plotContainer.physicsSystem();
|
||||
if (physicsSystem == null) {
|
||||
throw new IllegalStateException("Plot container does not have physics");
|
||||
}
|
||||
|
||||
final Vector3dc spawnPos = absolutePosition(helper, new Vector3d(2.5, 12, 2.5));
|
||||
final ServerSubLevel subLevel = spawnSingleBlockSubLevel(plotContainer, spawnPos, Blocks.DIAMOND_BLOCK.defaultBlockState());
|
||||
|
||||
helper.runAfterDelay(20, () -> {
|
||||
if (subLevel.isRemoved()) {
|
||||
helper.fail("Sublevel was removed");
|
||||
return;
|
||||
}
|
||||
|
||||
final Vector3dc gravity = DimensionPhysicsData.getGravity(helper.getLevel(), spawnPos);
|
||||
final RigidBodyHandle handle = physicsSystem.getPhysicsHandle(subLevel);
|
||||
final Vector3dc linearVelocity = handle.getLinearVelocity(new Vector3d());
|
||||
|
||||
if (!gravity.equals(linearVelocity, 1e-2)) {
|
||||
final Vector3d localPos = localPosition(helper, spawnPos);
|
||||
helper.fail("Sublevel velocity didn't follow gravity: Delta: " + gravity.distance(linearVelocity), BlockPos.containing(localPos.x, localPos.y, localPos.z));
|
||||
return;
|
||||
}
|
||||
|
||||
// 1/2 * a * t * t
|
||||
// t = 1
|
||||
final Vector3d expectedDelta = gravity.mul(0.5, new Vector3d());
|
||||
final Vector3d delta = subLevel.logicalPose().position().sub(spawnPos, new Vector3d());
|
||||
|
||||
// FIXME for some reason the sublevels don't have a consistent distance travelled. It seems like they aren't spawned on the first tick?
|
||||
if (!expectedDelta.equals(delta, 1e-2)) {
|
||||
final Vector3d localPos = localPosition(helper, spawnPos);
|
||||
helper.fail("Sublevel position didn't follow gravity. Delta: " + expectedDelta.distance(delta), BlockPos.containing(localPos.x, localPos.y, localPos.z));
|
||||
}
|
||||
|
||||
helper.succeed();
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "snag", attempts = 10, requiredSuccesses = 10, required = false)
|
||||
public static void testSnag(final GameTestHelper helper) {
|
||||
final ServerLevel level = helper.getLevel();
|
||||
final ServerSubLevelContainer plotContainer = SubLevelContainer.getContainer(level);
|
||||
if (plotContainer == null) {
|
||||
throw new IllegalStateException("Plot container not found in level");
|
||||
}
|
||||
|
||||
final SubLevelPhysicsSystem physicsSystem = plotContainer.physicsSystem();
|
||||
if (physicsSystem == null) {
|
||||
throw new IllegalStateException("Plot container does not have physics");
|
||||
}
|
||||
|
||||
final Vector3dc spawnPos = absolutePosition(helper, new Vector3d(13, 3.5, 3.5));
|
||||
final ServerSubLevel subLevel = spawnSingleBlockSubLevel(plotContainer, spawnPos, Blocks.DIAMOND_BLOCK.defaultBlockState());
|
||||
final RigidBodyHandle handle = physicsSystem.getPhysicsHandle(subLevel);
|
||||
final Vector3d impulse = absoluteDirection(helper, new Vector3d(-60, 0, 0));
|
||||
|
||||
helper.startSequence()
|
||||
.thenExecuteAfter(10, () -> handle.applyLinearImpulse(impulse))
|
||||
.thenExecuteFor(40, () -> {
|
||||
final Vector3d globalPos = subLevel.logicalPose().position();
|
||||
final Vector3d localPos = localPosition(helper, globalPos);
|
||||
if (localPos.x <= 9 && isInBounds(helper, globalPos)) {
|
||||
helper.succeed();
|
||||
}
|
||||
}).thenFail(() -> {
|
||||
final Vector3dc position = subLevel.logicalPose().position();
|
||||
final BlockPos globalPos = BlockPos.containing(position.x(), position.y(), position.z());
|
||||
return new GameTestAssertPosException("Sub-level got stuck", globalPos, helper.relativePos(globalPos), helper.getTick());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package dev.ryanhcode.sable.neoforge.gametest;
|
||||
|
||||
import dev.ryanhcode.sable.companion.math.Pose3d;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.plot.LevelPlot;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.CommonLevelAccessor;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3dc;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class SableTestHelper {
|
||||
|
||||
public static ServerSubLevel spawnSubLevel(final SubLevelContainer plotContainer, final Vector3dc pos, final Consumer<CommonLevelAccessor> setter) {
|
||||
final Pose3d pose = new Pose3d();
|
||||
pose.position().set(pos);
|
||||
|
||||
final SubLevel subLevel = plotContainer.allocateNewSubLevel(pose);
|
||||
final LevelPlot plot = subLevel.getPlot();
|
||||
|
||||
final ChunkPos center = plot.getCenterChunk();
|
||||
plot.newEmptyChunk(center);
|
||||
|
||||
setter.accept(plot.getEmbeddedLevelAccessor());
|
||||
subLevel.updateLastPose();
|
||||
return (ServerSubLevel) subLevel;
|
||||
}
|
||||
|
||||
public static ServerSubLevel spawnSingleBlockSubLevel(final SubLevelContainer plotContainer, final Vector3dc pos, final BlockState state) {
|
||||
return spawnSubLevel(plotContainer, pos, accessor -> accessor.setBlock(BlockPos.ZERO, state, 3));
|
||||
}
|
||||
|
||||
public static Vector3d absoluteDirection(final GameTestHelper helper, final Vector3dc localDirection) {
|
||||
return new Vector3d(localDirection).rotateY(-getAngle(helper.getTestRotation()));
|
||||
}
|
||||
|
||||
public static Vector3d localDirection(final GameTestHelper helper, final Vector3dc globalDirection) {
|
||||
return new Vector3d(globalDirection).rotateY(getAngle(helper.getTestRotation()));
|
||||
}
|
||||
|
||||
public static Vector3d absolutePosition(final GameTestHelper helper, final Vector3dc localPosition) {
|
||||
final BlockPos origin = helper.testInfo.getStructureBlockPos();
|
||||
final Vector3d pos = localPosition.sub(0.5, 0.5, 0.5, new Vector3d()).rotateY(-getAngle(helper.getTestRotation()));
|
||||
return pos.add(origin.getX() + 0.5, origin.getY() + 0.5, origin.getZ() + 0.5);
|
||||
}
|
||||
|
||||
public static Vector3d localPosition(final GameTestHelper helper, final Vector3dc globalPosition) {
|
||||
final BlockPos origin = helper.testInfo.getStructureBlockPos();
|
||||
final Vector3d pos = globalPosition.sub(origin.getX(), origin.getY(), origin.getZ(), new Vector3d());
|
||||
return pos.rotateY(getAngle(helper.getTestRotation()));
|
||||
}
|
||||
|
||||
public static double getAngle(final Rotation rotation) {
|
||||
return switch (rotation) {
|
||||
case NONE -> 0;
|
||||
case CLOCKWISE_90 -> Math.PI / 2.0;
|
||||
case CLOCKWISE_180 -> Math.PI;
|
||||
case COUNTERCLOCKWISE_90 -> -Math.PI / 2.0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified global position is within the bounds of the test.
|
||||
*
|
||||
* @param helper The game test helper instance
|
||||
* @param globalPosition The position in global space to check
|
||||
* @return Whether that position is inside the bounds of the test
|
||||
*/
|
||||
public static boolean isInBounds(final GameTestHelper helper, final Vector3dc globalPosition) {
|
||||
final AABB box = helper.getBounds();
|
||||
return box.contains(globalPosition.x(), globalPosition.y(), globalPosition.z());
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.block_entity_visible;
|
||||
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3d;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(value = LevelRenderer.class, priority = 2000)
|
||||
public class LevelRendererMixin {
|
||||
|
||||
/**
|
||||
* @author RyanH
|
||||
* @reason Take sub-levels into account for visibility check
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Redirect(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/client/ClientHooks;isBlockEntityRendererVisible(Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderDispatcher;Lnet/minecraft/world/level/block/entity/BlockEntity;Lnet/minecraft/client/renderer/culling/Frustum;)Z"), require = 0)
|
||||
private static <T extends BlockEntity> boolean isBlockEntityRendererVisible(final BlockEntityRenderDispatcher dispatcher, final BlockEntity blockEntity, final Frustum frustum) {
|
||||
final BlockEntityRenderer<T> renderer = (BlockEntityRenderer<T>) dispatcher.getRenderer(blockEntity);
|
||||
|
||||
if (renderer == null) return false;
|
||||
|
||||
AABB renderBounds = renderer.getRenderBoundingBox((T) blockEntity);
|
||||
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(renderBounds.getCenter());
|
||||
|
||||
if (subLevel != null) {
|
||||
final BoundingBox3d bb = new BoundingBox3d(renderBounds);
|
||||
renderBounds = bb.transform(subLevel.logicalPose(), bb).toMojang();
|
||||
}
|
||||
|
||||
return frustum.isVisible(renderBounds);
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.block_outline_render;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3dc;
|
||||
import dev.ryanhcode.sable.mixinhelpers.block_outline_render.SubLevelCamera;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Quaterniondc;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3dc;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Transforms block hover outlines for sublevels.
|
||||
*/
|
||||
@Mixin(value = LevelRenderer.class, priority = 2000) // This makes sure it applies after normal mixins
|
||||
public abstract class LevelRendererMixin {
|
||||
|
||||
// Storage vectors to avoid repeated allocation
|
||||
private final @Unique Quaternionf sable$orientationStorage = new Quaternionf();
|
||||
private final @Unique SubLevelCamera sable$sublevelCamera = new SubLevelCamera();
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
private ClientLevel level;
|
||||
|
||||
@WrapOperation(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/client/ClientHooks;onDrawHighlight(Lnet/minecraft/client/renderer/LevelRenderer;Lnet/minecraft/client/Camera;Lnet/minecraft/world/phys/HitResult;Lnet/minecraft/client/DeltaTracker;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;)Z"))
|
||||
private boolean sable$preRenderHitOutline(final LevelRenderer context, final Camera camera, final HitResult target, final DeltaTracker deltaTracker, final PoseStack poseStack, final MultiBufferSource bufferSource, final Operation<Boolean> original, @Share("drawn") final LocalBooleanRef drawnRef) {
|
||||
if (!(target instanceof final BlockHitResult blockTarget)) {
|
||||
return original.call(context, camera, target, deltaTracker, poseStack, bufferSource);
|
||||
}
|
||||
|
||||
final BlockPos blockPos = blockTarget.getBlockPos();
|
||||
final ClientSubLevel subLevel = (ClientSubLevel) Sable.HELPER.getContaining(this.level, blockPos);
|
||||
|
||||
if (subLevel == null) {
|
||||
return original.call(context, camera, target, deltaTracker, poseStack, bufferSource);
|
||||
}
|
||||
|
||||
poseStack.pushPose();
|
||||
|
||||
final Pose3dc pose = subLevel.renderPose();
|
||||
|
||||
this.sable$sublevelCamera.setCamera(camera);
|
||||
this.sable$sublevelCamera.setPose(pose);
|
||||
final Vec3 cameraPosition = this.sable$sublevelCamera.getPosition();
|
||||
final Vec3 realCameraPosition = camera.getPosition();
|
||||
|
||||
final Vector3dc position = pose.position();
|
||||
final Vector3dc rotationPoint = pose.rotationPoint();
|
||||
final Quaterniondc orientation = pose.orientation();
|
||||
final Vector3dc scale = pose.scale();
|
||||
|
||||
poseStack.translate(
|
||||
(float) (position.x() - realCameraPosition.x),
|
||||
(float) (position.y() - realCameraPosition.y),
|
||||
(float) (position.z() - realCameraPosition.z)
|
||||
);
|
||||
poseStack.mulPose(this.sable$orientationStorage.set(orientation));
|
||||
poseStack.translate(
|
||||
(float) -(rotationPoint.x() - cameraPosition.x),
|
||||
(float) -(rotationPoint.y() - cameraPosition.y),
|
||||
(float) -(rotationPoint.z() - cameraPosition.z)
|
||||
);
|
||||
poseStack.scale((float) scale.x(), (float) scale.y(), (float) scale.z());
|
||||
|
||||
drawnRef.set(true);
|
||||
return original.call(context, this.sable$sublevelCamera, target, deltaTracker, poseStack, bufferSource);
|
||||
}
|
||||
|
||||
@Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/debug/DebugRenderer;render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;DDD)V"))
|
||||
public void sable$poseRenderHitOutline(final CallbackInfo ci, @Local final PoseStack poseStack, @Share("drawn") final LocalBooleanRef drawnRef) {
|
||||
if (drawnRef.get()) {
|
||||
poseStack.popPose();
|
||||
this.sable$sublevelCamera.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyArg(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;renderHitOutline(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/entity/Entity;DDDLnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V"), index = 3)
|
||||
public double modifyX(final double original, @Share("drawn") final LocalBooleanRef drawnRef) {
|
||||
return drawnRef.get() ? this.sable$sublevelCamera.getPosition().x : original;
|
||||
}
|
||||
|
||||
@ModifyArg(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;renderHitOutline(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/entity/Entity;DDDLnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V"), index = 4)
|
||||
public double modifyY(final double original, @Share("drawn") final LocalBooleanRef drawnRef) {
|
||||
return drawnRef.get() ? this.sable$sublevelCamera.getPosition().y : original;
|
||||
}
|
||||
|
||||
@ModifyArg(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;renderHitOutline(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/entity/Entity;DDDLnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V"), index = 5)
|
||||
public double modifyZ(final double original, @Share("drawn") final LocalBooleanRef drawnRef) {
|
||||
return drawnRef.get() ? this.sable$sublevelCamera.getPosition().z : original;
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.camera_rotation;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3dc;
|
||||
import dev.ryanhcode.sable.mixinhelpers.camera.camera_rotation.EntitySubLevelRotationHelper;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.client.event.ViewportEvent;
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
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;
|
||||
|
||||
@Mixin(Camera.class)
|
||||
public abstract class CameraMixin {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static Vector3f FORWARDS;
|
||||
@Shadow
|
||||
@Final
|
||||
private static Vector3f UP;
|
||||
@Shadow
|
||||
@Final
|
||||
private static Vector3f LEFT;
|
||||
@Shadow
|
||||
@Final
|
||||
private Quaternionf rotation;
|
||||
@Shadow
|
||||
@Final
|
||||
private Vector3f left;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Vector3f up;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Vector3f forwards;
|
||||
|
||||
@Shadow
|
||||
private float yRot;
|
||||
|
||||
@Shadow
|
||||
private float xRot;
|
||||
|
||||
@Shadow
|
||||
private Entity entity;
|
||||
|
||||
@Shadow protected abstract void setRotation(float f, float g, float roll);
|
||||
|
||||
@Redirect(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setRotation(FFF)V", ordinal = 1))
|
||||
private void sable$redirectSetRotation(final Camera camera, final float f, final float g, final float roll, @Local final ViewportEvent.ComputeCameraAngles event) {
|
||||
this.setRotation(event.getYaw() + 180.0f, -event.getPitch(), roll);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "setPosition(Lnet/minecraft/world/phys/Vec3;)V")
|
||||
private void sable$setPosition(final Vec3 arg, final Operation<Void> original) {
|
||||
if (this.entity == null) {
|
||||
original.call(arg);
|
||||
return;
|
||||
}
|
||||
final Level level = this.entity.level();
|
||||
|
||||
final ClientSubLevel subLevel = (ClientSubLevel) Sable.HELPER.getContaining(level, arg);
|
||||
if(subLevel == null) {
|
||||
original.call(arg);
|
||||
return;
|
||||
}
|
||||
|
||||
final Pose3dc pose = subLevel.renderPose();
|
||||
final Vec3 pos = pose.transformPosition(arg);
|
||||
original.call(pos);
|
||||
}
|
||||
|
||||
@Inject(method = "setRotation(FFF)V", at = @At(value = "INVOKE", target = "Lorg/joml/Quaternionf;rotationYXZ(FFF)Lorg/joml/Quaternionf;", shift = At.Shift.AFTER))
|
||||
public void sable$rotateView(final float f, final float g, final float roll, final CallbackInfo ci) {
|
||||
final float pt = Minecraft.getInstance().getTimer().getGameTimeDeltaPartialTick(true);
|
||||
final Quaterniond ridingOrientation = EntitySubLevelRotationHelper.getEntityOrientation(this.entity, (x) -> ((ClientSubLevel) x).renderPose(), pt, EntitySubLevelRotationHelper.Type.CAMERA);
|
||||
|
||||
if (ridingOrientation != null) {
|
||||
this.rotation.premul(new Quaternionf(ridingOrientation));
|
||||
FORWARDS.rotate(this.rotation, this.forwards);
|
||||
UP.rotate(this.rotation, this.up);
|
||||
LEFT.rotate(this.rotation, this.left);
|
||||
|
||||
final Vector3f euler = this.rotation.getEulerAnglesYXZ(new Vector3f());
|
||||
this.yRot = -180.0f - (float) Math.toDegrees(euler.y);
|
||||
this.xRot = (float) -Math.toDegrees(euler.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.backpacks;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
|
||||
import com.spydnel.backpacks.events.BackpackPickupEvents;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(BackpackPickupEvents.class)
|
||||
public class BackpackPickupEventsMixin {
|
||||
|
||||
@Inject(method = "onRightClickBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;isUnobstructed(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/phys/shapes/CollisionContext;)Z"), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
|
||||
private static void sable$onRightClickBlock(final PlayerInteractEvent.RightClickBlock event, final CallbackInfo ci, @Local(name = "isAbove") final LocalBooleanRef isAbove) {
|
||||
final Player player = event.getEntity();
|
||||
final BlockPos pos = event.getPos();
|
||||
final SubLevel containing = Sable.HELPER.getContaining(player.level(), pos);
|
||||
if (containing != null) {
|
||||
final Vec3 world = containing.logicalPose().transformPosition(pos.above().getBottomCenter());
|
||||
isAbove.set(world.y - 0.1 > player.getEyeY());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.airflow;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.fan.AirCurrent;
|
||||
import com.simibubi.create.content.kinetics.fan.IAirCurrentSource;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3d;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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 java.lang.ref.WeakReference;
|
||||
|
||||
@Mixin(AirCurrent.class)
|
||||
public abstract class AirCurrentMixin {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
public IAirCurrentSource source;
|
||||
|
||||
@Unique
|
||||
private WeakReference<SubLevel> sable$subLevelReference;
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
public void sable$updateSubLevel(final CallbackInfo ci) {
|
||||
if (this.sable$subLevelReference == null) {
|
||||
this.sable$subLevelReference = new WeakReference<>(Sable.HELPER.getContaining(this.source.getAirCurrentWorld(), this.source.getAirCurrentPos()));
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "tickAffectedEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getBoundingBox()Lnet/minecraft/world/phys/AABB;"))
|
||||
public AABB sable$reverseProjectEntityBB(final Entity instance) {
|
||||
final SubLevel subLevel = this.sable$subLevelReference.get();
|
||||
if (subLevel != null) {
|
||||
return new BoundingBox3d(instance.getBoundingBox()).transformInverse(subLevel.logicalPose(), new BoundingBox3d()).toMojang();
|
||||
}
|
||||
|
||||
return instance.getBoundingBox();
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tickAffectedEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;setDeltaMovement(Lnet/minecraft/world/phys/Vec3;)V"))
|
||||
public void sable$transformFlowVector(final Entity instance, final Vec3 vec3, final Operation<Void> original, @Local final Vec3i flow, @Local(ordinal = 2) final float acceleration, @Local final Vec3 previousMotion, @Local(ordinal = 3) final float maxAcceleration) {
|
||||
final SubLevel subLevel = this.sable$subLevelReference.get();
|
||||
if (subLevel != null) {
|
||||
final Vector3d nonIntFlow = JOMLConversion.atLowerCornerOf(flow);
|
||||
subLevel.logicalPose().transformNormal(nonIntFlow);
|
||||
|
||||
final double xIn = Mth.clamp((double) ((float) nonIntFlow.get(0) * acceleration) - previousMotion.x, -maxAcceleration, maxAcceleration);
|
||||
final double yIn = Mth.clamp((double) ((float) nonIntFlow.get(1) * acceleration) - previousMotion.y, -maxAcceleration, maxAcceleration);
|
||||
final double zIn = Mth.clamp((double) ((float) nonIntFlow.get(2) * acceleration) - previousMotion.z, -maxAcceleration, maxAcceleration);
|
||||
|
||||
original.call(instance, previousMotion.add(new Vec3(xIn, yIn, zIn).scale(0.125f)));
|
||||
return;
|
||||
}
|
||||
|
||||
original.call(instance, vec3);
|
||||
}
|
||||
|
||||
@Redirect(method = "tickAffectedEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;position()Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$reverseProjectAllPositions(final Entity instance) {
|
||||
final SubLevel subLevel = this.sable$subLevelReference.get();
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPositionInverse(instance.position());
|
||||
}
|
||||
|
||||
return instance.position();
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.airflow;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.simibubi.create.content.kinetics.fan.processing.FanProcessingType;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(FanProcessingType.class)
|
||||
public interface FanProcessingTypeMixin {
|
||||
@WrapMethod(method = "getAt")
|
||||
private static FanProcessingType getAt(Level level, BlockPos pos, Operation<FanProcessingType> original) {
|
||||
ActiveSableCompanion helper = Sable.HELPER;
|
||||
return helper.runIncludingSubLevels(level, pos.getCenter(), true, helper.getContaining(level, pos),
|
||||
(subLevel, relativePos) -> original.call(level, relativePos));
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.basin_interactions;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.processing.basin.BasinBlockEntity;
|
||||
import com.simibubi.create.content.processing.burner.BlazeBurnerBlock;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
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;
|
||||
|
||||
@Mixin(BasinBlockEntity.class)
|
||||
public class BasinBlockEntityMixin extends BlockEntity {
|
||||
|
||||
@Shadow @Nullable private BlazeBurnerBlock.@Nullable HeatLevel cachedHeatLevel;
|
||||
|
||||
public BasinBlockEntityMixin(final BlockEntityType<?> arg, final BlockPos arg2, final BlockState arg3) {
|
||||
super(arg, arg2, arg3);
|
||||
}
|
||||
|
||||
@Inject(method = "getHeatLevel", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/processing/basin/BasinBlockEntity;getHeatLevelOf(Lnet/minecraft/world/level/block/state/BlockState;)Lcom/simibubi/create/content/processing/burner/BlazeBurnerBlock$HeatLevel;", shift = At.Shift.AFTER), cancellable = true)
|
||||
private void sable$accountForSubLevels(final CallbackInfoReturnable<BlazeBurnerBlock.HeatLevel> cir) {
|
||||
if (this.cachedHeatLevel != null && this.cachedHeatLevel != BlazeBurnerBlock.HeatLevel.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Level level = this.getLevel();
|
||||
final BlockPos originalPos = this.getBlockPos().below();
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final BlazeBurnerBlock.HeatLevel heatLevel = helper.runIncludingSubLevels(level, originalPos.getCenter(), false, helper.getContaining(level, originalPos), (subLevel, pos) -> {
|
||||
final BlazeBurnerBlock.HeatLevel internalHeat = BasinBlockEntity.getHeatLevelOf(level.getBlockState(pos));
|
||||
|
||||
if (internalHeat != BlazeBurnerBlock.HeatLevel.NONE) {
|
||||
return internalHeat;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (heatLevel != null) {
|
||||
cir.setReturnValue(heatLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockEntity(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;"))
|
||||
public BlockEntity sable$accountForSubLevels(final Level level, final BlockPos pos, final Operation<BlockEntity> original) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
return helper.runIncludingSubLevels(level, pos.getCenter(), true, helper.getContaining(level, pos), (subLevel, internalPos) -> original.call(level, internalPos));
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.basin_interactions;
|
||||
|
||||
import com.simibubi.create.content.processing.basin.BasinOperatingBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.behaviour.simple.DeferralBehaviour;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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;
|
||||
|
||||
@Mixin(BasinOperatingBlockEntity.class)
|
||||
public abstract class BasinOperatingBlockEntityMixin {
|
||||
@Shadow
|
||||
public DeferralBehaviour basinChecker;
|
||||
|
||||
@Unique
|
||||
private int sable$forceUpdateTicks = 0;
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), remap = false)
|
||||
private void sable$forceUpdate(final CallbackInfo ci) {
|
||||
if (this.sable$forceUpdateTicks == 5) { //only update every 5 ticks
|
||||
this.basinChecker.scheduleUpdate();
|
||||
|
||||
this.sable$forceUpdateTicks = 0;
|
||||
}
|
||||
|
||||
this.sable$forceUpdateTicks++;
|
||||
}
|
||||
|
||||
@Redirect(method = "getBasin", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockEntity(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;"))
|
||||
private BlockEntity sable$accountForSubLevels(final Level level, final BlockPos pos) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
return helper.runIncludingSubLevels(level, pos.getCenter(), true, helper.getContaining(level, pos), (subLevel, internalPos) -> level.getBlockEntity(internalPos));
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility;
|
||||
|
||||
import com.simibubi.create.content.kinetics.belt.behaviour.BeltProcessingBehaviour;
|
||||
import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour;
|
||||
import com.simibubi.create.content.kinetics.belt.behaviour.TransportedItemStackHandlerBehaviour;
|
||||
import com.simibubi.create.foundation.blockEntity.behaviour.BehaviourType;
|
||||
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
|
||||
import com.simibubi.create.foundation.blockEntity.behaviour.edgeInteraction.EdgeInteractionBehaviour;
|
||||
import com.simibubi.create.foundation.blockEntity.behaviour.inventory.InvManipulationBehaviour;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(BlockEntityBehaviour.class)
|
||||
public abstract class BlockEntityBehaviourMixin {
|
||||
|
||||
@Shadow
|
||||
public static <T extends BlockEntityBehaviour> T get(final BlockEntity be, final BehaviourType<T> type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Inject(method = "get(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lcom/simibubi/create/foundation/blockEntity/behaviour/BehaviourType;)Lcom/simibubi/create/foundation/blockEntity/behaviour/BlockEntityBehaviour;",
|
||||
at = @At(value = "HEAD"), remap = false, cancellable = true)
|
||||
private static <T extends BlockEntityBehaviour> void sable$accountForSubLevels(final BlockGetter reader, final BlockPos pos, final BehaviourType<T> type, final CallbackInfoReturnable<T> cir) {
|
||||
if (reader instanceof final Level level && BlockEntityBehaviourMixin.sable$checkType(type)) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final BlockEntity caughtBE = helper.runIncludingSubLevels(level, pos.getCenter(), true, helper.getContaining(level, pos), (subLevel, internalPos) -> level.getBlockEntity(internalPos));
|
||||
|
||||
if (caughtBE != null) {
|
||||
cir.setReturnValue(get(caughtBE, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean sable$checkType(final BehaviourType<?> type) {
|
||||
return type == BeltProcessingBehaviour.TYPE || type == DirectBeltInputBehaviour.TYPE ||
|
||||
type == TransportedItemStackHandlerBehaviour.TYPE || type == InvManipulationBehaviour.TYPE ||
|
||||
type == EdgeInteractionBehaviour.TYPE;
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.block_breaking_behaviour;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.simibubi.create.api.behaviour.movement.MovementBehaviour;
|
||||
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
|
||||
import com.simibubi.create.content.kinetics.base.BlockBreakingMovementBehaviour;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.block_breakers.SubLevelBlockBreakingUtility;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
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(BlockBreakingMovementBehaviour.class)
|
||||
public abstract class BlockBreakingMovementBehaviourMixin implements MovementBehaviour {
|
||||
|
||||
@Shadow
|
||||
public abstract boolean canBreak(Level world, BlockPos breakingPos, BlockState state);
|
||||
|
||||
@WrapMethod(method = "visitNewPosition")
|
||||
public void sable$checkPosition(final MovementContext context, final BlockPos pos, final Operation<Void> original) {
|
||||
if (!context.stall) {
|
||||
original.call(context, pos);
|
||||
|
||||
if (!context.stall) {
|
||||
final Vec3 localCenter = context.localPos.getCenter();
|
||||
final Vec3 sublevelLocalCenter = context.contraption.entity.toGlobalVector(localCenter, 1);
|
||||
final Vec3 subLevelLocalDir = context.rotation.apply(this.getActiveAreaOffset(context));
|
||||
|
||||
|
||||
final BlockPos breakingPosWSublevel = SubLevelBlockBreakingUtility.findBreakingPos(
|
||||
(blockPos, state) -> this.canBreak(context.world, blockPos, state),
|
||||
Sable.HELPER.getContaining(context.world, context.contraption.anchor),
|
||||
context.world,
|
||||
subLevelLocalDir,
|
||||
sublevelLocalCenter,
|
||||
pos
|
||||
);
|
||||
|
||||
original.call(context, breakingPosWSublevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
|
||||
public void sable$testBreakingPosDist(final MovementContext context, final CallbackInfo ci) {
|
||||
final CompoundTag data = context.data;
|
||||
if (data.contains("BreakingPos") || data.contains("LastPos")) {
|
||||
final BlockPos blockPos = NbtUtils.readBlockPos(data, "BreakingPos").orElseGet(() -> NbtUtils.readBlockPos(data, "LastPos").orElse(null));
|
||||
|
||||
if (blockPos != null) {
|
||||
final Vec3 localCenter = context.localPos.getCenter();
|
||||
|
||||
Vec3 sublevelLocalCenter = context.contraption.entity.toGlobalVector(localCenter, 1);
|
||||
Vec3 targetCenter = blockPos.getCenter();
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final SubLevel parentSublevel = helper.getContaining(context.world, context.contraption.anchor);
|
||||
final SubLevel targetSubLevel = helper.getContaining(context.world, blockPos);
|
||||
|
||||
if (parentSublevel != null) {
|
||||
sublevelLocalCenter = parentSublevel.logicalPose().transformPosition(sublevelLocalCenter);
|
||||
}
|
||||
|
||||
if (targetSubLevel != null) {
|
||||
targetCenter = targetSubLevel.logicalPose().transformPosition(targetCenter);
|
||||
}
|
||||
|
||||
if (sublevelLocalCenter.distanceToSqr(targetCenter) > 2 * 2) {
|
||||
data.remove("Progress");
|
||||
data.remove("TicksUntilNextProgress");
|
||||
data.remove("BreakingPos");
|
||||
data.remove("LastPos");
|
||||
data.remove("WaitingTicks");
|
||||
|
||||
context.stall = false;
|
||||
context.world.destroyBlockProgress(data.getInt("BreakerId"), blockPos, -1);
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.block_breaking_behaviour;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
|
||||
import com.simibubi.create.content.kinetics.saw.SawMovementBehaviour;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(SawMovementBehaviour.class)
|
||||
public class SawMovementBehaviourMixin {
|
||||
@Redirect(method = "dropItemFromCutTree", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceTo(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
public double sable$fixSpeed(final Vec3 instance, final Vec3 vec3, @Local(argsOnly = true) final MovementContext context) {
|
||||
return Math.sqrt(Sable.HELPER.distanceSquaredWithSubLevels(context.world, instance, vec3));
|
||||
}
|
||||
|
||||
@Redirect(method = "dropItemFromCutTree", at = @At(value = "FIELD", target = "Lcom/simibubi/create/content/contraptions/behaviour/MovementContext;relativeMotion:Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$fixRelativeMotion(final MovementContext instance, @Local(argsOnly = true) final MovementContext context, @Local(ordinal = 0) final Vec3 dropPos) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final SubLevel parentSublevel = helper.getContaining(context.world, context.contraption.anchor);
|
||||
final SubLevel targetSublevel = helper.getContaining(context.world, dropPos);
|
||||
Vec3 orignalMotion = context.relativeMotion;
|
||||
|
||||
if (parentSublevel != null) {
|
||||
orignalMotion = parentSublevel.logicalPose().transformNormal(orignalMotion);
|
||||
}
|
||||
|
||||
if (targetSublevel != null) {
|
||||
orignalMotion = targetSublevel.logicalPose().transformNormalInverse(orignalMotion);
|
||||
}
|
||||
|
||||
return orignalMotion;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.harvester_behaviour;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterMovementBehaviour;
|
||||
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.behavior_compatibility.harvester_block_entity.DummyMovementContext;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(HarvesterMovementBehaviour.class)
|
||||
public class HarvesterMovementBehaviourMixin {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
@WrapMethod(method = "visitNewPosition")
|
||||
public void sable$checkAllPositions(final MovementContext context, final BlockPos pos, final Operation<Void> original) {
|
||||
if (context instanceof DummyMovementContext) {
|
||||
original.call(context, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
helper.runIncludingSubLevels(context.world, pos.getCenter(), true, helper.getContaining(context.contraption.entity), (sublevel, blockPos) -> {
|
||||
original.call(context, blockPos);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.harvester_block_entity;
|
||||
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.CachedRenderBBBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.block.BlockEntitySubLevelActor;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.harvester.HarvesterLerpedSpeed;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.harvester.HarvesterTicker;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import net.createmod.catnip.animation.LerpedFloat;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Position;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(HarvesterBlockEntity.class)
|
||||
public abstract class HarvesterBlockEntityMixin extends CachedRenderBBBlockEntity implements HarvesterLerpedSpeed, BlockEntitySubLevelActor {
|
||||
|
||||
@Unique
|
||||
private final LerpedFloat sable$lerpedSpeed = LerpedFloat.angular();
|
||||
|
||||
@Unique
|
||||
private BlockPos sable$previousPos = BlockPos.ZERO;
|
||||
|
||||
public HarvesterBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sable$clientTick() {
|
||||
final double velocity = Sable.HELPER.getVelocity(this.getLevel(), JOMLConversion.atCenterOf(this.getBlockPos())).length();
|
||||
this.sable$lerpedSpeed.chase(this.sable$lerpedSpeed.getValue() + (velocity * 5), 20f, LerpedFloat.Chaser.LINEAR);
|
||||
|
||||
this.sable$lerpedSpeed.tickChaser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Server tick
|
||||
*/
|
||||
@Override
|
||||
public void sable$tick(final ServerSubLevel subLevel) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final Position center = this.getBlockPos().getCenter();
|
||||
BlockPos gatheredPos = helper.runIncludingSubLevels(this.level, center, false, helper.getContaining(this), (sublevel, pos) -> {
|
||||
if (HarvesterTicker.blockEntityBehaviour.isValidCrop(this.level, pos, this.level.getBlockState(pos))) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (gatheredPos == null) {
|
||||
gatheredPos = BlockPos.containing(helper.projectOutOfSubLevel(this.level, center));
|
||||
}
|
||||
|
||||
if (!this.sable$previousPos.equals(gatheredPos)) {
|
||||
this.sable$previousPos = gatheredPos;
|
||||
HarvesterTicker.dummyMovementContext.update(this.level, this.getBlockPos(), this.getBlockState(), null);
|
||||
HarvesterTicker.blockEntityBehaviour.visitNewPosition(HarvesterTicker.dummyMovementContext, this.sable$previousPos);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LerpedFloat sable$getLerpedFloat() {
|
||||
return this.sable$lerpedSpeed;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.harvester_block_entity;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterMovementBehaviour;
|
||||
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.behavior_compatibility.harvester_block_entity.DummyMovementContext;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.harvester.HarvesterTicker;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(HarvesterMovementBehaviour.class)
|
||||
public class HarvesterBlockEntityUsageMixin {
|
||||
|
||||
@WrapOperation(method = "lambda$visitNewPosition$0", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/actors/harvester/HarvesterMovementBehaviour;collectOrDropItem(Lcom/simibubi/create/content/contraptions/behaviour/MovementContext;Lnet/minecraft/world/item/ItemStack;)V"))
|
||||
public void sable$replaceDropItem(final HarvesterMovementBehaviour instance, final MovementContext movementContext, final ItemStack itemStack, final Operation<Void> original) {
|
||||
if (movementContext instanceof DummyMovementContext) {
|
||||
HarvesterTicker.dropItem(movementContext.world, itemStack, movementContext.localPos);
|
||||
} else {
|
||||
original.call(instance, movementContext, itemStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.harvester_block_entity;
|
||||
|
||||
import com.simibubi.create.content.contraptions.actors.AttachedActorBlock;
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterBlock;
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterBlockEntity;
|
||||
import com.simibubi.create.foundation.block.IBE;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.harvester.HarvesterTicker;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(HarvesterBlock.class)
|
||||
public abstract class HarvesterBlockMixin extends AttachedActorBlock implements IBE<HarvesterBlockEntity> {
|
||||
protected HarvesterBlockMixin(final Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends BlockEntity> BlockEntityTicker<S> getTicker(final Level level, final BlockState state, final BlockEntityType<S> blockEntityType) {
|
||||
if (level.isClientSide) {
|
||||
return new HarvesterTicker();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.behaviour_compatibility.harvester_block_entity;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterBlockEntity;
|
||||
import com.simibubi.create.content.contraptions.actors.harvester.HarvesterRenderer;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.harvester.HarvesterLerpedSpeed;
|
||||
import net.createmod.catnip.math.AngleHelper;
|
||||
import net.createmod.catnip.render.SuperByteBuffer;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(HarvesterRenderer.class)
|
||||
public class HarvesterRendererMixin {
|
||||
|
||||
@WrapOperation(method = "renderSafe(Lcom/simibubi/create/content/contraptions/actors/harvester/HarvesterBlockEntity;FLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;II)V", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/actors/harvester/HarvesterRenderer;transform(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/Direction;Lnet/createmod/catnip/render/SuperByteBuffer;FLnet/minecraft/world/phys/Vec3;)V"))
|
||||
public void sable$smoothSpeed(final Level world, final Direction facing, final SuperByteBuffer superBuffer, final float speed, final Vec3 pivot, final Operation<Void> original, @Local final HarvesterBlockEntity be, @Local final float pt) {
|
||||
if (be.getAnimatedSpeed() != 0) {
|
||||
original.call(world, facing, superBuffer, speed, pivot);
|
||||
} else { // use our own transformation
|
||||
final float originOffset = 1.0f / 16.0f;
|
||||
final Vec3 rotOffset = new Vec3(0, pivot.y * originOffset, pivot.z * originOffset);
|
||||
|
||||
superBuffer.rotateCentered(AngleHelper.rad(AngleHelper.horizontalAngle(facing)), Direction.UP)
|
||||
.translate(rotOffset.x, rotOffset.y, rotOffset.z)
|
||||
.rotate(AngleHelper.rad(-((HarvesterLerpedSpeed) be).sable$getLerpedFloat().getValue(pt)), Direction.WEST)
|
||||
.translate(-rotOffset.x, -rotOffset.y, -rotOffset.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.belt;
|
||||
|
||||
import com.simibubi.create.content.kinetics.base.KineticBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.belt.BeltBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.sublevel.ServerSubLevelContainer;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(BeltBlockEntity.class)
|
||||
public abstract class BeltBlockEntityMixin extends KineticBlockEntity {
|
||||
|
||||
public BeltBlockEntityMixin(final BlockEntityType<?> typeIn, final BlockPos pos, final BlockState state) {
|
||||
super(typeIn, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpeedChanged(final float previousSpeed) {
|
||||
super.onSpeedChanged(previousSpeed);
|
||||
final SubLevelContainer container = SubLevelContainer.getContainer(this.level);
|
||||
|
||||
if (container instanceof final ServerSubLevelContainer serverSubLevelContainer) {
|
||||
final SubLevelPhysicsSystem physicsSystem = serverSubLevelContainer.physicsSystem();
|
||||
|
||||
final BlockPos blockPos = this.getBlockPos();
|
||||
physicsSystem.wakeUpObjectsAt(blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(this);
|
||||
if (subLevel instanceof final ServerSubLevel serverSubLevel) {
|
||||
physicsSystem.getPipeline().wakeUp(serverSubLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.belt;
|
||||
|
||||
import com.simibubi.create.content.kinetics.belt.BeltBlock;
|
||||
import dev.ryanhcode.sable.api.block.BlockWithSubLevelCollisionCallback;
|
||||
import dev.ryanhcode.sable.api.physics.callback.BlockSubLevelCollisionCallback;
|
||||
import dev.ryanhcode.sable.neoforge.physics.callback.BeltBlockCallback;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
/**
|
||||
* Allows belts on Sub-Levels to obtain entities as passengers
|
||||
*/
|
||||
@Mixin(BeltBlock.class)
|
||||
public class BeltBlockMixin implements BlockWithSubLevelCollisionCallback {
|
||||
|
||||
@Override
|
||||
public BlockSubLevelCollisionCallback sable$getCallback() {
|
||||
return BeltBlockCallback.INSTANCE;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.belt;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import com.simibubi.create.content.kinetics.belt.BeltBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.belt.transport.BeltMovementHandler;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
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.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(BeltMovementHandler.class)
|
||||
public class BeltMovementHandlerMixin {
|
||||
|
||||
@WrapOperation(method = "transportEntity", at = {
|
||||
@At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getY()D", ordinal = 0),
|
||||
@At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getY()D", ordinal = 1),
|
||||
@At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getY()D", ordinal = 2)
|
||||
})
|
||||
private static double sable$getLocalEntityY(final Entity instance, final Operation<Double> original, @Local(argsOnly = true) final BeltBlockEntity be) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(be);
|
||||
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPositionInverse(instance.position()).y;
|
||||
}
|
||||
|
||||
return original.call(instance);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "transportEntity", at = @At("STORE"), ordinal = 0)
|
||||
private static double sable$diffCenter(final double originalValue,
|
||||
@Local(argsOnly = true) final BeltBlockEntity be,
|
||||
@Local(argsOnly = true) final Entity entity,
|
||||
@Local(ordinal = 0) final BlockPos pos,
|
||||
@Local final Direction.Axis axis) {
|
||||
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(be);
|
||||
|
||||
if (subLevel == null) {
|
||||
return originalValue;
|
||||
}
|
||||
|
||||
final Vec3 entityPos = subLevel.logicalPose().transformPositionInverse(entity.position());
|
||||
|
||||
return axis == Direction.Axis.Z ? (pos.getX() + 0.5 - entityPos.x()) : (pos.getZ() + 0.5 - entityPos.z());
|
||||
}
|
||||
|
||||
@Inject(method = "transportEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;maxUpStep()F"))
|
||||
private static void sable$maxUpStep(final BeltBlockEntity beltBE, final Entity entityIn, final BeltMovementHandler.TransportedEntityInfo info, final CallbackInfo ci, @Local(ordinal = 0) final LocalRef<Vec3> movement) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(beltBE);
|
||||
|
||||
if (subLevel != null) {
|
||||
movement.set(subLevel.logicalPose().transformNormal(movement.get()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.belt;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.belt.BeltBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.belt.BeltRenderer;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Make upright items face the the camera properly on belts
|
||||
*/
|
||||
@Mixin(BeltRenderer.class)
|
||||
public class BeltRendererMixin {
|
||||
|
||||
@ModifyExpressionValue(method = "renderItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getPosition()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private Vec3 sable$renderViewEntityPosition(final Vec3 original, @Local(argsOnly = true) final BeltBlockEntity be) {
|
||||
final ClientSubLevel subLevel = Sable.HELPER.getContainingClient(be);
|
||||
if (subLevel != null) {
|
||||
return subLevel.renderPose().transformPositionInverse(original);
|
||||
} else {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.big_outlines_interaction;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.foundation.block.BigOutlines;
|
||||
import com.simibubi.create.foundation.utility.RaycastHelper;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.raycasts.SableRaycastHelper;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import net.createmod.catnip.animation.AnimationTickHolder;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Makes it so big outline tracing (which affects mc.hitResult) is able to target sublevels
|
||||
*/
|
||||
@Mixin(BigOutlines.class)
|
||||
public class BigOutlinesMixin {
|
||||
|
||||
/**
|
||||
* Needed to pass the sublevel from the function to its lambda,
|
||||
* This code is client side only so this should be thread safe
|
||||
*
|
||||
*/
|
||||
@Unique
|
||||
private static ClientSubLevel sable$predicateSubLevel = null;
|
||||
|
||||
@WrapOperation(method = "pick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceToSqr(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
private static double sable$modifyMaxRange(Vec3 worldHitPos,
|
||||
final Vec3 origin,
|
||||
final Operation<Double> original,
|
||||
@Local final Minecraft minecraft) {
|
||||
final ClientSubLevel containing = Sable.HELPER.getContainingClient(worldHitPos);
|
||||
final float pt = AnimationTickHolder.getPartialTicks(minecraft.level);
|
||||
|
||||
if (containing != null) {
|
||||
worldHitPos = containing.renderPose(pt).transformPosition(worldHitPos);
|
||||
}
|
||||
|
||||
return original.call(worldHitPos, origin);
|
||||
}
|
||||
|
||||
@Redirect(method = "pick", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/foundation/utility/RaycastHelper;rayTraceUntil(Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Ljava/util/function/Predicate;)Lcom/simibubi/create/foundation/utility/RaycastHelper$PredicateTraceResult;"))
|
||||
private static RaycastHelper.PredicateTraceResult sable$useSubLevelInclusiveCast(final Vec3 worldOrigin,
|
||||
final Vec3 worldTarget,
|
||||
final Predicate<BlockPos> predicate,
|
||||
@Local final Minecraft minecraft) {
|
||||
return SableRaycastHelper.rayCastUntilWithSublevels(minecraft.level, worldOrigin, worldTarget, (subLevel, pos) -> {
|
||||
sable$predicateSubLevel = (ClientSubLevel) subLevel;
|
||||
return predicate.test(pos);
|
||||
});
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "lambda$pick$0",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/phys/shapes/VoxelShape;clip(Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/BlockHitResult;"
|
||||
)
|
||||
)
|
||||
private static BlockHitResult sable$clipUsingLocalSubLevel(final VoxelShape instance,
|
||||
final Vec3 origin,
|
||||
final Vec3 target,
|
||||
final BlockPos blockPos,
|
||||
final Operation<BlockHitResult> original) {
|
||||
final float pt = AnimationTickHolder.getPartialTicks(Minecraft.getInstance().level);
|
||||
|
||||
if (sable$predicateSubLevel == null) {
|
||||
return original.call(instance, origin, target, blockPos);
|
||||
} else {
|
||||
final Vec3 localOrigin = sable$predicateSubLevel.renderPose(pt).transformPositionInverse(origin);
|
||||
final Vec3 localTarget = sable$predicateSubLevel.renderPose(pt).transformPositionInverse(target);
|
||||
return original.call(instance, localOrigin, localTarget, blockPos);
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "lambda$pick$0",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/phys/Vec3;distanceToSqr(Lnet/minecraft/world/phys/Vec3;)D"
|
||||
)
|
||||
)
|
||||
private static double sable$distanceToWithSubLevel(final Vec3 instance,
|
||||
final Vec3 origin,
|
||||
final Operation<Double> original) {
|
||||
final float pt = AnimationTickHolder.getPartialTicks(Minecraft.getInstance().level);
|
||||
|
||||
if (sable$predicateSubLevel == null) {
|
||||
return original.call(instance, origin);
|
||||
} else {
|
||||
final Vec3 localOrigin = sable$predicateSubLevel.renderPose(pt).transformPositionInverse(origin);
|
||||
return original.call(instance, localOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.blaze_burner;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalDoubleRef;
|
||||
import com.simibubi.create.content.processing.burner.BlazeBurnerBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(BlazeBurnerBlockEntity.class)
|
||||
public abstract class BlazeBurnerBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
@Unique
|
||||
private static Vector3d sable$playerPos = new Vector3d();
|
||||
|
||||
public BlazeBurnerBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Inject(method = "tickAnimation", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/client/player/LocalPlayer;getZ()D"))
|
||||
private void sable$projectPlayerPosition(final CallbackInfo ci, @Local(name = "x") final LocalDoubleRef x, @Local(name = "z") final LocalDoubleRef z, @Local(name = "player") final LocalPlayer player) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(this);
|
||||
if (subLevel != null) {
|
||||
sable$playerPos.set(x.get(), player.getEyeY(), z.get());
|
||||
subLevel.logicalPose().transformPositionInverse(sable$playerPos);
|
||||
x.set(sable$playerPos.x);
|
||||
z.set(sable$playerPos.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.block_breakers;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.drill.DrillBlock;
|
||||
import com.simibubi.create.content.kinetics.saw.SawBlock;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.math.LevelReusedVectors;
|
||||
import dev.ryanhcode.sable.api.math.OrientedBoundingBox3d;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.mixinterface.entity.entity_sublevel_collision.LevelExtension;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.entity_collision.SubLevelEntityCollision;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin({SawBlock.class, DrillBlock.class})
|
||||
public class BlockBreakingKineticBlockEntityDamageMixin {
|
||||
|
||||
@WrapOperation(method = "entityInside", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/AABB;intersects(Lnet/minecraft/world/phys/AABB;)Z"))
|
||||
public boolean sable$fixBlockBreakerDamage(final AABB blockAABB, final AABB entityAABB, final Operation<Boolean> original, @Local(argsOnly = true) final Level level) {
|
||||
final Vector3d jomlCenterPos = JOMLConversion.toJOML(blockAABB.getCenter());
|
||||
final SubLevel parentSublevel = Sable.HELPER.getContaining(level, jomlCenterPos);
|
||||
|
||||
if (parentSublevel != null) {
|
||||
final LevelReusedVectors jomlSink = ((LevelExtension) level).sable$getJOMLSink();
|
||||
|
||||
final Vector3d entityCenter = JOMLConversion.toJOML(entityAABB.getCenter());
|
||||
final Vector3d sideLengths = new Vector3d(entityAABB.getXsize(), entityAABB.getYsize(), entityAABB.getZsize());
|
||||
|
||||
final OrientedBoundingBox3d burnerBounds = new OrientedBoundingBox3d(parentSublevel.logicalPose().transformPosition(jomlCenterPos), new Vector3d(blockAABB.getXsize()), parentSublevel.logicalPose().orientation(), jomlSink);
|
||||
final OrientedBoundingBox3d entityBounds = new OrientedBoundingBox3d(entityCenter, sideLengths, JOMLConversion.QUAT_IDENTITY, jomlSink);
|
||||
|
||||
// use the rotated player hit-box for consistency with sub-level collision
|
||||
jomlSink.entityBoxOrientation.identity();
|
||||
final double yaw = SubLevelEntityCollision.getHitBoxYaw(parentSublevel.logicalPose());
|
||||
jomlSink.entityBoxOrientation.rotateY(yaw);
|
||||
entityBounds.setOrientation(jomlSink.entityBoxOrientation);
|
||||
|
||||
if (OrientedBoundingBox3d.sat(burnerBounds, entityBounds).lengthSquared() > 0.0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return original.call(blockAABB, entityAABB);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.block_breakers;
|
||||
|
||||
import com.simibubi.create.content.kinetics.base.BlockBreakingKineticBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.saw.SawBlock;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.block_breakers.SubLevelBlockBreakingUtility;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Makes blocks such as drills and saws work between sublevels and the main level
|
||||
*/
|
||||
@Mixin(BlockBreakingKineticBlockEntity.class)
|
||||
public abstract class BlockBreakingKineticBlockEntityMixin extends BlockEntity {
|
||||
|
||||
public BlockBreakingKineticBlockEntityMixin(final BlockEntityType<?> pType, final BlockPos pPos, final BlockState pBlockState) {
|
||||
super(pType, pPos, pBlockState);
|
||||
}
|
||||
|
||||
@Shadow
|
||||
public abstract boolean canBreak(BlockState stateToBreak, float blockHardness);
|
||||
|
||||
@Redirect(remap = false, method = "tick", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/kinetics/base/BlockBreakingKineticBlockEntity;getBreakingPos()Lnet/minecraft/core/BlockPos;"))
|
||||
private BlockPos sable$preGetBlockToBreak(final BlockBreakingKineticBlockEntity be) {
|
||||
assert this.level != null;
|
||||
|
||||
final BlockPos breakingPos = this.getBlockPos().relative(this.getBlockState().getValue(BlockStateProperties.FACING));
|
||||
final BlockState originalStateToBreak = this.level.getBlockState(breakingPos);
|
||||
|
||||
if (!this.canBreak(originalStateToBreak, originalStateToBreak.getDestroySpeed(this.level, breakingPos))) {
|
||||
return SubLevelBlockBreakingUtility.findBreakingPos((pos, state) -> this.canBreak(state, state.getDestroySpeed(this.level, pos)),
|
||||
Sable.HELPER.getContaining(this),
|
||||
this.getLevel(),
|
||||
Vec3.atLowerCornerOf(this.getBlockState().getValue(SawBlock.FACING).getNormal()),
|
||||
this.getBlockPos().getCenter(),
|
||||
breakingPos);
|
||||
} else {
|
||||
return breakingPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.blueprint;
|
||||
|
||||
import com.simibubi.create.content.equipment.blueprint.BlueprintEntity;
|
||||
import dev.ryanhcode.sable.annotation.MixinModVersionConstraint;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
@MixinModVersionConstraint("(,6.0.11)")
|
||||
@Mixin(BlueprintEntity.class)
|
||||
public abstract class BlueprintEntityMixin extends Entity {
|
||||
public BlueprintEntityMixin(final EntityType<?> entityType, final Level level) {
|
||||
super(entityType, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author IThundxr
|
||||
* @reason Switch to Player#canInteractWithEntity, which is patched by sable.
|
||||
*/
|
||||
@Overwrite
|
||||
public boolean canPlayerUse(final Player player) {
|
||||
return player.canInteractWithEntity(this, 8);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.chain_conveyor;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorPackage;
|
||||
import net.minecraft.core.BlockPos;
|
||||
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 java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ChainConveyorBlockEntity.class)
|
||||
public abstract class ChainConveyorBlockEntityMixin {
|
||||
|
||||
@Shadow
|
||||
public Map<BlockPos, ChainConveyorBlockEntity.ConnectionStats> connectionStats;
|
||||
|
||||
@Shadow
|
||||
Map<BlockPos, List<ChainConveyorPackage>> travellingPackages;
|
||||
|
||||
@Shadow
|
||||
protected abstract void drop(ChainConveyorPackage box);
|
||||
|
||||
@Inject(method = "removeInvalidConnections", at = @At(value = "INVOKE", target = "Ljava/util/Iterator;remove()V"))
|
||||
public void dropInvalidPackages(final CallbackInfo ci, @Local(name = "next") final BlockPos next) {
|
||||
this.connectionStats.remove(next);
|
||||
|
||||
final List<ChainConveyorPackage> packages = this.travellingPackages.remove(next);
|
||||
if (packages != null && !packages.isEmpty()) {
|
||||
for (final ChainConveyorPackage box : packages) {
|
||||
this.drop(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.chain_conveyor;
|
||||
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlock;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity;
|
||||
import dev.ryanhcode.sable.api.block.BlockSubLevelAssemblyListener;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(ChainConveyorBlock.class)
|
||||
public class ChainConveyorBlockMixin implements BlockSubLevelAssemblyListener {
|
||||
|
||||
@Override
|
||||
public void beforeMove(final ServerLevel originLevel, final ServerLevel resultingLevel, final BlockState newState, final BlockPos oldPos, final BlockPos newPos) {
|
||||
if (originLevel.getBlockEntity(oldPos) instanceof final ChainConveyorBlockEntity be) {
|
||||
// This tells all connected conveyors to re-check and detach if necessary next tick
|
||||
be.notifyConnectedToValidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterMove(final ServerLevel originLevel, final ServerLevel resultingLevel, final BlockState newState, final BlockPos oldPos, final BlockPos newPos) {
|
||||
if (resultingLevel.getBlockEntity(newPos) instanceof final ChainConveyorBlockEntity be) {
|
||||
be.checkInvalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.Contraption;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.block.BlockSubLevelLiftProvider;
|
||||
import dev.ryanhcode.sable.api.physics.mass.MassTracker;
|
||||
import dev.ryanhcode.sable.api.sublevel.KinematicContraption;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3i;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.physics.config.block_properties.PhysicsBlockPropertyHelper;
|
||||
import dev.ryanhcode.sable.physics.floating_block.FloatingBlockCluster;
|
||||
import dev.ryanhcode.sable.physics.floating_block.FloatingClusterContainer;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.createmod.catnip.math.VecHelper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Matrix3d;
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3dc;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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 java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(AbstractContraptionEntity.class)
|
||||
public abstract class AbstractContraptionEntityMixin extends Entity implements KinematicContraption {
|
||||
|
||||
@Unique
|
||||
private final Vector3d sable$cachedGlobalPosition = new Vector3d();
|
||||
@Unique
|
||||
private final Object2ObjectMap<BlockPos, BlockSubLevelLiftProvider.LiftProviderContext> sable$liftProviderContexts = new Object2ObjectOpenHashMap<>();
|
||||
@Unique
|
||||
private final FloatingClusterContainer sable$floatingClusterContainer = new FloatingClusterContainer();
|
||||
@Shadow
|
||||
protected Contraption contraption;
|
||||
@Unique
|
||||
private BoundingBox3i sable$localBounds;
|
||||
@Unique
|
||||
private MassTracker sable$massTracker;
|
||||
@Unique
|
||||
private boolean sable$added = false;
|
||||
|
||||
public AbstractContraptionEntityMixin(final EntityType<?> arg, final Level arg2) {
|
||||
super(arg, arg2);
|
||||
}
|
||||
|
||||
@Shadow
|
||||
public abstract Vec3 applyRotation(Vec3 localPos, float partialTicks);
|
||||
|
||||
@Shadow
|
||||
public abstract Vec3 getPrevAnchorVec();
|
||||
|
||||
@Shadow
|
||||
public abstract Vec3 getAnchorVec();
|
||||
|
||||
@Redirect(method = "moveCollidedEntitiesOnDisassembly", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;toLocalVector(Lnet/minecraft/world/phys/Vec3;F)Lnet/minecraft/world/phys/Vec3;"))
|
||||
private Vec3 sable$applyTransform(final AbstractContraptionEntity instance, final Vec3 localVec, final float partialTicks) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(instance);
|
||||
|
||||
return instance.toLocalVector(subLevel != null ? subLevel.logicalPose().transformPositionInverse(localVec) : localVec, partialTicks);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "moveCollidedEntitiesOnDisassembly", at = {
|
||||
@At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;setPos(DDD)V"),
|
||||
@At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;teleportTo(DDD)V")
|
||||
})
|
||||
private void sable$applyTransform(final Entity instance, final double x, final double y, final double z, final Operation<Void> original) {
|
||||
final Vector3d pos = Sable.HELPER.projectOutOfSubLevel(instance.level(), new Vector3d(x, y, z));
|
||||
|
||||
original.call(instance, pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;"), remap = false)
|
||||
private void sable$contraptionInitialize(final CallbackInfo ci) {
|
||||
if (!this.sable$added && this.level() instanceof final ServerLevel serverLevel) {
|
||||
this.sable$buildProperties();
|
||||
this.sable$addToPlot();
|
||||
this.sable$addToPipeline(serverLevel);
|
||||
this.sable$added = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<BlockPos, BlockSubLevelLiftProvider.LiftProviderContext> sable$liftProviders() {
|
||||
return this.sable$liftProviderContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author RyanH
|
||||
* @reason Players shouldn't be saved to the same chunks as contraptions in sub-levels
|
||||
*/
|
||||
@Overwrite
|
||||
@Override
|
||||
public CompoundTag saveWithoutId(final CompoundTag nbt) {
|
||||
final Vec3 vec = this.position();
|
||||
final List<Entity> passengers = this.getPassengers();
|
||||
|
||||
for (final Entity entity : passengers) {
|
||||
// Only part added by sable \/
|
||||
if (entity instanceof Player) continue;
|
||||
|
||||
// setPos has world accessing side-effects when removed == null
|
||||
entity.removalReason = RemovalReason.UNLOADED_TO_CHUNK;
|
||||
|
||||
// Gather passengers into same chunk when saving
|
||||
final Vec3 prevVec = entity.position();
|
||||
entity.setPosRaw(vec.x, prevVec.y, vec.z);
|
||||
|
||||
// Super requires all passengers to not be removed in order to write them to the
|
||||
// tag
|
||||
entity.removalReason = null;
|
||||
}
|
||||
|
||||
final CompoundTag tag = super.saveWithoutId(nbt);
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void sable$buildProperties() {
|
||||
for (final Map.Entry<BlockPos, StructureTemplate.StructureBlockInfo> entry : this.contraption.getBlocks().entrySet()) {
|
||||
final BlockPos blockPos = entry.getKey();
|
||||
final StructureTemplate.StructureBlockInfo info = entry.getValue();
|
||||
final BlockState state = info.state();
|
||||
|
||||
if (state.isAir()) continue;
|
||||
|
||||
if (this.sable$localBounds == null) {
|
||||
this.sable$localBounds = new BoundingBox3i(blockPos.getX(), blockPos.getY(), blockPos.getZ(), blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
}
|
||||
|
||||
this.sable$localBounds.expandTo(blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
|
||||
if (state.getBlock() instanceof final BlockSubLevelLiftProvider prov) {
|
||||
final BlockSubLevelLiftProvider.LiftProviderContext context = new BlockSubLevelLiftProvider.LiftProviderContext(blockPos, state, Vec3.atLowerCornerOf(prov.sable$getNormal(state).getNormal()));
|
||||
this.sable$liftProviderContexts.put(blockPos, context);
|
||||
}
|
||||
if (PhysicsBlockPropertyHelper.getFloatingMaterial(state) != null)
|
||||
this.sable$floatingClusterContainer.addFloatingBlock(state, new Vector3d(blockPos.getX(), blockPos.getY(), blockPos.getZ()));
|
||||
}
|
||||
|
||||
assert this.sable$localBounds != null;
|
||||
this.sable$massTracker = MassTracker.build(this.sable$blockGetter(), this.sable$localBounds);
|
||||
final Vector3d temp = this.sable$massTracker.getCenterOfMass().negate(new Vector3d()).add(0.5, 0.5, 0.5);
|
||||
for (final FloatingBlockCluster cluster : this.sable$floatingClusterContainer.clusters) {
|
||||
cluster.getBlockData().translateOrigin(temp);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void sable$addToPlot() {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(this);
|
||||
|
||||
if (subLevel != null) {
|
||||
final ServerSubLevel serverSubLevel = (ServerSubLevel) subLevel;
|
||||
|
||||
serverSubLevel.getPlot().addContraption(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void sable$removeFromPlot() {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(this);
|
||||
|
||||
if (subLevel != null) {
|
||||
final ServerSubLevel serverSubLevel = (ServerSubLevel) subLevel;
|
||||
|
||||
serverSubLevel.getPlot().removeContraption(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoved(final RemovalReason removalReason) {
|
||||
if (this.level() instanceof final ServerLevel serverLevel) {
|
||||
this.sable$removeFromPlot();
|
||||
this.sable$removeFromPipeline(serverLevel);
|
||||
}
|
||||
|
||||
super.setRemoved(removalReason);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void sable$addToPipeline(final ServerLevel serverLevel) {
|
||||
final SubLevelPhysicsSystem physics = SubLevelPhysicsSystem.require(serverLevel);
|
||||
physics.getPipeline().add(this);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void sable$removeFromPipeline(final ServerLevel serverLevel) {
|
||||
final SubLevelPhysicsSystem physics = SubLevelPhysicsSystem.require(serverLevel);
|
||||
physics.getPipeline().remove(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sable$getLocalBounds(final BoundingBox3i bounds) {
|
||||
bounds.set(this.sable$localBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockGetter sable$blockGetter() {
|
||||
return this.contraption.getContraptionWorld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MassTracker sable$getMassTracker() {
|
||||
return this.sable$massTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3dc sable$getPosition(final double partialTick) {
|
||||
Vec3 localVec = JOMLConversion.toMojang(this.sable$massTracker.getCenterOfMass());
|
||||
|
||||
final Vec3 anchor = this.getPrevAnchorVec().lerp(this.getAnchorVec(), partialTick);
|
||||
final Vec3 rotationOffset = VecHelper.getCenterOf(BlockPos.ZERO);
|
||||
localVec = localVec.subtract(rotationOffset);
|
||||
localVec = this.applyRotation(localVec, (float) partialTick);
|
||||
localVec = localVec.add(rotationOffset).add(anchor);
|
||||
|
||||
return JOMLConversion.toJOML(localVec, this.sable$cachedGlobalPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Quaterniond sable$getOrientation(final double partialTick) {
|
||||
final Matrix3d matrix = new Matrix3d();
|
||||
final Vector3d tempColumn = new Vector3d();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
matrix.getColumn(i, tempColumn);
|
||||
|
||||
final Vec3 transformed = this.applyRotation(JOMLConversion.toMojang(tempColumn), (float) partialTick);
|
||||
matrix.setColumn(i, transformed.x, transformed.y, transformed.z);
|
||||
}
|
||||
|
||||
return matrix.getNormalizedRotation(new Quaterniond());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sable$isValid() {
|
||||
return !this.isRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sable$shouldCollide() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FloatingClusterContainer sable$getFloatingClusterContainer() {
|
||||
return this.sable$floatingClusterContainer;
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.ContraptionCollider;
|
||||
import com.simibubi.create.foundation.collision.Matrix3d;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3d;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3d;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(value = ContraptionCollider.class, remap = false)
|
||||
public class ContraptionColliderMixin {
|
||||
|
||||
@Unique
|
||||
private static org.joml.Matrix3d sable$toJOML(final Matrix3d createMatrix) {
|
||||
final org.joml.Matrix3d jomlMatrix = new org.joml.Matrix3d();
|
||||
|
||||
final Matrix3dAccessor accessor = ((Matrix3dAccessor) createMatrix);
|
||||
jomlMatrix.set(
|
||||
accessor.getM00(), accessor.getM01(), accessor.getM02(),
|
||||
accessor.getM10(), accessor.getM11(), accessor.getM12(),
|
||||
accessor.getM20(), accessor.getM21(), accessor.getM22()
|
||||
);
|
||||
|
||||
return jomlMatrix;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static Matrix3d sable$toCreate(final org.joml.Matrix3d jomlMatrix) {
|
||||
final Matrix3d createMatrix = new Matrix3d();
|
||||
final Matrix3dAccessor accessor = ((Matrix3dAccessor) createMatrix);
|
||||
|
||||
accessor.setM00(jomlMatrix.m00);
|
||||
accessor.setM01(jomlMatrix.m01);
|
||||
accessor.setM02(jomlMatrix.m02);
|
||||
|
||||
accessor.setM10(jomlMatrix.m10);
|
||||
accessor.setM11(jomlMatrix.m11);
|
||||
accessor.setM12(jomlMatrix.m12);
|
||||
|
||||
accessor.setM20(jomlMatrix.m20);
|
||||
accessor.setM21(jomlMatrix.m21);
|
||||
accessor.setM22(jomlMatrix.m22);
|
||||
|
||||
return createMatrix;
|
||||
}
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;getBoundingBox()Lnet/minecraft/world/phys/AABB;"))
|
||||
private static AABB sable$contraptionBounds(final AbstractContraptionEntity instance, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(instance);
|
||||
contraptionSubLevel.set(subLevel);
|
||||
|
||||
if (subLevel != null) {
|
||||
final BoundingBox3d globalBB = new BoundingBox3d(instance.getBoundingBox());
|
||||
globalBB.transform(subLevel.logicalPose(), globalBB);
|
||||
return globalBB.toMojang();
|
||||
}
|
||||
|
||||
return instance.getBoundingBox();
|
||||
}
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/AABB;expandTowards(DDD)Lnet/minecraft/world/phys/AABB;"))
|
||||
private static AABB sable$entityQueryBounds(final AABB instance, final double d, final double e, final double f, @Local(argsOnly = true) final AbstractContraptionEntity contraption, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
|
||||
if (subLevel != null) {
|
||||
final BoundingBox3d globalBB = new BoundingBox3d(contraption.getBoundingBox().inflate(2.0).expandTowards(d, e, f));
|
||||
globalBB.transform(subLevel.logicalPose(), globalBB);
|
||||
return globalBB.toMojang();
|
||||
}
|
||||
|
||||
return instance.expandTowards(d, e, f);
|
||||
}
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;position()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$contraptionPosition(final AbstractContraptionEntity instance, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPosition(instance.position());
|
||||
}
|
||||
|
||||
return instance.position();
|
||||
}
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;getPrevPositionVec()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$getPrevPositionVec(final AbstractContraptionEntity instance, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPosition(instance.getPrevPositionVec());
|
||||
}
|
||||
|
||||
return instance.getPrevPositionVec();
|
||||
}
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;getAnchorVec()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$getAnchorVec(final AbstractContraptionEntity instance, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPosition(instance.getAnchorVec().add(0.5, 0.5, 0.5)).subtract(0.5, 0.5, 0.5);
|
||||
}
|
||||
|
||||
return instance.getAnchorVec();
|
||||
}
|
||||
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity$ContraptionRotationState;asMatrix()Lcom/simibubi/create/foundation/collision/Matrix3d;"))
|
||||
private static Matrix3d sable$rotationMatrix(final AbstractContraptionEntity.ContraptionRotationState rotationState, @Local(argsOnly = true) final AbstractContraptionEntity contraption, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
if (subLevel != null) {
|
||||
final Pose3d pose = subLevel.logicalPose();
|
||||
final org.joml.Matrix3d jomlMatrix = sable$toJOML(rotationState.asMatrix());
|
||||
|
||||
jomlMatrix.rotateLocal(pose.orientation());
|
||||
return sable$toCreate(jomlMatrix);
|
||||
}
|
||||
|
||||
return rotationState.asMatrix();
|
||||
}
|
||||
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;toLocalVector(Lnet/minecraft/world/phys/Vec3;F)Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$toLocalVector(final AbstractContraptionEntity instance, final Vec3 localVec, final float partialTicks, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
|
||||
if (subLevel != null) {
|
||||
final Pose3d pose = subLevel.logicalPose();
|
||||
return instance.toLocalVector(pose.transformPositionInverse(localVec), partialTicks);
|
||||
}
|
||||
|
||||
return instance.toLocalVector(localVec, partialTicks);
|
||||
}
|
||||
@Redirect(method = "collideEntities", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;getContactPointMotion(Lnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$getContactPointMotion(final AbstractContraptionEntity instance, final Vec3 globalContactPoint, @Share("subLevel") final LocalRef<SubLevel> contraptionSubLevel) {
|
||||
final SubLevel subLevel = contraptionSubLevel.get();
|
||||
if (subLevel != null) {
|
||||
final Pose3d pose = subLevel.logicalPose();
|
||||
final Vec3 localContactPoint = pose.transformPositionInverse(globalContactPoint);
|
||||
final Vec3 motion = pose.transformNormal(instance.getContactPointMotion(localContactPoint))
|
||||
.add(globalContactPoint.subtract(subLevel.lastPose().transformPosition(localContactPoint)));
|
||||
return motion;
|
||||
}
|
||||
|
||||
return instance.getContactPointMotion(globalContactPoint);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.simibubi.create.content.contraptions.actors.contraptionControls.ContraptionControlsRenderer;
|
||||
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
|
||||
import com.simibubi.create.content.contraptions.render.ContraptionMatrices;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Fixes the rendering for the Contraption Controls to take sublevels into account
|
||||
*/
|
||||
@Mixin(value = ContraptionControlsRenderer.class, remap = false)
|
||||
public class ContraptionControlsRendererMixin {
|
||||
@Redirect(method = "renderInContraption",
|
||||
at = @At(value = "FIELD", target = "Lcom/simibubi/create/content/contraptions/behaviour/MovementContext;position:Lnet/minecraft/world/phys/Vec3;", ordinal = 1))
|
||||
private static Vec3 sable$distanceRemix(final MovementContext instance) {
|
||||
return Sable.HELPER.projectOutOfSubLevel(instance.world, instance.position);
|
||||
}
|
||||
|
||||
@Redirect(method = "renderInContraption", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/render/ContraptionMatrices;getViewProjection()Lcom/mojang/blaze3d/vertex/PoseStack;"))
|
||||
private static PoseStack sable$getViewProjection(final ContraptionMatrices instance) {
|
||||
return instance.getModelViewProjection();
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.ContraptionHandlerClient;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Fixes the right click interaction and contraption raytracing in Create to take into account sublevels
|
||||
*/
|
||||
@Mixin(ContraptionHandlerClient.class)
|
||||
public abstract class ContraptionHandlerClientMixin {
|
||||
|
||||
@Redirect(method = "getRayInputs", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceTo(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
private static double sable$projectDistanceTo1(final Vec3 eyePos, final Vec3 itemPos) {
|
||||
return Math.sqrt(Sable.HELPER.distanceSquaredWithSubLevels(Minecraft.getInstance().level, eyePos, itemPos));
|
||||
}
|
||||
|
||||
@Redirect(method = "rightClickingOnContraptionsGetsHandledLocally", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceTo(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
private static double sable$projectDistanceTo2(final Vec3 eyePos, final Vec3 itemPos) {
|
||||
return Math.sqrt(Sable.HELPER.distanceSquaredWithSubLevels(Minecraft.getInstance().level, eyePos, itemPos));
|
||||
}
|
||||
|
||||
@Redirect(method = "rightClickingOnContraptionsGetsHandledLocally",
|
||||
at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;getBoundingBox()Lnet/minecraft/world/phys/AABB;"))
|
||||
private static AABB sable$moveBoundingBoxToProjectedPos(final AbstractContraptionEntity instance){
|
||||
final Vec3 projectedPos = Sable.HELPER.projectOutOfSubLevel(instance.level(), instance.getAnchorVec());
|
||||
final AABB boundingBox = instance.getBoundingBox();
|
||||
|
||||
return boundingBox.move(Vec3.ZERO.subtract(boundingBox.getCenter())).move(projectedPos);
|
||||
}
|
||||
|
||||
@Redirect(method = "rayTraceContraption",
|
||||
at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;toLocalVector(Lnet/minecraft/world/phys/Vec3;F)Lnet/minecraft/world/phys/Vec3;"),
|
||||
remap = false)
|
||||
private static Vec3 sable$projectedContraptionClip(final AbstractContraptionEntity abce, Vec3 localVec, final float partialTicks){
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final SubLevel sublevel1 = helper.getContaining(abce.level(), localVec);
|
||||
final SubLevel contraptionSublevel = helper.getContaining(abce);
|
||||
|
||||
if (contraptionSublevel != sublevel1) {
|
||||
if (sublevel1 != null)
|
||||
localVec = sublevel1.logicalPose().transformPosition(localVec);
|
||||
|
||||
if (contraptionSublevel != null)
|
||||
localVec = contraptionSublevel.logicalPose().transformPositionInverse(localVec);
|
||||
}
|
||||
|
||||
return abce.toLocalVector(localVec, 1);
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.render.ContraptionVisual;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
|
||||
import dev.engine_room.flywheel.lib.visual.AbstractEntityVisual;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3dc;
|
||||
import dev.ryanhcode.sable.neoforge.compatibility.flywheel.FlywheelCompatNeoForge;
|
||||
import dev.ryanhcode.sable.neoforge.mixinterface.compatibility.flywheel.EmbeddedEnvironmentExtension;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Fixes the rendering of contraption visuals on sub-levels
|
||||
*/
|
||||
@Mixin(ContraptionVisual.class)
|
||||
public abstract class ContraptionVisualMixin extends AbstractEntityVisual<AbstractContraptionEntity> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
protected VisualEmbedding embedding;
|
||||
@Shadow
|
||||
@Final
|
||||
private PoseStack contraptionMatrix;
|
||||
|
||||
public ContraptionVisualMixin(final VisualizationContext ctx, final AbstractContraptionEntity entity, final float partialTick) {
|
||||
super(ctx, entity, partialTick);
|
||||
}
|
||||
|
||||
@Inject(method = "setEmbeddingMatrices", at = @At(value = "HEAD"), cancellable = true)
|
||||
private void sable$setEmbeddingMatrices(final float partialTick, final CallbackInfo ci) {
|
||||
final SubLevelContainer container = SubLevelContainer.getContainer(this.entity.level());
|
||||
|
||||
if (container == null)
|
||||
return;
|
||||
|
||||
final ChunkPos chunkPos = this.entity.chunkPosition();
|
||||
final boolean inBounds = container.inBounds(chunkPos);
|
||||
|
||||
if (!inBounds)
|
||||
return;
|
||||
|
||||
final int plotX = (chunkPos.x >> container.getLogPlotSize()) - container.getOrigin().x;
|
||||
final int plotZ = (chunkPos.z >> container.getLogPlotSize()) - container.getOrigin().y;
|
||||
|
||||
final FlywheelCompatNeoForge.SubLevelFlwRenderState state = FlywheelCompatNeoForge.getInfo(ChunkPos.asLong(plotX, plotZ));
|
||||
|
||||
if (state == null) return;
|
||||
|
||||
final Vec3i origin = this.renderOrigin();
|
||||
|
||||
final Vector3d pos = new Vector3d();
|
||||
if (this.entity.isPrevPosInvalid()) {
|
||||
pos.x = this.entity.getX();
|
||||
pos.y = this.entity.getY();
|
||||
pos.z = this.entity.getZ();
|
||||
} else {
|
||||
pos.x = Mth.lerp(partialTick, this.entity.xo, this.entity.getX());
|
||||
pos.y = Mth.lerp(partialTick, this.entity.yo, this.entity.getY());
|
||||
pos.z = Mth.lerp(partialTick, this.entity.zo, this.entity.getZ());
|
||||
}
|
||||
|
||||
final ChunkPos centerChunk = state.centerChunk;
|
||||
final PoseStack sceneMatrix = new PoseStack();
|
||||
sceneMatrix.translate(
|
||||
(float) (pos.x - centerChunk.getMinBlockX()),
|
||||
(float) pos.y,
|
||||
(float) (pos.z - centerChunk.getMinBlockZ())
|
||||
);
|
||||
this.entity.applyLocalTransforms(sceneMatrix, partialTick);
|
||||
|
||||
final Pose3dc renderPose = state.renderPose;
|
||||
renderPose.transformPosition(pos).sub(origin.getX(), origin.getY(), origin.getZ());
|
||||
|
||||
this.contraptionMatrix.setIdentity();
|
||||
this.contraptionMatrix.translate(pos.x, pos.y, pos.z);
|
||||
this.contraptionMatrix.mulPose(new Quaternionf(renderPose.orientation()));
|
||||
this.entity.applyLocalTransforms(this.contraptionMatrix, partialTick);
|
||||
this.embedding.transforms(this.contraptionMatrix.last().pose(), this.contraptionMatrix.last().normal());
|
||||
|
||||
if (this.embedding instanceof final EmbeddedEnvironmentExtension extension) {
|
||||
extension.sable$setLightingInfo(sceneMatrix.last().pose(), state.sceneID, state.latestSkyLightScale / 15.0f);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import com.simibubi.create.foundation.collision.Matrix3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Matrix3d.class)
|
||||
public interface Matrix3dAccessor {
|
||||
@Accessor("m00")
|
||||
double getM00();
|
||||
|
||||
@Accessor("m00")
|
||||
void setM00(double value);
|
||||
|
||||
@Accessor("m01")
|
||||
double getM01();
|
||||
|
||||
@Accessor("m01")
|
||||
void setM01(double value);
|
||||
|
||||
@Accessor("m02")
|
||||
double getM02();
|
||||
|
||||
@Accessor("m02")
|
||||
void setM02(double value);
|
||||
|
||||
@Accessor("m10")
|
||||
double getM10();
|
||||
|
||||
@Accessor("m10")
|
||||
void setM10(double value);
|
||||
|
||||
@Accessor("m11")
|
||||
double getM11();
|
||||
|
||||
@Accessor("m11")
|
||||
void setM11(double value);
|
||||
|
||||
@Accessor("m12")
|
||||
double getM12();
|
||||
|
||||
@Accessor("m12")
|
||||
void setM12(double value);
|
||||
|
||||
@Accessor("m20")
|
||||
double getM20();
|
||||
|
||||
@Accessor("m20")
|
||||
void setM20(double value);
|
||||
|
||||
@Accessor("m21")
|
||||
double getM21();
|
||||
|
||||
@Accessor("m21")
|
||||
void setM21(double value);
|
||||
|
||||
@Accessor("m22")
|
||||
double getM22();
|
||||
|
||||
@Accessor("m22")
|
||||
void setM22(double value);
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.contraptions;
|
||||
|
||||
import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.neoforge.compatibility.flywheel.FlywheelCompatNeoForge;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
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(VisualizationEventHandler.class)
|
||||
public class VisualizationEventHandlerMixin {
|
||||
|
||||
@Inject(method = "onEntityJoinLevel", at = @At("TAIL"))
|
||||
private static void sable$onEntityJoinLevel(final Level level, final Entity entity, final CallbackInfo ci) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(entity);
|
||||
|
||||
if (subLevel != null) {
|
||||
FlywheelCompatNeoForge.createRenderInfo(level, subLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.crushing_wheel;
|
||||
|
||||
import com.simibubi.create.content.kinetics.base.RotatedPillarKineticBlock;
|
||||
import com.simibubi.create.content.kinetics.crusher.CrushingWheelBlock;
|
||||
import com.simibubi.create.content.kinetics.crusher.CrushingWheelBlockEntity;
|
||||
import com.simibubi.create.foundation.block.IBE;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
@Mixin(CrushingWheelBlock.class)
|
||||
public abstract class CrushingWheelBlockMixin extends RotatedPillarKineticBlock implements IBE<CrushingWheelBlockEntity> {
|
||||
|
||||
public CrushingWheelBlockMixin(final Properties arg) {
|
||||
super(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author RyanH
|
||||
* @reason Take into account sub-levels existing
|
||||
*/
|
||||
@Overwrite
|
||||
public void entityInside(final BlockState state, final Level level, final BlockPos pos, final Entity entityIn) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(level, pos);
|
||||
Vec3 entityPos = entityIn.position();
|
||||
if (subLevel != null) {
|
||||
entityPos = subLevel.logicalPose().transformPositionInverse(entityPos);
|
||||
}
|
||||
|
||||
if (entityPos.y() < pos.getY() + 1.25f || !entityIn.onGround())
|
||||
return;
|
||||
|
||||
final float speed = this.getBlockEntityOptional(level, pos).map(CrushingWheelBlockEntity::getSpeed)
|
||||
.orElse(0f);
|
||||
|
||||
double x = 0;
|
||||
double z = 0;
|
||||
|
||||
final double entityX = entityPos.x();
|
||||
final double entityZ = entityPos.z();
|
||||
|
||||
if (state.getValue(AXIS) == Direction.Axis.X) {
|
||||
z = speed / 20f;
|
||||
x += (pos.getX() + .5f - entityX) * .1f;
|
||||
}
|
||||
|
||||
if (state.getValue(AXIS) == Direction.Axis.Z) {
|
||||
x = speed / -20f;
|
||||
z += (pos.getZ() + .5f - entityZ) * .1f;
|
||||
}
|
||||
|
||||
Vec3 impulse = new Vec3(x, 0, z);
|
||||
if (subLevel != null) {
|
||||
impulse = subLevel.logicalPose().transformNormal(impulse);
|
||||
}
|
||||
|
||||
entityIn.setDeltaMovement(entityIn.getDeltaMovement().add(impulse));
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.crushing_wheel_entity_processing;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.kinetics.crusher.CrushingWheelControllerBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3d;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(CrushingWheelControllerBlockEntity.class)
|
||||
public abstract class CrushingWheelControllerBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
@Shadow
|
||||
public Entity processingEntity;
|
||||
@Unique
|
||||
private SubLevel sable$parentSublevel = null;
|
||||
|
||||
public CrushingWheelControllerBlockEntityMixin(final BlockEntityType<?> typeIn, final BlockPos pos, final BlockState state) {
|
||||
super(typeIn, pos, state);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
public void sable$initSublevel(final CallbackInfo ci) {
|
||||
this.sable$parentSublevel = Sable.HELPER.getContaining(this);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getBoundingBox()Lnet/minecraft/world/phys/AABB;"))
|
||||
public AABB sable$pushEntityLocalAABB(final Entity instance, final Operation<AABB> original) {
|
||||
final AABB boundingBox = original.call(instance);
|
||||
if (this.sable$parentSublevel != null) {
|
||||
final BoundingBox3d bb3d = new BoundingBox3d(boundingBox);
|
||||
bb3d.transformInverse(this.sable$parentSublevel.logicalPose());
|
||||
return bb3d.toMojang();
|
||||
}
|
||||
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getX()D"))
|
||||
public double sable$pushEntityLocalX(final Entity instance, final Operation<Double> original) {
|
||||
Double x = original.call(instance);
|
||||
if (this.sable$parentSublevel != null) {
|
||||
x = this.sable$parentSublevel.logicalPose().transformPositionInverse(instance.position()).x;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getY()D"))
|
||||
public double sable$pushEntityLocalY(final Entity instance, final Operation<Double> original) {
|
||||
Double y = original.call(instance);
|
||||
if (this.sable$parentSublevel != null) {
|
||||
y = this.sable$parentSublevel.logicalPose().transformPositionInverse(instance.position()).y;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getZ()D"))
|
||||
public double sable$pushEntityLocalZ(final Entity instance, final Operation<Double> original) {
|
||||
Double z = original.call(instance);
|
||||
if (this.sable$parentSublevel != null) {
|
||||
z = this.sable$parentSublevel.logicalPose().transformPositionInverse(instance.position()).z;
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.deployer;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.deployer.DeployerBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.createmod.catnip.data.Iterate;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Mixin(DeployerBlockEntity.class)
|
||||
public abstract class DeployerBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
public DeployerBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Redirect(method = "start", at = @At(value = "INVOKE", target = "Ljava/lang/Math;min(DD)D"))
|
||||
private double sable$deployerMin(final double a, final double b, @Local(ordinal = 1) final Vec3 rayOrigin, @Local(ordinal = 0) final BlockHitResult result) {
|
||||
return Math.min(Math.sqrt(Sable.HELPER.distanceSquaredWithSubLevels(this.level, result.getLocation(), rayOrigin)), b);
|
||||
}
|
||||
|
||||
|
||||
@ModifyArg(method = "activate", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/kinetics/deployer/DeployerHandler;activate(Lcom/simibubi/create/content/kinetics/deployer/DeployerFakePlayer;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/phys/Vec3;Lcom/simibubi/create/content/kinetics/deployer/DeployerBlockEntity$Mode;)V"), index = 2, remap = false)
|
||||
private BlockPos sable$checkPositions(final BlockPos pos) {
|
||||
final Vec3 centerPos = Vec3.atCenterOf(pos);
|
||||
|
||||
ActiveSableCompanion helper = Sable.HELPER;
|
||||
final BlockPos gatheredPos = helper.runIncludingSubLevels(this.getLevel(), centerPos, true, helper.getContaining(this), this::sable$getState);
|
||||
|
||||
if (gatheredPos != null)
|
||||
return gatheredPos;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
@Unique
|
||||
@Nullable
|
||||
private BlockPos sable$getState(final SubLevel subLevel, final BlockPos pos) {
|
||||
final Level level = this.getLevel();
|
||||
assert level != null;
|
||||
|
||||
final BlockState state = level.getBlockState(pos);
|
||||
|
||||
if (!state.isAir()) {
|
||||
return pos;
|
||||
} else {
|
||||
for (final Direction direction : Iterate.directions) {
|
||||
if (!level.getBlockState(pos.relative(direction)).isAir())
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.depot;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.logistics.depot.DepotRenderer;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(DepotRenderer.class)
|
||||
public class DepotRendererMixin {
|
||||
@ModifyExpressionValue(method = "renderItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getPosition()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$renderViewEntityPosition(final Vec3 original, @Local(argsOnly = true) final Vec3 position) {
|
||||
final ClientSubLevel subLevel = Sable.HELPER.getContainingClient(position);
|
||||
if (subLevel != null) {
|
||||
return subLevel.renderPose().transformPositionInverse(original);
|
||||
} else {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.display_link;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.redstone.displayLink.ClickToLinkBlockItem;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ClickToLinkBlockItem.class)
|
||||
public class ClickToLinkBlockItemMixin {
|
||||
|
||||
@WrapOperation(method = "useOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerThan(Lnet/minecraft/core/Vec3i;D)Z"))
|
||||
public boolean sable$accountForSubLevels(final BlockPos instance, final Vec3i pos, final double v, final Operation<Boolean> original, @Local final Level level) {
|
||||
return Sable.HELPER.distanceSquaredWithSubLevels(level, instance.getX() + 0.5, instance.getY() + 0.5, instance.getZ() + 0.5, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5) < v * v;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.display_link;
|
||||
|
||||
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlockEntity;
|
||||
import com.simibubi.create.content.redstone.displayLink.LinkWithBulbBlockEntity;
|
||||
import com.simibubi.create.infrastructure.config.AllConfigs;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
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(DisplayLinkBlockEntity.class)
|
||||
public abstract class DisplayLinkBlockEntityMixin extends LinkWithBulbBlockEntity {
|
||||
|
||||
private DisplayLinkBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Inject(method = "getTargetPosition", at = @At("TAIL"), cancellable = true)
|
||||
public void sable$accountForSubLevels(final CallbackInfoReturnable<BlockPos> cir) {
|
||||
final BlockPos target = cir.getReturnValue();
|
||||
final int range = AllConfigs.server().logistics.displayLinkRange.get();
|
||||
final BlockPos pos = this.getBlockPos();
|
||||
if (Sable.HELPER.distanceSquaredWithSubLevels(this.level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, target.getX() + 0.5, target.getY() + 0.5, target.getZ() + 0.5) >= range * range) {
|
||||
cir.setReturnValue(BlockPos.ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.display_link;
|
||||
|
||||
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlock;
|
||||
import com.simibubi.create.content.redstone.displayLink.DisplayLinkBlockEntity;
|
||||
import dev.ryanhcode.sable.api.block.BlockSubLevelAssemblyListener;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(DisplayLinkBlock.class)
|
||||
public class DisplayLinkBlockMixin implements BlockSubLevelAssemblyListener {
|
||||
|
||||
@SuppressWarnings("AddedMixinMembersNamePattern")
|
||||
@Override
|
||||
public void afterMove(final ServerLevel originLevel, final ServerLevel resultingLevel, final BlockState newState, final BlockPos oldPos, final BlockPos newPos) {
|
||||
if (originLevel.getBlockEntity(oldPos) instanceof final DisplayLinkBlockEntity be && resultingLevel.getBlockEntity(newPos) instanceof final DisplayLinkBlockEntity newBe) {
|
||||
newBe.target(be.getTargetPosition());
|
||||
}
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.ejector;
|
||||
|
||||
import com.simibubi.create.content.logistics.depot.EjectorBlockEntity;
|
||||
import com.simibubi.create.content.logistics.depot.EntityLauncher;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.physics.handle.RigidBodyHandle;
|
||||
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
|
||||
import dev.ryanhcode.sable.mixinterface.clip_overwrite.ClipContextExtension;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.ejector.SubLevelScanResult;
|
||||
import dev.ryanhcode.sable.physics.config.dimension_physics.DimensionPhysicsData;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Makes weighted ejectors apply an impulse to sub-levels above them
|
||||
*/
|
||||
@Mixin(EjectorBlockEntity.class)
|
||||
public abstract class EjectorBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
@Unique
|
||||
private static final int SUB_LEVEL_SCAN_TIME = 2;
|
||||
@Shadow
|
||||
private boolean launch;
|
||||
@Shadow
|
||||
private EjectorBlockEntity.State state;
|
||||
@Shadow
|
||||
private boolean powered;
|
||||
@Shadow
|
||||
private EntityLauncher launcher;
|
||||
@Unique
|
||||
private int sable$scanTimer = SUB_LEVEL_SCAN_TIME;
|
||||
@Unique
|
||||
private int sable$readyTimer = 0;
|
||||
|
||||
public EjectorBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Shadow
|
||||
public abstract void activate();
|
||||
|
||||
@Shadow
|
||||
protected abstract Direction getFacing();
|
||||
|
||||
@Inject(method = "activateDeferred", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/depot/EjectorBlockEntity;launchItems()V"))
|
||||
public void sable$launchSubLevels(final CallbackInfo ci) {
|
||||
final SubLevelScanResult scanResult = this.sable$lookForLaunchableSubLevels();
|
||||
if (scanResult == null) return;
|
||||
|
||||
final ServerSubLevel otherSubLevel = scanResult.serverSubLevel();
|
||||
final SubLevelPhysicsSystem physicsSystem = SubLevelContainer.getContainer((ServerLevel) this.level).physicsSystem();
|
||||
|
||||
final BlockPos blockPos = this.getBlockPos();
|
||||
final SubLevel containingSubLevel = Sable.HELPER.getContaining(this.level, blockPos);
|
||||
|
||||
// math to compute the impulse to launch a 1 kpg sub-level to the target
|
||||
// authored by Eriksonn
|
||||
final double c = 3.0 * Math.max(1.0, this.launcher.getHorizontalDistance() / 10.0); // velocity constraint [m/s]
|
||||
final double px = this.launcher.getHorizontalDistance();
|
||||
final double py = this.launcher.getVerticalDistance();
|
||||
|
||||
// TODO: Make this use gravity at the ejector position
|
||||
final double g = -DimensionPhysicsData.getGravity(this.level).y;
|
||||
|
||||
double vx = c;
|
||||
if (py > 0) {
|
||||
vx = Math.min(c, px * Math.sqrt(0.5 * g / py));
|
||||
}
|
||||
|
||||
final double vy = vx * py / px + 0.5 * g * px / vx;
|
||||
|
||||
final Vec3 verticalImpulse = new Vec3(0.0, vy, 0.0);
|
||||
|
||||
final Vec3 localHit = Vec3.atLowerCornerOf(this.getFacing().getNormal()).scale(vx)
|
||||
.add(verticalImpulse);
|
||||
|
||||
final Vec3 globalHitDirection = containingSubLevel != null ?
|
||||
containingSubLevel.logicalPose().transformNormal(localHit) :
|
||||
localHit;
|
||||
|
||||
final RigidBodyHandle otherHandle = physicsSystem.getPhysicsHandle(otherSubLevel);
|
||||
otherHandle.applyImpulseAtPoint(scanResult.result().getBlockPos().getCenter(), otherSubLevel.logicalPose().transformNormalInverse(globalHitDirection));
|
||||
|
||||
if (containingSubLevel != null) {
|
||||
final RigidBodyHandle handle = physicsSystem.getPhysicsHandle((ServerSubLevel) containingSubLevel);
|
||||
handle.applyImpulseAtPoint(blockPos.getCenter(), containingSubLevel.logicalPose().transformNormalInverse(globalHitDirection).scale(-1.0));
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable SubLevelScanResult sable$lookForLaunchableSubLevels() {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final SubLevel containingSubLevel = helper.getContaining(this);
|
||||
final BlockPos blockPos = this.getBlockPos();
|
||||
final ClipContext clipContext = new ClipContext(blockPos.getCenter(), Vec3.upFromBottomCenterOf(blockPos, 1.0), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, CollisionContext.empty());
|
||||
final ClipContextExtension extension = (ClipContextExtension) clipContext;
|
||||
|
||||
// Ignore the main world. We only care about sub-levels.
|
||||
extension.sable$setIgnoreMainLevel(true);
|
||||
extension.sable$setIgnoredSubLevel(containingSubLevel);
|
||||
|
||||
final BlockHitResult result = this.level.clip(clipContext);
|
||||
|
||||
if (result.getType() == HitResult.Type.MISS) return null;
|
||||
|
||||
final SubLevel subLevel = helper.getContaining(this.level, result.getLocation());
|
||||
if (!(subLevel instanceof final ServerSubLevel serverSubLevel)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SubLevelScanResult(result, serverSubLevel);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
public void sable$tick(final CallbackInfo ci) {
|
||||
if (this.level.isClientSide && !this.isVirtual()) return;
|
||||
|
||||
this.sable$scanTimer--;
|
||||
|
||||
if (this.sable$scanTimer <= 0) {
|
||||
this.sable$scanTimer = SUB_LEVEL_SCAN_TIME;
|
||||
|
||||
if (this.state == EjectorBlockEntity.State.RETRACTING ||
|
||||
this.powered ||
|
||||
this.launcher.getHorizontalDistance() == 0) {
|
||||
this.sable$readyTimer = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
final SubLevelScanResult result = this.sable$lookForLaunchableSubLevels();
|
||||
|
||||
if (result != null) {
|
||||
this.sable$readyTimer++;
|
||||
} else {
|
||||
this.sable$readyTimer = 0;
|
||||
}
|
||||
|
||||
if (this.sable$readyTimer > 3) {
|
||||
this.activate();
|
||||
this.notifyUpdate();
|
||||
this.sable$readyTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.elevator_controls;
|
||||
|
||||
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
|
||||
import com.simibubi.create.content.contraptions.elevator.ElevatorControlsHandler;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.BoundingBox3d;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Fixes the range check on Elevator Controls in Create to take into account sub-levels
|
||||
*/
|
||||
@Mixin(ElevatorControlsHandler.class)
|
||||
public class ElevatorControlsHandlerMixin {
|
||||
|
||||
@Redirect(method = "onScroll",
|
||||
at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/contraptions/AbstractContraptionEntity;getBoundingBox()Lnet/minecraft/world/phys/AABB;"))
|
||||
private static AABB sable$projectAABB(final AbstractContraptionEntity instance) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(instance.level(), instance.getBoundingBox().getCenter());
|
||||
final AABB projectedBB = instance.getBoundingBox();
|
||||
|
||||
if (subLevel != null) {
|
||||
final BoundingBox3d bb = new BoundingBox3d(projectedBB);
|
||||
return bb.transform(subLevel.logicalPose(), bb).toMojang();
|
||||
}
|
||||
|
||||
return projectedBB;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.entity_falls_on_block;
|
||||
|
||||
import com.simibubi.create.content.processing.basin.BasinBlock;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Makes basins use the standing on position of items instead of their block position for picking them up, as the
|
||||
* on position of entities will be overwritten by Sable to be inside of the plot of a sub-level an item is resting on
|
||||
*/
|
||||
@Mixin(BasinBlock.class)
|
||||
public class BasinBlockMixin {
|
||||
|
||||
@Redirect(method = "updateEntityAfterFallOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;blockPosition()Lnet/minecraft/core/BlockPos;"))
|
||||
private BlockPos sable$updateEntityAfterFallOn(final Entity instance) {
|
||||
return instance.getOnPos();
|
||||
}
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.entity_falls_on_block;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.AllBlocks;
|
||||
import com.simibubi.create.content.kinetics.belt.BeltBlock;
|
||||
import com.simibubi.create.content.kinetics.millstone.MillstoneBlock;
|
||||
import com.tterrag.registrate.util.entry.BlockEntry;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin({ BeltBlock.class, MillstoneBlock.class })
|
||||
public class BeltMillstoneBlocksMixin extends Block {
|
||||
|
||||
public BeltMillstoneBlocksMixin(final Properties pProperties) {
|
||||
super(pProperties);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "updateEntityAfterFallOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;blockPosition()Lnet/minecraft/core/BlockPos;"))
|
||||
public BlockPos sable$checkForSubLevels(final Entity instance, final Operation<BlockPos> original) {
|
||||
final Level level = instance.level();
|
||||
|
||||
BlockEntry<?> entry;
|
||||
if ((Object) this instanceof BeltBlock) {
|
||||
entry = AllBlocks.BELT;
|
||||
} else {
|
||||
entry = AllBlocks.MILLSTONE;
|
||||
}
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final BlockPos gatheredBeltPos = helper.runIncludingSubLevels(level, instance.position(), true, null, (subLevel, internalPos) -> {
|
||||
if (entry.has(level.getBlockState(internalPos))) {
|
||||
return internalPos;
|
||||
} else if (entry.has(level.getBlockState(internalPos.below()))) {
|
||||
return internalPos.below();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (gatheredBeltPos != null) {
|
||||
return gatheredBeltPos;
|
||||
}
|
||||
|
||||
return original.call(instance);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.entity_falls_on_block;
|
||||
|
||||
import com.simibubi.create.content.kinetics.saw.SawBlock;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(SawBlock.class)
|
||||
public class SawBlockMixin {
|
||||
|
||||
@Redirect(method = "updateEntityAfterFallOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;blockPosition()Lnet/minecraft/core/BlockPos;"))
|
||||
private BlockPos sable$updateEntityAfterFallOn(final Entity instance) {
|
||||
return instance.getOnPos();
|
||||
}
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.entity_falls_on_block;
|
||||
|
||||
import com.simibubi.create.content.contraptions.actors.seat.SeatBlock;
|
||||
import com.simibubi.create.content.processing.basin.BasinBlock;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Makes seats use the standing on position of entities instead of their block position for sitting them down, as the
|
||||
* on position of entities will be overwritten by Sable to be inside of the plot of a sub-level an entity is resting on
|
||||
*/
|
||||
@Mixin(SeatBlock.class)
|
||||
public class SeatBlockMixin {
|
||||
|
||||
@Redirect(method = "updateEntityAfterFallOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;blockPosition()Lnet/minecraft/core/BlockPos;"))
|
||||
private BlockPos sable$updateEntityAfterFallOn(final Entity instance) {
|
||||
return instance.getOnPos();
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.factory_panel;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.logistics.factoryBoard.FactoryPanelConnectionHandler;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(FactoryPanelConnectionHandler.class)
|
||||
public class FactoryPanelConnectionHandlerMixin {
|
||||
|
||||
@Redirect(method = "clientTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerThan(Lnet/minecraft/core/Vec3i;D)Z"))
|
||||
private static boolean closerThan(final BlockPos instance, final Vec3i pos, final double maxDistance, @Local final Minecraft mc) {
|
||||
return Sable.HELPER.distanceSquaredWithSubLevels(mc.level, instance.getX(), instance.getY(), instance.getZ(), pos.getX(), pos.getY(), pos.getZ()) < maxDistance * maxDistance;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.fans_provide_force;
|
||||
|
||||
import com.simibubi.create.content.kinetics.base.KineticBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.fan.EncasedFanBlock;
|
||||
import com.simibubi.create.content.kinetics.fan.EncasedFanBlockEntity;
|
||||
import dev.ryanhcode.sable.api.block.propeller.BlockEntityPropeller;
|
||||
import dev.ryanhcode.sable.api.block.propeller.BlockEntitySubLevelPropellerActor;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(EncasedFanBlockEntity.class)
|
||||
public class EncasedFanBlockEntityMixin extends KineticBlockEntity implements BlockEntitySubLevelPropellerActor, BlockEntityPropeller {
|
||||
|
||||
@Unique
|
||||
private boolean sable$blocked;
|
||||
|
||||
public EncasedFanBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sable$tick(final ServerSubLevel subLevel) {
|
||||
final BlockPos frontPos = this.getBlockPos().relative(this.getBlockState().getValue(EncasedFanBlock.FACING));
|
||||
this.sable$blocked = !this.level.getBlockState(frontPos).isAir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntityPropeller getPropeller() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getBlockDirection() {
|
||||
return this.getBlockState().getValue(EncasedFanBlock.FACING);
|
||||
}
|
||||
|
||||
protected float sable$getPropSpeed() {
|
||||
final float rotationSpeed = convertToAngular(this.getSpeed());
|
||||
return this.getBlockDirection().getAxisDirection().getStep() * rotationSpeed * (10 / 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAirflow() {
|
||||
return 0.1f * this.sable$getPropSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getThrust() {
|
||||
return 0.3f * this.sable$getPropSpeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return !this.sable$blocked && Math.abs(this.sable$getPropSpeed()) > 0.01f;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.fluid_handling;
|
||||
|
||||
import com.simibubi.create.content.fluids.OpenEndedPipe;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.createmod.catnip.math.BlockFace;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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;
|
||||
|
||||
@Mixin(OpenEndedPipe.class)
|
||||
public abstract class OpenEndedPipeMixin {
|
||||
@Shadow
|
||||
private BlockPos outputPos;
|
||||
|
||||
@Shadow
|
||||
private Level world;
|
||||
|
||||
@Shadow
|
||||
public abstract BlockPos getPos();
|
||||
|
||||
@Unique
|
||||
private BlockPos sable$plotOutputPos;
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"), remap = false)
|
||||
private void sable$saveCurrentPos(final BlockFace face, final CallbackInfo ci) {
|
||||
this.sable$plotOutputPos = this.outputPos;
|
||||
}
|
||||
|
||||
@Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"))
|
||||
private BlockState sable$getBlockstateInclSublevels(final Level level, final BlockPos pos) {
|
||||
this.outputPos = this.sable$plotOutputPos;
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final Vec3 checkPos = Vec3.atCenterOf(this.sable$plotOutputPos);
|
||||
BlockState gatheredState = helper.runIncludingSubLevels(level, checkPos, true, helper.getContaining(level, checkPos), this::sable$gatherState);
|
||||
if (gatheredState == null) {
|
||||
this.outputPos = this.sable$plotOutputPos;
|
||||
gatheredState = level.getBlockState(this.sable$plotOutputPos);
|
||||
}
|
||||
|
||||
return gatheredState;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private BlockState sable$gatherState(final SubLevel level, final BlockPos b) {
|
||||
final BlockState checkedState = this.world.getBlockState(b);
|
||||
if (!checkedState.isAir()) {
|
||||
this.outputPos = b;
|
||||
return checkedState;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Redirect(method = "provideFluidToSpace",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;setBlock(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Z", ordinal = 1))
|
||||
private boolean sable$preventInWorldPlace(final Level instance, final BlockPos pPos, final BlockState pNesubleveltate, final int pFlags) {
|
||||
return instance.setBlock(this.sable$plotOutputPos, pNesubleveltate, 3);
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.fluid_handling;
|
||||
|
||||
import com.simibubi.create.content.fluids.PipeConnection;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(PipeConnection.class)
|
||||
public class PipeConnectionMixin {
|
||||
@Redirect(method = "isRenderEntityWithinDistance", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceTo(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
private static double sable$distanceIncludingSubLevels(final Vec3 instance, final Vec3 vec3) {
|
||||
return Math.sqrt(Sable.HELPER.distanceSquaredWithSubLevels(Minecraft.getInstance().level, instance, vec3));
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.fluid_tank_heating;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.fluids.tank.BoilerData;
|
||||
import com.simibubi.create.content.fluids.tank.FluidTankBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(BoilerData.class)
|
||||
public class BoilerDataMixin {
|
||||
|
||||
@Shadow
|
||||
public boolean needsHeatLevelUpdate;
|
||||
|
||||
@Unique
|
||||
private int sable$ticksUntilUpdate = 20;
|
||||
|
||||
|
||||
@Inject(method = "tick", at = @At(value = "FIELD", target = "Lcom/simibubi/create/content/fluids/tank/BoilerData;ticksUntilNextSample:I", ordinal = 0))
|
||||
public void sable$forceUpdateHeatIfDisconnected(final FluidTankBlockEntity controller, final CallbackInfo ci) {
|
||||
if (this.sable$ticksUntilUpdate-- <= 0) {
|
||||
this.sable$ticksUntilUpdate = 20;
|
||||
this.needsHeatLevelUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
@WrapOperation(method = "updateTemperature", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/api/boiler/BoilerHeater;findHeat(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)F"))
|
||||
public float sable$subLevelHeating(final Level level, final BlockPos pos, final BlockState state, final Operation<Float> original) {
|
||||
final Float originalHeat = original.call(level, pos, state);
|
||||
if (originalHeat != -1) {
|
||||
return originalHeat;
|
||||
}
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final Float gatheredHeat = helper.runIncludingSubLevels(level, pos.getCenter(), false, helper.getContaining(level, pos), (subLevel, internalPos) -> {
|
||||
final Float internalHeat = original.call(level, internalPos, level.getBlockState(internalPos));
|
||||
|
||||
if (internalHeat != -1) {
|
||||
return internalHeat;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
});
|
||||
|
||||
if (gatheredHeat != null) {
|
||||
return gatheredHeat;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.flywheel;
|
||||
|
||||
import com.simibubi.create.content.kinetics.base.IRotate;
|
||||
import com.simibubi.create.content.kinetics.base.KineticBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.flywheel.FlywheelBlockEntity;
|
||||
import dev.ryanhcode.sable.api.block.BlockEntitySubLevelReactionWheel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(FlywheelBlockEntity.class)
|
||||
public abstract class FlywheelBlockEntityMixin extends KineticBlockEntity implements BlockEntitySubLevelReactionWheel {
|
||||
|
||||
@Unique float sable$smoothedSpeed = 0;
|
||||
public FlywheelBlockEntityMixin(BlockEntityType<?> arg, BlockPos arg2, BlockState arg3) {
|
||||
super(arg, arg2, arg3);
|
||||
}
|
||||
|
||||
@Inject(method = "tick",at = @At("HEAD"))
|
||||
public void sable$tick(CallbackInfo ci)
|
||||
{
|
||||
sable$smoothedSpeed += (speed - sable$smoothedSpeed) / 32f;
|
||||
}
|
||||
|
||||
@Inject(method = "write",at = @At("TAIL"))
|
||||
public void sable$write(CompoundTag compound, HolderLookup.Provider registries, boolean clientPacket, CallbackInfo ci)
|
||||
{
|
||||
compound.putFloat("SmoothedSpeed",sable$smoothedSpeed);
|
||||
}
|
||||
@Inject(method = "read",at = @At("TAIL"))
|
||||
public void sable$read(CompoundTag compound, HolderLookup.Provider registries, boolean clientPacket, CallbackInfo ci)
|
||||
{
|
||||
sable$smoothedSpeed = compound.getFloat("SmoothedSpeed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sable$getAngularVelocity(Vector3d v) {
|
||||
Direction.Axis axis = ((IRotate) getBlockState()
|
||||
.getBlock()).getRotationAxis(getBlockState());
|
||||
Direction dir = Direction.get(Direction.AxisDirection.NEGATIVE,axis);
|
||||
float angularSpeed = sable$smoothedSpeed * (float)Math.TAU / 60f;
|
||||
v.set(dir.getStepX(),dir.getStepY(),dir.getStepZ()).mul(angularSpeed);
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorPackage;
|
||||
import com.simibubi.create.content.logistics.packagePort.frogport.FrogportBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import com.simibubi.create.infrastructure.config.AllConfigs;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ChainConveyorBlockEntity.class)
|
||||
public abstract class ChainConveyorBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
public ChainConveyorBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "exportToPort", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/packagePort/frogport/FrogportBlockEntity;isBackedUp()Z"))
|
||||
public boolean sable$testSublevelDistance(final FrogportBlockEntity instance, final Operation<Boolean> original, @Local(argsOnly = true) final ChainConveyorPackage chainPackage) {
|
||||
final Vec3 packagePos = chainPackage.worldPosition;
|
||||
if (packagePos == null) {
|
||||
return original.call(instance);
|
||||
}
|
||||
|
||||
final Vec3 frogPos = instance.getBlockPos().getCenter();
|
||||
|
||||
final int maxRange = AllConfigs.server().logistics.packagePortRange.get() + 2;
|
||||
return original.call(instance) || Sable.HELPER.distanceSquaredWithSubLevels(instance.getLevel(), packagePos, frogPos) > maxRange * maxRange;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorBlockEntity;notifyPortToAnticipate(Lnet/minecraft/core/BlockPos;)V"))
|
||||
public void sable$testSublevelDistance1(final ChainConveyorBlockEntity instance, final BlockPos blockPos, final Operation<Void> original, @Local(name = "portEntry") final Map.Entry<BlockPos, ChainConveyorBlockEntity.ConnectedPort> entry, @Local final ChainConveyorPackage chainPackage) {
|
||||
final Vec3 packagePos = chainPackage.worldPosition;
|
||||
if (packagePos == null) {
|
||||
original.call(instance, blockPos);
|
||||
return;
|
||||
}
|
||||
|
||||
final Vec3 frogPos = this.worldPosition.offset(entry.getKey()).getCenter();
|
||||
|
||||
final int maxRange = AllConfigs.server().logistics.packagePortRange.get() + 2;
|
||||
|
||||
if (Sable.HELPER.distanceSquaredWithSubLevels(this.getLevel(), packagePos, frogPos) < maxRange * maxRange) {
|
||||
original.call(instance, blockPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorInteractionHandler;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorShape;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3dc;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Quaternionf;
|
||||
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;
|
||||
|
||||
@Mixin(ChainConveyorInteractionHandler.class)
|
||||
public class ChainConveyorInteractionHandlerMixin {
|
||||
|
||||
@Shadow
|
||||
public static BlockPos selectedLift;
|
||||
|
||||
@Shadow
|
||||
public static ChainConveyorShape selectedShape;
|
||||
|
||||
@Redirect(method = "clientTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceToSqr(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
private static double sable$addParticleInternal(final Vec3 instance, final Vec3 vec3) {
|
||||
return Sable.HELPER.distanceSquaredWithSubLevels(Minecraft.getInstance().level, instance, vec3);
|
||||
}
|
||||
|
||||
@Redirect(method = "clientTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;subtract(Lnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;", ordinal = 0))
|
||||
private static Vec3 sable$fromSubLiftVec(final Vec3 from, final Vec3 liftVec, @Local(ordinal = 0) final ChainConveyorShape shape) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(liftVec);
|
||||
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPositionInverse(from).subtract(liftVec);
|
||||
}
|
||||
|
||||
return from.subtract(liftVec);
|
||||
}
|
||||
|
||||
@Redirect(method = "clientTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;subtract(Lnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;", ordinal = 1))
|
||||
private static Vec3 sable$toSubLiftVec(final Vec3 to, final Vec3 liftVec, @Local(ordinal = 0) final ChainConveyorShape shape) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(liftVec);
|
||||
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPositionInverse(to).subtract(liftVec);
|
||||
}
|
||||
|
||||
return to.subtract(liftVec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author RyanH
|
||||
* @reason Take sub-levels into account
|
||||
*/
|
||||
@Overwrite
|
||||
public static void drawCustomBlockSelection(final PoseStack ms, final MultiBufferSource buffer, final Vec3 camera) {
|
||||
if (selectedLift == null || selectedShape == null)
|
||||
return;
|
||||
|
||||
final VertexConsumer vb = buffer.getBuffer(RenderType.lines());
|
||||
|
||||
ms.pushPose();
|
||||
|
||||
Vec3 pos = Vec3.atLowerCornerOf(selectedLift);
|
||||
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(pos);
|
||||
|
||||
if (subLevel instanceof final ClientSubLevel clientSubLevel) {
|
||||
final Pose3dc renderPose = clientSubLevel.renderPose();
|
||||
pos = renderPose.transformPosition(pos);
|
||||
ms.translate(pos.x() - camera.x, pos.y() - camera.y, pos.z() - camera.z);
|
||||
ms.mulPose(new Quaternionf(renderPose.orientation()));
|
||||
} else {
|
||||
ms.translate(pos.x() - camera.x, pos.y() - camera.y, pos.z() - camera.z);
|
||||
}
|
||||
|
||||
((ChainConveyorShapeAccessor) selectedShape).invokeDrawOutline(selectedLift, ms, vb);
|
||||
ms.popPose();
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorRidingHandler;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.companion.math.Pose3dc;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ChainConveyorRidingHandler.class)
|
||||
public class ChainConveyorRidingHandlerMixin {
|
||||
|
||||
@Redirect(method = "clientTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;subtract(Lnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;", ordinal = 1))
|
||||
private static Vec3 sable$fixDiff(final Vec3 targetPosition, final Vec3 playerPosition) {
|
||||
return JOMLConversion.toMojang(Sable.HELPER.projectOutOfSubLevel(Minecraft.getInstance().level, JOMLConversion.toJOML(targetPosition))
|
||||
.sub(playerPosition.x, playerPosition.y, playerPosition.z));
|
||||
}
|
||||
|
||||
@Redirect(method = "updateTargetPosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getLookAngle()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$fixLookAngle(final LocalPlayer instance, @Local(ordinal = 1) final BlockPos connection, @Local final ChainConveyorBlockEntity clbe) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(clbe);
|
||||
|
||||
if (subLevel != null) {
|
||||
final Pose3dc pose = subLevel.logicalPose();
|
||||
final Vec3 lookAngle = instance.getLookAngle();
|
||||
return pose.transformNormalInverse(lookAngle);
|
||||
}
|
||||
|
||||
return instance.getLookAngle();
|
||||
}
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorShape;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(ChainConveyorShape.class)
|
||||
public interface ChainConveyorShapeAccessor {
|
||||
|
||||
@Invoker
|
||||
void invokeDrawOutline(BlockPos anchor, PoseStack ms, VertexConsumer vb);
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorPackage;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainPackageInteractionHandler;
|
||||
import com.simibubi.create.foundation.utility.RaycastHelper;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ChainPackageInteractionHandler.class)
|
||||
public class ChainPackageInteractionHandlerMixin {
|
||||
|
||||
@Redirect(method = "lambda$onUse$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getEyePosition()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$getTraceOrigin(final LocalPlayer instance, @Local(argsOnly = true) final ChainConveyorPackage.ChainConveyorPackagePhysicsData data) {
|
||||
Vec3 origin = instance.getEyePosition();
|
||||
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(data.targetPos);
|
||||
if (subLevel != null) {
|
||||
origin = subLevel.logicalPose().transformPositionInverse(origin);
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
@Redirect(method = "lambda$onUse$0", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/foundation/utility/RaycastHelper;getTraceTarget(Lnet/minecraft/world/entity/player/Player;DLnet/minecraft/world/phys/Vec3;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$getTraceTarget(final Player playerIn, final double range, final Vec3 from, @Local(argsOnly = true) final ChainConveyorPackage.ChainConveyorPackagePhysicsData data) {
|
||||
Vec3 target = RaycastHelper.getTraceTarget(playerIn, range, playerIn.getEyePosition());
|
||||
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(data.targetPos);
|
||||
if (subLevel != null) {
|
||||
target = subLevel.logicalPose().transformPositionInverse(target);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorPackage;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainPackageInteractionPacket;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
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(ChainPackageInteractionPacket.class)
|
||||
public class ChainPackageInteractionPacketMixin {
|
||||
@Shadow @Final private BlockPos selectedConnection;
|
||||
|
||||
@Shadow @Final private float chainPosition;
|
||||
|
||||
@Inject(method = "applySettings(Lnet/minecraft/server/level/ServerPlayer;Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorBlockEntity;)V", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorBlockEntity;addLoopingPackage(Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorPackage;)Z"))
|
||||
private void sable$initialiseLoopingWorldPosition(final ServerPlayer player, final ChainConveyorBlockEntity be, final CallbackInfo ci, @Local(name = "chainConveyorPackage") final ChainConveyorPackage chainConveyorPackage) {
|
||||
chainConveyorPackage.worldPosition = be.getPackagePosition(this.chainPosition, null);
|
||||
}
|
||||
|
||||
@Inject(method = "applySettings(Lnet/minecraft/server/level/ServerPlayer;Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorBlockEntity;)V", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorBlockEntity;addTravellingPackage(Lcom/simibubi/create/content/kinetics/chainConveyor/ChainConveyorPackage;Lnet/minecraft/core/BlockPos;)Z"))
|
||||
private void sable$initialiseTravellingWorldPosition(final ServerPlayer player, final ChainConveyorBlockEntity be, final CallbackInfo ci, @Local(name = "chainConveyorPackage") final ChainConveyorPackage chainConveyorPackage) {
|
||||
chainConveyorPackage.worldPosition = be.getPackagePosition(this.chainPosition, this.selectedConnection);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortBlockEntity;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortTarget;
|
||||
import com.simibubi.create.content.logistics.packagePort.frogport.FrogportBlockEntity;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.frogports.FrogportMixinHelper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
/**
|
||||
* Makes frogports face the proper way when picking up and depositing cross-sub-level
|
||||
*/
|
||||
@Mixin(FrogportBlockEntity.class)
|
||||
public class FrogportBlockEntityMixin {
|
||||
|
||||
@WrapOperation(method = "getYaw", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/packagePort/PackagePortTarget;getExactTargetLocation(Lcom/simibubi/create/content/logistics/packagePort/PackagePortBlockEntity;Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$getExactTargetLocation(final PackagePortTarget instance,
|
||||
final PackagePortBlockEntity packagePortBlockEntity,
|
||||
final LevelAccessor levelAccessor,
|
||||
final BlockPos blockPos,
|
||||
final Operation<Vec3> original) {
|
||||
return FrogportMixinHelper.getExactTargetLocation(instance, packagePortBlockEntity, levelAccessor, blockPos, original);
|
||||
}
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.logistics.packagePort.frogport.FrogportBlock;
|
||||
import com.simibubi.create.content.logistics.packagePort.frogport.FrogportBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(FrogportBlock.class)
|
||||
public class FrogportBlockMixin {
|
||||
@WrapOperation(method = "lambda$setPlacedBy$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;position()Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$projectPlayerPosition(final LivingEntity instance, final Operation<Vec3> original, @Local(argsOnly = true, name = "arg2") final FrogportBlockEntity be) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(be);
|
||||
if (subLevel == null) {
|
||||
return original.call(instance);
|
||||
}
|
||||
return subLevel.logicalPose().transformPositionInverse(original.call(instance));
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortBlockEntity;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortTarget;
|
||||
import com.simibubi.create.content.logistics.packagePort.frogport.FrogportRenderer;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.frogports.FrogportMixinHelper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
/**
|
||||
* Makes frogports face & extend the proper amount when picking up and depositing cross-sub-level
|
||||
*/
|
||||
@Mixin(FrogportRenderer.class)
|
||||
public class FrogportRendererMixin {
|
||||
|
||||
@WrapOperation(method = "renderSafe(Lcom/simibubi/create/content/logistics/packagePort/frogport/FrogportBlockEntity;FLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;II)V", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/packagePort/PackagePortTarget;getExactTargetLocation(Lcom/simibubi/create/content/logistics/packagePort/PackagePortBlockEntity;Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$getExactTargetLocation(final PackagePortTarget instance,
|
||||
final PackagePortBlockEntity packagePortBlockEntity,
|
||||
final LevelAccessor levelAccessor,
|
||||
final BlockPos blockPos,
|
||||
final Operation<Vec3> original) {
|
||||
return FrogportMixinHelper.getExactTargetLocation(instance, packagePortBlockEntity, levelAccessor, blockPos, original);
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortBlockEntity;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortTarget;
|
||||
import com.simibubi.create.content.logistics.packagePort.frogport.FrogportVisual;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.frogports.FrogportMixinHelper;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
/**
|
||||
* Makes frogports face & extend the proper amount when picking up and depositing cross-sub-level
|
||||
*/
|
||||
@Mixin(FrogportVisual.class)
|
||||
public class FrogportVisualMixin {
|
||||
|
||||
@WrapOperation(method = "animate", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/logistics/packagePort/PackagePortTarget;getExactTargetLocation(Lcom/simibubi/create/content/logistics/packagePort/PackagePortBlockEntity;Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$getExactTargetLocation(final PackagePortTarget instance,
|
||||
final PackagePortBlockEntity packagePortBlockEntity,
|
||||
final LevelAccessor levelAccessor,
|
||||
final BlockPos blockPos,
|
||||
final Operation<Vec3> original) {
|
||||
return FrogportMixinHelper.getExactTargetLocation(instance, packagePortBlockEntity, levelAccessor, blockPos, original);
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortPlacementPacket;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.Position;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Makes the distance check done by Create's {@link PackagePortPlacementPacket} handling take sub-levels into account.
|
||||
*/
|
||||
@Mixin(PackagePortPlacementPacket.class)
|
||||
public class PackagePortPlacementPacketMixin {
|
||||
|
||||
@Redirect(method = "handle", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;closerThan(Lnet/minecraft/core/Position;D)Z"))
|
||||
private boolean sable$handle(final Vec3 instance, final Position position, final double d, @Local(argsOnly = true) final ServerPlayer player) {
|
||||
return Sable.HELPER.distanceSquaredWithSubLevels(player.level(), instance, position.x(), position.y(), position.z()) < d * d;
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.chainConveyor.ChainConveyorBlockEntity;
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortTarget;
|
||||
import com.simibubi.create.infrastructure.config.AllConfigs;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
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;
|
||||
|
||||
@Mixin(PackagePortTarget.ChainConveyorFrogportTarget.class)
|
||||
public class PackagePortTargetMixin {
|
||||
|
||||
@Shadow
|
||||
public float chainPos;
|
||||
|
||||
@Shadow
|
||||
@Nullable
|
||||
public BlockPos connection;
|
||||
|
||||
@Inject(method = "export", at = @At(value = "INVOKE", target = "Ljava/util/Set;contains(Ljava/lang/Object;)Z"), cancellable = true)
|
||||
public void sable$testSublevelDistance(final LevelAccessor level, final BlockPos portPos, final ItemStack box, final boolean simulate, final CallbackInfoReturnable<Boolean> cir, @Local final ChainConveyorBlockEntity cbe) {
|
||||
final Vec3 targetPos = cbe.getPackagePosition(this.chainPos, this.connection);
|
||||
|
||||
final int maxRange = AllConfigs.server().logistics.packagePortRange.get() + 2;
|
||||
if (Sable.HELPER.distanceSquaredWithSubLevels((Level) level, targetPos, portPos.getX() + 0.5, portPos.getY() + 0.5, portPos.getZ() + 0.5) > maxRange * maxRange) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.simibubi.create.content.logistics.packagePort.PackagePortTargetSelectionHandler;
|
||||
import com.simibubi.create.infrastructure.config.AllConfigs;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(PackagePortTargetSelectionHandler.class)
|
||||
public class PackagePortTargetSelectionHandlerMixin {
|
||||
@Shadow
|
||||
public static boolean isPostbox;
|
||||
|
||||
/**
|
||||
* @author RyanH
|
||||
* @reason Take into account sub-level
|
||||
*/
|
||||
@Overwrite
|
||||
public static String validateDiff(final Vec3 nonProjectedTarget, final BlockPos placedPos) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final Minecraft mc = Minecraft.getInstance();
|
||||
final LocalPlayer player = mc.player;
|
||||
final Level level = player.level();
|
||||
|
||||
final Vector3d target = helper.projectOutOfSubLevel(level, JOMLConversion.toJOML(nonProjectedTarget));
|
||||
final SubLevel frogSubLevel = helper.getContaining(level, placedPos);
|
||||
|
||||
if (frogSubLevel != null) {
|
||||
frogSubLevel.logicalPose().transformPositionInverse(target);
|
||||
}
|
||||
|
||||
final Vector3d localDiff = target.sub(placedPos.getX() + 0.5, placedPos.getY(), placedPos.getZ() + 0.5);
|
||||
if (localDiff.y < 0.0 && !isPostbox) {
|
||||
return "package_port.cannot_reach_down";
|
||||
}
|
||||
|
||||
final double packagePortRange = AllConfigs.server().logistics.packagePortRange.get();
|
||||
if (localDiff.lengthSquared() > packagePortRange * packagePortRange) {
|
||||
return "package_port.too_far";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.frogports;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.renderer.SmartBlockEntityRenderer;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
|
||||
import org.joml.Quaterniondc;
|
||||
import org.joml.Quaternionf;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(SmartBlockEntityRenderer.class)
|
||||
public class SmartBlockEntityRendererMixin<T extends SmartBlockEntity> {
|
||||
|
||||
@Redirect(method = "renderNameplateOnHover", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRenderDispatcher;cameraOrientation()Lorg/joml/Quaternionf;"))
|
||||
private Quaternionf sable$renderNameTag(final EntityRenderDispatcher instance, @Local(argsOnly = true) final T be) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContaining(be);
|
||||
|
||||
if (subLevel == null) {
|
||||
return instance.cameraOrientation();
|
||||
}
|
||||
|
||||
final Quaterniondc subLevelOrientation = ((ClientSubLevel) subLevel).renderPose().orientation();
|
||||
return instance.cameraOrientation().premul(new Quaternionf(subLevelOrientation).conjugate());
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.funnels;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.logistics.funnel.FunnelBlock;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
/**
|
||||
* Fixes a Funnel check in Create to take into account sub-levels
|
||||
*/
|
||||
@Mixin(FunnelBlock.class)
|
||||
public class FunnelBlockMixin {
|
||||
|
||||
@Redirect(method = "entityInside",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/createmod/catnip/math/VecHelper;getCenterOf(Lnet/minecraft/core/Vec3i;)Lnet/minecraft/world/phys/Vec3;"),
|
||||
remap = false)
|
||||
private Vec3 sable$projectFunnelPos(final Vec3i pos, @Local(argsOnly = true) final Level level) {
|
||||
return JOMLConversion.toMojang(Sable.HELPER.projectOutOfSubLevel(level, JOMLConversion.atCenterOf(pos)));
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.hose_pulley;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.fluids.hosePulley.HosePulleyBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(HosePulleyBlockEntity.class)
|
||||
public abstract class HosePulleyBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
public HosePulleyBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "lazyTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"))
|
||||
public BlockState sable$checkForCollisions1(final Level instance, final BlockPos blockPos, final Operation<BlockState> original) {
|
||||
return this.sable$getBlockState(instance, blockPos, original, true);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"))
|
||||
public BlockState sable$checkForCollisions2(final Level instance, final BlockPos blockPos, final Operation<BlockState> original) {
|
||||
return this.sable$getBlockState(instance, blockPos, original, false);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "onSpeedChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"))
|
||||
public BlockState sable$checkForCollisions3(final Level instance, final BlockPos blockPos, final Operation<BlockState> original) {
|
||||
return this.sable$getBlockState(instance, blockPos, original, false);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private BlockState sable$getBlockState(final Level level, final BlockPos blockPos, final Operation<BlockState> original, boolean inverseReplaceCheck) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final BlockState gatheredState = helper.runIncludingSubLevels(level, blockPos.getCenter(), true, helper.getContaining(level, this.getBlockPos()), (sublevel, pos) -> {
|
||||
final BlockState innerState = original.call(level, pos);
|
||||
if (inverseReplaceCheck ^ innerState.canBeReplaced()) {
|
||||
return innerState;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (gatheredState != null) {
|
||||
return gatheredState;
|
||||
}
|
||||
|
||||
return original.call(level, blockPos);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.hose_pulley;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.simibubi.create.content.fluids.hosePulley.HosePulleyFluidHandler;
|
||||
import com.simibubi.create.content.fluids.transfer.FluidDrainingBehaviour;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.neoforged.neoforge.fluids.FluidStack;
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(HosePulleyFluidHandler.class)
|
||||
public abstract class HosePulleyFluidHandlerMixin {
|
||||
|
||||
@Shadow
|
||||
private FluidDrainingBehaviour drainer;
|
||||
|
||||
@Shadow
|
||||
private Supplier<BlockPos> rootPosGetter;
|
||||
@Unique
|
||||
private BlockPos sable$lastValidPos = null;
|
||||
|
||||
@Inject(method = "drainInternal", at = @At("HEAD"))
|
||||
public void sable$updateLastValidPos(final int maxDrain, final FluidStack resource, final IFluidHandler.FluidAction action, final CallbackInfoReturnable<FluidStack> cir) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final Level level = this.drainer.getWorld();
|
||||
final float distance = 1.5f;
|
||||
|
||||
this.sable$lastValidPos = helper.runIncludingSubLevels(level, this.rootPosGetter.get().getCenter(), true, helper.getContaining(level, this.drainer.getPos()), (sublevel, pos) -> {
|
||||
if (sable$hasFluid(level, pos)) {
|
||||
//add some leniency to the fluid gathering, while keeping large jumps (local -> other sublevel etc) possible
|
||||
if (this.sable$lastValidPos == null || this.sable$lastValidPos.distSqr(pos) > distance * distance) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
//no changes needed
|
||||
return this.sable$lastValidPos;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@WrapOperation(method = "drainInternal", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/fluids/transfer/FluidDrainingBehaviour;getDrainableFluid(Lnet/minecraft/core/BlockPos;)Lnet/neoforged/neoforge/fluids/FluidStack;"))
|
||||
public FluidStack sable$modifyGetDrainableFluid(final FluidDrainingBehaviour instance, final BlockPos rootPos, final Operation<FluidStack> original) {
|
||||
if (this.sable$lastValidPos != null) {
|
||||
return original.call(instance, this.sable$lastValidPos);
|
||||
}
|
||||
|
||||
return original.call(instance, rootPos);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "drainInternal", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/fluids/transfer/FluidDrainingBehaviour;pullNext(Lnet/minecraft/core/BlockPos;Z)Z"))
|
||||
public boolean sable$modifyPullNext(final FluidDrainingBehaviour instance, final BlockPos root, final boolean simulate, final Operation<Boolean> original) {
|
||||
if (this.sable$lastValidPos != null) {
|
||||
return original.call(instance, this.sable$lastValidPos, simulate);
|
||||
}
|
||||
|
||||
return original.call(instance, root, simulate);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "getFluidInTank", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/fluids/transfer/FluidDrainingBehaviour;getDrainableFluid(Lnet/minecraft/core/BlockPos;)Lnet/neoforged/neoforge/fluids/FluidStack;"))
|
||||
public FluidStack sable$modifyGetFluidInTank(final FluidDrainingBehaviour instance, final BlockPos rootPos, final Operation<FluidStack> original) {
|
||||
if (this.sable$lastValidPos != null) {
|
||||
return original.call(instance, this.sable$lastValidPos);
|
||||
}
|
||||
|
||||
return original.call(instance, rootPos);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean sable$hasFluid(final Level level, final BlockPos pos) {
|
||||
return !level.getFluidState(pos).isEmpty();
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.impact;
|
||||
|
||||
import com.simibubi.create.content.equipment.bell.AbstractBellBlock;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(AbstractBellBlock.class)
|
||||
public interface AbstractBellBlockAccessor {
|
||||
|
||||
@Invoker
|
||||
boolean invokeRing(Level world, BlockPos pos, Direction direction, Player player);
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.impact;
|
||||
|
||||
import com.simibubi.create.content.equipment.bell.AbstractBellBlock;
|
||||
import dev.ryanhcode.sable.api.block.BlockWithSubLevelCollisionCallback;
|
||||
import dev.ryanhcode.sable.api.physics.callback.BlockSubLevelCollisionCallback;
|
||||
import dev.ryanhcode.sable.neoforge.physics.callback.AbstractBellBlockCallback;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(AbstractBellBlock.class)
|
||||
public class AbstractBellBlockMixin implements BlockWithSubLevelCollisionCallback {
|
||||
|
||||
@Override
|
||||
public BlockSubLevelCollisionCallback sable$getCallback() {
|
||||
return AbstractBellBlockCallback.INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.inventory_manipulation;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.foundation.blockEntity.behaviour.inventory.CapManipulationBehaviourBase;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.createmod.catnip.math.BlockFace;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.neoforged.neoforge.capabilities.BlockCapability;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(CapManipulationBehaviourBase.class)
|
||||
public class CapManipulationBehaviourBaseMixin {
|
||||
|
||||
@Shadow protected Predicate<BlockEntity> filter;
|
||||
|
||||
@Shadow protected boolean bypassSided;
|
||||
|
||||
@Unique
|
||||
private BlockPos sable$caughtPos;
|
||||
|
||||
@Redirect(method = "findNewCapability", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getBlockEntity(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;"))
|
||||
public BlockEntity sable$findNewCapOnSubLevel(final Level level, final BlockPos blockPos) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
return helper.runIncludingSubLevels(level, blockPos.getCenter(), true, helper.getContaining(level, blockPos), (subLevel, internalPos) -> {
|
||||
final BlockEntity caughtBE = level.getBlockEntity(internalPos);
|
||||
if (this.filter.apply(caughtBE)) {
|
||||
this.sable$caughtPos = internalPos;
|
||||
return caughtBE;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Redirect(method = "findNewCapability", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getCapability(Lnet/neoforged/neoforge/capabilities/BlockCapability;Lnet/minecraft/core/BlockPos;Ljava/lang/Object;)Ljava/lang/Object;"))
|
||||
public <T> T sable$redirectPos(final Level instance, final BlockCapability<T, Direction> blockCapability, final BlockPos pos, final Object dir, @Local final BlockFace targetBlockFace) {
|
||||
return instance.getCapability(blockCapability, this.sable$caughtPos, this.bypassSided ? null : targetBlockFace.getFace());
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.inventory_manipulation;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.simibubi.create.content.logistics.chute.ChuteBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.capabilities.Capabilities;
|
||||
import net.neoforged.neoforge.items.IItemHandler;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(ChuteBlockEntity.class)
|
||||
public abstract class ChuteBlockEntityMixin extends SmartBlockEntity {
|
||||
|
||||
public ChuteBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@WrapMethod(method = "grabCapability")
|
||||
public IItemHandler sable$grabCap(final Direction side, final Operation<IItemHandler> original) {
|
||||
final IItemHandler handler = original.call(side);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
|
||||
// anything past this, we don't really need a cache... It has the potential to constantly move as it's not local
|
||||
final Level level = this.getLevel();
|
||||
assert level != null;
|
||||
|
||||
final BlockPos checkPos = this.worldPosition.relative(side);
|
||||
final Direction opposite = side.getOpposite();
|
||||
final Vector3d mut = new Vector3d(opposite.getStepX(), opposite.getStepY(), opposite.getStepZ());
|
||||
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
final SubLevel parentSublevel = helper.getContaining(level, checkPos);
|
||||
if (parentSublevel != null) {
|
||||
parentSublevel.logicalPose().transformNormalInverse(mut);
|
||||
}
|
||||
|
||||
final Vector3d includSublevelDir = new Vector3d(mut);
|
||||
return helper.runIncludingSubLevels(
|
||||
level,
|
||||
checkPos.getCenter(),
|
||||
false,
|
||||
parentSublevel,
|
||||
(sublevel, pos) -> {
|
||||
includSublevelDir.set(mut);
|
||||
if (sublevel != null) {
|
||||
sublevel.logicalPose().transformNormal(includSublevelDir);
|
||||
}
|
||||
|
||||
return level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.getNearest(includSublevelDir.x, includSublevelDir.y, includSublevelDir.z));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.lectern_controller;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.redstone.link.controller.LecternControllerBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.neoforge.mixinterface.compatibility.create.LecternControllerBlockEntityExtension;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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 java.util.UUID;
|
||||
|
||||
@Mixin(LecternControllerBlockEntity.class)
|
||||
public abstract class LecternControllerBlockEntityMixin extends SmartBlockEntity implements LecternControllerBlockEntityExtension {
|
||||
|
||||
public LecternControllerBlockEntityMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Shadow
|
||||
protected abstract void stopUsing(Player player);
|
||||
|
||||
@Shadow
|
||||
private UUID user;
|
||||
@Unique
|
||||
private boolean sable$noDrop;
|
||||
|
||||
@Inject(method = "dropController", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z"), cancellable = true)
|
||||
public void sable$dropController(final BlockState state, final CallbackInfo ci) {
|
||||
if (!this.sable$noDrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
final Entity entity = ((ServerLevel) this.level).getEntity(this.user);
|
||||
if (entity instanceof final Player player) {
|
||||
this.stopUsing(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "playerInRange", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;distanceToSqr(Lnet/minecraft/world/phys/Vec3;)D"))
|
||||
private static double sable$fixDistanceCheck(final Vec3 a, final Vec3 b, @Local(argsOnly = true) final Level level) {
|
||||
return Sable.HELPER.distanceSquaredWithSubLevels(level, a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sable$setNoDrop() {
|
||||
this.sable$noDrop = true;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.lectern_controller;
|
||||
|
||||
import com.simibubi.create.content.redstone.link.controller.LecternControllerBlock;
|
||||
import com.simibubi.create.content.redstone.link.controller.LecternControllerBlockEntity;
|
||||
import dev.ryanhcode.sable.api.block.BlockSubLevelAssemblyListener;
|
||||
import dev.ryanhcode.sable.neoforge.mixinterface.compatibility.create.LecternControllerBlockEntityExtension;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(LecternControllerBlock.class)
|
||||
public class LecternControllerBlockMixin implements BlockSubLevelAssemblyListener {
|
||||
|
||||
@Override
|
||||
public void afterMove(final ServerLevel originLevel, final ServerLevel resultingLevel, final BlockState newState, final BlockPos oldPos, final BlockPos newPos) {
|
||||
if (originLevel.getBlockEntity(oldPos) instanceof final LecternControllerBlockEntity be) {
|
||||
((LecternControllerBlockEntityExtension) be).sable$setNoDrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.mechnical_arm;
|
||||
|
||||
import com.simibubi.create.content.kinetics.mechanicalArm.ArmBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
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(ArmBlockEntity.class)
|
||||
public abstract class MechanicalArmBlockEntity extends SmartBlockEntity {
|
||||
|
||||
public MechanicalArmBlockEntity(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Inject(method = "isAreaActuallyLoaded", at = @At("HEAD"), cancellable = true)
|
||||
private void sable$forceMechArmsLoad(final BlockPos center, final int range, final CallbackInfoReturnable<Boolean> cir) {
|
||||
if (Sable.HELPER.getContaining(this.getLevel(), center) != null) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.mechnical_arm;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPoint;
|
||||
import com.simibubi.create.content.kinetics.mechanicalArm.ArmInteractionPointHandler;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.createmod.catnip.lang.LangBuilder;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
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;
|
||||
|
||||
@Mixin(ArmInteractionPointHandler.class)
|
||||
public class MechanicalArmSublevelFailure {
|
||||
|
||||
@Inject(method = "flushSettings", at = @At("HEAD"))
|
||||
private static void sable$gatherSublevelInformation(final BlockPos pos, final CallbackInfo ci, @Share("parentSublevel") final LocalRef<SubLevel> parentSublevel, @Share("pointsRemovedSublevel") final LocalRef<Integer> pointsRemovedSublevel) {
|
||||
parentSublevel.set(Sable.HELPER.getContainingClient(pos));
|
||||
pointsRemovedSublevel.set(0);
|
||||
}
|
||||
|
||||
@Inject(method = "flushSettings", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerThan(Lnet/minecraft/core/Vec3i;D)Z"))
|
||||
private static void sable$removeDifferentSublevelPoints(final BlockPos pos, final CallbackInfo ci, @Local(name = "point") final ArmInteractionPoint point, @Share("pointsRemovedSublevel") final LocalRef<Integer> pointsRemovedSublevel, @Share("parentSublevel") final LocalRef<SubLevel> parentSublevel) {
|
||||
final SubLevel pointsublevel = Sable.HELPER.getContainingClient(point.getPos());
|
||||
if (parentSublevel.get() != pointsublevel) {
|
||||
pointsRemovedSublevel.set(pointsRemovedSublevel.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "flushSettings", at = @At(value = "INVOKE", target = "Lnet/createmod/catnip/lang/LangBuilder;translate(Ljava/lang/String;[Ljava/lang/Object;)Lnet/createmod/catnip/lang/LangBuilder;"))
|
||||
private static LangBuilder sable$relayRemovedPoints(final LangBuilder instance, final String langKey, final Object[] args, @Local(name = "removed") final int removed, @Share("pointsRemovedSublevel") final LocalRef<Integer> pointsRemovedSublevel) {
|
||||
|
||||
final Integer arg = (Integer) args[0];
|
||||
Component errorComponent = Component.empty();
|
||||
if (pointsRemovedSublevel.get() == 0) {
|
||||
instance.translate(langKey, args);
|
||||
} else if (arg - pointsRemovedSublevel.get() == 0) {
|
||||
errorComponent = Component.translatable("sable.create.remove.points_removed_sublevel", removed)
|
||||
.withStyle(ChatFormatting.RED);
|
||||
} else {
|
||||
errorComponent = Component.translatable("sable.create.mechanical_arm.points_removed_sublevel_and_range", removed)
|
||||
.withStyle(ChatFormatting.RED);
|
||||
}
|
||||
|
||||
instance.add(errorComponent);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.nozzle;
|
||||
|
||||
import com.simibubi.create.content.kinetics.fan.NozzleBlockEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(NozzleBlockEntity.class)
|
||||
public interface NozzleBlockEntityAccessor {
|
||||
|
||||
@Accessor
|
||||
float getRange();
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.nozzle.block_entity;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.content.kinetics.fan.NozzleBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.neoforge.mixinterface.compatibility.create.NozzleBlockEntityExtension;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(NozzleBlockEntity.class)
|
||||
public abstract class NozzleBEFixesMixin extends SmartBlockEntity {
|
||||
|
||||
public NozzleBEFixesMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Redirect(remap = false, method = "tick", at = @At(value = "INVOKE", target = "Lnet/createmod/catnip/math/VecHelper;getCenterOf(Lnet/minecraft/core/Vec3i;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$nozzlePosition(final Vec3i pos) {
|
||||
return JOMLConversion.toMojang(Sable.HELPER.projectOutOfSubLevel(this.getLevel(), JOMLConversion.atCenterOf(pos)));
|
||||
}
|
||||
|
||||
@Redirect(remap = false, method = "lazyTick", at = @At(value = "INVOKE", target = "Lnet/createmod/catnip/math/VecHelper;getCenterOf(Lnet/minecraft/core/Vec3i;)Lnet/minecraft/world/phys/Vec3;"))
|
||||
public Vec3 sable$nozzlePositionLazy(final Vec3i pos) {
|
||||
return JOMLConversion.toMojang(Sable.HELPER.projectOutOfSubLevel(this.getLevel(), JOMLConversion.atCenterOf(pos)));
|
||||
}
|
||||
|
||||
@Redirect(method = "canSee", at = @At(value = "INVOKE", target = "Lnet/createmod/catnip/math/VecHelper;getCenterOf(Lnet/minecraft/core/Vec3i;)Lnet/minecraft/world/phys/Vec3;"), remap = false)
|
||||
private Vec3 sable$projectCenter(final Vec3i pos) {
|
||||
return JOMLConversion.toMojang(Sable.HELPER.projectOutOfSubLevel(this.getLevel(), JOMLConversion.atCenterOf(pos)));
|
||||
}
|
||||
|
||||
@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;clamp(III)I"))
|
||||
public int sable$clampParticlesMore(final int value, final int min, final int max, final Operation<Integer> original) {
|
||||
return original.call(value, 3, max);
|
||||
}
|
||||
|
||||
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;addParticle(Lnet/minecraft/core/particles/ParticleOptions;DDDDDD)V"))
|
||||
public void sable$checkDirection(final Level instance, final ParticleOptions particleOptions, final double x, final double y, final double z, final double mx, final double my, final double mz, @Local(ordinal = 0) final Vec3 origin, @Local(ordinal = 1) final Vec3 start) {
|
||||
final Vec3 direction = start.subtract(origin).normalize();
|
||||
|
||||
final Direction nearest = Direction.getNearest(direction.x, direction.y, direction.z);
|
||||
if (!((NozzleBlockEntityExtension) this).sable$getValidDirections().contains(nearest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance.addParticle(particleOptions, x, y, z, mx, my, mz);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.nozzle.block_entity;
|
||||
|
||||
import com.simibubi.create.content.kinetics.fan.NozzleBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.ActiveSableCompanion;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.block.BlockEntitySubLevelActor;
|
||||
import dev.ryanhcode.sable.api.physics.force.ForceGroups;
|
||||
import dev.ryanhcode.sable.api.physics.force.QueuedForceGroup;
|
||||
import dev.ryanhcode.sable.api.physics.handle.RigidBodyHandle;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.nozzles.NozzleHoveringHelper;
|
||||
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
|
||||
import net.createmod.catnip.data.Couple;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(NozzleBlockEntity.class)
|
||||
public abstract class NozzleHoveringMixin extends SmartBlockEntity implements BlockEntitySubLevelActor {
|
||||
|
||||
@Shadow private boolean pushing;
|
||||
@Shadow private float range;
|
||||
@Unique
|
||||
private List<Couple<Vec3>> sable$rayPoints = null;
|
||||
|
||||
public NozzleHoveringMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
public void sable$generateRays(final BlockEntityType type, final BlockPos pos, final BlockState state, final CallbackInfo ci) {
|
||||
this.sable$rayPoints = NozzleHoveringHelper.gatherRaycastPoints(state);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/RandomSource;nextInt(I)I"))
|
||||
private void addPhysicsParticles(final CallbackInfo ci) {
|
||||
final ActiveSableCompanion helper = Sable.HELPER;
|
||||
if (helper.getContaining(this) != null && this.pushing) {
|
||||
final Vec3 blockCorner = Vec3.atLowerCornerOf(this.getBlockPos());
|
||||
final Couple<Vec3> ray = this.sable$rayPoints.get(this.level.random.nextInt(this.sable$rayPoints.size()));
|
||||
final Vec3 start = ray.getFirst().add(blockCorner);
|
||||
final Vec3 end = ray.getSecond().add(blockCorner);
|
||||
final ClipContext context = new ClipContext(
|
||||
start,
|
||||
end,
|
||||
ClipContext.Block.OUTLINE,
|
||||
ClipContext.Fluid.ANY,
|
||||
CollisionContext.empty()
|
||||
);
|
||||
final BlockHitResult clip = this.level.clip(context);
|
||||
NozzleHoveringHelper.spawnWindHitParticle(
|
||||
this.level, helper.getContaining(this), clip,
|
||||
JOMLConversion.toJOML(start), this.range / 40
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sable$physicsTick(final ServerSubLevel subLevel, final RigidBodyHandle handle, final double timeStep) {
|
||||
final Vector3d force = NozzleHoveringHelper.gatherForceFromRays(subLevel, timeStep, this.getLevel(), this.getBlockPos(), (NozzleBlockEntity) (Object) this, this.sable$rayPoints);
|
||||
|
||||
if (force != null) {
|
||||
final QueuedForceGroup forceGroup = subLevel.getOrCreateQueuedForceGroup(ForceGroups.PROPULSION.get());
|
||||
forceGroup.applyAndRecordPointForce(JOMLConversion.toJOML(Vec3.atCenterOf(this.getBlockPos())), force);
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.nozzle.block_entity;
|
||||
|
||||
import com.simibubi.create.content.kinetics.fan.NozzleBlockEntity;
|
||||
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
|
||||
import dev.ryanhcode.sable.neoforge.mixinterface.compatibility.create.NozzleBlockEntityExtension;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@Mixin(NozzleBlockEntity.class)
|
||||
public abstract class ValidNozzledirectionMixin extends SmartBlockEntity implements NozzleBlockEntityExtension {
|
||||
|
||||
@Unique
|
||||
private final EnumSet<Direction> sable$validDirections = EnumSet.noneOf(Direction.class);
|
||||
|
||||
public ValidNozzledirectionMixin(final BlockEntityType<?> type, final BlockPos pos, final BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<Direction> sable$getValidDirections() {
|
||||
return this.sable$validDirections;
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
public void sable$updateValidDirections(final CallbackInfo ci) {
|
||||
this.sable$validDirections.clear();
|
||||
|
||||
if (this.getLevel() != null) {
|
||||
for (final Direction value : Direction.values()) {
|
||||
final BlockState state = this.getLevel().getBlockState(this.getBlockPos().relative(value));
|
||||
if (state.canBeReplaced()) {
|
||||
this.sable$validDirections.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.particles;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import com.simibubi.create.content.kinetics.fan.AirFlowParticle;
|
||||
import com.simibubi.create.content.kinetics.fan.IAirCurrentSource;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.particle.SimpleAnimatedParticle;
|
||||
import net.minecraft.client.particle.SpriteSet;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
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;
|
||||
|
||||
@Mixin(AirFlowParticle.class)
|
||||
public abstract class AirFlowParticleMixin extends SimpleAnimatedParticle {
|
||||
|
||||
@Unique
|
||||
Vec3 sable$subLevelOrientation;
|
||||
@Shadow
|
||||
@Final
|
||||
private IAirCurrentSource source;
|
||||
|
||||
protected AirFlowParticleMixin(final ClientLevel arg, final double d, final double e, final double f, final SpriteSet arg2, final float g) {
|
||||
super(arg, d, e, f, arg2, g);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
|
||||
public void sable$fixAirflowParticle(final CallbackInfo ci) {
|
||||
if (this.source == null || this.source.getAirCurrent() == null || this.source.getAirCurrent().direction == null) {
|
||||
this.remove();
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/AABB;contains(DDD)Z", ordinal = 0))
|
||||
public boolean sable$reverseProjectPos(final AABB instance, final double x, final double y, final double z) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(this.source.getAirCurrentPos());
|
||||
if (subLevel != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return instance.contains(x, y, z);
|
||||
}
|
||||
|
||||
@Redirect(method = "tick", at = @At(value = "NEW", target = "(DDD)Lnet/minecraft/world/phys/Vec3;", ordinal = 0))
|
||||
public Vec3 sable$reverseProjectPos2(final double x, final double y, final double z) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(this.source.getAirCurrentPos());
|
||||
if (subLevel != null) {
|
||||
return subLevel.logicalPose().transformPositionInverse(new Vec3(x, y, z));
|
||||
}
|
||||
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lcom/simibubi/create/content/kinetics/fan/IAirCurrentSource;getAirCurrent()Lcom/simibubi/create/content/kinetics/fan/AirCurrent;", ordinal = 1))
|
||||
public void sable$transformNormal(final CallbackInfo ci, @Local(ordinal = 1) final LocalRef<Vec3> motion) {
|
||||
final SubLevel subLevel = Sable.HELPER.getContainingClient(this.source.getAirCurrentPos());
|
||||
|
||||
if (subLevel != null) {
|
||||
if (this.sable$subLevelOrientation == null /*|| !this.source.getAirCurrent().pushing*/) {
|
||||
this.sable$subLevelOrientation = subLevel.logicalPose().transformNormal(motion.get());
|
||||
}
|
||||
} else {
|
||||
this.sable$subLevelOrientation = motion.get();
|
||||
}
|
||||
|
||||
motion.set(this.sable$subLevelOrientation);
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.particles;
|
||||
|
||||
import com.simibubi.create.foundation.particle.AirParticle;
|
||||
import com.simibubi.create.foundation.particle.AirParticleData;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.api.particle.ParticleSubLevelKickable;
|
||||
import net.createmod.catnip.math.VecHelper;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.particle.SimpleAnimatedParticle;
|
||||
import net.minecraft.client.particle.SpriteSet;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Vector3d;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(AirParticle.class)
|
||||
public abstract class AirParticleMixin extends SimpleAnimatedParticle implements ParticleSubLevelKickable {
|
||||
|
||||
@Shadow
|
||||
private float twirlAngleOffset;
|
||||
|
||||
@Shadow
|
||||
private float twirlRadius;
|
||||
|
||||
@Shadow
|
||||
private float drag;
|
||||
|
||||
@Unique
|
||||
private double sable$originX;
|
||||
@Unique
|
||||
private double sable$originZ;
|
||||
@Unique
|
||||
private double sable$originY;
|
||||
@Unique
|
||||
private double sable$targetY;
|
||||
@Unique
|
||||
private double sable$targetX;
|
||||
@Unique
|
||||
private double sable$targetZ;
|
||||
|
||||
@Shadow
|
||||
private Direction.Axis twirlAxis;
|
||||
|
||||
protected AirParticleMixin(final ClientLevel arg, final double d, final double e, final double f, final SpriteSet arg2, final float g) {
|
||||
super(arg, d, e, f, arg2, g);
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void sable$postInit(final ClientLevel world, final AirParticleData data, final double x, final double y, final double z, final double dx, final double dy, final double dz, final SpriteSet sprite, final CallbackInfo ci) {
|
||||
this.sable$originX = x;
|
||||
this.sable$originY = y;
|
||||
this.sable$originZ = z;
|
||||
this.sable$targetX = x + dx;
|
||||
this.sable$targetY = y + dy;
|
||||
this.sable$targetZ = z + dz;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author RyanH
|
||||
* @reason Fix target / origin handling with sub-levels
|
||||
*/
|
||||
@Overwrite
|
||||
public void tick() {
|
||||
this.xo = this.x;
|
||||
this.yo = this.y;
|
||||
this.zo = this.z;
|
||||
if (this.age++ >= this.lifetime) {
|
||||
this.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
final float progress = (float) Math.pow(((float) this.age) / this.lifetime, this.drag);
|
||||
final float angle = (progress * 2 * 360 + this.twirlAngleOffset) % 360;
|
||||
final Vec3 twirl = VecHelper.rotate(new Vec3(0, this.twirlRadius, 0), angle, this.twirlAxis);
|
||||
|
||||
final double desiredX = (Mth.lerp(progress, this.sable$originX, this.sable$targetX) + twirl.x);
|
||||
final double desiredY = (Mth.lerp(progress, this.sable$originY, this.sable$targetY) + twirl.y);
|
||||
final double desiredZ = (Mth.lerp(progress, this.sable$originZ, this.sable$targetZ) + twirl.z);
|
||||
final Vector3d desiredVec = Sable.HELPER.projectOutOfSubLevel(this.level, new Vector3d(desiredX, desiredY, desiredZ));
|
||||
|
||||
this.xd = desiredVec.x - this.x;
|
||||
this.yd = desiredVec.y - this.y;
|
||||
this.zd = desiredVec.z - this.z;
|
||||
|
||||
this.setSpriteFromAge(this.sprites);
|
||||
this.move(this.xd, this.yd, this.zd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sable$shouldKickFromTracking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sable$shouldCollideWithTrackingSubLevel() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.raycast;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.simibubi.create.foundation.utility.RaycastHelper;
|
||||
import dev.ryanhcode.sable.Sable;
|
||||
import dev.ryanhcode.sable.companion.math.JOMLConversion;
|
||||
import dev.ryanhcode.sable.sublevel.SubLevel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Vector3d;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.raycasts.SableRaycastHelper.rayCastUntilWithSublevels;
|
||||
|
||||
/**
|
||||
* Fixes Create's {@link RaycastHelper} to take into account sub-levels in raycasts
|
||||
*/
|
||||
@Mixin(RaycastHelper.class)
|
||||
public class RaycastHelperMixin {
|
||||
|
||||
|
||||
@Redirect(method = "getTraceTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;"))
|
||||
private static Vec3 sable$rotateWithSublevels(final Vec3 instance, final double pX, final double pY, final double pZ, @Local(argsOnly = true) final Player player) {
|
||||
Vec3 resultTarget = new Vec3(pX, pY, pZ);
|
||||
|
||||
final Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
final SubLevel vehicleSubLevel = Sable.HELPER.getContaining(player.level(), vehicle.position());
|
||||
|
||||
// Rotate the target if the player is in a vehicle
|
||||
if (vehicleSubLevel != null) {
|
||||
final Vector3d vec = JOMLConversion.toJOML(resultTarget);
|
||||
|
||||
vehicleSubLevel.logicalPose().orientation().transform(vec);
|
||||
resultTarget = JOMLConversion.toMojang(vec);
|
||||
}
|
||||
}
|
||||
|
||||
return instance.add(resultTarget);
|
||||
}
|
||||
|
||||
@Inject(method = "rayTraceUntil(Lnet/minecraft/world/entity/player/Player;DLjava/util/function/Predicate;)Lcom/simibubi/create/foundation/utility/RaycastHelper$PredicateTraceResult;",
|
||||
at = @At(value = "HEAD"),
|
||||
remap = false, cancellable = true)
|
||||
private static void sable$rayTraceSublevels(final Player playerIn, final double range, final Predicate<BlockPos> predicate, final CallbackInfoReturnable<RaycastHelper.PredicateTraceResult> cir) {
|
||||
final Vec3 start = playerIn.getEyePosition();
|
||||
final Vec3 end = RaycastHelper.getTraceTarget(playerIn, range, start);
|
||||
|
||||
cir.setReturnValue(rayCastUntilWithSublevels(playerIn.level(), start, end, predicate));
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.redstone_contacts;
|
||||
|
||||
import com.simibubi.create.AllBlockEntityTypes;
|
||||
import com.simibubi.create.AllBlocks;
|
||||
import com.simibubi.create.foundation.data.CreateRegistrate;
|
||||
import com.tterrag.registrate.util.entry.BlockEntityEntry;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.redstone_contact.RedstoneContactBlockEntity;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.redstone_contact.RedstoneContactBlockEntityTypeGetter;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(AllBlockEntityTypes.class)
|
||||
public class AllBlockEntityTypesMixin implements RedstoneContactBlockEntityTypeGetter {
|
||||
|
||||
@Shadow @Final private static CreateRegistrate REGISTRATE;
|
||||
@Unique
|
||||
private static final BlockEntityEntry<RedstoneContactBlockEntity> REDSTONE_CONTACT = REGISTRATE
|
||||
.blockEntity("redstone_contact", RedstoneContactBlockEntity::new)
|
||||
.validBlock(AllBlocks.REDSTONE_CONTACT)
|
||||
.register();
|
||||
|
||||
|
||||
@Override
|
||||
public BlockEntityEntry<RedstoneContactBlockEntity> sable$getRedstoneContactType() {
|
||||
return REDSTONE_CONTACT;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.redstone_contacts;
|
||||
|
||||
import com.simibubi.create.AllBlockEntityTypes;
|
||||
import com.simibubi.create.content.redstone.contact.RedstoneContactBlock;
|
||||
import com.simibubi.create.foundation.block.IBE;
|
||||
import com.simibubi.create.foundation.block.WrenchableDirectionalBlock;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.redstone_contact.RedstoneContactBlockEntity;
|
||||
import dev.ryanhcode.sable.neoforge.mixinhelper.compatibility.create.redstone_contact.RedstoneContactBlockEntityTypeGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(RedstoneContactBlock.class)
|
||||
public class RedstoneContactBlockMixin extends WrenchableDirectionalBlock implements IBE<RedstoneContactBlockEntity> {
|
||||
|
||||
@Unique
|
||||
private static final AllBlockEntityTypes sable$cursed = new AllBlockEntityTypes();
|
||||
|
||||
public RedstoneContactBlockMixin(final Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RedstoneContactBlockEntity> getBlockEntityClass() {
|
||||
return RedstoneContactBlockEntity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntityType<? extends RedstoneContactBlockEntity> getBlockEntityType() {
|
||||
return ((RedstoneContactBlockEntityTypeGetter) sable$cursed).sable$getRedstoneContactType().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends BlockEntity> BlockEntityTicker<S> getTicker(final Level level, final BlockState p_153213_, final BlockEntityType<S> p_153214_) {
|
||||
if (!level.isClientSide) {
|
||||
return IBE.super.getTicker(level, p_153213_, p_153214_);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user