Multiloader I

move all of src
This commit is contained in:
techno-sam
2026-04-05 11:56:59 +02:00
parent b16c3e4ec9
commit 7d48193324
135 changed files with 0 additions and 0 deletions
@@ -0,0 +1,44 @@
// 1.21.1 2026-04-04T17:09:36.734955339 Crystalline Sky/Model Definitions
fceb0c59b7f6d431200764ccf87596541bacb17f assets/crystalline_sky/models/block/sky_light_07.json
dff33a68a694de0f3ef177886c002204ffe49a8c assets/crystalline_sky/models/item/sky_light_07.json
51fa40ff02afcec303e7a18478cbae5375dfb140 assets/crystalline_sky/models/item/sky_light_04.json
14508173fcca14cb42755d47374c94d3eace42ce assets/crystalline_sky/models/block/weeping_sky.json
317f9881c20b1aaa3902b3999dd1c76d00afbde6 assets/crystalline_sky/models/block/sky_light_04.json
9b3d01168a1cc29b99cf69fe225ad69b0923c9fe assets/crystalline_sky/blockstates/weeping_sky_light.json
a87d8988452529a1f5af5662ad636223fdd98fec assets/crystalline_sky/models/block/sky_light_15.json
0e8a96cfb88f4c58679709cc9397b5700878a9c4 assets/crystalline_sky/models/item/sky_light_01.json
ecc125a24d3e2c86d922352d8449f26a4e06d122 assets/crystalline_sky/blockstates/sky_light.json
b8795df31017499b35a5b37a4dfbe555a8242cb0 assets/crystalline_sky/models/item/sky_light_12.json
d500e56057c0c2f3c948894694e2826f1726fd9e assets/crystalline_sky/models/item/sky_light_15.json
0b4015acca6117ea633ae3cdaf3fe40fcab6b628 assets/crystalline_sky/models/block/sky_light_13.json
7115b88da2cc0bfdc68646d034de8d14f181eb01 assets/crystalline_sky/models/item/sky_light_09.json
910c51120a0931851db6b75b337bc21241165c2f assets/crystalline_sky/models/item/sky_light_06.json
59463cef438e58b137e5ce27402acf42a3a0ae87 assets/crystalline_sky/models/block/sky_light_02.json
9c6ba4d90f2c4f2a9beb910c1b32023e49445749 assets/crystalline_sky/models/block/sky_light_10.json
ef5afdbcc8401a27029b6f0a29fa1117761c620d assets/crystalline_sky/models/item/sky_light.json
9f0d9fe5dfb05bbe18177db5c40e600c5b256858 assets/crystalline_sky/models/item/sky_light_00.json
b29c14f77a08da7a6994cff4c6801d6f04b12ec0 assets/crystalline_sky/models/item/sky_light_03.json
19859c3124196f3788bfbae1ec2bcd4d1e46db2e assets/crystalline_sky/models/block/sky_light_06.json
508ffb97ee711b53767b9d33659596197f69ee20 assets/crystalline_sky/models/block/weeping_sky_light.json
ef9e5aca345b59cb4692a7bcfaac539aed6f6889 assets/crystalline_sky/models/block/sky_light_09.json
04f1e4eba184b8e7b1669fc61006e3d00e09d164 assets/crystalline_sky/models/item/sky_light_11.json
0ac5351c3a01e0f9e76517452918e12770b3b54b assets/crystalline_sky/models/block/sky_light_14.json
58349dc5dedb8016fb0bc958fcd4a9ccde13a1cd assets/crystalline_sky/models/item/sky_light_14.json
03e2eb768bc34fbeaabffbff8ed3a2a3044a6db1 assets/crystalline_sky/blockstates/weeping_sky.json
be0be7e1d7ce1ccabed01ef09c6499a2a7f2b132 assets/crystalline_sky/models/item/weeping_sky_light.json
e91808daecfc454b784f5fb27003c58bb0caedce assets/crystalline_sky/models/block/sky_light_11.json
3183aeb9fff78d0c65f1ddd0dfb599420573e281 assets/crystalline_sky/models/block/sky_light_00.json
ee9cb7604922cdb2ad71d021a593ca82f0baf493 assets/crystalline_sky/models/block/sky_light_03.json
c0d693e4f78ce15c238909130382909abda4c922 assets/crystalline_sky/models/block/sky_light_08.json
2ce612b8445da92c6cecd07bd5364a25bcfb127f assets/crystalline_sky/models/block/sky_light_05.json
25f0831f8c64db0e5c08fc968445e329f1e3a91c assets/crystalline_sky/models/item/sky_light_13.json
15c0a47d504ddd41bbecd4ac58c0c8ccd0f37943 assets/crystalline_sky/models/item/weeping_sky.json
ed5894de7afa66f73c72093f2dd7d046cf647070 assets/crystalline_sky/models/item/sky_light_02.json
42f8b696ba0f511edce656b9055764ed13013221 assets/crystalline_sky/models/item/sky_light_05.json
9c6c61b7f805a614d2d15d8dde390ed068913182 assets/crystalline_sky/models/item/sky_light_10.json
6d1e10716c05150e73b2b9e254091125264a7a3d assets/crystalline_sky/models/block/sky.json
e2c7bebd9eca446bb06fff866ec4d4996917e7e4 assets/crystalline_sky/models/block/sky_light_12.json
e832366c216102270b75085db7a7169881b853b3 assets/crystalline_sky/blockstates/sky.json
7eaa4c3d4913e0cb117585204dd1dd9e58402b83 assets/crystalline_sky/models/block/sky_light_01.json
89582890605d371d936384a83950af603d2fe495 assets/crystalline_sky/models/item/sky_light_08.json
008a0d0a76a34d8f375fa355452a5d0f93f9af3b assets/crystalline_sky/models/item/sky.json
@@ -0,0 +1,3 @@
// 1.21.1 2026-04-04T17:09:36.733906405 Crystalline Sky/Tags for minecraft:block
606477607a09045ff2830cbc036e744d6a728bbd data/minecraft/tags/block/crystal_sound_blocks.json
1389c0b812f7a6e4d5dd00edac1dc07beb5f9552 data/crystalline_sky/tags/block/sky_light_emitters.json
@@ -0,0 +1,3 @@
// 1.21.1 2026-04-04T17:09:36.73618123 Crystalline Sky/Block Loot Tables
337de0acfc1b9b86adde0705e342ed1d4b5c9c4e data/crystalline_sky/loot_table/blocks/weeping_sky.json
6a9b7a08424cbd3d699dfd3252d432517254aa45 data/crystalline_sky/loot_table/blocks/sky.json
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "crystalline_sky:block/sky"
}
}
}
@@ -0,0 +1,52 @@
{
"variants": {
"level=0": {
"model": "crystalline_sky:block/sky_light_00"
},
"level=1": {
"model": "crystalline_sky:block/sky_light_01"
},
"level=10": {
"model": "crystalline_sky:block/sky_light_10"
},
"level=11": {
"model": "crystalline_sky:block/sky_light_11"
},
"level=12": {
"model": "crystalline_sky:block/sky_light_12"
},
"level=13": {
"model": "crystalline_sky:block/sky_light_13"
},
"level=14": {
"model": "crystalline_sky:block/sky_light_14"
},
"level=15": {
"model": "crystalline_sky:block/sky_light_15"
},
"level=2": {
"model": "crystalline_sky:block/sky_light_02"
},
"level=3": {
"model": "crystalline_sky:block/sky_light_03"
},
"level=4": {
"model": "crystalline_sky:block/sky_light_04"
},
"level=5": {
"model": "crystalline_sky:block/sky_light_05"
},
"level=6": {
"model": "crystalline_sky:block/sky_light_06"
},
"level=7": {
"model": "crystalline_sky:block/sky_light_07"
},
"level=8": {
"model": "crystalline_sky:block/sky_light_08"
},
"level=9": {
"model": "crystalline_sky:block/sky_light_09"
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "crystalline_sky:block/weeping_sky"
}
}
}
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "crystalline_sky:block/weeping_sky_light"
}
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "crystalline_sky:block/sky"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_00"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_01"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_02"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_03"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_04"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_05"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_06"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_07"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_08"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_09"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_10"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_11"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_12"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_13"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_14"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/sky_light_15"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "crystalline_sky:block/weeping_sky"
}
}
@@ -0,0 +1,5 @@
{
"textures": {
"particle": "crystalline_sky:item/weeping_sky_light"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:block/sky"
}
}
@@ -0,0 +1,104 @@
{
"parent": "minecraft:item/generated",
"overrides": [
{
"model": "crystalline_sky:item/sky_light_00",
"predicate": {
"level": 0.0
}
},
{
"model": "crystalline_sky:item/sky_light_01",
"predicate": {
"level": 0.0625
}
},
{
"model": "crystalline_sky:item/sky_light_02",
"predicate": {
"level": 0.125
}
},
{
"model": "crystalline_sky:item/sky_light_03",
"predicate": {
"level": 0.1875
}
},
{
"model": "crystalline_sky:item/sky_light_04",
"predicate": {
"level": 0.25
}
},
{
"model": "crystalline_sky:item/sky_light_05",
"predicate": {
"level": 0.3125
}
},
{
"model": "crystalline_sky:item/sky_light_06",
"predicate": {
"level": 0.375
}
},
{
"model": "crystalline_sky:item/sky_light_07",
"predicate": {
"level": 0.4375
}
},
{
"model": "crystalline_sky:item/sky_light_08",
"predicate": {
"level": 0.5
}
},
{
"model": "crystalline_sky:item/sky_light_09",
"predicate": {
"level": 0.5625
}
},
{
"model": "crystalline_sky:item/sky_light_10",
"predicate": {
"level": 0.625
}
},
{
"model": "crystalline_sky:item/sky_light_11",
"predicate": {
"level": 0.6875
}
},
{
"model": "crystalline_sky:item/sky_light_12",
"predicate": {
"level": 0.75
}
},
{
"model": "crystalline_sky:item/sky_light_13",
"predicate": {
"level": 0.8125
}
},
{
"model": "crystalline_sky:item/sky_light_14",
"predicate": {
"level": 0.875
}
},
{
"model": "crystalline_sky:item/sky_light_15",
"predicate": {
"level": 0.9375
}
}
],
"textures": {
"layer0": "crystalline_sky:item/sky_light"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_00"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_01"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_02"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_03"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_04"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_05"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_06"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_07"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_08"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_09"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_10"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_11"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_12"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_13"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_14"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/sky_light_15"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:block/weeping_sky"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "crystalline_sky:item/weeping_sky_light"
}
}
@@ -0,0 +1,32 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"predicates": {
"minecraft:enchantments": [
{
"enchantments": "minecraft:silk_touch",
"levels": {
"min": 1
}
}
]
}
}
}
],
"entries": [
{
"type": "minecraft:item",
"name": "crystalline_sky:sky"
}
],
"rolls": 1.0
}
]
}
@@ -0,0 +1,32 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"predicates": {
"minecraft:enchantments": [
{
"enchantments": "minecraft:silk_touch",
"levels": {
"min": 1
}
}
]
}
}
}
],
"entries": [
{
"type": "minecraft:item",
"name": "crystalline_sky:weeping_sky"
}
],
"rolls": 1.0
}
]
}
@@ -0,0 +1,8 @@
{
"values": [
"crystalline_sky:sky",
"crystalline_sky:sky_light",
"crystalline_sky:weeping_sky",
"crystalline_sky:weeping_sky_light"
]
}
@@ -0,0 +1,6 @@
{
"values": [
"crystalline_sky:sky",
"crystalline_sky:weeping_sky"
]
}
@@ -0,0 +1,26 @@
package io.github.slimeistdev.crystalline_sky;
import net.fabricmc.api.ModInitializer;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CrystallineSky implements ModInitializer {
public static final String ID = "crystalline_sky";
public static final Logger LOG = LoggerFactory.getLogger(ID);
@Override
public void onInitialize() {
ModSetup.init();
}
public static ResourceLocation id(String path) {
return ResourceLocation.fromNamespaceAndPath(ID, path);
}
public static <T> ResourceKey<T> key(ResourceKey<? extends Registry<T>> registryKey, String path) {
return ResourceKey.create(registryKey, id(path));
}
}
@@ -0,0 +1,60 @@
package io.github.slimeistdev.crystalline_sky;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.DebugRenderer_Duck;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.world.level.block.LightBlock;
import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.client.KeyMapping;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.component.BlockItemStateProperties;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting;
import net.minecraft.resources.ResourceLocation;
import org.lwjgl.glfw.GLFW;
public class CrystallineSkyClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(CrystallineBlocks.SKY, CrystallineRenderTypes.SKY);
BlockRenderLayerMap.INSTANCE.putBlock(CrystallineBlocks.WEEPING_SKY, CrystallineRenderTypes.SKY);
var weepingSkyKeybind = KeyBindingHelper.registerKeyBinding(new KeyMapping(
"crystalline_sky.key.toggle_weeping_sky_debug",
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_UNKNOWN,
"crystalline_sky.category.crystalline_sky"
));
ClientTickEvents.END_CLIENT_TICK.register(client -> {
while (weepingSkyKeybind.consumeClick()) {
int mode = ((DebugRenderer_Duck) client.debugRenderer).crystalline_sky$toggleWeepingSky();
Component message = getDebugMessage(ChatFormatting.YELLOW,
Component.translatable("message.crystalline_sky.weeping_sky_debug." + mode)
);
client.gui.getChat().addMessage(message);
}
});
ItemProperties.register(CrystallineItems.SKY_LIGHT, ResourceLocation.withDefaultNamespace("level"),
(stack, world, entity, seed) -> {
BlockItemStateProperties blockStateComponent = stack.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY);
Integer integer = blockStateComponent.get(LightBlock.LEVEL);
return integer != null ? integer / 16.0F : 1.0F;
});
}
@SuppressWarnings("SameParameterValue")
private static Component getDebugMessage(ChatFormatting formatting, Component message) {
return Component.empty().append(Component.translatable("debug.prefix").withStyle(formatting, ChatFormatting.BOLD)).append(CommonComponents.SPACE).append(message);
}
}
@@ -0,0 +1,13 @@
package io.github.slimeistdev.crystalline_sky;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineDataComponentTypes;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
public class ModSetup {
public static void init() {
CrystallineDataComponentTypes.init();
CrystallineItems.init();
CrystallineBlocks.init();
}
}
@@ -0,0 +1,40 @@
/*
* MIT License
*
* Copyright (c) 2023-Present Bawnorton
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.github.slimeistdev.crystalline_sky.annotation.mixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalMixin {
Mods[] mods();
boolean applyIfPresent() default true;
}
@@ -0,0 +1,42 @@
package io.github.slimeistdev.crystalline_sky.compat;
import net.fabricmc.loader.api.FabricLoader;
import java.util.Optional;
import java.util.function.Supplier;
public enum Mods {
SODIUM("sodium"),
AXIOM("axiom");
public final String id;
public final boolean isLoaded;
Mods(String id) {
this.id = id;
this.isLoaded = FabricLoader.getInstance().isModLoaded(id);
}
/**
* Simple hook to run code if a mod is installed
*
* @param toRun will be run only if the mod is loaded
* @return Optional.empty() if the mod is not loaded, otherwise an Optional of the return value of the given supplier
*/
public <T> Optional<T> runIfInstalled(Supplier<Supplier<T>> toRun) {
if (isLoaded)
return Optional.of(toRun.get().get());
return Optional.empty();
}
/**
* Simple hook to execute code if a mod is installed
*
* @param toExecute will be executed only if the mod is loaded
*/
public void executeIfInstalled(Supplier<Runnable> toExecute) {
if (isLoaded) {
toExecute.get().run();
}
}
}
@@ -0,0 +1,105 @@
package io.github.slimeistdev.crystalline_sky.compat.sodium;
import com.mojang.blaze3d.platform.GlStateManager;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformFloat3v;
import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformFloat4v;
import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformInt;
import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformMatrix4f;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderFogComponent;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderInterface;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderOptions;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderTextureSlot;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ShaderBindingContext;
import net.caffeinemc.mods.sodium.client.util.TextureUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL32C;
import java.util.EnumMap;
import java.util.Map;
@SuppressWarnings("DeprecatedIsStillUsed")
public class SkyShaderInterface implements ChunkShaderInterface {
private final Map<ChunkShaderTextureSlot, GlUniformInt> uniformTextures;
private final GlUniformMatrix4f uniformModelViewMatrix;
private final GlUniformMatrix4f uniformProjectionMatrix;
private final GlUniformFloat3v uniformRegionOffset;
private final GlUniformFloat4v uniformColorModulator;
// The fog shader component used by this program in order to set up the appropriate GL state
private final ChunkShaderFogComponent fogShader;
private final float[] colorModulator = new float[] {1.0f, 1.0f, 1.0f, 1.0f};
public SkyShaderInterface(ShaderBindingContext context, ChunkShaderOptions options) {
this.uniformModelViewMatrix = context.bindUniform("u_ModelViewMatrix", GlUniformMatrix4f::new);
this.uniformProjectionMatrix = context.bindUniform("u_ProjectionMatrix", GlUniformMatrix4f::new);
this.uniformRegionOffset = context.bindUniform("u_RegionOffset", GlUniformFloat3v::new);
this.uniformColorModulator = context.bindUniform("u_ColorModulator", GlUniformFloat4v::new);
this.uniformTextures = new EnumMap<>(ChunkShaderTextureSlot.class);
this.uniformTextures.put(ChunkShaderTextureSlot.BLOCK, context.bindUniform("u_BlockTex", GlUniformInt::new));
this.fogShader = options.fog().getFactory().apply(context);
}
@Override
public void setupState() {
this.bindTexture(ChunkShaderTextureSlot.BLOCK, TextureUtil.getBlockTextureId());
Minecraft client = Minecraft.getInstance();
if (client.level != null
&& client.player != null
&& (client.player.getMainHandItem().is(CrystallineItems.SKY)
|| client.player.getOffhandItem().is(CrystallineItems.SKY)
|| client.player.getMainHandItem().is(CrystallineItems.WEEPING_SKY)
|| client.player.getOffhandItem().is(CrystallineItems.WEEPING_SKY))) {
float f = client.level.getGameTime() + client.getTimer().getGameTimeDeltaPartialTick(true);
float alpha = (Mth.sin(f / 10.0f) + 1.0f) / 2.0f;
// remap alpha from [0, 1] to [0, 0.75]
float maxAlpha = 1.0f - 0.25f;
alpha = alpha * maxAlpha;
colorModulator[3] = alpha;
} else {
colorModulator[3] = 1.0f;
}
this.uniformColorModulator.set(colorModulator);
this.fogShader.setup();
}
@Override // the shader interface should not modify pipeline state
public void resetState() {
// This is used by alternate implementations.
}
/** @deprecated */
@Deprecated(
forRemoval = true
)
private void bindTexture(ChunkShaderTextureSlot slot, int textureId) {
GlStateManager._activeTexture(GL32C.GL_TEXTURE0 + slot.ordinal());
GlStateManager._bindTexture(textureId);
GlUniformInt uniform = this.uniformTextures.get(slot);
uniform.setInt(slot.ordinal());
}
@Override
public void setProjectionMatrix(Matrix4fc matrix) {
this.uniformProjectionMatrix.set(matrix);
}
@Override
public void setModelViewMatrix(Matrix4fc matrix) {
this.uniformModelViewMatrix.set(matrix);
}
@Override
public void setRegionOffset(float x, float y, float z) {
this.uniformRegionOffset.set(x, y, z);
}
}
@@ -0,0 +1,22 @@
package io.github.slimeistdev.crystalline_sky.content.blocks;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.LightBlock;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.BlockGetter;
public class SkyLightBlock extends LightBlock {
public SkyLightBlock(Properties settings) {
super(settings);
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
return context.isHoldingItem(CrystallineItems.SKY_LIGHT) ? Shapes.block() : Shapes.empty();
}
}
@@ -0,0 +1,75 @@
package io.github.slimeistdev.crystalline_sky.content.blocks;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
public class WeepingSkyLightBlock extends Block implements SimpleWaterloggedBlock {
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public WeepingSkyLightBlock(Properties settings) {
super(settings);
registerDefaultState(getStateDefinition().any().setValue(WATERLOGGED, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(WATERLOGGED);
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
return context.isHoldingItem(CrystallineItems.WEEPING_SKY_LIGHT) ? Shapes.block() : Shapes.empty();
}
@Override
protected boolean propagatesSkylightDown(BlockState state, BlockGetter world, BlockPos pos) {
return state.getFluidState().isEmpty();
}
@Override
protected RenderShape getRenderShape(BlockState state) {
return RenderShape.INVISIBLE;
}
@Override
protected float getShadeBrightness(BlockState state, BlockGetter world, BlockPos pos) {
return 1.0f;
}
@Override
protected BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor world,
BlockPos pos,
BlockPos neighborPos
) {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
}
return super.updateShape(state, direction, neighborState, world, pos, neighborPos);
}
@Override
protected FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
}
@@ -0,0 +1,48 @@
package io.github.slimeistdev.crystalline_sky.content.items;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineDataComponentTypes;
import net.minecraft.world.item.Item.Properties;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ColumnPos;
import net.minecraft.world.level.Level;
public class WeepingSkyBlockItem extends BlockItem {
public WeepingSkyBlockItem(Block block, Properties settings) {
super(block, settings);
}
@Override
public InteractionResult useOn(UseOnContext context) {
ItemStack stack = context.getItemInHand();
if (stack.get(CrystallineDataComponentTypes.WEEPING_SKY_DEBUG_COLUMN_TOOL) != null) {
if (context.getLevel().isClientSide) {
BlockPos pos = context.getClickedPos();
WeepingSkyBlockItemClient.setSelectedColumn(new ColumnPos(pos.getX(), pos.getZ()));
}
return InteractionResult.SUCCESS;
}
return super.useOn(context);
}
@Override
public InteractionResultHolder<ItemStack> use(Level world, Player user, InteractionHand hand) {
ItemStack stack = user.getItemInHand(hand);
if (stack.get(CrystallineDataComponentTypes.WEEPING_SKY_DEBUG_COLUMN_TOOL) != null) {
if (world.isClientSide) {
WeepingSkyBlockItemClient.setSelectedColumn(null);
}
return InteractionResultHolder.success(stack);
}
return super.use(world, user, hand);
}
}
@@ -0,0 +1,25 @@
package io.github.slimeistdev.crystalline_sky.content.items;
import io.github.slimeistdev.crystalline_sky.infrastructure.client.WeepingSkyDebugRenderer;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.DebugRenderer_Duck;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ColumnPos;
import org.jetbrains.annotations.Nullable;
public class WeepingSkyBlockItemClient {
// todone check if this is server-safe
@Environment(EnvType.CLIENT)
public static void setSelectedColumn(@Nullable ColumnPos column) {
Minecraft mc = Minecraft.getInstance();
WeepingSkyDebugRenderer weepingSkyDebugRenderer = ((DebugRenderer_Duck) mc.debugRenderer).crystalline_sky$getWeepingSkyDebugRenderer();
weepingSkyDebugRenderer.setColumn(column);
Component text = column == null
? Component.translatable("message.crystalline_sky.weeping_sky_debug.column.cleared")
: Component.translatable("message.crystalline_sky.weeping_sky_debug.column.set", column.x(), column.z());
mc.gui.setOverlayMessage(text, false);
}
}
@@ -0,0 +1,18 @@
package io.github.slimeistdev.crystalline_sky.datagen;
import io.github.slimeistdev.crystalline_sky.datagen.providers.CrystallineSkyBlockTagProvider;
import io.github.slimeistdev.crystalline_sky.datagen.providers.CrystallineSkyLootProvider;
import io.github.slimeistdev.crystalline_sky.datagen.providers.CrystallineSkyModelProvider;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
public class CrystallineSkyDataGenerator implements DataGeneratorEntrypoint {
@Override
public void onInitializeDataGenerator(FabricDataGenerator gen) {
FabricDataGenerator.Pack pack = gen.createPack();
pack.addProvider(CrystallineSkyModelProvider::new);
pack.addProvider(CrystallineSkyLootProvider::new);
pack.addProvider(CrystallineSkyBlockTagProvider::new);
}
}
@@ -0,0 +1,31 @@
package io.github.slimeistdev.crystalline_sky.datagen.providers;
import io.github.slimeistdev.crystalline_sky.CrystallineSky;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.minecraft.core.registries.Registries;
import net.minecraft.core.HolderLookup;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.TagKey;
import java.util.concurrent.CompletableFuture;
public class CrystallineSkyBlockTagProvider extends FabricTagProvider.BlockTagProvider {
public CrystallineSkyBlockTagProvider(FabricDataOutput output, CompletableFuture<HolderLookup.Provider> registriesFuture) {
super(output, registriesFuture);
}
@Override
protected void addTags(HolderLookup.Provider wrapperLookup) {
getOrCreateTagBuilder(TagKey.create(Registries.BLOCK, CrystallineSky.id("sky_light_emitters")))
.add(CrystallineBlocks.SKY)
.add(CrystallineBlocks.SKY_LIGHT)
.add(CrystallineBlocks.WEEPING_SKY)
.add(CrystallineBlocks.WEEPING_SKY_LIGHT);
getOrCreateTagBuilder(BlockTags.CRYSTAL_SOUND_BLOCKS)
.add(CrystallineBlocks.SKY)
.add(CrystallineBlocks.WEEPING_SKY);
}
}
@@ -0,0 +1,20 @@
package io.github.slimeistdev.crystalline_sky.datagen.providers;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider;
import net.minecraft.core.HolderLookup;
import java.util.concurrent.CompletableFuture;
public class CrystallineSkyLootProvider extends FabricBlockLootTableProvider {
public CrystallineSkyLootProvider(FabricDataOutput dataOutput, CompletableFuture<HolderLookup.Provider> registryLookup) {
super(dataOutput, registryLookup);
}
@Override
public void generate() {
dropWhenSilkTouch(CrystallineBlocks.SKY);
dropWhenSilkTouch(CrystallineBlocks.WEEPING_SKY);
}
}
@@ -0,0 +1,93 @@
package io.github.slimeistdev.crystalline_sky.datagen.providers;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider;
import net.minecraft.world.level.block.Block;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.blockstates.Variant;
import net.minecraft.data.models.blockstates.PropertyDispatch;
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.models.model.ModelLocationUtils;
import net.minecraft.data.models.model.ModelTemplates;
import net.minecraft.data.models.model.TextureMapping;
import net.minecraft.data.models.blockstates.VariantProperties;
import net.minecraft.data.models.blockstates.MultiVariantGenerator;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.resources.ResourceLocation;
import java.util.Locale;
@SuppressWarnings("SameParameterValue")
public class CrystallineSkyModelProvider extends FabricModelProvider {
public CrystallineSkyModelProvider(FabricDataOutput output) {
super(output);
}
@Override
public void generateBlockStateModels(BlockModelGenerators gen) {
registerSkyBlock(gen, CrystallineBlocks.SKY);
registerSkyBlock(gen, CrystallineBlocks.WEEPING_SKY);
registerSkyLightBlock(gen, CrystallineBlocks.SKY_LIGHT, CrystallineItems.SKY_LIGHT);
gen.createAirLikeBlock(CrystallineBlocks.WEEPING_SKY_LIGHT, CrystallineItems.WEEPING_SKY_LIGHT);
gen.createSimpleFlatItemModel(CrystallineItems.WEEPING_SKY_LIGHT);
}
private void registerSkyBlock(BlockModelGenerators gen, Block block) {
gen.createTrivialCube(block);
gen.createSimpleFlatItemModel(block);
}
private void registerSkyLightBlock(BlockModelGenerators gen, Block block, Item item) {
gen.skipAutoItemBlock(block);
PropertyDispatch.C1<Integer> singleProperty = PropertyDispatch.property(BlockStateProperties.LEVEL);
for (int level = 0; level <= 15; level++) {
String suffix = String.format(Locale.ROOT, "_%02d", level);
ResourceLocation texture = TextureMapping.getItemTexture(item, suffix);
singleProperty.select(level, Variant.variant().with(
VariantProperties.MODEL,
ModelTemplates.PARTICLE_ONLY.createWithSuffix(block, suffix, TextureMapping.particle(texture), gen.modelOutput))
);
ModelTemplates.FLAT_ITEM.create(
ModelLocationUtils.getModelLocation(item, suffix),
TextureMapping.layer0(texture),
gen.modelOutput
);
}
ModelTemplates.FLAT_ITEM.create(
ModelLocationUtils.getModelLocation(item),
TextureMapping.layer0(item),
gen.modelOutput,
(id, textures) -> {
JsonObject base = ModelTemplates.FLAT_ITEM.createBaseTemplate(id, textures);
var overrides = new JsonArray();
for (int level = 0; level <= 15; level++) {
JsonObject override = new JsonObject();
JsonObject predicate = new JsonObject();
predicate.addProperty("level", level / 16f);
override.add("predicate", predicate);
String suffix = String.format(Locale.ROOT, "_%02d", level);
ResourceLocation model = TextureMapping.getItemTexture(item, suffix);
override.addProperty("model", model.toString());
overrides.add(override);
}
base.add("overrides", overrides);
return base;
});
gen.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block).with(singleProperty));
}
@Override
public void generateItemModels(ItemModelGenerators gen) {}
}
@@ -0,0 +1,753 @@
package io.github.slimeistdev.crystalline_sky.infrastructure;
import com.google.common.base.Preconditions;
import io.github.slimeistdev.crystalline_sky.util.ArrayUtils;
import it.unimi.dsi.fastutil.shorts.ShortList;
import java.util.*;
import java.util.function.Consumer;
public class IntWeepingStorage implements WeepingStorage {
private final int[] sizes;
// each long is (y_sky << 32 | y_bottom)
// y_sky is the position of the highest weeping sky block in a layer
// y_bottom is the lowest illuminated position (so 1 above a solid block) (less than y_sky) in a layer, or -1 if the layer is unbounded
// must be sorted top-to-bottom by y_sky
private final int[][] columns;
private final int[] tempRunSplitSizes;
private final int[][] tempRunSplits;
// only used for iterators
private final int minY;
public IntWeepingStorage(int minY) {
this.sizes = new int[16*16];
this.columns = new int[16*16][0];
this.tempRunSplitSizes = new int[16*16*2];
this.tempRunSplits = new int[16*16*2][0];
this.minY = minY;
}
private static int getIndex(int localX, int localZ) {
return localX + localZ * 16;
}
private static int pack(short ySky, short yBottom) {
return ((int)ySky << 16) | Short.toUnsignedInt(yBottom);
}
private static short unpackSky(int packed) {
return (short)(packed >>> 16);
}
private static short unpackBottom(int packed) {
return (short)(packed & 0xFFFF);
}
private static int calculateIncreasedLength(int currentLength) {
if (currentLength == 0)
return 1;
// calculate next power of two
return Integer.highestOneBit(currentLength) << 1;
}
private void maybeShrinkColumn(int index) {
int[] column = columns[index];
int size = sizes[index];
int newLength = calculateIncreasedLength(size);
if (column.length > newLength) {
int[] newColumn = new int[newLength];
System.arraycopy(column, 0, newColumn, 0, size);
columns[index] = newColumn;
}
}
private void insertValue(int columnIndex, int i, int packed) {
int[] column = columns[columnIndex];
int size = sizes[columnIndex];
if (size + 1 > column.length) {
int newLength = calculateIncreasedLength(column.length);
int[] newColumn = new int[newLength];
System.arraycopy(column, 0, newColumn, 0, i);
newColumn[i] = packed;
System.arraycopy(column, i, newColumn, i + 1, size - i);
columns[columnIndex] = newColumn;
} else {
System.arraycopy(column, i, column, i + 1, size - i);
column[i] = packed;
}
sizes[columnIndex] = ++size;
}
/*
unlit sections are iterated downward
lit sections are iterated upward
a newly-unlit section should call insertTempRunSplit at its topY
a newly-lit section should call insertTempRunSplit at its bottomY
*/
/**
* Will split a run in subsequent iterators
* @param columnIndex column to split
* @param splitY the new (isLit ? bottomY : topY) of a run that intersects this one
* @param isLit whether this should be added to the splits for lit or unlit runs
*/
private void insertTempRunSplit(int columnIndex, int splitY, boolean isLit) {
int index = columnIndex * 2 + (isLit ? 1 : 0);
int size = tempRunSplitSizes[index];
int[] splits = tempRunSplits[index];
if (size == 0) {
if (splits.length == 0) {
tempRunSplits[index] = new int[] {splitY};
} else {
splits[0] = splitY;
}
tempRunSplitSizes[index] = ++size;
} else {
// binary search for insertion point
int insertIndex = ArrayUtils.binarySearchSortedInsertionPoint(splits, splitY, size);
if (insertIndex != -1) {
if (size + 1 > splits.length) {
int newLength = calculateIncreasedLength(splits.length);
int[] newSplits = new int[newLength];
System.arraycopy(splits, 0, newSplits, 0, insertIndex);
newSplits[insertIndex] = splitY;
System.arraycopy(splits, insertIndex, newSplits, insertIndex + 1, splits.length - insertIndex);
tempRunSplits[index] = newSplits;
} else {
System.arraycopy(splits, insertIndex, splits, insertIndex + 1, size - insertIndex);
splits[insertIndex] = splitY;
}
tempRunSplitSizes[index] = ++size;
}
}
}
private void litTempSplit(int columnIndex, int bottomY) {
insertTempRunSplit(columnIndex, bottomY, true);
}
private void unlitTempSplit(int columnIndex, int topY) {
insertTempRunSplit(columnIndex, topY, false);
}
@Override
public void insertSky(int localX, int y, int localZ, WeepingScanner scanner) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
short yShort = (short) y;
if (size == 0) {
int solidY = scanner.scan(localX, y, localZ);
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
int packed = pack(yShort, shortLowestLitY);
if (column.length == 0) {
columns[index] = new int[] {packed};
} else {
column[0] = packed;
}
sizes[index] = ++size;
litTempSplit(index, shortLowestLitY);
} else {
// possible cases:
// 1. we are in a layer, and do nothing
// 2. we are below the lowest layer, and create a new layer
// 3. we are above a layer, and must scan to determine whether to join it or insert one above it
for (int i = 0; i < size; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
if (yShort == sky)
return; // no change
if (bottom != -1 && yShort < sky && (yShort == bottom || yShort == bottom - 1)) {
// scan if layer can continue now, and extend it down if so. Else, fall through to in-or-below handling
if (!scanner.faceBlocksLight(localX, bottom, localZ)) {
int solidY = scanner.scan(localX, yShort, localZ);
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
if (i + 1 < size) { // maybe join layer below
int packedBelow = column[i + 1];
short skyBelow = unpackSky(packedBelow);
short bottomBelow = unpackBottom(packedBelow);
if (solidY == -1 || solidY > skyBelow) { // join layer[i] and layer[i + 1]
litTempSplit(index, skyBelow + 1); // the newly-lit region is [skyBelow + 1, bottom - 1]
column[i] = pack(sky, bottomBelow);
System.arraycopy(column, i + 2, column, i + 1, size - (i + 2));
sizes[index] = --size;
maybeShrinkColumn(index);
return;
}
}
// extend layer[i] down
column[i] = pack(sky, shortLowestLitY);
litTempSplit(index, shortLowestLitY);
return;
}
}
if (yShort > sky) { // above layer
int solidY = scanner.scan(localX, y, localZ);
if (solidY == -1 || solidY < sky) { // join layer
column[i] = pack(yShort, bottom);
litTempSplit(index, sky + 1); // the newly-lit region is [sky + 1, yShort]
} else { // insert new layer above (at index i)
insertValue(index, i, pack(yShort, (short) (solidY + 1)));
litTempSplit(index, solidY + 1);
}
return;
} else { // (yShort < sky) in or below
if (bottom == -1 || yShort >= bottom) { // in layer, do nothing
return;
} else if (i == size - 1) { // below layer, add if last layer
int solidY = scanner.scan(localX, y, localZ);
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
insertValue(index, size, pack(yShort, shortLowestLitY));
litTempSplit(index, shortLowestLitY);
return;
}
}
}
}
}
@Override
public void insertSolid(int localX, int y, int localZ, WeepingScanner scanner) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
short yShort = (short) y;
// if we're inside a layer, limit it
boolean lightBlockedHere;
boolean lightBlockedAbove;
int lastLitY = -1;
boolean blockingCalculated = false;
for (int i = 0; i < size; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
if (yShort > sky) { // we're above all remaining layers, so nothing to do
return;
} else if (yShort == sky) { // either remove layer or lower the start (scan for sky)
int nextSky = scanner.scanForWeepingSky(localX, y, localZ);
if (nextSky == -1 || nextSky < bottom) { // remove layer
System.arraycopy(column, i + 1, column, i, size - (i + 1));
sizes[index] = --size;
maybeShrinkColumn(index);
} else { // lower start of layer
column[i] = pack((short) nextSky, bottom);
}
unlitTempSplit(index, sky);
return;
} else { // (yShort < sky) limit layer and potentially split layer
if (!blockingCalculated) {
lightBlockedHere = scanner.faceBlocksLight(localX, y, localZ);
lightBlockedAbove = scanner.faceBlocksLight(localX, y + 1, localZ);
if (lightBlockedAbove) {
lastLitY = y + 1;
} else if (lightBlockedHere) {
lastLitY = y;
} else { // no effect
return;
}
blockingCalculated = true;
}
if (lastLitY <= bottom) // no effect
continue;
int nextSky = scanner.scanForWeepingSky(localX, y, localZ);
// limit
column[i] = pack(sky, (short) lastLitY);
unlitTempSplit(index, lastLitY - 1);
if (nextSky != -1 && nextSky >= bottom) { // split
insertValue(index, i + 1, pack((short) nextSky, bottom));
}
return;
}
}
}
@Override
public void insertAir(int localX, int y, int localZ, WeepingScanner scanner) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
short yShort = (short) y;
for (int i = 0; i < size; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
if (yShort > sky) { // above all remaining layers, nothing to do
return;
} else if (yShort == sky) { // rescan for sky
int nextSky = scanner.scanForWeepingSky(localX, y, localZ);
if (nextSky == -1 || nextSky < bottom) { // remove layer
System.arraycopy(column, i + 1, column, i, size - (i + 1));
sizes[index] = --size;
maybeShrinkColumn(index);
unlitTempSplit(index, sky);
} else { // lower start of layer
column[i] = pack((short) nextSky, bottom);
unlitTempSplit(index, sky);
}
return;
} else { // (yShort < sky) maybe extend layer down
if (bottom != -1 && (yShort == bottom || yShort == bottom - 1)) { // we may have freed things up
if (!scanner.faceBlocksLight(localX, bottom, localZ)) { // extend layer down or join with layer below
int solidY = scanner.scan(localX, yShort, localZ);
short shortLowestLitY = (short) (solidY == -1 ? -1 : solidY + 1);
if (i + 1 < size) { // maybe join layer below
int packedBelow = column[i + 1];
short skyBelow = unpackSky(packedBelow);
short bottomBelow = unpackBottom(packedBelow);
if (solidY == -1 || solidY < skyBelow) { // join layer[i] and layer[i + 1]
column[i] = pack(sky, bottomBelow);
System.arraycopy(column, i + 2, column, i + 1, size - (i + 2));
sizes[index] = --size;
litTempSplit(index, skyBelow + 1);
maybeShrinkColumn(index);
return;
}
}
// extend layer[i] down
column[i] = pack(sky, shortLowestLitY);
litTempSplit(index, shortLowestLitY);
return;
}
}
}
}
}
@Override
public void clear() {
int[] empty = new int[0];
for (int i = 0; i < 16*16; i++) {
sizes[i] = 0;
columns[i] = empty;
tempRunSplitSizes[i] = 0;
tempRunSplits[i] = empty;
}
}
public void set(int localX, int localZ, ShortList layers) {
Preconditions.checkArgument(layers.size() % 2 == 0, "Expected even number of shorts in layers");
int index = getIndex(localX, localZ);
int size = layers.size() / 2;
sizes[index] = size;
int[] column = columns[index];
if (column.length < size) {
column = new int[calculateIncreasedLength(size)];
columns[index] = column;
}
for (int i = 0; i < size; i++) {
short sky = layers.getShort(i * 2);
short bottom = layers.getShort(i * 2 + 1);
column[i] = pack(sky, bottom);
}
maybeShrinkColumn(index);
}
@Override
public boolean isLit(int localX, int y, int localZ) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
if (size == 0)
return false;
short yShort = (short) (y - minY);
// binary search
int left = 0;
int right = size - 1;
while (left <= right) {
int mid = (left + right) / 2;
int packed = column[mid];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
if (yShort > sky) { // we're higher
right = mid - 1;
} else if (yShort < sky) { // we're lower
if (bottom == -1 || yShort >= bottom) {
return true;
} else {
left = mid + 1;
}
} else { // yShort == sky
return true;
}
}
return false;
}
/**
* NOTE: unlike all other methods in this class, this uses global y coordinates.
*/
@Override
public Iterable<Run> iterateLitRuns(int localX, int localZ, int lowestSourceY) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
int splitsColumnIndex = index * 2 + 1;
int splitsSize = tempRunSplitSizes[splitsColumnIndex];
int[] splits = tempRunSplits[splitsColumnIndex];
List<Run> runs = new ArrayList<>(size);
runs.add(new Run(lowestSourceY, Integer.MAX_VALUE));
if (lowestSourceY == Integer.MIN_VALUE) {
return wrapUnlitRuns(splitsSize, splits, runs);
}
for (int i = 0; i < size; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
int globalSky = sky + minY;
int globalBottom = bottom == -1 ? Integer.MIN_VALUE : bottom + minY;
// if we're above lowestSourceY, don't bother
if (globalBottom >= lowestSourceY)
continue;
// if we intersect with default sky, merge
if (globalSky >= lowestSourceY - 1) {
runs.set(0, new Run(globalBottom, Integer.MAX_VALUE));
continue;
}
// otherwise, add a new run
runs.add(new Run(globalBottom, globalSky));
}
return wrapLitRuns(splitsSize, splits, runs);
}
/**
* NOTE: unlike all other methods in this class, this uses global y coordinates.
*/
@Override
public Iterable<Run> iterateUnlitRuns(int localX, int localZ, int lowestSourceY) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
int splitsColumnIndex = index * 2;
int splitsSize = tempRunSplitSizes[splitsColumnIndex];
int[] splits = tempRunSplits[splitsColumnIndex];
// fill with all gaps between layers, which are below lowestSourceY
List<Run> runs = new ArrayList<>(size);
if (lowestSourceY == Integer.MIN_VALUE) {
return wrapUnlitRuns(splitsSize, splits, runs);
}
if (size == 0) {
runs.add(new Run(Integer.MIN_VALUE, lowestSourceY - 1));
} else {
int lastLowestLit = lowestSourceY;
// each iteration adds the gap above layer[i]
for (int i = 0; i < size && lastLowestLit > Integer.MIN_VALUE; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
int globalSky = sky + minY;
int globalBottom = bottom == -1 ? Integer.MIN_VALUE : bottom + minY;
if (globalBottom >= lowestSourceY) // completely lit, what's the point?
continue;
if (globalSky < lastLowestLit - 1) // gap
runs.add(new Run(globalSky + 1, lastLowestLit - 1));
lastLowestLit = globalBottom;
}
// add the gap below the lowest layer
if (lastLowestLit > Integer.MIN_VALUE && lastLowestLit != lowestSourceY) {
runs.add(new Run(Integer.MIN_VALUE, lastLowestLit - 1));
}
}
return wrapUnlitRuns(splitsSize, splits, runs);
}
@Override
public void clearTempRunSplits(int localX, int localZ) {
int[] empty = new int[0];
int baseIndex = getIndex(localX, localZ) * 2;
for (int i = 0; i < 2; i++) {
int index = baseIndex + i;
tempRunSplitSizes[index] = 0;
tempRunSplits[index] = empty;
}
}
@Override
public boolean isColumnEmpty(int localX, int localZ) {
return sizes[getIndex(localX, localZ)] <= 0;
}
@Override
public boolean hasNoUnlitSplits(int localX, int localZ) {
return tempRunSplitSizes[getIndex(localX, localZ) * 2] <= 0;
}
@Override
public void debugState(int localX, int localZ, Consumer<String> stringConsumer) {
int index = getIndex(localX, localZ);
int size = sizes[index];
int[] column = columns[index];
if (size == 0) {
stringConsumer.accept(" <empty>");
} else {
for (int i = 0; i < size; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
stringConsumer.accept(" layer " + i + ": sky=" + (sky + minY) + ", bottom=" + (bottom == -1 ? "-1" : (bottom + minY)));
}
}
}
/** wrapped iterable must be sorted top-to-bottom, splits must be sorted bottom-to-top */
public Iterable<Run> wrapLitRuns(int splitsSize, int[] splits, Iterable<Run> iterable) {
if (splitsSize <= 0) return iterable;
return () -> new LitSplittingIterator(splitsSize, splits, iterable.iterator());
}
private class LitSplittingIterator implements Iterator<Run> {
private final int[] splits;
private final Iterator<Run> iterator;
private final Deque<Run> remainingSplits = new ArrayDeque<>();
private int currentSplitIndex;
private LitSplittingIterator(int splitsSize, int[] splits, Iterator<Run> iterator) {
this.splits = splits;
this.iterator = iterator;
this.currentSplitIndex = splitsSize - 1;
}
@Override
public boolean hasNext() {
return !remainingSplits.isEmpty() || iterator.hasNext();
}
@Override
public Run next() {
if (!remainingSplits.isEmpty()) {
return remainingSplits.removeFirst();
}
Run run = iterator.next();
while (currentSplitIndex >= 0) {
int splitY = splits[currentSplitIndex] + minY; // becomes the new bottomY of a run
if (splitY < run.bottomY()) { // split is below run, not relevant (yet)
return run;
} else if (splitY > run.topY()) { // split is above run, will never be relevant
currentSplitIndex--;
} else if (splitY == run.bottomY()) { // run already fulfills the split
currentSplitIndex--;
return run;
} else { // split is inside run, split it
Run upper = new Run(splitY, run.topY());
run = new Run(run.bottomY(), splitY - 1);
remainingSplits.addFirst(upper);
currentSplitIndex--;
}
}
return run;
}
}
/** wrapped iterable must be sorted top-to-bottom, splits must be sorted bottom-to-top */
private Iterable<Run> wrapUnlitRuns(int splitsSize, int[] splits, Iterable<Run> iterable) {
if (splitsSize <= 0) return iterable;
return () -> new UnlitSplittingIterator(splitsSize, splits, iterable.iterator());
}
private class UnlitSplittingIterator implements Iterator<Run> {
private final int[] splits;
private final Iterator<Run> iterator;
private final Deque<Run> remainingSplits = new ArrayDeque<>();
private int currentSplitIndex;
private UnlitSplittingIterator(int splitsSize, int[] splits, Iterator<Run> iterator) {
this.splits = splits;
this.iterator = iterator;
this.currentSplitIndex = splitsSize - 1;
}
@Override
public boolean hasNext() {
return !remainingSplits.isEmpty() || iterator.hasNext();
}
@Override
public Run next() {
if (!remainingSplits.isEmpty()) {
return remainingSplits.removeFirst();
}
Run run = iterator.next();
while (currentSplitIndex >= 0) {
int splitY = splits[currentSplitIndex] + minY; // becomes the new topY of a run
if (splitY < run.bottomY()) { // split is below run, not relevant (yet)
return run;
} else if (splitY > run.topY()) { // split is above run, will never be relevant
currentSplitIndex--;
return run;
} else if (splitY == run.topY()) { // run already fulfills the split
currentSplitIndex--;
return run;
} else { // split is inside run, split it
Run lower = new Run(run.bottomY(), splitY);
run = new Run(splitY + 1, run.topY());
remainingSplits.addFirst(lower);
currentSplitIndex--;
}
}
return run;
}
}
/*private class DownwardIterator implements IntIterator {
private final int[] column;
private final int size;
private int i;
private int currentY;
public DownwardIterator(int localX, int localZ, int yMax) {
int index = getIndex(localX, localZ);
int actualYMax = yMax - minY;
this.column = columns[index];
this.size = sizes[index];
this.i = -1;
// advance to first layer
for (int i = 0; i < size; i++) {
int packed = column[i];
short sky = unpackSky(packed);
short bottom = unpackBottom(packed);
if (bottom != -1 && bottom > actualYMax)
continue;
this.i = i;
currentY = Math.min(currentY, sky);
break;
}
}
@Override
public int nextInt() {
if (i == -1) {
throw new NoSuchElementException();
}
int ret = currentY;
int packed = column[i];
short bottom = unpackBottom(packed);
if (bottom != -1 && ret == bottom) {
i++;
if (i == size) {
i = -1;
} else {
packed = column[i];
currentY = unpackSky(packed);
}
} else {
currentY--;
}
return ret + minY;
}
@Override
public boolean hasNext() {
return i != -1;
}
}*/
}
@@ -0,0 +1,122 @@
package io.github.slimeistdev.crystalline_sky.infrastructure;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.lighting.LightEngine;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MutableWeepingScanner implements WeepingStorage.WeepingScanner {
private @Nullable BlockGetter blockView;
private final int minY;
private final BlockPos.MutableBlockPos reusablePos1 = new BlockPos.MutableBlockPos();
private final BlockPos.MutableBlockPos reusablePos2 = new BlockPos.MutableBlockPos();
public MutableWeepingScanner(int minY) {
this.minY = minY;
}
public void setBlockView(@Nullable BlockGetter blockView) {
this.blockView = blockView;
}
private int transformInputY(int y) {
return y + minY;
}
private int transformOutputY(int y) {
return y == Integer.MIN_VALUE ? -1 : y - minY;
}
private @NotNull BlockGetter checkBlockView() {
if (blockView == null) {
throw new IllegalStateException("blockView is not set");
} else {
return blockView;
}
}
@Override
public int scan(int localX, int yMax, int localZ) {
BlockGetter blockView = checkBlockView();
int actualYMax = transformInputY(yMax);
BlockPos.MutableBlockPos topPos = reusablePos1.set(localX, actualYMax, localZ);
BlockPos.MutableBlockPos bottomPos = reusablePos2.set(localX, actualYMax - 1, localZ);
BlockState topState = blockView.getBlockState(topPos);
while (bottomPos.getY() >= minY) {
BlockState bottomState = blockView.getBlockState(bottomPos);
if (faceBlocksLight(blockView, topState, bottomState, topPos, bottomPos)) {
return transformOutputY(bottomPos.getY());
}
topState = bottomState;
topPos.set(bottomPos);
bottomPos.move(Direction.DOWN);
}
return -1;
}
@Override
public int scanForWeepingSky(int localX, int yMax, int localZ) {
BlockGetter blockView = checkBlockView();
int actualYMax = transformInputY(yMax);
BlockPos.MutableBlockPos topPos = reusablePos1.set(localX, actualYMax, localZ);
BlockPos.MutableBlockPos bottomPos = reusablePos2.set(localX, actualYMax - 1, localZ);
BlockState topState = blockView.getBlockState(topPos);
if (CrystallineBlocks.isWeepingSky(topState))
return yMax;
while (bottomPos.getY() >= minY) {
BlockState bottomState = blockView.getBlockState(bottomPos);
if (CrystallineBlocks.isWeepingSky(bottomState)) {
return transformOutputY(bottomPos.getY());
}
if (faceBlocksLight(blockView, topState, bottomState, topPos, bottomPos)) {
return -1;
}
topState = bottomState;
topPos.set(bottomPos);
bottomPos.move(Direction.DOWN);
}
return -1;
}
@Override
public boolean faceBlocksLight(int localX, int upperY, int localZ) {
BlockGetter blockView = checkBlockView();
int actualY = transformInputY(upperY);
reusablePos1.set(localX, actualY, localZ);
reusablePos2.set(localX, actualY - 1, localZ);
BlockState upper = blockView.getBlockState(reusablePos1);
BlockState lower = blockView.getBlockState(reusablePos2);
return faceBlocksLight(blockView, upper, lower, reusablePos1, reusablePos2);
}
private static boolean faceBlocksLight(BlockGetter world, BlockState upper, BlockState lower, BlockPos upperPos, BlockPos lowerPos) {
if (lower.getLightBlock(world, lowerPos) != 0) {
return true;
} else {
VoxelShape voxelShape = LightEngine.getOcclusionShape(world, upperPos, upper, Direction.DOWN);
VoxelShape voxelShape2 = LightEngine.getOcclusionShape(world, lowerPos, lower, Direction.UP);
return Shapes.faceShapeOccludes(voxelShape, voxelShape2);
}
}
}
@@ -0,0 +1,42 @@
package io.github.slimeistdev.crystalline_sky.infrastructure;
import it.unimi.dsi.fastutil.ints.IntIterator;
import java.util.NoSuchElementException;
public class PeekableIntIterator implements IntIterator {
private final IntIterator wrapped;
private boolean hasPeeked = false;
private int peekedValue;
public PeekableIntIterator(IntIterator wrapped) {
this.wrapped = wrapped;
}
public int peek() {
if (!hasPeeked) {
if (!wrapped.hasNext()) {
throw new NoSuchElementException();
}
peekedValue = wrapped.nextInt();
hasPeeked = true;
}
return peekedValue;
}
@Override
public int nextInt() {
if (hasPeeked) {
hasPeeked = false;
return peekedValue;
}
return wrapped.nextInt();
}
@Override
public boolean hasNext() {
return hasPeeked || wrapped.hasNext();
}
}
@@ -0,0 +1,53 @@
package io.github.slimeistdev.crystalline_sky.infrastructure;
import java.util.function.Consumer;
/**
* Note: y coordinates are [0, ...)
*/
public interface WeepingStorage {
void insertSky(int localX, int y, int localZ, WeepingScanner scanner);
void insertSolid(int localX, int y, int localZ, WeepingScanner scanner);
void insertAir(int localX, int y, int localZ, WeepingScanner scanner);
void clear();
boolean isLit(int localX, int y, int localZ);
Iterable<Run> iterateLitRuns(int localX, int localZ, int lowestSourceY);
Iterable<Run> iterateUnlitRuns(int localX, int localZ, int lowestSourceY);
void clearTempRunSplits(int localX, int localZ);
boolean isColumnEmpty(int localX, int localZ);
boolean hasNoUnlitSplits(int localX, int localZ);
void debugState(int localX, int localZ, Consumer<String> stringConsumer);
/** A continuous run of positions [bottomY, topY] */
record Run(int bottomY, int topY) {
public Run withBottomY(int newBottomY) {
return new Run(newBottomY, this.topY);
}
public Run withTopY(int newTopY) {
return new Run(this.bottomY, newTopY);
}
}
interface WeepingScanner {
/** Scans downwards from yMax (exclusive) to find the first solid block.
* Returns the y coordinate of the first solid block, or -1 if none was found.
* The returned value is guaranteed to be less than yMax.
*/
int scan(int localX, int yMax, int localZ);
int scanForWeepingSky(int localX, int yMax, int localZ);
boolean faceBlocksLight(int localX, int upperY, int localZ);
}
}
@@ -0,0 +1,143 @@
package io.github.slimeistdev.crystalline_sky.infrastructure.client;
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.ChunkSkyLight_Duck;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.debug.DebugRenderer;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.server.level.ColumnPos;
import net.minecraft.world.level.lighting.ChunkSkyLightSources;
import net.minecraft.world.level.chunk.LightChunk;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
@Environment(EnvType.CLIENT)
public class WeepingSkyDebugRenderer implements DebugRenderer.SimpleDebugRenderer {
private final Minecraft client;
private @Nullable ColumnPos column = null;
private boolean litMode = true;
private static final int[] COLORS = {
0xFFFF0000,
0xFFFF7F00,
0xFFFFFF00,
0xFF00FF00,
0xFF00FFFF,
0xFF007FFF,
0xFF0000FF,
0xFF7F00FF,
0xFFFF00FF,
0xFFFF007F,
};
public WeepingSkyDebugRenderer(Minecraft client) {
this.client = client;
}
public void setColumn(@Nullable ColumnPos column) {
this.column = column;
}
public void setLitMode(boolean lit) {
this.litMode = lit;
}
@Override
public void render(PoseStack matrices, MultiBufferSource vertexConsumers, double cameraX, double cameraY, double cameraZ) {
if (column == null)
return;
if (client.level == null)
return;
ChunkPos chunkPos = column.toChunkPos();
LightChunk chunk = client.level.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z);
if (chunk == null)
return;
ChunkSkyLightSources chunkSkyLight = chunk.getSkyLightSources();
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
if (weepingStorage == null)
return;
int localX = column.x() & 15;
int localZ = column.z() & 15;
int lowestSourceY = chunkSkyLight.getLowestSourceY(localX, localZ);
Matrix4f matrix4f = matrices.last().pose();
float x1 = (float) (column.x() - cameraX);
float x2 = x1 + 1;
float z1 = (float) (column.z() - cameraZ);
float z2 = z1 + 1;
int colorIndex = 0;
Iterable<WeepingStorage.Run> runs = this.litMode
? weepingStorage.iterateLitRuns(localX, localZ, lowestSourceY)
: weepingStorage.iterateUnlitRuns(localX, localZ, lowestSourceY);
for (WeepingStorage.Run run : runs) {
int color = COLORS[colorIndex++ % COLORS.length];
// limit things to a renderable range
int bottomY = Math.max(-0xFFFF, run.bottomY());
int topY = Math.min(run.topY(), 0xFFFF) + 1;
float y1 = (float) (bottomY - cameraY);
float y2 = (float) (topY - cameraY);
// draw outlined box
renderOutlinedBox(
vertexConsumers, matrix4f,
x1, x2,
y1, y2,
z1, z2,
color
);
}
}
private static void renderOutlinedBox(MultiBufferSource vertexConsumers, Matrix4f matrix, float x1, float x2, float y1, float y2, float z1, float z2, int color) {
VertexConsumer vertexConsumer = vertexConsumers.getBuffer(RenderType.debugLineStrip(2.0));
// render top face
renderHorizontalSquare(matrix, x1, x2, y1, z1, z2, color, vertexConsumer);
// render bottom face
renderHorizontalSquare(matrix, x1, x2, y2, z1, z2, color, vertexConsumer);
// the (x1, z1) vertical edge has been drawn, draw the other 3
// (x2, z1)
vertexConsumer.addVertex(matrix, x2, y2, z1).setColor(color);
vertexConsumer.addVertex(matrix, x2, y1, z1).setColor(color);
// (x2, z2)
vertexConsumer.addVertex(matrix, x2, y1, z2).setColor(color);
vertexConsumer.addVertex(matrix, x2, y2, z2).setColor(color);
// (x1, z2)
vertexConsumer.addVertex(matrix, x1, y2, z2).setColor(color);
vertexConsumer.addVertex(matrix, x1, y1, z2).setColor(color);
}
private static void renderHorizontalSquare(Matrix4f matrix, float x1, float x2, float y, float z1, float z2, int color, VertexConsumer vertexConsumer) {
vertexConsumer.addVertex(matrix, x1, y, z1).setColor(color);
vertexConsumer.addVertex(matrix, x2, y, z1).setColor(color);
vertexConsumer.addVertex(matrix, x2, y, z2).setColor(color);
vertexConsumer.addVertex(matrix, x1, y, z2).setColor(color);
vertexConsumer.addVertex(matrix, x1, y, z1).setColor(color);
}
@Override
public void clear() {
this.column = null;
}
}
@@ -0,0 +1,152 @@
package io.github.slimeistdev.crystalline_sky.mixin;
import io.github.slimeistdev.crystalline_sky.CrystallineSky;
import io.github.slimeistdev.crystalline_sky.infrastructure.IntWeepingStorage;
import io.github.slimeistdev.crystalline_sky.infrastructure.MutableWeepingScanner;
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.ChunkSkyLight_Duck;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import it.unimi.dsi.fastutil.shorts.ShortList;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.lighting.ChunkSkyLightSources;
import org.spongepowered.asm.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.CallbackInfoReturnable;
@Mixin(ChunkSkyLightSources.class)
public abstract class ChunkSkyLightSourcesMixin implements ChunkSkyLight_Duck {
@Shadow
@Final
private int minY;
@Shadow
@Final
private BlockPos.MutableBlockPos mutablePos1;
@Shadow
private static boolean isEdgeOccluded(BlockGetter blockView, BlockPos upperPos, BlockState upperState, BlockPos lowerPos, BlockState lowerState) {
throw new RuntimeException("Mixin failed to apply");
}
@Shadow
@Final
private BlockPos.MutableBlockPos mutablePos2;
@Unique
@Final
@Mutable
private IntWeepingStorage crystalline_sky$weepingStorage;
@Unique
@Final
@Mutable
private MutableWeepingScanner crystalline_sky$weepingScanner;
@Inject(method = "<init>", at = @At("RETURN"))
private void init(LevelHeightAccessor heightLimitView, CallbackInfo ci) {
crystalline_sky$weepingStorage = new IntWeepingStorage(minY + 1);
crystalline_sky$weepingScanner = new MutableWeepingScanner(minY + 1);
}
@Override
public WeepingStorage crystalline_sky$getWeepingStorage() {
return crystalline_sky$weepingStorage;
}
@Inject(method = "fillFrom", at = @At("HEAD"))
private void refreshWeepingSurfaceY(ChunkAccess chunk, CallbackInfo ci) {
int highestNonEmptySectionIndex = chunk.getHighestFilledSectionIndex();
if (highestNonEmptySectionIndex == -1) {
crystalline_sky$weepingStorage.clear();
} else {
int topY = SectionPos.sectionToBlockCoord(chunk.getSectionYFromSectionIndex(highestNonEmptySectionIndex) + 1);
int weepingMinY = minY + 1;
ShortList data = new ShortArrayList();
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
data.clear();
BlockPos.MutableBlockPos topPos = mutablePos1.set(x, topY, z);
BlockPos.MutableBlockPos bottomPos = mutablePos2.set(x, topY - 1, z);
BlockState topState = Blocks.AIR.defaultBlockState();
short foundSky = -1;
for (int sectionIndex = highestNonEmptySectionIndex; sectionIndex >= 0; sectionIndex--) {
LevelChunkSection section = chunk.getSection(sectionIndex);
if (section.hasOnlyAir()) {
topState = Blocks.AIR.defaultBlockState();
topPos.setY(SectionPos.sectionToBlockCoord(chunk.getSectionYFromSectionIndex(sectionIndex)));
bottomPos.setY(topPos.getY() - 1);
} else {
for (int y = 15; y >= 0; y--) {
BlockState bottomState = section.getBlockState(x, y, z);
if (CrystallineBlocks.isWeepingSky(bottomState)) {
if (foundSky == -1) {
foundSky = (short) (bottomPos.getY() - weepingMinY);
}
} else if (isEdgeOccluded(chunk, topPos, topState, bottomPos, bottomState)) {
if (foundSky != -1) {
short lastLitY = (short) (bottomPos.getY() - weepingMinY + 1);
data.add(foundSky);
data.add(lastLitY);
foundSky = -1;
}
}
topState = bottomState;
topPos.set(bottomPos);
bottomPos.move(Direction.DOWN);
}
}
}
if (foundSky != -1) {
data.add(foundSky);
data.add((short) -1);
}
crystalline_sky$weepingStorage.set(x, z, data);
}
}
}
}
@Inject(method = "update(Lnet/minecraft/world/level/BlockGetter;III)Z", at = @At("HEAD"))
private void updateWeepingSkyState(BlockGetter blockView, int localX, int y, int localZ, CallbackInfoReturnable<Boolean> cir) {
BlockPos pos = mutablePos1.set(localX, y, localZ);
BlockState state = blockView.getBlockState(pos);
crystalline_sky$weepingScanner.setBlockView(blockView);
int weepingMinY = minY + 1;
if (state.isAir()) {
crystalline_sky$weepingStorage.insertAir(localX, y - weepingMinY, localZ, crystalline_sky$weepingScanner);
} else if (CrystallineBlocks.isWeepingSky(state)) {
crystalline_sky$weepingStorage.insertSky(localX, y - weepingMinY, localZ, crystalline_sky$weepingScanner);
} else if (state.canOcclude()) {
crystalline_sky$weepingStorage.insertSolid(localX, y - weepingMinY, localZ, crystalline_sky$weepingScanner);
}
CrystallineSky.LOG.debug("Weeping sky ranges for local column [{}, {}] in {}:", localX, localZ, blockView);
crystalline_sky$weepingStorage.debugState(localX, localZ, CrystallineSky.LOG::debug);
crystalline_sky$weepingScanner.setBlockView(null);
}
}
@@ -0,0 +1,38 @@
package io.github.slimeistdev.crystalline_sky.mixin;
import io.github.slimeistdev.crystalline_sky.util.ConditionalMixinManager;
import org.objectweb.asm.tree.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Set;
public class CrystallineSkyMixinPlugin implements IMixinConfigPlugin {
public static final Logger LOGGER = LoggerFactory.getLogger("CrystallineSky/MixinPlugin");
@Override
public void onLoad(String mixinPackage) { } // NO-OP
@Override
public String getRefMapperConfig() { return null; } // DEFAULT
@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return ConditionalMixinManager.shouldApply(mixinClassName);
}
@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { } // NO-OP
@Override
public List<String> getMixins() { return null; } // DEFAULT
@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } // NO-OP
@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } // NO-OP
}
@@ -0,0 +1,25 @@
package io.github.slimeistdev.crystalline_sky.mixin;
import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.world.level.lighting.LayerLightSectionStorage;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(LayerLightSectionStorage.class)
public interface LayerLightSectionStorageAccessor {
@Invoker("lightOnInSection")
boolean crystalline_sky$callLightOnInSection(long sectionPos);
@Invoker("storingLightForSection")
boolean crystalline_sky$hasSection(long sectionPos);
@Invoker("getStoredLevel")
int crystalline_sky$get(long blockPos);
@Invoker("setStoredLevel")
void crystalline_sky$set(long blockPos, int lightLevel);
@Invoker("getDataLayerToWrite")
@Nullable DataLayer crystalline_sky$method_51547(long sectionPos);
}
@@ -0,0 +1,20 @@
package io.github.slimeistdev.crystalline_sky.mixin;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.lighting.LightEngine;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(LightEngine.class)
public class LightEngineMixin {
@WrapMethod(method = "hasDifferentLightProperties")
private static boolean updateSky(BlockGetter blockView, BlockPos pos, BlockState oldState, BlockState newState, Operation<Boolean> original) {
return original.call(blockView, pos, oldState, newState)
|| (CrystallineBlocks.getSkyLightLevel(oldState) != CrystallineBlocks.getSkyLightLevel(newState))
|| (CrystallineBlocks.isWeepingSky(oldState) != CrystallineBlocks.isWeepingSky(newState));
}
}
@@ -0,0 +1,371 @@
package io.github.slimeistdev.crystalline_sky.mixin;
import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.ChunkSkyLight_Duck;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineBlocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.world.level.lighting.DataLayerStorageMap;
import net.minecraft.world.level.lighting.LightEngine;
import net.minecraft.world.level.lighting.ChunkSkyLightSources;
import net.minecraft.world.level.lighting.SkyLightEngine;
import net.minecraft.world.level.chunk.LightChunk;
import net.minecraft.world.level.lighting.LayerLightSectionStorage;
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;
// todone add ordinal to all name = "..." locals
@Mixin(SkyLightEngine.class)
public abstract class SkyLightEngineMixin<M extends DataLayerStorageMap<M>, S extends LayerLightSectionStorage<M>> extends LightEngine<M, S> {
@Shadow
@Final
private BlockPos.MutableBlockPos mutablePos;
@Shadow
private static boolean isSourceLevel(int lightLevel) {
throw new RuntimeException("Mixin failed to apply");
}
@Shadow
@Final
private static long REMOVE_TOP_SKY_SOURCE_ENTRY;
@Shadow
@Final
private static long REMOVE_SKY_SOURCE_ENTRY;
@Shadow
@Final
private static long ADD_SKY_SOURCE_ENTRY;
protected SkyLightEngineMixin(LightChunkGetter chunkProvider, S lightStorage) {
super(chunkProvider, lightStorage);
}
@Inject(method = "checkNode", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/SkyLightSectionStorage;getStoredLevel(J)I"))
private void emitFromCrystallineSky(long blockPos, CallbackInfo ci) {
BlockState state = getState(mutablePos.set(blockPos));
int level = CrystallineBlocks.isWeepingSky(state)
? 15
: CrystallineBlocks.getSkyLightLevel(state);
if (level > 0 && ((LayerLightSectionStorageAccessor) storage).crystalline_sky$callLightOnInSection(SectionPos.blockToSection(blockPos))) {
enqueueIncrease(blockPos, QueueEntry.increaseLightFromEmission(level, isEmptyShape(state)));
}
}
@SuppressWarnings("LocalMayBeArgsOnly")
@Definition(id = "y", local = @Local(type = int.class, name = "y", ordinal = 1))
@Definition(id = "lowestSourceY", local = @Local(type = int.class, name = "lowestSourceY", ordinal = 3))
@Expression("y >= lowestSourceY")
@WrapOperation(method = "checkNode", at = @At("MIXINEXTRAS:EXPRESSION"))
private boolean weepBelowTheLowest(int y, int lowestSourceY, Operation<Boolean> original, @Local(name = "x", ordinal = 0) int x, @Local(name = "z", ordinal = 2) int z) {
if (original.call(y, lowestSourceY)) {
return true;
}
LightChunk chunk = chunkSource.getChunkForLighting(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z));
if (chunk == null) return false;
ChunkSkyLightSources chunkSkyLight = chunk.getSkyLightSources();
if (chunkSkyLight == null) return false;
int localX = SectionPos.sectionRelative(x);
int localZ = SectionPos.sectionRelative(z);
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
if (weepingStorage.isColumnEmpty(localX, localZ)) return false;
return weepingStorage.isLit(localX, y, localZ);
}
@WrapOperation(method = "propagateDecrease", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/SkyLightEngine;enqueueDecrease(JJ)V"))
private void decreaseTakesLightIntoAccount1(SkyLightEngine instance, long blockPos, long flags,
Operation<Void> original, @Local(name = "k", ordinal = 2) int k) {
// k is the old light level at blockPos
BlockState state = getState(mutablePos.set(blockPos));
int luminance = CrystallineBlocks.getSkyLightLevel(state);
if (luminance < k) {
original.call(instance, blockPos, flags);
}
if (luminance > 0) {
enqueueIncrease(blockPos, QueueEntry.increaseLightFromEmission(luminance, isEmptyShape(state)));
}
}
@WrapOperation(method = "propagateDecrease", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/SkyLightEngine;propagateFromEmptySections(JLnet/minecraft/core/Direction;IZI)V"))
private void decreaseTakesLightIntoAccount2(SkyLightEngine instance, long blockPos, Direction direction,
int lightLevel, boolean shouldIncrease, int emptySections,
Operation<Void> original, @Local(name = "k", ordinal = 2) int k) {
// k is the old light level at blockPos
BlockState state = getState(mutablePos.set(blockPos));
int luminance = CrystallineBlocks.getSkyLightLevel(state);
if (luminance < k) {
original.call(instance, blockPos, direction, lightLevel, shouldIncrease, emptySections);
}
if (luminance > 0) {
original.call(instance, blockPos, direction, luminance, true, emptySections);
}
}
@Inject(method = "propagateLightSources", at = @At("RETURN"))
private void propagateCrystallineSkyLight(ChunkPos chunkPos, CallbackInfo ci) {
LightChunk lightSourceView = this.chunkSource.getChunkForLighting(chunkPos.x, chunkPos.z);
if (!(lightSourceView instanceof ChunkAccess chunk)) return;
chunk.findBlocks(CrystallineBlocks::isCrystallineSky, (pos, state) -> {
enqueueIncrease(pos.asLong(), QueueEntry.increaseLightFromEmission(CrystallineBlocks.getSkyLightLevel(state), isEmptyShape(state)));
});
}
// NOTE: lowestSourceY becomes minY, and minY becomes bottomSectionY under parchment.
// We're keeping the old names since it makes things more legible
@Inject(method = "removeSourcesBelow", at = @At("HEAD"), cancellable = true)
private void removeWeepingSourcesBelow(int x, int z, int lowestSourceY, int minY, CallbackInfo ci) {
LightChunk chunk = chunkSource.getChunkForLighting(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z));
if (chunk == null) return;
ChunkSkyLightSources chunkSkyLight = chunk.getSkyLightSources();
if (chunkSkyLight == null) return;
int localX = SectionPos.sectionRelative(x);
int localZ = SectionPos.sectionRelative(z);
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
if (weepingStorage.isColumnEmpty(localX, localZ) && weepingStorage.hasNoUnlitSplits(localX, localZ)) return;
// there's little chance of accidental mixin compatibility anyway, so just cancel
ci.cancel();
int sectionX = SectionPos.blockToSectionCoord(x);
int sectionZ = SectionPos.blockToSectionCoord(z);
int effectiveLowestSourceY = lowestSourceY <= minY ? Integer.MIN_VALUE : lowestSourceY;
SkyLightSectionStorageAccessor lightStorageAccessor = (SkyLightSectionStorageAccessor) this.storage;
RunIter: for (WeepingStorage.Run run : weepingStorage.iterateUnlitRuns(localX, localZ, effectiveLowestSourceY)) {
// check lighting state of run
int topUnlit = run.topY();
int bottomUnlit = run.bottomY();
for (int sectionY = SectionPos.blockToSectionCoord(topUnlit); lightStorageAccessor.crystalline_sky$isAboveMinHeight(sectionY); sectionY--) {
if (lightStorageAccessor.crystalline_sky$hasSection(SectionPos.asLong(sectionX, sectionY, sectionZ))) {
int minYInSection = SectionPos.sectionToBlockCoord(sectionY);
int maxYInSection = minYInSection + 15;
if (maxYInSection < bottomUnlit) { // todone break outer early if we're below bottomUnlit
break;
}
for (int it_y = Math.min(maxYInSection, topUnlit); it_y >= Math.max(minYInSection, bottomUnlit); it_y--) {
long it_pos = BlockPos.asLong(x, it_y, z);
if (CrystallineBlocks.isCrystallineSky(chunk.getBlockState(mutablePos.set(it_pos)))) {
continue;
}
if (!isSourceLevel(lightStorageAccessor.crystalline_sky$get(it_pos))) {
continue RunIter;
}
lightStorageAccessor.crystalline_sky$set(it_pos, 0);
// todone which of these is correct?
enqueueDecrease(it_pos, it_y == topUnlit ? REMOVE_TOP_SKY_SOURCE_ENTRY : REMOVE_SKY_SOURCE_ENTRY);
//enqueueDecrease(it_pos, REMOVE_SKY_SOURCE_ENTRY);
}
}
}
}
}
// NOTE: lowestSourceY becomes maxY, and minY becomes bottomSectionY under parchment.
// We're keeping the old names since it makes things more legible
@Inject(method = "addSourcesAbove", at = @At("RETURN"))
private void addAdditionalWeepingSourcesAbove(int x, int z, int lowestSourceY, int minY, CallbackInfo ci,
@Local(name = "maxAdjacentLowestSourceY", ordinal = 6) int maxAdjacentLowestSourceY) {
LightChunk chunk = chunkSource.getChunkForLighting(SectionPos.blockToSectionCoord(x), SectionPos.blockToSectionCoord(z));
if (chunk == null) return;
ChunkSkyLightSources chunkSkyLight = chunk.getSkyLightSources();
if (chunkSkyLight == null) return;
int localX = SectionPos.sectionRelative(x);
int localZ = SectionPos.sectionRelative(z);
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) chunkSkyLight).crystalline_sky$getWeepingStorage();
if (weepingStorage.isColumnEmpty(localX, localZ)) {
weepingStorage.clearTempRunSplits(localX, localZ);
return;
}
int sectionX = SectionPos.blockToSectionCoord(x);
int sectionZ = SectionPos.blockToSectionCoord(z);
int effectiveLowestSourceY = lowestSourceY <= minY ? Integer.MIN_VALUE : lowestSourceY;
if (effectiveLowestSourceY == Integer.MIN_VALUE) {
weepingStorage.clearTempRunSplits(localX, localZ);
return;
}
SkyLightSectionStorageAccessor lightStorageAccessor = (SkyLightSectionStorageAccessor) this.storage;
RunIter: for (WeepingStorage.Run run : weepingStorage.iterateLitRuns(localX, localZ, effectiveLowestSourceY)) {
if (run.topY() == Integer.MAX_VALUE) {
if (run.bottomY() == effectiveLowestSourceY) {
continue;
} else {
run = run.withTopY(effectiveLowestSourceY - 1);
}
}
int lowestRelevantY = Math.max(run.bottomY(), minY);
for (long it_packedSection = SectionPos.asLong(sectionX, SectionPos.blockToSectionCoord(lowestRelevantY), sectionZ);
!lightStorageAccessor.crystalline_sky$isAtOrAboveTopmostSection(it_packedSection);
it_packedSection = SectionPos.offset(it_packedSection, Direction.UP)
) {
if (lightStorageAccessor.crystalline_sky$hasSection(it_packedSection)) {
int minYInSection = SectionPos.sectionToBlockCoord(SectionPos.y(it_packedSection));
int maxYInSection = minYInSection + 15;
if (minYInSection > run.topY()) { // todone break outer early if we're above run.topY()
break;
}
for (int it_y = Math.max(minYInSection, lowestRelevantY); it_y <= Math.min(maxYInSection, run.topY()); it_y++) {
long it_pos = BlockPos.asLong(x, it_y, z);
if (CrystallineBlocks.isCrystallineSky(chunk.getBlockState(mutablePos.set(it_pos)))) {
continue;
}
if (isSourceLevel(lightStorageAccessor.crystalline_sky$get(it_pos))) {
continue RunIter;
}
lightStorageAccessor.crystalline_sky$set(it_pos, 15);
if (it_y < maxAdjacentLowestSourceY || it_y == lowestRelevantY) {
enqueueIncrease(it_pos, ADD_SKY_SOURCE_ENTRY);
}
}
}
}
}
weepingStorage.clearTempRunSplits(localX, localZ);
}
@Inject(method = "propagateLightSources", at = @At("RETURN"))
private void propagateWeepingLight(ChunkPos chunkPos, CallbackInfo ci,
@Local(name = "sourcesO", ordinal = 0) ChunkSkyLightSources sourcesO,
@Local(name = "sourcesZN", ordinal = 1) ChunkSkyLightSources sourcesZN,
@Local(name = "sourcesZP", ordinal = 2) ChunkSkyLightSources sourcesZP,
@Local(name = "sourcesXN", ordinal = 3) ChunkSkyLightSources sourcesXN,
@Local(name = "sourcesXP", ordinal = 4) ChunkSkyLightSources sourcesXP
) {
WeepingStorage weepingStorage = ((ChunkSkyLight_Duck) sourcesO).crystalline_sky$getWeepingStorage();
if (weepingStorage == null) return;
boolean hasAny = false;
Outer: for (int localX = 0; localX < 16; localX++) {
for (int localZ = 0; localZ < 16; localZ++) {
if (!weepingStorage.isColumnEmpty(localX, localZ)) {
hasAny = true;
break Outer;
}
}
}
if (!hasAny)
return;
long packedZeroPos = SectionPos.getZeroNode(chunkPos.x, chunkPos.z);
SkyLightSectionStorageAccessor lightStorageAccessor = (SkyLightSectionStorageAccessor) this.storage;
int topSectionY = lightStorageAccessor.crystalline_sky$getTopSectionForColumn(packedZeroPos);
int bottomSectionY = lightStorageAccessor.crystalline_sky$getMinSectionY();
int baseX = SectionPos.sectionToBlockCoord(chunkPos.x);
int baseZ = SectionPos.sectionToBlockCoord(chunkPos.z);
int minY = SectionPos.sectionToBlockCoord(bottomSectionY);
DataLayer[] lightArrays = new DataLayer[topSectionY - bottomSectionY];
for (int sectionY = topSectionY - 1; sectionY >= bottomSectionY; sectionY--) {
long packedSectionPos = SectionPos.asLong(chunkPos.x, sectionY, chunkPos.z);
lightArrays[sectionY - bottomSectionY] = lightStorageAccessor.crystalline_sky$method_51547(packedSectionPos);
}
for (int localZ = 0; localZ < 16; localZ++) {
for (int localX = 0; localX < 16; localX++) {
if (weepingStorage.isColumnEmpty(localX, localZ)) continue;
int lowestSourceY = sourcesO.getLowestSourceY(localX, localZ);
int effectiveLowestSourceY = lowestSourceY <= minY ? Integer.MIN_VALUE : lowestSourceY;
if (effectiveLowestSourceY == Integer.MIN_VALUE) continue;
int minSourceY_ZN = localZ == 0 ? sourcesZN.getLowestSourceY(localX, 15) : sourcesO.getLowestSourceY(localX, localZ - 1);
int minSourceY_ZP = localZ == 15 ? sourcesZP.getLowestSourceY(localX, 0) : sourcesO.getLowestSourceY(localX, localZ + 1);
int minSourceY_XN = localX == 0 ? sourcesXN.getLowestSourceY(15, localZ) : sourcesO.getLowestSourceY(localX - 1, localZ);
int minSourceY_XP = localX == 15 ? sourcesXP.getLowestSourceY(0, localZ) : sourcesO.getLowestSourceY(localX + 1, localZ);
int minSourceY_max = Math.max(Math.max(minSourceY_ZN, minSourceY_ZP), Math.max(minSourceY_XN, minSourceY_XP));
for (WeepingStorage.Run run : weepingStorage.iterateLitRuns(localX, localZ, effectiveLowestSourceY)) {
if (run.topY() == Integer.MAX_VALUE) {
if (run.bottomY() == effectiveLowestSourceY) {
continue;
} else {
run = run.withTopY(effectiveLowestSourceY - 1);
}
}
int lowestRelevantY = Math.max(run.bottomY(), minY);
for (int sectionY = SectionPos.blockToSectionCoord(lowestRelevantY); sectionY <= topSectionY - 1; sectionY++) {
DataLayer lightArray = lightArrays[sectionY - bottomSectionY];
if (lightArray == null) continue;
int minYInSection = SectionPos.sectionToBlockCoord(sectionY);
int maxYInSection = minYInSection + 15;
if (minYInSection > run.topY()) { // todone break outer early if we're above run.topY()
break;
}
for (int it_y = Math.max(minYInSection, lowestRelevantY); it_y <= Math.min(maxYInSection, run.topY()); it_y++) {
lightArray.set(localX, SectionPos.sectionRelative(it_y), localZ, 15);
if (it_y == lowestRelevantY || it_y < minSourceY_max) {
long it_pos = BlockPos.asLong(baseX + localX, it_y, baseZ + localZ);
enqueueIncrease(
it_pos,
QueueEntry.increaseSkySourceInDirections(
it_y == lowestRelevantY,
it_y < minSourceY_ZN,
it_y < minSourceY_ZP,
it_y < minSourceY_XN,
it_y < minSourceY_XP
)
);
}
}
}
}
}
}
}
}
@@ -0,0 +1,20 @@
package io.github.slimeistdev.crystalline_sky.mixin;
import net.minecraft.world.level.lighting.SkyLightSectionStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(SkyLightSectionStorage.class)
public interface SkyLightSectionStorageAccessor extends LayerLightSectionStorageAccessor {
@Invoker("hasLightDataAtOrBelow")
boolean crystalline_sky$isAboveMinHeight(int sectionY);
@Invoker("isAboveData")
boolean crystalline_sky$isAtOrAboveTopmostSection(long sectionPos);
@Invoker("getTopSectionY")
int crystalline_sky$getTopSectionForColumn(long columnPos);
@Invoker("getBottomSectionY")
int crystalline_sky$getMinSectionY();
}
@@ -0,0 +1,32 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.item.Item;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
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.HashSet;
import java.util.Set;
@Mixin(ClientLevel.class)
public class ClientLevelMixin {
@Shadow
@Final
@Mutable
private static Set<Item> MARKER_PARTICLE_ITEMS;
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void modifyBlockMarkerItems(CallbackInfo ci) {
MARKER_PARTICLE_ITEMS = new HashSet<>(MARKER_PARTICLE_ITEMS);
MARKER_PARTICLE_ITEMS.add(CrystallineItems.SKY_LIGHT);
MARKER_PARTICLE_ITEMS.add(CrystallineItems.WEEPING_SKY_LIGHT);
MARKER_PARTICLE_ITEMS.add(CrystallineItems.SKY);
MARKER_PARTICLE_ITEMS.add(CrystallineItems.WEEPING_SKY);
}
}
@@ -0,0 +1,55 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import io.github.slimeistdev.crystalline_sky.infrastructure.client.WeepingSkyDebugRenderer;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.DebugRenderer_Duck;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.debug.DebugRenderer;
import com.mojang.blaze3d.vertex.PoseStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
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(DebugRenderer.class)
public class DebugRendererMixin implements DebugRenderer_Duck {
@Unique
@Mutable
@Final
private WeepingSkyDebugRenderer crystalline_sky$weepingSkyDebugRenderer;
@Unique
private int crystalline_sky$weepingSkyMode = 0;
@Override
public WeepingSkyDebugRenderer crystalline_sky$getWeepingSkyDebugRenderer() {
return crystalline_sky$weepingSkyDebugRenderer;
}
@Override
public int crystalline_sky$toggleWeepingSky() {
crystalline_sky$weepingSkyMode = (crystalline_sky$weepingSkyMode + 1) % 3;
return crystalline_sky$weepingSkyMode;
}
@Inject(method = "<init>", at = @At("RETURN"))
private void init(Minecraft client, CallbackInfo ci) {
this.crystalline_sky$weepingSkyDebugRenderer = new WeepingSkyDebugRenderer(client);
}
@Inject(method = "render", at = @At("RETURN"))
private void render(PoseStack matrices, MultiBufferSource.BufferSource vertexConsumers, double cameraX, double cameraY, double cameraZ, CallbackInfo ci) {
if (crystalline_sky$weepingSkyMode != 0) {
crystalline_sky$weepingSkyDebugRenderer.setLitMode(crystalline_sky$weepingSkyMode == 1);
crystalline_sky$weepingSkyDebugRenderer.render(matrices, vertexConsumers, cameraX, cameraY, cameraZ);
}
}
@Inject(method = "clear", at = @At("RETURN"))
private void reset(CallbackInfo ci) {
crystalline_sky$weepingSkyDebugRenderer.clear();
}
}
@@ -0,0 +1,34 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.datafixers.util.Pair;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineShaderInstances;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.GameRenderer;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@WrapOperation(method = "reloadShaders", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 0))
private <E extends Pair<ShaderInstance, Consumer<ShaderInstance>>> boolean loadSkyProgram(
List<E> instance,
Object e,
Operation<Boolean> original,
ResourceProvider factory
) throws IOException {
original.call(instance, Pair.of(new ShaderInstance(
factory,
"crystalline_sky_rendertype_sky",
DefaultVertexFormat.BLOCK
), (Consumer<ShaderInstance>) program -> CrystallineShaderInstances.renderTypeSkyProgram = program));
return original.call(instance, e);
}
}
@@ -0,0 +1,138 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.LevelRenderer_Duck;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.RenderTarget_Duck;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.minecraft.client.Minecraft;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.renderer.LevelRenderer;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
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.callback.CallbackInfo;
@Mixin(value = LevelRenderer.class, remap = false)
public abstract class LevelRendererMixin implements LevelRenderer_Duck {
@Shadow
@Final
private Minecraft minecraft;
@Shadow
protected abstract boolean doesMobEffectBlockSky(Camera camera);
@Shadow
protected abstract void renderSectionLayer(RenderType renderLayer, double x, double y, double z, Matrix4f matrix4f, Matrix4f positionMatrix);
@Shadow
private @Nullable RenderTarget cloudsTarget;
@Shadow
public abstract void renderClouds(PoseStack matrices, Matrix4f matrix4f, Matrix4f matrix4f2, float tickDelta, double cameraX, double cameraY, double cameraZ);
@Unique
@Nullable
private RenderTarget crystalline_sky$skyBuffer;
@Override
public RenderTarget crystalline_sky$getSkyFramebuffer() {
if (crystalline_sky$skyBuffer == null) {
var fb = minecraft.getMainRenderTarget();
crystalline_sky$skyBuffer = new TextureTarget(fb.width, fb.height, true, Minecraft.ON_OSX);
}
return crystalline_sky$skyBuffer;
}
@Inject(
method = "renderLevel",
at = @At(
value = "INVOKE_STRING",
target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V",
args = "ldc=clear",
shift = At.Shift.AFTER
)
)
private void setupSkyBuffer(DeltaTracker tickCounter, boolean renderBlockOutline, Camera camera,
GameRenderer gameRenderer, LightTexture lightmapTextureManager,
Matrix4f matrix4f, Matrix4f matrix4f2, CallbackInfo ci) {
crystalline_sky$getSkyFramebuffer();
}
@Inject(method = "resize", at = @At("RETURN"))
private void resizeSkyBuffer(int width, int height, CallbackInfo ci) {
if (crystalline_sky$skyBuffer != null) {
crystalline_sky$skyBuffer.resize(width, height, Minecraft.ON_OSX);
}
}
@Inject(method = "close", at = @At("RETURN"))
private void closeSkyBuffer(CallbackInfo ci) {
if (crystalline_sky$skyBuffer != null) {
crystalline_sky$skyBuffer.destroyBuffers();
crystalline_sky$skyBuffer = null;
}
}
@Inject(
method = "renderLevel",
at = @At(
value = "INVOKE_STRING",
target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V",
args = "ldc=terrain_setup"
)
)
private void copyAfterSky(DeltaTracker tickCounter, boolean renderBlockOutline, Camera camera,
GameRenderer gameRenderer, LightTexture lightmapTextureManager,
Matrix4f matrix4f, Matrix4f matrix4f2, CallbackInfo ci) {
var skyBuffer = crystalline_sky$getSkyFramebuffer();
skyBuffer.clear(Minecraft.ON_OSX);
((RenderTarget_Duck) skyBuffer).crystalline_sky$copyColorFrom(minecraft.getMainRenderTarget());
// render clouds
CloudStatus cloudRenderMode = this.minecraft.options.getCloudsType();
if (cloudRenderMode != CloudStatus.OFF && !this.doesMobEffectBlockSky(camera)) {
float f = tickCounter.getGameTimeDeltaPartialTick(false);
Vec3 vec3d = camera.getPosition();
double cx = vec3d.x();
double cy = vec3d.y();
double cz = vec3d.z();
var cloudsFramebuffer0 = this.cloudsTarget;
this.cloudsTarget = skyBuffer;
cloudsTarget.bindWrite(false);
this.renderClouds(new PoseStack(), matrix4f, matrix4f2, f, cx, cy, cz);
minecraft.getMainRenderTarget().bindWrite(false);
this.cloudsTarget = cloudsFramebuffer0;
}
}
// after solid, cutoutMipped, and cutout
@WrapOperation(
method = "renderLevel",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/renderer/LevelRenderer;renderSectionLayer(Lnet/minecraft/client/renderer/RenderType;DDDLorg/joml/Matrix4f;Lorg/joml/Matrix4f;)V",
ordinal = 2
)
)
private void renderCrystallineSky(LevelRenderer instance, RenderType renderLayer, double x, double y, double z,
Matrix4f matrix4f, Matrix4f positionMatrix, Operation<Void> original) {
original.call(instance, renderLayer, x, y, z, matrix4f, positionMatrix);
renderSectionLayer(CrystallineRenderTypes.SKY, x, y, z, matrix4f, positionMatrix);
}
}
@@ -0,0 +1,58 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.systems.RenderSystem;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import io.github.slimeistdev.crystalline_sky.registry.CrystallineItems;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.RenderType;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.LevelRenderer;
import com.mojang.blaze3d.platform.Window;
import net.minecraft.util.Mth;
import org.joml.Matrix4f;
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;
@ConditionalMixin(mods = Mods.SODIUM, applyIfPresent = false)
@Mixin(LevelRenderer.class)
public class LevelRenderer_NoSodium_Mixin {
@Shadow
@Final
private Minecraft minecraft;
@Shadow
private int ticks;
@WrapOperation(method = "renderSectionLayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ShaderInstance;setDefaultUniforms(Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;Lorg/joml/Matrix4f;Lorg/joml/Matrix4f;Lcom/mojang/blaze3d/platform/Window;)V"))
private void tintSkyBlocks(ShaderInstance instance, VertexFormat.Mode drawMode, Matrix4f viewMatrix,
Matrix4f projectionMatrix, Window window, Operation<Void> original,
RenderType renderLayer) {
original.call(instance, drawMode, viewMatrix, projectionMatrix, window);
if (renderLayer == CrystallineRenderTypes.SKY
&& instance.COLOR_MODULATOR != null
&& minecraft.player != null
&& (minecraft.player.getMainHandItem().is(CrystallineItems.SKY)
|| minecraft.player.getOffhandItem().is(CrystallineItems.SKY)
|| minecraft.player.getMainHandItem().is(CrystallineItems.WEEPING_SKY)
|| minecraft.player.getOffhandItem().is(CrystallineItems.WEEPING_SKY))) {
float f = ticks + minecraft.getTimer().getGameTimeDeltaPartialTick(true);
float alpha = (Mth.sin(f / 10.0f) + 1.0f) / 2.0f;
// remap alpha from [0, 1] to [0, 0.75]
float maxAlpha = 1.0f - 0.25f;
alpha = alpha * maxAlpha;
float[] colorModulator = RenderSystem.getShaderColor().clone();
colorModulator[3] *= alpha;
instance.COLOR_MODULATOR.set(colorModulator);
}
}
}
@@ -0,0 +1,43 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.RenderTarget_Duck;
import com.mojang.blaze3d.pipeline.RenderTarget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(RenderTarget.class)
public class RenderTargetMixin implements RenderTarget_Duck {
@Shadow
protected int colorTextureId;
@Shadow
public int width;
@Shadow
public int height;
@Shadow
public int frameBufferId;
@Override
public void crystalline_sky$copyColorFrom(RenderTarget framebuffer) {
RenderSystem.assertOnRenderThreadOrInit();
if (this.colorTextureId == -1) {
throw new IllegalStateException("Trying to copy color texture to a RenderTarget without a color texture");
} else if (framebuffer.getColorTextureId() == -1) {
throw new IllegalStateException("Trying to copy color texture from a RenderTarget without a color texture");
} else {
GlStateManager._glBindFramebuffer(GlConst.GL_READ_FRAMEBUFFER, framebuffer.frameBufferId);
GlStateManager._glBindFramebuffer(GlConst.GL_DRAW_FRAMEBUFFER, this.frameBufferId);
GlStateManager._glBlitFrameBuffer(
0, 0, framebuffer.width, framebuffer.height,
0, 0, this.width, this.height,
GlConst.GL_COLOR_BUFFER_BIT, GlConst.GL_NEAREST
);
GlStateManager._glBindFramebuffer(GlConst.GL_FRAMEBUFFER, framebuffer.frameBufferId);
}
}
}
@@ -0,0 +1,25 @@
package io.github.slimeistdev.crystalline_sky.mixin.client;
import com.google.common.collect.ImmutableList;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.RenderType_Duck;
import net.minecraft.client.renderer.RenderType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(RenderType.class)
public class RenderTypeMixin implements RenderType_Duck {
@Shadow
@Final
@Mutable
private static ImmutableList<RenderType> CHUNK_BUFFER_LAYERS;
@Override
public void crystalline_sky$markAsBlockLayer() {
CHUNK_BUFFER_LAYERS = ImmutableList.<RenderType>builder()
.addAll(CHUNK_BUFFER_LAYERS)
.add((RenderType) (Object) this)
.build();
}
}
@@ -0,0 +1,22 @@
package io.github.slimeistdev.crystalline_sky.mixin.client.compat.axiom;
import com.moulberry.axiom.render.ChunkRenderOverrider;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.minecraft.client.renderer.RenderType;
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;
@ConditionalMixin(mods = Mods.AXIOM)
@Mixin(ChunkRenderOverrider.AxiomChunkOverrideLayer.class)
public class ChunkRenderOverrider_AxiomChunkOverrideLayerMixin {
@Inject(method = "fromVanilla", at = @At("HEAD"), cancellable = true)
private static void unexplodeSky(RenderType layer, CallbackInfoReturnable<ChunkRenderOverrider.AxiomChunkOverrideLayer> cir) {
if (layer == CrystallineRenderTypes.SKY) {
cir.setReturnValue(ChunkRenderOverrider.AxiomChunkOverrideLayer.CUTOUT_MIPPED);
}
}
}
@@ -0,0 +1,39 @@
package io.github.slimeistdev.crystalline_sky.mixin.client.compat.sodium;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.parameters.AlphaCutoffParameter;
import net.minecraft.client.renderer.RenderType;
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.CallbackInfoReturnable;
import java.util.Arrays;
@ConditionalMixin(mods = Mods.SODIUM)
@Mixin(DefaultMaterials.class)
public class DefaultMaterialsMixin {
@Unique
@SuppressWarnings("OptionalGetWithoutIsPresent")
private static final Material crystalline_sky$SKY = new Material(
Arrays.stream(DefaultTerrainRenderPasses.ALL)
.filter(pass -> ((TerrainRenderPassAccessor) pass).crystalline_sky$getRenderLayer() == CrystallineRenderTypes.SKY)
.findFirst()
.get(),
AlphaCutoffParameter.ZERO,
false
);
@Inject(method = "forRenderLayer", at = @At(value = "NEW", target = "(Ljava/lang/String;)Ljava/lang/IllegalArgumentException;"), cancellable = true)
private static void addCrystallineSkyMaterial(RenderType layer, CallbackInfoReturnable<Material> cir) {
if (layer == CrystallineRenderTypes.SKY) {
cir.setReturnValue(crystalline_sky$SKY);
}
}
}
@@ -0,0 +1,31 @@
package io.github.slimeistdev.crystalline_sky.mixin.client.compat.sodium;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
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;
@ConditionalMixin(mods = Mods.SODIUM)
@Mixin(DefaultTerrainRenderPasses.class)
public class DefaultTerrainRenderPassesMixin {
@Mutable
@Shadow
@Final
public static TerrainRenderPass[] ALL;
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void addCrystallineSkyPass(CallbackInfo ci) {
var ALL$ = new TerrainRenderPass[ALL.length + 1];
System.arraycopy(ALL, 0, ALL$, 0, ALL.length);
ALL$[ALL.length] = new TerrainRenderPass(CrystallineRenderTypes.SKY, false, true);
ALL = ALL$;
}
}
@@ -0,0 +1,6 @@
The following mixins in this package are from derived from https://github.com/wisp-forest/affinity, and as such are licensed under the MIT license:
- DefaultMaterialsMixin
- DefaultTerrainRenderPassesMixin
- SodiumWorldRendererMixin
- TerrainRenderPassAccessor
@@ -0,0 +1,50 @@
package io.github.slimeistdev.crystalline_sky.mixin.client.compat.sodium;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import io.github.slimeistdev.crystalline_sky.compat.sodium.SkyShaderInterface;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.caffeinemc.mods.sodium.client.gl.shader.GlProgram;
import net.caffeinemc.mods.sodium.client.render.chunk.ShaderChunkRenderer;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderInterface;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderOptions;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ShaderBindingContext;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.function.Function;
@ConditionalMixin(mods = Mods.SODIUM)
@Mixin(ShaderChunkRenderer.class)
public class ShaderChunkRendererMixin {
@WrapOperation(method = "compileProgram", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer;createShader(Ljava/lang/String;Lnet/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderOptions;)Lnet/caffeinemc/mods/sodium/client/gl/shader/GlProgram;"), remap = false)
private GlProgram<ChunkShaderInterface> createSkyShader(ShaderChunkRenderer instance, String path, ChunkShaderOptions options, Operation<GlProgram<ChunkShaderInterface>> original) {
if (options.pass() == DefaultMaterials.forRenderLayer(CrystallineRenderTypes.SKY).pass) {
return original.call(instance, "sodium_compat/block_layer_sky", options);
}
return original.call(instance, path, options);
}
@WrapOperation(method = "createShader", at = @At(value = "INVOKE", target = "Lnet/minecraft/resources/ResourceLocation;fromNamespaceAndPath(Ljava/lang/String;Ljava/lang/String;)Lnet/minecraft/resources/ResourceLocation;"))
private ResourceLocation redirectShaderNamespace(String namespace, String path, Operation<ResourceLocation> original, String pathArg) {
if (pathArg.equals("sodium_compat/block_layer_sky")) {
namespace = "crystalline_sky";
}
return original.call(namespace, path);
}
@WrapOperation(method = "createShader", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/gl/shader/GlProgram$Builder;link(Ljava/util/function/Function;)Lnet/caffeinemc/mods/sodium/client/gl/shader/GlProgram;"), remap = false)
private static GlProgram<ChunkShaderInterface> useSkyShaderInterface(GlProgram.Builder instance, Function<ShaderBindingContext, ChunkShaderInterface> factory, Operation<GlProgram<ChunkShaderInterface>> original, String argPath, ChunkShaderOptions argOptions) {
if (argPath.equals("sodium_compat/block_layer_sky")) {
factory = (shader) -> new SkyShaderInterface(shader, argOptions);
}
return original.call(instance, factory);
}
}
@@ -0,0 +1,29 @@
package io.github.slimeistdev.crystalline_sky.mixin.client.compat.sodium;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import io.github.slimeistdev.crystalline_sky.registry.client.CrystallineRenderTypes;
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionManager;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials;
import net.minecraft.client.renderer.RenderType;
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;
@ConditionalMixin(mods = Mods.SODIUM)
@Mixin(SodiumWorldRenderer.class)
public class SodiumWorldRendererMixin {
@Shadow
private RenderSectionManager renderSectionManager;
@Inject(method = "drawChunkLayer", at = @At("RETURN"))
private void drawCrystallineLayer(RenderType renderLayer, ChunkRenderMatrices matrices, double x, double y, double z, CallbackInfo ci) {
if (renderLayer != CrystallineRenderTypes.SKY) return;
renderSectionManager.renderLayer(matrices, DefaultMaterials.forRenderLayer(CrystallineRenderTypes.SKY).pass, x, y, z);
}
}
@@ -0,0 +1,15 @@
package io.github.slimeistdev.crystalline_sky.mixin.client.compat.sodium;
import io.github.slimeistdev.crystalline_sky.annotation.mixin.ConditionalMixin;
import io.github.slimeistdev.crystalline_sky.compat.Mods;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.minecraft.client.renderer.RenderType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@ConditionalMixin(mods = Mods.SODIUM)
@Mixin(TerrainRenderPass.class)
public interface TerrainRenderPassAccessor {
@Accessor("renderType")
RenderType crystalline_sky$getRenderLayer();
}
@@ -0,0 +1,7 @@
package io.github.slimeistdev.crystalline_sky.mixin_ducks;
import io.github.slimeistdev.crystalline_sky.infrastructure.WeepingStorage;
public interface ChunkSkyLight_Duck {
WeepingStorage crystalline_sky$getWeepingStorage();
}
@@ -0,0 +1,12 @@
package io.github.slimeistdev.crystalline_sky.mixin_ducks.client;
import io.github.slimeistdev.crystalline_sky.infrastructure.client.WeepingSkyDebugRenderer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(EnvType.CLIENT)
public interface DebugRenderer_Duck {
WeepingSkyDebugRenderer crystalline_sky$getWeepingSkyDebugRenderer();
int crystalline_sky$toggleWeepingSky();
}
@@ -0,0 +1,7 @@
package io.github.slimeistdev.crystalline_sky.mixin_ducks.client;
import com.mojang.blaze3d.pipeline.RenderTarget;
public interface LevelRenderer_Duck {
RenderTarget crystalline_sky$getSkyFramebuffer();
}
@@ -0,0 +1,7 @@
package io.github.slimeistdev.crystalline_sky.mixin_ducks.client;
import com.mojang.blaze3d.pipeline.RenderTarget;
public interface RenderTarget_Duck {
void crystalline_sky$copyColorFrom(RenderTarget framebuffer);
}
@@ -0,0 +1,5 @@
package io.github.slimeistdev.crystalline_sky.mixin_ducks.client;
public interface RenderType_Duck {
void crystalline_sky$markAsBlockLayer();
}
@@ -0,0 +1,83 @@
package io.github.slimeistdev.crystalline_sky.registry;
import io.github.slimeistdev.crystalline_sky.CrystallineSky;
import io.github.slimeistdev.crystalline_sky.content.blocks.SkyLightBlock;
import io.github.slimeistdev.crystalline_sky.content.blocks.WeepingSkyLightBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LightBlock;
import net.minecraft.world.level.block.TransparentBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.material.MapColor;
import java.util.function.Function;
@SuppressWarnings("SameParameterValue")
public class CrystallineBlocks {
public static final Block SKY = register("sky", TransparentBlock::new, Properties.of()
.strength(0.3f)
.sound(SoundType.AMETHYST)
.noOcclusion()
.isValidSpawn(Blocks::never)
.isRedstoneConductor(Blocks::never)
.isSuffocating(Blocks::never)
.isViewBlocking(Blocks::never)
.emissiveRendering(Blocks::always));
public static final Block WEEPING_SKY = register("weeping_sky", TransparentBlock::new, Properties.ofFullCopy(SKY));
public static final Block SKY_LIGHT = register(
"sky_light",
SkyLightBlock::new,
BlockBehaviour.Properties.of()
.replaceable()
.strength(-1.0F, 3600000.8F)
.mapColor(state -> state.getValue(BlockStateProperties.WATERLOGGED) ? MapColor.WATER : MapColor.NONE)
.noLootTable()
.noOcclusion()
);
public static final Block WEEPING_SKY_LIGHT = register(
"weeping_sky_light",
WeepingSkyLightBlock::new,
BlockBehaviour.Properties.of()
.replaceable()
.strength(-1.0F, 3600000.8F)
.mapColor(state -> state.getValue(BlockStateProperties.WATERLOGGED) ? MapColor.WATER : MapColor.NONE)
.noLootTable()
.noOcclusion()
);
public static void init() {}
private static <T extends Block> T register(String id, Function<Properties, T> factory, Properties settings) {
ResourceKey<Block> key = CrystallineSky.key(Registries.BLOCK, id);
return Registry.register(BuiltInRegistries.BLOCK, key, factory.apply(settings));
}
public static boolean isCrystallineSky(BlockState state) {
return getSkyLightLevel(state) > 0;
}
public static int getSkyLightLevel(BlockState state) {
if (state.is(SKY_LIGHT)) {
return state.getValue(LightBlock.LEVEL);
} else if (state.is(SKY)) { // weeping sky explicitly excluded, that's handled differently
return 15;
}
return 0;
}
public static boolean isWeepingSky(BlockState state) {
return state.is(WEEPING_SKY) || state.is(WEEPING_SKY_LIGHT);
}
}
@@ -0,0 +1,26 @@
package io.github.slimeistdev.crystalline_sky.registry;
import io.github.slimeistdev.crystalline_sky.CrystallineSky;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.Registry;
import net.minecraft.util.Unit;
import java.util.function.UnaryOperator;
public class CrystallineDataComponentTypes {
public static final DataComponentType<Unit> WEEPING_SKY_DEBUG_COLUMN_TOOL = register(
"weeping_sky_debug_column_tool",
b -> b
.networkSynchronized(StreamCodec.unit(Unit.INSTANCE))
.persistent(Unit.CODEC)
);
@SuppressWarnings("SameParameterValue")
private static <T> DataComponentType<T> register(String id, UnaryOperator<DataComponentType.Builder<T>> builderOperator) {
return Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, CrystallineSky.id(id), builderOperator.apply(DataComponentType.builder()).build());
}
public static void init() {}
}
@@ -0,0 +1,66 @@
package io.github.slimeistdev.crystalline_sky.registry;
import io.github.slimeistdev.crystalline_sky.content.blocks.SkyLightBlock;
import io.github.slimeistdev.crystalline_sky.content.items.WeepingSkyBlockItem;
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LightBlock;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.component.BlockItemStateProperties;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.Rarity;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
public class CrystallineItems {
public static final Item SKY = Items.registerBlock(
CrystallineBlocks.SKY,
settings -> settings.rarity(Rarity.EPIC)
);
public static final Item WEEPING_SKY = register(
CrystallineBlocks.WEEPING_SKY,
(block, settings) -> new WeepingSkyBlockItem(block, settings.rarity(Rarity.EPIC))
);
public static final Item SKY_LIGHT = Items.registerBlock(
CrystallineBlocks.SKY_LIGHT,
settings -> settings.rarity(Rarity.EPIC)
.component(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY.with(LightBlock.LEVEL, 15))
);
public static final Item WEEPING_SKY_LIGHT = Items.registerBlock(
CrystallineBlocks.WEEPING_SKY_LIGHT,
settings -> settings.rarity(Rarity.EPIC)
);
@SuppressWarnings("SameParameterValue")
private static Item register(Block block, BiFunction<Block, Item.Properties, BlockItem> factory) {
return Items.registerBlock(factory.apply(block, new Item.Properties()));
}
public static void init() {
ItemGroupEvents.modifyEntriesEvent(CreativeModeTabs.OP_BLOCKS).register(entries -> {
if (!entries.getContext().hasPermissions()) return;
List<ItemStack> stacks = new ArrayList<>();
stacks.add(new ItemStack(SKY));
stacks.add(new ItemStack(WEEPING_SKY));
for (int i = 15; i >= 0; i--) {
stacks.add(SkyLightBlock.setLightOnStack(new ItemStack(SKY_LIGHT), i));
}
stacks.add(new ItemStack(WEEPING_SKY_LIGHT));
entries.addAfter(Items.LIGHT, stacks);
});
}
}
@@ -0,0 +1,50 @@
package io.github.slimeistdev.crystalline_sky.registry.client;
import com.mojang.blaze3d.systems.RenderSystem;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.RenderType_Duck;
import io.github.slimeistdev.crystalline_sky.mixin_ducks.client.LevelRenderer_Duck;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderStateShard;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import static net.minecraft.client.renderer.RenderType.create;
import static net.minecraft.client.renderer.RenderStateShard.LIGHTMAP;
@Environment(EnvType.CLIENT)
public class CrystallineRenderTypes {
private static final RenderStateShard.EmptyTextureStateShard SKY_TEXTURE = new SkyBufferTexture();
private static final RenderStateShard.ShaderStateShard SKY_PROGRAM = new RenderStateShard.ShaderStateShard(CrystallineShaderInstances::getRenderTypeSkyProgram);
public static final RenderType SKY = create(
"crystalline_sky_sky",
DefaultVertexFormat.BLOCK,
VertexFormat.Mode.QUADS,
4194304,
true,
false,
RenderType.CompositeState.builder()
.setLightmapState(LIGHTMAP)
.setShaderState(SKY_PROGRAM)
.setTextureState(SKY_TEXTURE)
.createCompositeState(true)
);
static {
//noinspection DataFlowIssue
((RenderType_Duck) SKY).crystalline_sky$markAsBlockLayer();
}
private static class SkyBufferTexture extends RenderStateShard.EmptyTextureStateShard {
public SkyBufferTexture() {
super(() -> {
Minecraft mc = Minecraft.getInstance();
var fb = ((LevelRenderer_Duck) mc.levelRenderer).crystalline_sky$getSkyFramebuffer();
RenderSystem.setShaderTexture(0, fb.getColorTextureId());
}, () -> {});
}
}
}
@@ -0,0 +1,14 @@
package io.github.slimeistdev.crystalline_sky.registry.client;
import net.minecraft.client.renderer.ShaderInstance;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public class CrystallineShaderInstances {
@ApiStatus.Internal
public static @Nullable ShaderInstance renderTypeSkyProgram;
public static @Nullable ShaderInstance getRenderTypeSkyProgram() {
return renderTypeSkyProgram;
}
}
@@ -0,0 +1,47 @@
package io.github.slimeistdev.crystalline_sky.util;
public class ArrayUtils {
/**
* Binary search for the first index i in the sorted array where array[i] < value and array[i + 1] > value.
* @param array must be sorted in ascending order
* @param value the value to find a place for
* @return the index to insert at, or -1 if no such index exists
*/
public static int binarySearchSortedInsertionPoint(int[] array, int value) {
return binarySearchSortedInsertionPoint(array, value, array.length);
}
/**
* Binary search for the first index i in the sorted array where array[i] < value and array[i + 1] > value.
* @param array must be sorted in ascending order
* @param value the value to find a place for
* @param length limits the search to indexes [0, length)
* @return the index to insert at, or -1 if no such index exists
*/
public static int binarySearchSortedInsertionPoint(int[] array, int value, int length) {
if (length == 0) return 0;
if (value < array[0]) return 0;
if (value > array[length - 1]) return length;
int left = 0;
int right = length - 1;
while (left < right) {
int mid = (left + right) / 2;
int midValue = array[mid];
if (midValue == value)
return -1;
if (midValue < value) {
left = mid + 1;
} else { // midValue > value
right = mid;
}
}
if (array[left] == value) return -1;
if (array[right] == value) return -1;
return left;
}
}

Some files were not shown because too many files have changed in this diff Show More