This commit is contained in:
2025-11-16 15:27:46 -08:00
commit f8f6bbbf19
21 changed files with 1344 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
package org.ruffles.horrorUtils.client;
import foundry.veil.api.client.render.VeilRenderer;
import foundry.veil.api.client.render.light.data.AreaLightData;
import foundry.veil.api.client.render.light.renderer.LightRenderHandle;
import foundry.veil.platform.VeilEventPlatform;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
import net.minecraft.util.Pair;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import static org.ruffles.horrorUtils.HorrorUtils.FLASHLIGHT_ON;
public class HorrorUtilsClient implements ClientModInitializer {
public static VeilRenderer veil;
private static LightRenderHandle<AreaLightData> areaHandle;
private static LightRenderHandle<AreaLightData> areaOuterHandle;
public static Logger logger = LoggerFactory.getLogger("Horror Utils (client)");
private static HashMap<UUID, Pair<LightRenderHandle<AreaLightData>, LightRenderHandle<AreaLightData>>> otherLights = new HashMap<>();
public @Nullable PlayerEntity getPlayerByUuid(UUID Uuid, List<PlayerEntity> players) {
for (PlayerEntity player : players) {
if (player.getUuid().equals(Uuid)) return player;
}
return null;
}
@Override
public void onInitializeClient() {
VeilEventPlatform.INSTANCE.onVeilRendererAvailable((renderer) -> {
veil = renderer;
// 2) Create a quad “area” light
AreaLightData data = new AreaLightData()
.setSize(0.0, 0.0) // width, height in blocks
.setColor(1.0f, 0.95f, 0.8f)// warm-ish white
.setBrightness(0.0f) // multiplier
.setAngle((float) Math.toRadians(20)) // spread from surface normal
.setDistance(20.0f); // max influence radius
// 2) Create a quad “area” light
AreaLightData dataOuter = new AreaLightData()
.setSize(0.0, 0.0) // width, height in blocks
.setColor(1.0f, 0.95f, 0.8f)// warm-ish white
.setBrightness(0.0f) // multiplier
.setAngle((float) Math.toRadians(60)) // spread from surface normal
.setDistance(20.0f); // max influence radius
// Put it in front of the player camera initially
data.setTo(MinecraftClient.getInstance().gameRenderer.getCamera());
dataOuter.setTo(MinecraftClient.getInstance().gameRenderer.getCamera());
logger.info("created light!");
// 3) Add to Veils LightRenderer, keep the handle
areaHandle = veil.getLightRenderer().addLight(data);
areaOuterHandle = veil.getLightRenderer().addLight(dataOuter);
});
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (veil == null || areaHandle == null || !areaHandle.isValid() || client.player == null) return;
AreaLightData data = areaHandle.getLightData();
AreaLightData dataOuter = areaOuterHandle.getLightData();
float bright = 0.0f;
for (ItemStack handItem : client.player.getHandItems()) {
if (Boolean.TRUE.equals(handItem.get(FLASHLIGHT_ON))) {
bright += 2.0f;
}
}
data.setBrightness(bright);
dataOuter.setBrightness(bright / 1.2f);
if (!client.gameRenderer.getCamera().isThirdPerson()) {
areaHandle.markDirty();
areaOuterHandle.markDirty();
return;
}
// Position: player's head/eyes
Vec3d eye = client.player.getEyePos(); // same as head position
data.getPosition().set(eye.x, eye.y, eye.z);
dataOuter.getPosition().set(eye.x, eye.y, eye.z);
// (Optional) also aim the light where the player looks:
// Build a rotation from yaw/pitch (degrees -> radians)
float yaw = client.player.getYaw(); // degrees
float pitch = client.player.getPitch(); // degrees
// If AreaLightData exposes a JOML quaternion:
data.getOrientation().identity().rotateX((float) Math.toRadians(-pitch)).rotateY((float) Math.toRadians(yaw));
dataOuter.getOrientation().identity().rotateX((float) Math.toRadians(-pitch)).rotateY((float) Math.toRadians(yaw));
// Push changes to GPU
areaHandle.markDirty();
areaOuterHandle.markDirty();
});
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (veil == null || areaHandle == null || !areaHandle.isValid() || client.player == null) return;
List<PlayerEntity> entities = client.player.getWorld().getOtherEntities(client.player,new Box(
-1000, client.player.getWorld().getBottomY(), -1000,
1000, client.player.getWorld().getTopY(), 1000
), e -> e instanceof PlayerEntity).stream().map((e) -> (PlayerEntity) e).toList();
for (PlayerEntity entity : entities) {
if (!otherLights.containsKey(entity.getUuid()) && (Boolean.TRUE.equals(entity.getStackInHand(Hand.MAIN_HAND).get(FLASHLIGHT_ON)) || Boolean.TRUE.equals(entity.getStackInHand(Hand.OFF_HAND).get(FLASHLIGHT_ON))) && veil != null) {
AreaLightData edata = new AreaLightData()
.setSize(0.0, 0.0) // width, height in blocks
.setColor(1.0f, 0.95f, 0.8f)// warm-ish white
.setBrightness(0.0f) // multiplier
.setAngle((float) Math.toRadians(20)) // spread from surface normal
.setDistance(20.0f); // max influence radius
// 2) Create a quad “area” light
AreaLightData edataOuter = new AreaLightData()
.setSize(0.0, 0.0) // width, height in blocks
.setColor(1.0f, 0.95f, 0.8f)// warm-ish white
.setBrightness(0.0f) // multiplier
.setAngle((float) Math.toRadians(60)) // spread from surface normal
.setDistance(20.0f); // max influence radius
float ebright = 0.0f;
for (ItemStack handItem : entity.getHandItems()) {
if (Boolean.TRUE.equals(handItem.get(FLASHLIGHT_ON))) {
ebright += 2.0f;
}
}
edata.setBrightness(ebright);
edataOuter.setBrightness(ebright / 1.2f);
Vec3d eeye = entity.getEyePos(); // same as head position
edata.getPosition().set(eeye.x, eeye.y, eeye.z);
edataOuter.getPosition().set(eeye.x, eeye.y, eeye.z);
// (Optional) also aim the light where the player looks:
// Build a rotation from yaw/pitch (degrees -> radians)
float eyaw = entity.getYaw(); // degrees
float epitch = entity.getPitch(); // degrees
// If AreaLightData exposes a JOML quaternion:
edata.getOrientation().identity().rotateX((float) Math.toRadians(-epitch)).rotateY((float) Math.toRadians(eyaw));
edataOuter.getOrientation().identity().rotateX((float) Math.toRadians(-epitch)).rotateY((float) Math.toRadians(eyaw));
LightRenderHandle<AreaLightData> eareaHandle = veil.getLightRenderer().addLight(edata);
LightRenderHandle<AreaLightData> eareaOuterHandle = veil.getLightRenderer().addLight(edataOuter);
otherLights.put(entity.getUuid(), new Pair<>(eareaHandle,eareaOuterHandle));
}
}
List<UUID> toRemove = new java.util.ArrayList<>(List.of());
for (UUID uuid : otherLights.keySet()) {
PlayerEntity entity = getPlayerByUuid(uuid, entities);
if (entity != null) {
float ebright = 0.0f;
Pair<LightRenderHandle<AreaLightData>, LightRenderHandle<AreaLightData>> handles = otherLights.get(uuid);
AreaLightData edata = handles.getLeft().getLightData();
AreaLightData edataOuter = handles.getRight().getLightData();
for (ItemStack handItem : entity.getHandItems()) {
if (Boolean.TRUE.equals(handItem.get(FLASHLIGHT_ON))) {
ebright += 2.0f;
}
}
edata.setBrightness(ebright);
edataOuter.setBrightness(ebright / 1.2f);
Vec3d eeye = entity.getEyePos(); // same as head position
edata.getPosition().set(eeye.x, eeye.y, eeye.z);
edataOuter.getPosition().set(eeye.x, eeye.y, eeye.z);
// (Optional) also aim the light where the player looks:
// Build a rotation from yaw/pitch (degrees -> radians)
float eyaw = entity.getYaw(); // degrees
float epitch = entity.getPitch(); // degrees
// If AreaLightData exposes a JOML quaternion:
edata.getOrientation().identity().rotateX((float) Math.toRadians(-epitch)).rotateY((float) Math.toRadians(eyaw));
edataOuter.getOrientation().identity().rotateX((float) Math.toRadians(-epitch)).rotateY((float) Math.toRadians(eyaw));
handles.getLeft().markDirty();
handles.getRight().markDirty();
} else {
Pair<LightRenderHandle<AreaLightData>, LightRenderHandle<AreaLightData>> handles = otherLights.get(uuid);
handles.getRight().free();
handles.getLeft().free();
toRemove.add(uuid);
}
}
for (UUID uuid : toRemove) {
otherLights.remove(uuid);
}
});
WorldRenderEvents.START.register((ctx) -> {
if (veil == null || areaHandle == null || !areaHandle.isValid() || ctx.camera().isThirdPerson()) return;
areaHandle.getLightData().setTo(ctx.camera());
areaOuterHandle.getLightData().getPosition().set(areaHandle.getLightData().getPosition());
areaOuterHandle.getLightData().getOrientation().set(areaHandle.getLightData().getOrientation());
areaHandle.markDirty();
areaOuterHandle.markDirty();
});
}
}

View File

@@ -0,0 +1,12 @@
package org.ruffles.horrorUtils.client;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
public class HorrorUtilsDataGenerator implements DataGeneratorEntrypoint {
@Override
public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
FabricDataGenerator.Pack pack = fabricDataGenerator.createPack();
}
}

View File

@@ -0,0 +1,73 @@
{
"format_version": "1.9.0",
"credit": "Made with Blockbench",
"textures": {
"0": "horror_utils:item/flashlight_on",
"particle": "horror_utils:item/flashlight_on"
},
"elements": [
{
"from": [7, 0, 9],
"to": [9, 2, 14],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 0, 14]},
"faces": {
"north": {"uv": [10, 0, 12, 2], "texture": "#0"},
"east": {"uv": [6, 4, 11, 6], "texture": "#0"},
"south": {"uv": [10, 2, 12, 4], "texture": "#0"},
"west": {"uv": [6, 6, 11, 8], "texture": "#0"},
"up": {"uv": [14, 0, 16, 5], "texture": "#0"},
"down": {"uv": [12, 0, 14, 5], "texture": "#0"}
}
},
{
"from": [6, -1, 7],
"to": [10, 3, 9],
"rotation": {"angle": 0, "axis": "y", "origin": [7, 0, 7]},
"faces": {
"north": {"uv": [0, 0, 4, 4], "texture": "#0"},
"east": {"uv": [4, 0, 6, 4], "texture": "#0"},
"south": {"uv": [0, 4, 4, 8], "texture": "#0"},
"west": {"uv": [4, 4, 6, 8], "texture": "#0"},
"up": {"uv": [10, 2, 6, 0], "texture": "#0"},
"down": {"uv": [10, 2, 6, 4], "texture": "#0"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [90, 0, 0],
"translation": [0, 1.25, 6.25]
},
"thirdperson_lefthand": {
"rotation": [90, 0, 0],
"translation": [0, 0.75, 6]
},
"firstperson_righthand": {
"translation": [-0.75, 9.25, 0]
},
"firstperson_lefthand": {
"translation": [-0.25, 9.25, 0]
},
"ground": {
"translation": [0, 6.5, -3]
},
"gui": {
"rotation": [38, 138, -1],
"translation": [-2.75, 9.75, 0],
"scale": [2, 2, 2]
},
"head": {
"translation": [0, 11.75, -7.25]
},
"fixed": {
"rotation": [91, 0, 0],
"translation": [0, 4.5, 11],
"scale": [2, 2, 2]
},
"on_shelf": {
"rotation": [-90, 115, 0],
"translation": [-4.25, 0, -14],
"scale": [2, 2, 2]
}
}
}

View File

@@ -0,0 +1,14 @@
{
"flashlight_on": {
"subtitle": "sound.horror_utils.flashlight_on",
"sounds": [
"horror_utils:flashlight_on"
]
},
"flashlight_off": {
"subtitle": "sound.horror_utils.flashlight_off",
"sounds": [
"horror_utils:flashlight_off"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

View File

@@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.ruffles.horrorUtils.mixin.client",
"compatibilityLevel": "JAVA_21",
"client": [
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}

View File

@@ -0,0 +1,70 @@
package org.ruffles.horrorUtils;
import com.mojang.serialization.Codec;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.component.ComponentType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroups;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.Identifier;
import org.ruffles.horrorUtils.blocks.FloodLight;
import org.ruffles.horrorUtils.blocks.entities.FloodLightEntity;
import org.ruffles.horrorUtils.items.FlashLight;
public class HorrorUtils implements ModInitializer {
public static String MOD_ID = "horror_utils";
public static RegistryKey<Item> flashlightKey = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MOD_ID,"flashlight"));
FlashLight FLASHLIGHT = new FlashLight(new Item.Settings().maxCount(1));
public static RegistryKey<Block> floodlightKey = RegistryKey.of(RegistryKeys.BLOCK, Identifier.of(MOD_ID,"floodlight"));
public static RegistryKey<Item> floodlightItemKey = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(MOD_ID,"floodlight"));
public static FloodLight floodLight = new FloodLight(AbstractBlock.Settings.create().sounds(BlockSoundGroup.METAL));
public static BlockItem FLOODLIGHT_ITEM = Registry.register(Registries.ITEM,floodlightItemKey,new BlockItem(floodLight, new Item.Settings()));
public static Block FLOODLIGHT = Registry.register(Registries.BLOCK, floodlightKey, floodLight);
public static final ComponentType<Boolean> FLASHLIGHT_ON = Registry.register(
Registries.DATA_COMPONENT_TYPE,
Identifier.of(MOD_ID, "flashlight_on"),
ComponentType.<Boolean>builder().codec(Codec.BOOL).build()
);
public static final SoundEvent FLASHLIGHT_ON_SOUND = Registry.register(Registries.SOUND_EVENT, Identifier.of(MOD_ID, "flashlight_on"),
SoundEvent.of(Identifier.of(MOD_ID, "flashlight_on")));
public static final SoundEvent FLASHLIGHT_OFF_SOUND = Registry.register(Registries.SOUND_EVENT, Identifier.of(MOD_ID, "flashlight_off"),
SoundEvent.of(Identifier.of(MOD_ID, "flashlight_off")));
public static RegistryKey<BlockEntityType<?>> floodlightEntityKey = RegistryKey.of(RegistryKeys.BLOCK_ENTITY_TYPE, Identifier.of(MOD_ID,"floodlight"));
public static BlockEntityType<FloodLightEntity> FLOODLIGHT_BLOCK_ENTITY = register("floodlight", FloodLightEntity::new, FLOODLIGHT);
private static <T extends BlockEntity> BlockEntityType<T> register(String name,
BlockEntityType.BlockEntityFactory<? extends T> entityFactory,
Block... blocks) {
Identifier id = Identifier.of(MOD_ID, name);
return Registry.register(Registries.BLOCK_ENTITY_TYPE, id, BlockEntityType.Builder.<T>create(entityFactory, blocks).build());
}
@Override
public void onInitialize() {
Registry.register(Registries.ITEM, flashlightKey, FLASHLIGHT);
ItemGroupEvents.modifyEntriesEvent(ItemGroups.TOOLS)
.register((itemGroup) -> itemGroup.add(FLASHLIGHT));
ItemGroupEvents.modifyEntriesEvent(ItemGroups.BUILDING_BLOCKS)
.register((itemGroup) -> itemGroup.add(FLOODLIGHT_ITEM));
}
}

View File

@@ -0,0 +1,26 @@
package org.ruffles.horrorUtils.blocks;
import com.mojang.serialization.MapCodec;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.BlockWithEntity;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.Nullable;
import org.ruffles.horrorUtils.blocks.entities.FloodLightEntity;
public class FloodLight extends BlockWithEntity {
public FloodLight(Settings settings) {
super(settings);
}
@Override
protected MapCodec<? extends BlockWithEntity> getCodec() {
return null;
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new FloodLightEntity(pos, state);
}
}

View File

@@ -0,0 +1,13 @@
package org.ruffles.horrorUtils.blocks.entities;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.util.math.BlockPos;
import org.ruffles.horrorUtils.HorrorUtils;
public class FloodLightEntity extends BlockEntity {
public FloodLightEntity(BlockPos pos, BlockState state) {
super(HorrorUtils.FLOODLIGHT_BLOCK_ENTITY, pos, state);
}
}

View File

@@ -0,0 +1,29 @@
package org.ruffles.horrorUtils.items;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;
import static org.ruffles.horrorUtils.HorrorUtils.*;
public class FlashLight extends Item {
public FlashLight(Settings settings) {
super(settings);
settings.component(FLASHLIGHT_ON,false);
}
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
boolean on = Boolean.TRUE.equals(user.getStackInHand(hand).getComponents().get(FLASHLIGHT_ON));
if (on) {
user.playSound(FLASHLIGHT_OFF_SOUND, 10, 1);
} else {
user.playSound(FLASHLIGHT_ON_SOUND, 10, 1);
}
user.getStackInHand(hand).set(FLASHLIGHT_ON, !on);
return TypedActionResult.success(user.getStackInHand(hand), false);
}
}

View File

@@ -0,0 +1,6 @@
{
"item.horror_utils.flashlight": "Flashlight",
"sound.horror_utils.flashlight_on": "Flashlight Switched On",
"sound.horror_utils.flashlight_off": "Flashlight Switched Off",
"block.horror_utils.floodlight": "Floodlight"
}

View File

@@ -0,0 +1,35 @@
{
"schemaVersion": 1,
"id": "horror-utils",
"version": "${version}",
"name": "Horror-Utils",
"description": "",
"authors": [],
"contact": {},
"license": "GPL-3.0",
"icon": "assets/horror-utils/icon.png",
"environment": "*",
"entrypoints": {
"fabric-datagen": [
"org.ruffles.horrorUtils.client.HorrorUtilsDataGenerator"
],
"client": [
"org.ruffles.horrorUtils.client.HorrorUtilsClient"
],
"main": [
"org.ruffles.horrorUtils.HorrorUtils"
]
},
"mixins": [
"horror-utils.mixins.json",
{
"config": "horror-utils.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "${minecraft_version}"
}
}

View File

@@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.ruffles.horrorUtils.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}