Browse Source

Initial commit

tags/release-0.1.0
Angeline 7 months ago
commit
78b0fc7661
28 changed files with 2150 additions and 0 deletions
  1. 23
    0
      .gitignore
  2. 104
    0
      build.gradle
  3. 3
    0
      gradle.properties
  4. BIN
      gradle/wrapper/gradle-wrapper.jar
  5. 5
    0
      gradle/wrapper/gradle-wrapper.properties
  6. 172
    0
      gradlew
  7. 84
    0
      gradlew.bat
  8. 8
    0
      src/api/java/me/jellysquid/mods/phosphor/api/IChunkLighting.java
  9. 13
    0
      src/api/java/me/jellysquid/mods/phosphor/api/IChunkLightingData.java
  10. 12
    0
      src/api/java/me/jellysquid/mods/phosphor/api/ILightingEngine.java
  11. 5
    0
      src/api/java/me/jellysquid/mods/phosphor/api/ILightingEngineProvider.java
  12. 12
    0
      src/main/java/me/jellysquid/mods/phosphor/client/PhosphorProxyClient.java
  13. 26
    0
      src/main/java/me/jellysquid/mods/phosphor/common/PhosphorMod.java
  14. 7
    0
      src/main/java/me/jellysquid/mods/phosphor/common/PhosphorProxy.java
  15. 39
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinAnvilChunkLoader.java
  16. 408
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinChunk.java
  17. 38
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinChunkProviderServer.java
  18. 30
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinMinecraft.java
  19. 17
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinSPacketChunkData.java
  20. 39
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinWorld.java
  21. 50
    0
      src/main/java/me/jellysquid/mods/phosphor/common/mixins/plugins/LightingEnginePlugin.java
  22. 561
    0
      src/main/java/me/jellysquid/mods/phosphor/common/world/LightingEngine.java
  23. 403
    0
      src/main/java/me/jellysquid/mods/phosphor/common/world/LightingHooks.java
  24. 43
    0
      src/main/java/me/jellysquid/mods/phosphor/core/PhosphorFMLLoadingPlugin.java
  25. 11
    0
      src/main/java/me/jellysquid/mods/phosphor/server/PhosphorProxyServer.java
  26. 13
    0
      src/main/resources/mcmod.info
  27. 20
    0
      src/main/resources/mixins.phosphor.json
  28. 4
    0
      src/main/resources/phosphor_at.cfg

+ 23
- 0
.gitignore View File

@@ -0,0 +1,23 @@
# eclipse
bin
*.launch
.settings
.metadata
.classpath
.project

# idea
out
*.ipr
*.iws
*.iml
.idea
classes

# gradle
build
.gradle

# other
eclipse
run

+ 104
- 0
build.gradle View File

@@ -0,0 +1,104 @@
buildscript {
repositories {
jcenter()

maven { url = 'https://repo.spongepowered.org/maven' }
maven { url = 'http://files.minecraftforge.net/maven' }
}

dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT'
}
}

apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'org.spongepowered.mixin'

version = "1.0"
group = "me.jellysquid.mods.phosphor"
archivesBaseName = "phosphor"

sourceSets {
api {
java { srcDir "src/api/java" }
}
}

compileJava {
sourceCompatibility = targetCompatibility = '1.8'
}

minecraft {
version = "1.12.2-14.23.5.2768"
runDir = "run"

mappings = "stable_39"
}

repositories {
maven { url = 'https://repo.spongepowered.org/maven' }
}

configurations {
shade

compile.extendsFrom shade
}

dependencies {
shade('org.spongepowered:mixin:0.7.11-SNAPSHOT') {
transitive = false
}
}

jar {
from sourceSets.api.output

manifest.mainAttributes(
"FMLAT": "phosphor_at.cfg",
"FMLCorePlugin": "me.jellysquid.mods.phosphor.core.AetherFMLLoadingPlugin",
"TweakClass": "org.spongepowered.asm.launch.MixinTweaker",
"TweakOrder": 0,
"FMLCorePluginContainsFMLMod": true,
"ForceLoadAsMod": true
)

configurations.shade.each { dep ->
from(project.zipTree(dep)) {
rename 'LICENSE.txt', 'org/spongepowered/LICENSE.txt'

exclude 'META-INF/*.SF'
exclude 'META-INF/*.RSA'
exclude 'META-INF/*.MF'
}
}

classifier = 'universal'
}

processResources {
inputs.property "version", project.version
inputs.property "mcversion", project.minecraft.version

from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info'

expand 'version': project.version, 'mcversion': project.minecraft.version
}

from(sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
}
}


mixin {
add sourceSets.main, "mixins.phosphor.refmap.json"
}

idea {
module {
inheritOutputDirs = true
}
}

+ 3
- 0
gradle.properties View File

@@ -0,0 +1,3 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx2G

BIN
gradle/wrapper/gradle-wrapper.jar View File


+ 5
- 0
gradle/wrapper/gradle-wrapper.properties View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

+ 172
- 0
gradlew View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh

##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
echo "$*"
}

die () {
echo
echo "$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option

if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi

# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"

+ 84
- 0
gradlew.bat View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

+ 8
- 0
src/api/java/me/jellysquid/mods/phosphor/api/IChunkLighting.java View File

@@ -0,0 +1,8 @@
package me.jellysquid.mods.phosphor.api;

import net.minecraft.util.math.BlockPos;
import net.minecraft.world.EnumSkyBlock;

public interface IChunkLighting {
int getCachedLightFor(EnumSkyBlock enumSkyBlock, BlockPos pos);
}

+ 13
- 0
src/api/java/me/jellysquid/mods/phosphor/api/IChunkLightingData.java View File

@@ -0,0 +1,13 @@
package me.jellysquid.mods.phosphor.api;

public interface IChunkLightingData {
short[] getNeighborLightChecks();

void setNeighborLightChecks(short[] data);

boolean isLightInitialized();

void setLightInitialized(boolean val);

void setSkylightUpdatedPublic();
}

+ 12
- 0
src/api/java/me/jellysquid/mods/phosphor/api/ILightingEngine.java View File

@@ -0,0 +1,12 @@
package me.jellysquid.mods.phosphor.api;

import net.minecraft.util.math.BlockPos;
import net.minecraft.world.EnumSkyBlock;

public interface ILightingEngine {
void scheduleLightUpdate(EnumSkyBlock lightType, BlockPos pos);

void procLightUpdates();

void procLightUpdates(EnumSkyBlock lightType);
}

+ 5
- 0
src/api/java/me/jellysquid/mods/phosphor/api/ILightingEngineProvider.java View File

@@ -0,0 +1,5 @@
package me.jellysquid.mods.phosphor.api;

public interface ILightingEngineProvider {
ILightingEngine getLightingEngine();
}

+ 12
- 0
src/main/java/me/jellysquid/mods/phosphor/client/PhosphorProxyClient.java View File

@@ -0,0 +1,12 @@
package me.jellysquid.mods.phosphor.client;

import me.jellysquid.mods.phosphor.common.PhosphorProxy;
import net.minecraft.client.Minecraft;
import net.minecraft.util.IThreadListener;

public class PhosphorProxyClient implements PhosphorProxy {
@Override
public IThreadListener getMinecraftThread() {
return Minecraft.getMinecraft();
}
}

+ 26
- 0
src/main/java/me/jellysquid/mods/phosphor/common/PhosphorMod.java View File

@@ -0,0 +1,26 @@
package me.jellysquid.mods.phosphor.common;

import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.SidedProxy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Mod(name = PhosphorMod.MOD_NAME, modid = PhosphorMod.MOD_ID, version = PhosphorMod.MOD_VERSION)
public class PhosphorMod {

public static final String MOD_ID = "phosphor-lighting";

public static final String MOD_NAME = "Phosphor";

public static final String MOD_VERSION = "1.12.2-0.1.0";

public static final Logger LOGGER = LogManager.getLogger("AetherII");

@Mod.Instance(PhosphorMod.MOD_ID)
public static PhosphorMod INSTANCE;

@SidedProxy(clientSide = "me.jellysquid.mods.phosphor.client.PhosphorProxyClient",
serverSide = "me.jellysquid.mods.phosphor.server.PhosphorProxyServer")
public static PhosphorProxy PROXY;

}

+ 7
- 0
src/main/java/me/jellysquid/mods/phosphor/common/PhosphorProxy.java View File

@@ -0,0 +1,7 @@
package me.jellysquid.mods.phosphor.common;

import net.minecraft.util.IThreadListener;

public interface PhosphorProxy {
IThreadListener getMinecraftThread();
}

+ 39
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinAnvilChunkLoader.java View File

@@ -0,0 +1,39 @@
package me.jellysquid.mods.phosphor.common.mixins.lighting;

import me.jellysquid.mods.phosphor.api.IChunkLightingData;
import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import me.jellysquid.mods.phosphor.common.world.LightingHooks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(AnvilChunkLoader.class)
public abstract class MixinAnvilChunkLoader {
@Inject(method = "saveChunk", at = @At("HEAD"))
private void onConstructed(World world, Chunk chunkIn, CallbackInfo callbackInfo) {
((ILightingEngineProvider) world).getLightingEngine().procLightUpdates();
}

@Inject(method = "readChunkFromNBT", at = @At("RETURN"))
private void onReadChunkFromNBT(World world, NBTTagCompound compound, CallbackInfoReturnable<Chunk> cir) {
Chunk chunk = cir.getReturnValue();

LightingHooks.readNeighborLightChecksFromNBT(chunk, compound);

((IChunkLightingData) chunk).setLightInitialized(compound.getBoolean("LightPopulated"));

}

@Inject(method = "writeChunkToNBT", at = @At("RETURN"))
private void onWriteChunkToNBT(Chunk chunk, World world, NBTTagCompound compound, CallbackInfo ci) {
LightingHooks.writeNeighborLightChecksToNBT(chunk, compound);

compound.setBoolean("LightPopulated", ((IChunkLightingData) chunk).isLightInitialized());
}
}

+ 408
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinChunk.java View File

@@ -0,0 +1,408 @@
package me.jellysquid.mods.phosphor.common.mixins.lighting;

import me.jellysquid.mods.phosphor.api.IChunkLighting;
import me.jellysquid.mods.phosphor.api.IChunkLightingData;
import me.jellysquid.mods.phosphor.api.ILightingEngine;
import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import me.jellysquid.mods.phosphor.common.world.LightingHooks;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import javax.annotation.Nullable;

@Mixin(Chunk.class)
public abstract class MixinChunk implements IChunkLighting, IChunkLightingData, ILightingEngineProvider {
// === START OF SHADOWS ===

@Shadow
@Final
private ExtendedBlockStorage[] storageArrays;

@Shadow
private boolean dirty;

@Shadow
@Final
private int[] heightMap;

@Shadow
private int heightMapMinimum;

@Shadow
@Final
public int x;

@Shadow
@Final
public int z;

@Shadow
@Final
private int[] precipitationHeightMap;

@Shadow
@Final
private World world;

@Shadow
private boolean isTerrainPopulated;

@Shadow
public abstract TileEntity shadow$getTileEntity(BlockPos pos, Chunk.EnumCreateEntityType type);

@Shadow
public abstract IBlockState shadow$getBlockState(BlockPos pos);

@Shadow
protected abstract int getBlockLightOpacity(int x, int y, int z);

@Shadow
public abstract boolean canSeeSky(BlockPos pos);

// === END OF SHADOWS ===

// === HOOKS ===

@Inject(method = "getLightSubtracted", at = @At("HEAD"))
private void onGetLightSubtracted(BlockPos pos, int amount, CallbackInfoReturnable<Integer> cir) {
this.lightingEngine.procLightUpdates();
}

@Inject(method = "onLoad", at = @At("RETURN"))
private void onLoad(CallbackInfo ci) {
LightingHooks.scheduleRelightChecksForChunkBoundaries(this.world, (Chunk) (Object) this);
}

// === END OF HOOKS ===

// === REPLACEMENTS ===

/**
* @author Angeline
*/
@Overwrite
public void setLightFor(EnumSkyBlock type, BlockPos pos, int value) {
int i = pos.getX() & 15;
int j = pos.getY();
int k = pos.getZ() & 15;
ExtendedBlockStorage extendedblockstorage = this.storageArrays[j >> 4];

if (extendedblockstorage == Chunk.NULL_BLOCK_STORAGE) {
extendedblockstorage = new ExtendedBlockStorage(j >> 4 << 4, this.world.provider.hasSkyLight());
this.storageArrays[j >> 4] = extendedblockstorage;

LightingHooks.initSkylightForSection(this.world, (Chunk) (Object) this, extendedblockstorage);
}

this.dirty = true;

if (type == EnumSkyBlock.SKY) {
if (this.world.provider.hasSkyLight()) {
extendedblockstorage.setSkyLight(i, j & 15, k, value);
}
} else if (type == EnumSkyBlock.BLOCK) {
extendedblockstorage.setBlockLight(i, j & 15, k, value);
}
}

/**
* @author Angeline
*/
@Nullable
@Overwrite
public IBlockState setBlockState(BlockPos pos, IBlockState state) {
int i = pos.getX() & 15;
int j = pos.getY();
int k = pos.getZ() & 15;
int l = k << 4 | i;

if (j >= this.precipitationHeightMap[l] - 1) {
this.precipitationHeightMap[l] = -999;
}

int i1 = this.heightMap[l];
IBlockState iblockstate = this.shadow$getBlockState(pos);

if (iblockstate == state) {
return null;
} else {
Block block = state.getBlock();
Block block1 = iblockstate.getBlock();
ExtendedBlockStorage extendedblockstorage = this.storageArrays[j >> 4];

if (extendedblockstorage == Chunk.NULL_BLOCK_STORAGE) {
if (block == Blocks.AIR) {
return null;
}

extendedblockstorage = new ExtendedBlockStorage(j >> 4 << 4, this.world.provider.hasSkyLight());
this.storageArrays[j >> 4] = extendedblockstorage;
LightingHooks.initSkylightForSection(this.world, (Chunk) (Object) this, extendedblockstorage);
}

extendedblockstorage.set(i, j & 15, k, state);

//if (block1 != block)
{
if (!this.world.isRemote) {
if (block1 != block) //Only fire block breaks when the block changes.
{
block1.breakBlock(this.world, pos, iblockstate);
}
TileEntity te = this.shadow$getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK);
if (te != null && te.shouldRefresh(this.world, pos, iblockstate, state)) {
this.world.removeTileEntity(pos);
}
} else if (block1.hasTileEntity(iblockstate)) {
TileEntity te = this.shadow$getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK);
if (te != null && te.shouldRefresh(this.world, pos, iblockstate, state)) {
this.world.removeTileEntity(pos);
}
}
}

if (extendedblockstorage.get(i, j & 15, k).getBlock() != block) {
return null;
} else {
int j1 = state.getLightOpacity(this.world, pos);

if (j1 > 0) {
if (j >= i1) {
this.relightBlock(i, j + 1, k);
}
} else if (j == i1 - 1) {
this.relightBlock(i, j, k);
}

// If capturing blocks, only run block physics for TE's. Non-TE's are handled in ForgeHooks.onPlaceItemIntoWorld
if (!this.world.isRemote && block1 != block && (!this.world.captureBlockSnapshots || block.hasTileEntity(state))) {
block.onBlockAdded(this.world, pos, state);
}

if (block.hasTileEntity(state)) {
TileEntity tileentity1 = this.shadow$getTileEntity(pos, Chunk.EnumCreateEntityType.CHECK);

if (tileentity1 == null) {
tileentity1 = block.createTileEntity(this.world, state);
this.world.setTileEntity(pos, tileentity1);
}

if (tileentity1 != null) {
tileentity1.updateContainingBlockInfo();
}
}

this.dirty = true;
return iblockstate;
}
}
}

/**
* @author Angeline
*/
@Overwrite
private void relightBlock(int x, int y, int z) {
int i = this.heightMap[z << 4 | x] & 255;
int j = i;

if (y > i) {
j = y;
}

while (j > 0 && this.getBlockLightOpacity(x, j - 1, z) == 0) {
--j;
}

if (j != i) {
this.heightMap[z << 4 | x] = j;

if (this.world.provider.hasSkyLight()) {
LightingHooks.relightSkylightColumn(this.world, (Chunk) (Object) this, x, z, i, j);
}

int l1 = this.heightMap[z << 4 | x];

if (l1 < this.heightMapMinimum) {
this.heightMapMinimum = l1;
}
}
}

@Shadow
public abstract IBlockState getBlockState(int x, int y, int z);

/**
* Hook for calculating light updates only as needed. {@link MixinChunk#getCachedLightFor(EnumSkyBlock, BlockPos)} does not
* call this hook.
*
* @author Angeline
*/
@Overwrite
public int getLightFor(EnumSkyBlock type, BlockPos pos) {
this.lightingEngine.procLightUpdates(type);

return this.getCachedLightFor(type, pos);
}

/**
* @author Angeline
*/
@Overwrite
public void checkLight() {
this.isTerrainPopulated = true;

LightingHooks.checkChunkLighting((Chunk) (Object) this, this.world);
}

@Override
public int getCachedLightFor(EnumSkyBlock type, BlockPos pos) {
int i = pos.getX() & 15;
int j = pos.getY();
int k = pos.getZ() & 15;

ExtendedBlockStorage extendedblockstorage = this.storageArrays[j >> 4];

if (extendedblockstorage == Chunk.NULL_BLOCK_STORAGE) {
return this.canSeeSky(pos) ? type.defaultLightValue : 0;
} else if (type == EnumSkyBlock.SKY) {
return !this.world.provider.hasSkyLight() ? 0 : extendedblockstorage.getSkyLight(i, j & 15, k);
} else {
return type == EnumSkyBlock.BLOCK ? extendedblockstorage.getBlockLight(i, j & 15, k) : type.defaultLightValue;
}
}

/**
* @author
*/
@Overwrite
public void generateSkylightMap() {
int maxY = this.getTopFilledSegment();

this.heightMapMinimum = Integer.MAX_VALUE;

BlockPos.PooledMutableBlockPos pos = BlockPos.PooledMutableBlockPos.retain();

for (int x = 0; x < 16; ++x) {
for (int z = 0; z < 16; ++z) {
this.precipitationHeightMap[x + (z << 4)] = -999;

for (int y = maxY + 16; y > 0; --y) {
if (this.getBlockState(x, y - 1, z).getLightOpacity() != 0) {
this.heightMap[z << 4 | x] = y;

if (y < this.heightMapMinimum) {
this.heightMapMinimum = y;
}

break;
}
}

if (this.world.provider.hasSkyLight()) {
int light = 15;
int y2 = maxY + 16 - 1;

do {
int opacity = this.getBlockState(x, y2, z).getLightOpacity();

if (opacity == 0 && light != 15) {
opacity = 1;
}

light -= opacity;

if (light > 0) {
ExtendedBlockStorage extendedblockstorage = this.storageArrays[y2 >> 4];

if (extendedblockstorage != Chunk.NULL_BLOCK_STORAGE) {
extendedblockstorage.setSkyLight(x, y2 & 15, z, light);

if (this.world.isRemote) {
this.world.notifyLightSet(pos.setPos((this.x << 4) + x, y2, (this.z << 4) + z));
}
}
}

--y2;

}
while (y2 > 0 && light > 0);
}
}
}


pos.release();

this.dirty = true;
}

@Shadow
public abstract int getTopFilledSegment();

// === END OF REPLACEMENTS ===

// === INTERFACE IMPL ===

private short[] neighborLightChecks;

private ILightingEngine lightingEngine;

private boolean isLightInitialized;

@Inject(method = "<init>", at = @At("RETURN"))
private void onConstructed(CallbackInfo ci) {
this.lightingEngine = ((ILightingEngineProvider) this.world).getLightingEngine();
}

@Override
public short[] getNeighborLightChecks() {
return this.neighborLightChecks;
}

@Override
public void setNeighborLightChecks(short[] data) {
this.neighborLightChecks = data;
}

@Override
public ILightingEngine getLightingEngine() {
return this.lightingEngine;
}

@Override
public boolean isLightInitialized() {
return this.isLightInitialized;
}

@Override
public void setLightInitialized(boolean lightInitialized) {
this.isLightInitialized = lightInitialized;
}

@Shadow
protected abstract void setSkylightUpdated();

@Override
public void setSkylightUpdatedPublic() {
this.setSkylightUpdated();
}

// === END OF INTERFACE IMPL ===
}

+ 38
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinChunkProviderServer.java View File

@@ -0,0 +1,38 @@
package me.jellysquid.mods.phosphor.common.mixins.lighting;

import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import net.minecraft.world.WorldServer;
import net.minecraft.world.gen.ChunkProviderServer;
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.CallbackInfoReturnable;

import java.util.Set;

@Mixin(ChunkProviderServer.class)
public abstract class MixinChunkProviderServer {
@Shadow
@Final
public WorldServer world;

@Shadow
@Final
private Set<Long> droppedChunks;

@Inject(method = "saveChunks", at = @At("HEAD"))
private void onSaveChunks(boolean all, CallbackInfoReturnable<Boolean> cir) {
((ILightingEngineProvider) this.world).getLightingEngine().procLightUpdates();
}

@Inject(method = "tick", at = @At("HEAD"))
private void onTick(CallbackInfoReturnable<Boolean> cir) {
if (!this.world.disableLevelSaving) {
if (!this.droppedChunks.isEmpty()) {
((ILightingEngineProvider) this.world).getLightingEngine().procLightUpdates();
}
}
}
}

+ 30
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinMinecraft.java View File

@@ -0,0 +1,30 @@
package me.jellysquid.mods.phosphor.common.mixins.lighting;

import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.profiler.Profiler;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(Minecraft.class)
public abstract class MixinMinecraft {
@Shadow
@Final
public Profiler profiler;

@Shadow
public WorldClient world;

@Inject(method = "runTick", at = @At(value = "CONSTANT", args = "stringValue=levelRenderer", shift = At.Shift.BY, by = -3))
private void onRunTick(CallbackInfo ci) {
this.profiler.endStartSection("lighting");

((ILightingEngineProvider) this.world).getLightingEngine().procLightUpdates();
}

}

+ 17
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinSPacketChunkData.java View File

@@ -0,0 +1,17 @@
package me.jellysquid.mods.phosphor.common.mixins.lighting;

import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import net.minecraft.network.play.server.SPacketChunkData;
import net.minecraft.world.chunk.Chunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(SPacketChunkData.class)
public abstract class MixinSPacketChunkData {
@Inject(method = "<init>(Lnet/minecraft/world/chunk/Chunk;I)V", at = @At("HEAD"))
private void onConstructed(Chunk chunkIn, int changedSectionFilter, CallbackInfo ci) {
((ILightingEngineProvider) chunkIn).getLightingEngine().procLightUpdates();
}
}

+ 39
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/lighting/MixinWorld.java View File

@@ -0,0 +1,39 @@
package me.jellysquid.mods.phosphor.common.mixins.lighting;

import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import me.jellysquid.mods.phosphor.common.world.LightingEngine;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(World.class)
public abstract class MixinWorld implements ILightingEngineProvider {
private LightingEngine lightingEngine;

@Inject(method = "<init>", at = @At("RETURN"))
private void onConstructed(CallbackInfo ci) {
this.lightingEngine = new LightingEngine((World) (Object) this);
}

@Override
public LightingEngine getLightingEngine() {
return this.lightingEngine;
}

/**
* Postpones lighting checks to the lighting engine and returns a success value.
*
* @author Angeline
*/
@Overwrite
public boolean checkLightFor(EnumSkyBlock type, BlockPos pos) {
this.lightingEngine.scheduleLightUpdate(type, pos);

return true;
}
}

+ 50
- 0
src/main/java/me/jellysquid/mods/phosphor/common/mixins/plugins/LightingEnginePlugin.java View File

@@ -0,0 +1,50 @@
package me.jellysquid.mods.phosphor.common.mixins.plugins;

import net.minecraft.launchwrapper.Launch;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;

import java.util.List;
import java.util.Set;

public class LightingEnginePlugin implements IMixinConfigPlugin {
@Override
public void onLoad(String mixinPackage) {

}

@Override
public String getRefMapperConfig() {
if (Launch.blackboard.get("fml.deobfuscatedEnvironment") == Boolean.TRUE) {
return null;
}

return "mixins.aether.refmap.json";
}

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
return true;
}

@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {

}

@Override
public List<String> getMixins() {
return null;
}

@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {

}

@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {

}
}

+ 561
- 0
src/main/java/me/jellysquid/mods/phosphor/common/world/LightingEngine.java View File

@@ -0,0 +1,561 @@
package me.jellysquid.mods.phosphor.common.world;

import me.jellysquid.mods.phosphor.api.IChunkLighting;
import me.jellysquid.mods.phosphor.api.ILightingEngine;
import me.jellysquid.mods.phosphor.common.PhosphorMod;
import net.minecraft.block.state.IBlockState;
import net.minecraft.profiler.Profiler;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayDeque;
import java.util.Deque;

@SuppressWarnings("unused")
public class LightingEngine implements ILightingEngine {
private static final int MAX_SCHEDULED_COUNT = 1 << 22;

private static final int MAX_LIGHT = 15;

private static final Logger logger = LogManager.getLogger();

private final World world;
private final Profiler profiler;

//Layout of longs: [padding(4)] [y(8)] [x(26)] [z(26)]
private final PooledLongQueue[] queuedLightUpdates = new PooledLongQueue[EnumSkyBlock.values().length];

//Layout of longs: see above
private final PooledLongQueue[] queuedDarkenings = new PooledLongQueue[MAX_LIGHT + 1];
private final PooledLongQueue[] queuedBrightenings = new PooledLongQueue[MAX_LIGHT + 1];

//Layout of longs: [newLight(4)] [pos(60)]
private final PooledLongQueue initialBrightenings = new PooledLongQueue();
//Layout of longs: [padding(4)] [pos(60)]
private final PooledLongQueue initialDarkenings = new PooledLongQueue();

private boolean updating = false;

//Layout parameters
//Length of bit segments
private static final int
lX = 26,
lY = 8,
lZ = 26,
lL = 4;

//Bit segment shifts/positions
private static final int
sZ = 0,
sX = sZ + lZ,
sY = sX + lX,
sL = sY + lY;

//Bit segment masks
private static final long
mX = (1L << lX) - 1,
mY = (1L << lY) - 1,
mZ = (1L << lZ) - 1,
mL = (1L << lL) - 1,
mPos = (mY << sY) | (mX << sX) | (mZ << sZ);

//Bit to check whether y had overflow
private static final long yCheck = 1L << (sY + lY);

private static final long[] neighborShifts = new long[6];

static {
for (int i = 0; i < 6; ++i) {
final Vec3i offset = EnumFacing.VALUES[i].getDirectionVec();
neighborShifts[i] = ((long) offset.getY() << sY) | ((long) offset.getX() << sX) | ((long) offset.getZ() << sZ);
}
}

//Mask to extract chunk idenitfier
private static final long mChunk = ((mX >> 4) << (4 + sX)) | ((mZ >> 4) << (4 + sZ));

//Stored light type to reduce amount of method parameters
private EnumSkyBlock lightType;

//Iteration state data
//Cache position to avoid allocation of new object each time
private final BlockPos.MutableBlockPos curPos = new BlockPos.MutableBlockPos();
private PooledLongQueue curQueue;
private Chunk curChunk;
private long curChunkIdentifier;
private long curData;

//Cached data about neighboring blocks (of tempPos)
private boolean isNeighborDataValid = false;
private final Chunk[] neighborsChunk = new Chunk[6];
private final BlockPos.MutableBlockPos[] neighborsPos = new BlockPos.MutableBlockPos[6];
private final long[] neighborsLongPos = new long[6];
private final int[] neighborsLight = new int[6];

public LightingEngine(final World world) {
this.world = world;
this.profiler = world.profiler;

for (int i = 0; i < EnumSkyBlock.values().length; ++i) {
this.queuedLightUpdates[i] = new PooledLongQueue();
}

for (int i = 0; i < this.queuedDarkenings.length; ++i) {
this.queuedDarkenings[i] = new PooledLongQueue();
}

for (int i = 0; i < this.queuedBrightenings.length; ++i) {
this.queuedBrightenings[i] = new PooledLongQueue();
}

for (int i = 0; i < this.neighborsPos.length; ++i) {
this.neighborsPos[i] = new BlockPos.MutableBlockPos();
}
}

/**
* Schedules a light update for the specified light type and position to be processed later by {@link #procLightUpdates(EnumSkyBlock)}
*/
@Override
public void scheduleLightUpdate(final EnumSkyBlock lightType, final BlockPos pos) {
this.scheduleLightUpdate(lightType, posToLong(pos));
}

/**
* Schedules a light update for the specified light type and position to be processed later by {@link #procLightUpdates()}
*/
private void scheduleLightUpdate(final EnumSkyBlock lightType, final long pos) {
final PooledLongQueue queue = this.queuedLightUpdates[lightType.ordinal()];

queue.add(pos);

//make sure there are not too many queued light updates
if (queue.size() >= MAX_SCHEDULED_COUNT) {
this.procLightUpdates(lightType);
}
}

/**
* Calls {@link #procLightUpdates(EnumSkyBlock)} for both light types
*/
@Override
public void procLightUpdates() {
this.procLightUpdates(EnumSkyBlock.SKY);
this.procLightUpdates(EnumSkyBlock.BLOCK);
}

/**
* Processes light updates of the given light type
*/
@Override
public void procLightUpdates(final EnumSkyBlock lightType) {
final PooledLongQueue queue = this.queuedLightUpdates[lightType.ordinal()];

if (queue.isEmpty()) {
return;
}

//renderer accesses world unsynchronized, don't modify anything in that case
if (this.world.isRemote && !PhosphorMod.PROXY.getMinecraftThread().isCallingFromMinecraftThread()) {
return;
}

//avoid nested calls
if (this.updating) {
logger.warn("Trying to access light values during relighting");
return;
}

this.updating = true;
this.curChunkIdentifier = -1; //reset chunk cache

this.profiler.startSection("lighting");

this.lightType = lightType;

this.profiler.startSection("checking");

//process the queued updates and enqueue them for further processing
for (this.curQueue = queue; this.nextItem(); ) {
if (this.curChunk == null) {
continue;
}

final int oldLight = this.curToCachedLight();
final int newLight = this.calcNewLightFromCur();

if (oldLight < newLight) {
//don't enqueue directly for brightening in order to avoid duplicate scheduling
this.initialBrightenings.add(((long) newLight << sL) | this.curData);
} else if (oldLight > newLight) {
//don't enqueue directly for darkening in order to avoid duplicate scheduling
this.initialDarkenings.add(this.curData);
}
}

for (this.curQueue = this.initialBrightenings; this.nextItem(); ) {
final int newLight = (int) (this.curData >> sL & mL);

if (newLight > this.curToCachedLight()) {
//Sets the light to newLight to only schedule once. Clear leading bits of curData for later
this.enqueueBrightening(this.curPos, this.curData & mPos, newLight, this.curChunk);
}
}

for (this.curQueue = this.initialDarkenings; this.nextItem(); ) {
final int oldLight = this.curToCachedLight();

if (oldLight != 0) {
//Sets the light to 0 to only schedule once
this.enqueueDarkening(this.curPos, this.curData, oldLight, this.curChunk);
}
}

this.profiler.endSection();

//Iterate through enqueued updates (brightening and darkening in parallel) from brightest to darkest so that we only need to iterate once
for (int curLight = MAX_LIGHT; curLight >= 0; --curLight) {
this.profiler.startSection("darkening");

for (this.curQueue = this.queuedDarkenings[curLight]; this.nextItem(); ) {
if (this.curToCachedLight() >= curLight) //don't darken if we got brighter due to some other change
{
continue;
}

final IBlockState state = this.curToState();
final int luminosity = this.curToLuminosity(state);
final int opacity = luminosity >= MAX_LIGHT - 1 ? 1 : this.curToOpac(state); //if luminosity is high enough, opacity is irrelevant

//only darken neighbors if we indeed became darker
if (this.calcNewLightFromCur(luminosity, opacity) < curLight) {
//need to calculate new light value from neighbors IGNORING neighbors which are scheduled for darkening
int newLight = luminosity;

this.fetchNeighborDataFromCur();

for (int i = 0; i < 6; ++i) {
final Chunk nChunk = this.neighborsChunk[i];

if (nChunk == null) {
continue;
}

final int nLight = this.neighborsLight[i];

if (nLight == 0) {
continue;
}

final BlockPos.MutableBlockPos nPos = this.neighborsPos[i];

if (curLight - this.posToOpac(nPos, posToState(nPos, nChunk)) >= nLight) //schedule neighbor for darkening if we possibly light it
{
this.enqueueDarkening(nPos, this.neighborsLongPos[i], nLight, nChunk);
} else //only use for new light calculation if not
{
//if we can't darken the neighbor, no one else can (because of processing order) -> safe to let us be illuminated by it
newLight = Math.max(newLight, nLight - opacity);
}
}

//schedule brightening since light level was set to 0
this.enqueueBrighteningFromCur(newLight);
} else //we didn't become darker, so we need to re-set our initial light value (was set to 0) and notify neighbors
{
this.enqueueBrighteningFromCur(curLight); //do not spread to neighbors immediately to avoid scheduling multiple times
}
}

this.profiler.endStartSection("brightening");

for (this.curQueue = this.queuedBrightenings[curLight]; this.nextItem(); ) {
final int oldLight = this.curToCachedLight();

if (oldLight == curLight) //only process this if nothing else has happened at this position since scheduling
{
this.world.notifyLightSet(this.curPos);

if (curLight > 1) {
this.spreadLightFromCur(curLight);
}
}
}

this.profiler.endSection();
}

this.profiler.endSection();

this.updating = false;
}

/**
* Gets data for neighbors of <code>curPos</code> and saves the results into neighbor state data members. If a neighbor can't be accessed/doesn't exist, the corresponding entry in <code>neighborChunks</code> is <code>null</code> - others are not reset
*/
private void fetchNeighborDataFromCur() {
//only update if curPos was changed
if (this.isNeighborDataValid) {
return;
}

this.isNeighborDataValid = true;

for (int i = 0; i < 6; ++i) {
final long nLongPos = this.neighborsLongPos[i] = this.curData + neighborShifts[i];

if ((nLongPos & yCheck) != 0) {
this.neighborsChunk[i] = null;
continue;
}

final BlockPos.MutableBlockPos nPos = longToPos(this.neighborsPos[i], nLongPos);

final long nChunkIdentifier = nLongPos & mChunk;

final Chunk nChunk = this.neighborsChunk[i] = nChunkIdentifier == this.curChunkIdentifier ? this.curChunk : this.posToChunk(nPos);

if (nChunk != null) {
this.neighborsLight[i] = this.posToCachedLight(nPos, nChunk);
}
}
}

private int calcNewLightFromCur() {
final IBlockState state = this.curToState();
final int luminosity = this.curToLuminosity(state);

return this.calcNewLightFromCur(luminosity, luminosity >= MAX_LIGHT - 1 ? 1 : this.curToOpac(state));
}

private int calcNewLightFromCur(final int luminosity, final int opacity) {
if (luminosity >= MAX_LIGHT - opacity) {
return luminosity;
}

int newLight = luminosity;
this.fetchNeighborDataFromCur();

for (int i = 0; i < 6; ++i) {
if (this.neighborsChunk[i] == null) {
continue;
}

final int nLight = this.neighborsLight[i];

newLight = Math.max(nLight - opacity, newLight);
}

return newLight;
}

private void spreadLightFromCur(final int curLight) {
this.fetchNeighborDataFromCur();

for (int i = 0; i < 6; ++i) {
final BlockPos.MutableBlockPos nPos = this.neighborsPos[i];

final Chunk nChunk = this.neighborsChunk[i];

if (nChunk == null) {
continue;
}

final int newLight = curLight - this.posToOpac(nPos, nChunk.getBlockState(nPos));

if (newLight > this.neighborsLight[i]) {
this.enqueueBrightening(nPos, this.neighborsLongPos[i], newLight, nChunk);
}
}
}

private void enqueueBrighteningFromCur(final int newLight) {
this.enqueueBrightening(this.curPos, this.curData, newLight, this.curChunk);
}

/**
* Enqueues the pos for brightening and sets its light value to <code>newLight</code>
*/
private void enqueueBrightening(final BlockPos pos, final long longPos, final int newLight, final Chunk chunk) {
this.queuedBrightenings[newLight].add(longPos);
chunk.setLightFor(this.lightType, pos, newLight);
}

/**
* Enqueues the pos for darkening and sets its light value to 0
*/
private void enqueueDarkening(final BlockPos pos, final long longPos, final int oldLight, final Chunk chunk) {
this.queuedDarkenings[oldLight].add(longPos);
chunk.setLightFor(this.lightType, pos, 0);
}

private static BlockPos.MutableBlockPos longToPos(final BlockPos.MutableBlockPos pos, final long longPos) {
final int posX = (int) (longPos >> sX & mX) - (1 << lX - 1);
final int posY = (int) (longPos >> sY & mY);
final int posZ = (int) (longPos >> sZ & mZ) - (1 << lZ - 1);
return pos.setPos(posX, posY, posZ);
}

private static long posToLong(final BlockPos pos) {
return posToLong(pos.getX(), pos.getY(), pos.getZ());
}

private static long posToLong(final long x, final long y, final long z) {
return (y << sY) | (x + (1 << lX - 1) << sX) | (z + (1 << lZ - 1) << sZ);
}

/**
* Polls a new item from <code>curQueue</code> and fills in state data members
*
* @return If there was an item to poll
*/
private boolean nextItem() {
if (this.curQueue.isEmpty()) {
return false;
}

this.curData = this.curQueue.poll();
this.isNeighborDataValid = false;
longToPos(this.curPos, this.curData);

final long chunkIdentifier = this.curData & mChunk;

if (this.curChunkIdentifier != chunkIdentifier) {
this.curChunk = this.curToChunk();
this.curChunkIdentifier = chunkIdentifier;
}

return true;
}

private int posToCachedLight(final BlockPos.MutableBlockPos pos, final Chunk chunk) {
return ((IChunkLighting) chunk).getCachedLightFor(this.lightType, pos);
}

private int curToCachedLight() {
return this.posToCachedLight(this.curPos, this.curChunk);
}

/**
* Calculates the luminosity for <code>curPos</code>, taking into account <code>lightType</code>
*/
private int curToLuminosity(final IBlockState state) {
if (this.lightType == EnumSkyBlock.SKY) {
return this.curChunk.canSeeSky(this.curPos) ? EnumSkyBlock.SKY.defaultLightValue : 0;
}

return MathHelper.clamp(state.getLightValue(this.world, this.curPos), 0, MAX_LIGHT);
}

private int curToOpac(final IBlockState state) {
return this.posToOpac(this.curPos, state);
}

private int posToOpac(final BlockPos pos, final IBlockState state) {
return MathHelper.clamp(state.getLightOpacity(this.world, pos), 1, MAX_LIGHT);
}

private IBlockState curToState() {
return posToState(this.curPos, this.curChunk);
}

private static IBlockState posToState(final BlockPos pos, final Chunk chunk) {
return chunk.getBlockState(pos.getX(), pos.getY(), pos.getZ());
}

private Chunk posToChunk(final BlockPos pos) {
return this.world.getChunkProvider().getLoadedChunk(pos.getX() >> 4, pos.getZ() >> 4);
}

private Chunk curToChunk() {
return this.posToChunk(this.curPos);
}

//PooledLongQueue code
//Implement own queue with pooled segments to reduce allocation costs and reduce idle memory footprint

private static final int CACHED_QUEUE_SEGMENTS_COUNT = 1 << 12;
private static final int QUEUE_SEGMENT_SIZE = 1 << 10;

private final Deque<PooledLongQueueSegment> segmentPool = new ArrayDeque<>();

private PooledLongQueueSegment getLongQueueSegment() {
if (this.segmentPool.isEmpty()) {
return new PooledLongQueueSegment();
}

return this.segmentPool.pop();
}

private class PooledLongQueueSegment {
private final long[] longArray = new long[QUEUE_SEGMENT_SIZE];
private int index = 0;
private PooledLongQueueSegment next;

private void release() {
this.index = 0;
this.next = null;

if (LightingEngine.this.segmentPool.size() < CACHED_QUEUE_SEGMENTS_COUNT) {
LightingEngine.this.segmentPool.push(this);
}
}

public PooledLongQueueSegment add(final long val) {
PooledLongQueueSegment ret = this;

if (this.index == QUEUE_SEGMENT_SIZE) {
ret = this.next = LightingEngine.this.getLongQueueSegment();
}

ret.longArray[ret.index++] = val;
return ret;
}
}

private class PooledLongQueue {
private PooledLongQueueSegment cur, last;
private int size = 0;

private int index = 0;

public int size() {
return this.size;
}

public boolean isEmpty() {
return this.cur == null;
}

public void add(final long val) {
if (this.cur == null) {
this.cur = this.last = LightingEngine.this.getLongQueueSegment();
}

this.last = this.last.add(val);
++this.size;
}

public long poll() {
final long ret = this.cur.longArray[this.index++];
--this.size;

if (this.index == this.cur.index) {
this.index = 0;
final PooledLongQueueSegment next = this.cur.next;
this.cur.release();
this.cur = next;
}

return ret;
}
}
}


+ 403
- 0
src/main/java/me/jellysquid/mods/phosphor/common/world/LightingHooks.java View File

@@ -0,0 +1,403 @@
package me.jellysquid.mods.phosphor.common.world;

import me.jellysquid.mods.phosphor.api.IChunkLighting;
import me.jellysquid.mods.phosphor.api.IChunkLightingData;
import me.jellysquid.mods.phosphor.api.ILightingEngine;
import me.jellysquid.mods.phosphor.api.ILightingEngineProvider;
import me.jellysquid.mods.phosphor.common.PhosphorMod;
import net.minecraft.block.state.IBlockState;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagShort;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;

@SuppressWarnings("unused")
public class LightingHooks {
private static final EnumSkyBlock[] ENUM_SKY_BLOCK_VALUES = EnumSkyBlock.values();

private static final EnumFacing.AxisDirection[] ENUM_AXIS_DIRECTION_VALUES = EnumFacing.AxisDirection.values();

private static final int FLAG_COUNT = 32; //2 light types * 4 directions * 2 halves * (inwards + outwards)

public static void relightSkylightColumn(final World world, final Chunk chunk, final int x, final int z, final int height1, final int height2) {
final int yMin = Math.min(height1, height2);
final int yMax = Math.max(height1, height2) - 1;

final ExtendedBlockStorage[] sections = chunk.getBlockStorageArray();

final int xBase = (chunk.x << 4) + x;
final int zBase = (chunk.z << 4) + z;

scheduleRelightChecksForColumn(world, EnumSkyBlock.SKY, xBase, zBase, yMin, yMax);

if (sections[yMin >> 4] == Chunk.NULL_BLOCK_STORAGE && yMin > 0) {
world.checkLightFor(EnumSkyBlock.SKY, new BlockPos(xBase, yMin - 1, zBase));
}

short emptySections = 0;

for (int sec = yMax >> 4; sec >= yMin >> 4; --sec) {
if (sections[sec] == Chunk.NULL_BLOCK_STORAGE) {
emptySections |= 1 << sec;
}
}

if (emptySections != 0) {
for (final EnumFacing dir : EnumFacing.HORIZONTALS) {
final int xOffset = dir.getXOffset();
final int zOffset = dir.getZOffset();

final boolean neighborColumnExists =
(((x + xOffset) | (z + zOffset)) & 16) == 0
//Checks whether the position is at the specified border (the 16 bit is set for both 15+1 and 0-1)
|| world.getChunkProvider().getLoadedChunk(chunk.x + xOffset, chunk.z + zOffset) != null;

if (neighborColumnExists) {
for (int sec = yMax >> 4; sec >= yMin >> 4; --sec) {
if ((emptySections & (1 << sec)) != 0) {
scheduleRelightChecksForColumn(world, EnumSkyBlock.SKY, xBase + xOffset, zBase + zOffset, sec << 4, (sec << 4) + 15);
}
}
} else {
flagChunkBoundaryForUpdate(chunk, emptySections, EnumSkyBlock.SKY, dir, getAxisDirection(dir, x, z), EnumBoundaryFacing.OUT);
}
}
}
}

public static void scheduleRelightChecksForArea(final World world, final EnumSkyBlock lightType, final int xMin, final int yMin, final int zMin,
final int xMax, final int yMax, final int zMax) {
for (int x = xMin; x <= xMax; ++x) {
for (int z = zMin; z <= zMax; ++z) {
scheduleRelightChecksForColumn(world, lightType, x, z, yMin, yMax);
}
}
}

private static void scheduleRelightChecksForColumn(final World world, final EnumSkyBlock lightType, final int x, final int z, final int yMin, final int yMax) {
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();

for (int y = yMin; y <= yMax; ++y) {
world.checkLightFor(lightType, pos.setPos(x, y, z));
}
}

public enum EnumBoundaryFacing {
IN, OUT;

public EnumBoundaryFacing getOpposite() {
return this == IN ? OUT : IN;
}
}

public static void flagSecBoundaryForUpdate(final Chunk chunk, final BlockPos pos, final EnumSkyBlock lightType, final EnumFacing dir,
final EnumBoundaryFacing boundaryFacing) {
flagChunkBoundaryForUpdate(chunk, (short) (1 << (pos.getY() >> 4)), lightType, dir, getAxisDirection(dir, pos.getX(), pos.getZ()), boundaryFacing);
}

public static void flagChunkBoundaryForUpdate(final Chunk chunk, final short sectionMask, final EnumSkyBlock lightType, final EnumFacing dir,
final EnumFacing.AxisDirection axisDirection, final EnumBoundaryFacing boundaryFacing) {
initNeighborLightChecks(chunk);
((IChunkLightingData) chunk).getNeighborLightChecks()[getFlagIndex(lightType, dir, axisDirection, boundaryFacing)] |= sectionMask;
chunk.markDirty();
}

public static int getFlagIndex(final EnumSkyBlock lightType, final int xOffset, final int zOffset, final EnumFacing.AxisDirection axisDirection,
final EnumBoundaryFacing boundaryFacing) {
return (lightType == EnumSkyBlock.BLOCK ? 0 : 16) | ((xOffset + 1) << 2) | ((zOffset + 1) << 1) | (axisDirection.getOffset() + 1) | boundaryFacing
.ordinal();
}

public static int getFlagIndex(final EnumSkyBlock lightType, final EnumFacing dir, final EnumFacing.AxisDirection axisDirection,
final EnumBoundaryFacing boundaryFacing) {
return getFlagIndex(lightType, dir.getXOffset(), dir.getZOffset(), axisDirection, boundaryFacing);
}

private static EnumFacing.AxisDirection getAxisDirection(final EnumFacing dir, final int x, final int z) {
return ((dir.getAxis() == EnumFacing.Axis.X ? z : x) & 15) < 8 ? EnumFacing.AxisDirection.NEGATIVE : EnumFacing.AxisDirection.POSITIVE;
}

public static void scheduleRelightChecksForChunkBoundaries(final World world, final Chunk chunk) {
for (final EnumFacing dir : EnumFacing.HORIZONTALS) {
final int xOffset = dir.getXOffset();
final int zOffset = dir.getZOffset();

final Chunk nChunk = world.getChunkProvider().getLoadedChunk(chunk.x + xOffset, chunk.z + zOffset);

if (nChunk == null) {
continue;
}

for (final EnumSkyBlock lightType : ENUM_SKY_BLOCK_VALUES) {
for (final EnumFacing.AxisDirection axisDir : ENUM_AXIS_DIRECTION_VALUES) {
//Merge flags upon loading of a chunk. This ensures that all flags are always already on the IN boundary below
mergeFlags(lightType, chunk, nChunk, dir, axisDir);
mergeFlags(lightType, nChunk, chunk, dir.getOpposite(), axisDir);

//Check everything that might have been canceled due to this chunk not being loaded.
//Also, pass in chunks if already known
//The boundary to the neighbor chunk (both ways)
scheduleRelightChecksForBoundary(world, chunk, nChunk, null, lightType, xOffset, zOffset, axisDir);
scheduleRelightChecksForBoundary(world, nChunk, chunk, null, lightType, -xOffset, -zOffset, axisDir);
//The boundary to the diagonal neighbor (since the checks in that chunk were aborted if this chunk wasn't loaded, see scheduleRelightChecksForBoundary)
scheduleRelightChecksForBoundary(world, nChunk, null, chunk, lightType, (zOffset != 0 ? axisDir.getOffset() : 0),
(xOffset != 0 ? axisDir.getOffset() : 0), dir.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE ?
EnumFacing.AxisDirection.NEGATIVE :
EnumFacing.AxisDirection.POSITIVE);
}
}
}
}

private static void mergeFlags(final EnumSkyBlock lightType, final Chunk inChunk, final Chunk outChunk, final EnumFacing dir,
final EnumFacing.AxisDirection axisDir) {
IChunkLightingData outChunkLightingData = (IChunkLightingData) outChunk;

if (outChunkLightingData.getNeighborLightChecks() == null) {
return;
}

IChunkLightingData inChunkLightingData = (IChunkLightingData) inChunk;

initNeighborLightChecks(inChunk);

final int inIndex = getFlagIndex(lightType, dir, axisDir, EnumBoundaryFacing.IN);
final int outIndex = getFlagIndex(lightType, dir.getOpposite(), axisDir, EnumBoundaryFacing.OUT);

inChunkLightingData.getNeighborLightChecks()[inIndex] |= outChunkLightingData.getNeighborLightChecks()[outIndex];
//no need to call Chunk.setModified() since checks are not deleted from outChunk
}

private static void scheduleRelightChecksForBoundary(final World world, final Chunk chunk, Chunk nChunk, Chunk sChunk, final EnumSkyBlock lightType,
final int xOffset, final int zOffset, final EnumFacing.AxisDirection axisDir) {
IChunkLightingData chunkLightingData = (IChunkLightingData) chunk;

if (chunkLightingData.getNeighborLightChecks() == null) {
return;
}

final int flagIndex = getFlagIndex(lightType, xOffset, zOffset, axisDir, EnumBoundaryFacing.IN); //OUT checks from neighbor are already merged

final int flags = chunkLightingData.getNeighborLightChecks()[flagIndex];

if (flags == 0) {
return;
}

if (nChunk == null) {
nChunk = world.getChunkProvider().getLoadedChunk(chunk.x + xOffset, chunk.z + zOffset);

if (nChunk == null) {
return;
}
}

if (sChunk == null) {
sChunk = world.getChunkProvider()
.getLoadedChunk(chunk.x + (zOffset != 0 ? axisDir.getOffset() : 0), chunk.z + (xOffset != 0 ? axisDir.getOffset() : 0));

if (sChunk == null) {
return; //Cancel, since the checks in the corner columns require the corner column of sChunk
}
}

final int reverseIndex = getFlagIndex(lightType, -xOffset, -zOffset, axisDir, EnumBoundaryFacing.OUT);

chunkLightingData.getNeighborLightChecks()[flagIndex] = 0;

IChunkLightingData nChunkLightingData = (IChunkLightingData) nChunk;

if (nChunkLightingData.getNeighborLightChecks() != null) {
nChunkLightingData.getNeighborLightChecks()[reverseIndex] = 0; //Clear only now that it's clear that the checks are processed
}

chunk.markDirty();
nChunk.markDirty();

//Get the area to check
//Start in the corner...
int xMin = chunk.x << 4;
int zMin = chunk.z << 4;

//move to other side of chunk if the direction is positive
if ((xOffset | zOffset) > 0) {
xMin += 15 * xOffset;
zMin += 15 * zOffset;
}

//shift to other half if necessary (shift perpendicular to dir)
if (axisDir == EnumFacing.AxisDirection.POSITIVE) {
xMin += 8 * (zOffset & 1); //x & 1 is same as abs(x) for x=-1,0,1
zMin += 8 * (xOffset & 1);
}

//get maximal values (shift perpendicular to dir)
final int xMax = xMin + 7 * (zOffset & 1);
final int zMax = zMin + 7 * (xOffset & 1);

for (int y = 0; y < 16; ++y) {
if ((flags & (1 << y)) != 0) {
scheduleRelightChecksForArea(world, lightType, xMin, y << 4, zMin, xMax, (y << 4) + 15, zMax);
}
}
}

public static void initNeighborLightChecks(final Chunk chunk) {
IChunkLightingData lightingData = (IChunkLightingData) chunk;

if (lightingData.getNeighborLightChecks() == null) {
lightingData.setNeighborLightChecks(new short[FLAG_COUNT]);
}
}

public static final String neighborLightChecksKey = "NeighborLightChecks";

public static void writeNeighborLightChecksToNBT(final Chunk chunk, final NBTTagCompound nbt) {
short[] neighborLightChecks = ((IChunkLightingData) chunk).getNeighborLightChecks();

if (neighborLightChecks == null) {
return;
}

boolean empty = true;

final NBTTagList list = new NBTTagList();

for (final short flags : neighborLightChecks) {
list.appendTag(new NBTTagShort(flags));

if (flags != 0) {
empty = false;
}
}

if (!empty) {
nbt.setTag(neighborLightChecksKey, list);
}
}

public static void readNeighborLightChecksFromNBT(final Chunk chunk, final NBTTagCompound nbt) {
if (nbt.hasKey(neighborLightChecksKey, 9)) {
final NBTTagList list = nbt.getTagList(neighborLightChecksKey, 2);

if (list.tagCount() == FLAG_COUNT) {
initNeighborLightChecks(chunk);

short[] neighborLightChecks = ((IChunkLightingData) chunk).getNeighborLightChecks();

for (int i = 0; i < FLAG_COUNT; ++i) {
neighborLightChecks[i] = ((NBTTagShort) list.get(i)).getShort();
}
} else {
PhosphorMod.LOGGER.warn("Chunk field %s had invalid length, ignoring it (chunk coordinates: %s %s)", neighborLightChecksKey, chunk.x, chunk.z);
}
}
}

public static void initChunkLighting(final Chunk chunk, final World world) {
final int xBase = chunk.x << 4;
final int zBase = chunk.z << 4;

final BlockPos.PooledMutableBlockPos pos = BlockPos.PooledMutableBlockPos.retain(xBase, 0, zBase);

if (world.isAreaLoaded(pos.add(-16, 0, -16), pos.add(31, 255, 31), false)) {
final ExtendedBlockStorage[] extendedBlockStorage = chunk.getBlockStorageArray();

for (int j = 0; j < extendedBlockStorage.length; ++j) {
final ExtendedBlockStorage storage = extendedBlockStorage[j];

if (storage == Chunk.NULL_BLOCK_STORAGE) {
continue;
}

int yBase = j * 16;

for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
int key = storage.data.storage.getAt(y << 8 | z << 4 | x);

if (key != 0) {
IBlockState state = storage.data.palette.getBlockState(key);

if (state != null) {
int light = state.getLightValue(world, pos);

if (light > 0) {
pos.setPos(xBase + x, yBase + y, zBase + z);

world.checkLightFor(EnumSkyBlock.BLOCK, pos);
}
}
}
}
}
}
}

if (world.provider.hasSkyLight()) {
((IChunkLightingData) chunk).setSkylightUpdatedPublic();
}

((IChunkLightingData) chunk).setLightInitialized(true);
}

pos.release();
}

public static void checkChunkLighting(final Chunk chunk, final World world) {
if (!((IChunkLightingData) chunk).isLightInitialized()) {
initChunkLighting(chunk, world);
}

for (int x = -1; x <= 1; ++x) {
for (int z = -1; z <= 1; ++z) {
if (x != 0 || z != 0) {
Chunk nChunk = world.getChunkProvider().getLoadedChunk(chunk.x + x, chunk.z + z);

if (nChunk == null || !((IChunkLightingData) nChunk).isLightInitialized()) {
return;
}
}
}
}

chunk.setLightPopulated(true);
}

public static void initSkylightForSection(final World world, final Chunk chunk, final ExtendedBlockStorage section) {
if (world.provider.hasSkyLight()) {
for (int x = 0; x < 16; ++x) {
for (int z = 0; z < 16; ++z) {
if (chunk.getHeightValue(x, z) <= section.getYLocation()) {
for (int y = 0; y < 16; ++y) {
section.setSkyLight(x, y, z, EnumSkyBlock.SKY.defaultLightValue);
}
}
}
}
}
}

private static short[] getNeighborLightChecks(Chunk chunk) {
return ((IChunkLightingData) chunk).getNeighborLightChecks();
}

private static void setNeighborLightChecks(Chunk chunk, short[] table) {
((IChunkLightingData) chunk).setNeighborLightChecks(table);
}

public static int getCachedLightFor(Chunk chunk, EnumSkyBlock type, BlockPos pos) {
return ((IChunkLighting) chunk).getCachedLightFor(type, pos);
}

public static ILightingEngine getLightingEngine(World world) {
return ((ILightingEngineProvider) world).getLightingEngine();
}

}

+ 43
- 0
src/main/java/me/jellysquid/mods/phosphor/core/PhosphorFMLLoadingPlugin.java View File

@@ -0,0 +1,43 @@
package me.jellysquid.mods.phosphor.core;

import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.Mixins;

import javax.annotation.Nullable;
import java.util.Map;

@SuppressWarnings("unused")
public class PhosphorFMLLoadingPlugin implements IFMLLoadingPlugin {
public PhosphorFMLLoadingPlugin() {
MixinBootstrap.init();

Mixins.addConfiguration("mixins.phosphor.json");
}

@Override
public String[] getASMTransformerClass() {
return new String[0];
}

@Override
public String getModContainerClass() {
return null;
}