Compare commits

...
51 Commits
Author SHA1 Message Date
Glease 9bd19b9254 fix frustrum culling using wrong bounding box 2022-09-09 17:43:12 +08:00
Glease df4c54ea8d address inspections 2022-09-06 16:34:46 +08:00
くぁーりぃ e71edd50b0 Allow client to auto build multiblocks (#12) 2022-09-05 19:42:57 +08:00
iouterandGlease 1b7cee7c4e Update zh_CN.lang (#11)
Co-authored-by: Glease <4586901+Glease@users.noreply.github.com>
2022-09-03 16:14:16 +08:00
Glease 03f2c31191 make hologram lifespan and transparency level configurable. 2022-09-02 16:44:12 +08:00
Glease 9b318f68fb throttle no explicit subchannel warning 2022-09-02 15:45:13 +08:00
Glease adc91bb1dd Add javadoc for APIs (#10) 2022-08-30 23:27:18 +08:00
Glease 21198c4d39 Create pages.yml 2022-08-29 12:12:19 +08:00
Glease e70ea765f8 spotless 2022-08-29 00:49:40 +08:00
Glease 56a083ce6f ofBlocksTiered bug fix 2022-08-29 00:37:50 +08:00
Glease 7389f349db add compat to hodgepodge
turns out we just cannot call render block here since our blocks are not 1x1x1 and I definitely don't want to do one draw call for every hint particle
2022-08-25 16:09:16 +08:00
Glease 5b6b51f80c use correct brightness 2022-08-25 16:07:42 +08:00
Glease 1481b68133 use profiler 2022-08-25 15:01:42 +08:00
Glease ebf2d6f0e1 try to reduce sorting 2022-08-24 19:43:10 +08:00
Glease 7e7064e006 add some todo... 2022-08-24 12:36:48 +08:00
Glease ebcf62fd97 allow some particle to render through walls 2022-08-24 12:23:56 +08:00
Glease 74dde96905 redo holograms render to decouple it from particle system 2022-08-24 12:23:56 +08:00
Glease d73d2bffc2 provide api to mark particle as error
we will probably need to have this render through the walls, but that's a task for later
2022-08-24 11:28:26 +08:00
Glease 94055bbd6a fix color 2022-08-24 10:40:14 +08:00
Glease dc1b1fc4db spotless 2022-08-23 21:33:25 +08:00
Glease fd38dfc31f add tab, arrow keys and enter handling to make this nicer 2022-08-23 20:22:38 +08:00
Glease cb82b73e78 add some warning if subchannel isn't set explicitly 2022-08-23 19:54:36 +08:00
Glease 91d4a8022e turn on debug in dev env by default 2022-08-23 18:57:17 +08:00
Glease a12722c4b0 configurable autoplace 2022-08-23 18:56:47 +08:00
Glease b2624a3d36 unconditionally mark unsatisfied hint as red
I don't think anyone would turn this off...
2022-08-23 16:59:51 +08:00
Glease b34e901a93 channels 2022-08-23 16:55:17 +08:00
Glease e155c69b20 fix incomplete creative build 2022-08-20 19:27:33 +08:00
Glease 82622d1e15 this is nbt sensitive 2022-08-20 19:27:05 +08:00
boubou19 a168702151 Merge pull request #9 from GTNewHorizons/gtnhlib
Use ItemStackMap from GTNHLib
2022-08-14 00:25:52 +02:00
Jason Mitchell 73f113ed9b Use ItemStackMap from GTNHLib 2022-08-13 15:20:48 -07:00
Glease c19ac860d4 fix too lenient should skip check in ofBlocksTiered 2022-08-12 21:13:32 +08:00
Glease d33cdd79c4 apply spotless 2022-08-12 12:31:46 +08:00
Glease ed24f15033 migrate iterate to iterateV2 2022-08-12 12:25:03 +08:00
Glease 15c34061bc suppress even more logging 2022-08-12 12:07:30 +08:00
Glease 728f25114b suppress logging unless debug mode 2022-08-11 23:38:36 +08:00
Glease 4a8c154855 update README to reflect commit hash change 2022-08-09 11:13:33 +08:00
iouter f65010a547 Update zh_CN.lang (#8) 2022-08-08 20:47:41 +08:00
Glease 609e50428e Implement autoplace (#6) 2022-08-08 17:40:12 +08:00
Jason Mitchell e8f5cd6489 [ci skip] Migrate github actions to GTNH-Actions-Workflows 2022-07-16 10:51:12 -07:00
Glease 8de7026a00 reformat project according to .editorconfig
Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2022-07-08 17:14:18 +08:00
Glease 33d377cea7 pull in buildscript changes 2022-03-02 00:32:50 +08:00
Glease da98b476b0 fix off by one overflow 2022-03-01 23:56:04 +08:00
Martin Robertz 9dd145bc67 Merge pull request #4 from iouter/master
Update zh_CN.lang
2022-02-07 18:13:46 +01:00
iouter 83b7c35ed1 Update zh_CN.lang 2022-02-05 00:06:12 +08:00
Glease bb0bebf3a2 add debug log on multi passing check 2022-01-19 18:47:13 +08:00
Jason Mitchell a49ed1996b maven publish buildscript update 2022-01-17 23:58:46 -08:00
Martin Robertz 6d5d31f5ab Merge pull request #3 from GTNewHorizons/GTNH-Colen-patch-1
QoL fix for item description
2022-01-02 12:32:48 +01:00
GTNH-Colen 3eafe426ba QoL fix for item description
This item will now show in NEI when schematic is searched, in line with old TT schematic. The description is also more grammatically correct (removed extra caps).
2022-01-02 08:58:49 +00:00
Glease eea5227fe3 Pull my onlyIf from GT++ 2021-12-22 21:20:27 +08:00
Glease 1f520cfd73 Add localization stuff I forgot 2021-12-22 21:17:47 +08:00
Martin Robertz 8357fdc826 ignore bat files 2021-12-02 23:44:16 +01:00
92 changed files with 9469 additions and 3252 deletions

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# This is the universal Text Editor Configuration
# for all GTNewHorizons projects
# See: https://editorconfig.org/
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{bat,ini}]
end_of_line = crlf
[*.{dtd,json,info,mcmeta,md,sh,svg,xml,xsd,xsl,yaml,yml}]
indent_size = 2
-9
View File
@@ -1,9 +0,0 @@
directory="run/crash-reports"
if [ -d $directory ]; then
echo "Crash reports detected:"
cat $directory/*
exit 1
else
echo "No crash reports detected"
exit 0
fi
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# bashsupport disable=BP5006 # Global environment variables
RUNDIR="run" \
CRASH="crash-reports" \
SERVERLOG="server.log"
# enable nullglob to get 0 results when no match rather than the pattern
shopt -s nullglob
# store matches in array
crash_reports=("$RUNDIR/$CRASH/crash"*.txt)
# if array not empty there are crash_reports
if [ "${#crash_reports[@]}" -gt 0 ]; then
# get the latest crash_report from array
latest_crash_report="${crash_reports[-1]}"
{
printf 'Latest crash report detected %s:\n' "${latest_crash_report##*/}"
cat "$latest_crash_report"
} >&2
exit 1
fi
if grep --quiet --fixed-strings 'Fatal errors were detected' "$SERVERLOG"; then
{
printf 'Fatal errors detected:\n'
cat server.log
} >&2
exit 1
fi
if grep --quiet --fixed-strings 'The state engine was in incorrect state ERRORED and forced into state SERVER_STOPPED' \
"$SERVERLOG"; then
{
printf 'Server force stopped:'
cat server.log
} >&2
exit 1
fi
if ! grep --quiet --perl-regexp --only-matching '.+Done \(.+\)\! For help, type "help" or "\?"' "$SERVERLOG"; then
{
printf 'Server did not finish startup:'
cat server.log
} >&2
exit 1
fi
printf 'No crash reports detected'
exit 0
+2 -34
View File
@@ -1,5 +1,3 @@
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Build and test
@@ -11,35 +9,5 @@ on:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup the workspace
run: ./gradlew setupCIWorkspace
- name: Build the mod
run: ./gradlew build
- name: Run server for 1 minute
run: |
mkdir run
echo "eula=true" > run/eula.txt
timeout 10 ./gradlew runServer || true
- name: Test no crashes happend
run: |
chmod +x .github/scripts/test-no-crash-reports.sh
.github/scripts/test-no-crash-reports.sh
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
secrets: inherit
+47
View File
@@ -0,0 +1,47 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["gh-pages"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Generate Index
shell: bash
run: |
find . -name javadoc -type d -print > index.txt
echo feature/doc >> index.txt
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload entire repository
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
+64
View File
@@ -0,0 +1,64 @@
name: Deploy Javadoc
on:
push:
branches:
- master
- feature/doc
tags:
- '*'
permissions:
contents: write
pages: write
id-token: write
jobs:
javadoc:
concurrency: javadoc-publish
runs-on: ubuntu-latest
outputs:
result_hash: ${{ steps.deploy.outputs.hash }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-java@v2
with:
java-version: 8
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Generate Javadoc
run: ./gradlew --no-daemon spotlessCheck javadoc
- name: Deploy 🚀
id: deploy
shell: bash
run: |
mv build/docs $RUNNER_TEMP/docs_output
rm build -rf
git checkout gh-pages
mkdir -p $TARGET_FOLDER
rm -rf $TARGET_FOLDER
mv $RUNNER_TEMP/docs_output $TARGET_FOLDER -v
git add $TARGET_FOLDER
if git diff --cached --exit-code > /dev/null ; then
echo Nothing to deploy
exit 0
fi
find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' > index.txt
git add index.txt
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Deploy javadoc from ref $TARGET_FOLDER"
git push
echo "::set-output name=hash::$(git rev-parse HEAD)"
env:
TARGET_FOLDER: ${{ github.ref_name }}
COMMIT_AUTHOR: ${{ github.pusher.name }}
COMMIT_EMAIL: ${{ github.pusher.email }}
+7 -38
View File
@@ -1,45 +1,14 @@
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Release tagged build
on:
push:
tags:
- '*'
tags: [ '*' ]
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set release version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK 8
uses: actions/setup-java@v2
with:
java-version: '8'
distribution: 'adopt'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Setup the workspace
run: ./gradlew setupCIWorkspace
- name: Build the mod
run: ./gradlew build
- name: Release under current tag
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "${{ env.RELEASE_VERSION }}"
prerelease: false
title: "${{ env.RELEASE_VERSION }}"
files: build/libs/*.jar
release-tags:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
secrets: inherit
+1
View File
@@ -26,3 +26,4 @@ whitelist.json
*.ipr
*.iws
src/main/resources/mixins.*.json
*.bat
+3
View File
@@ -0,0 +1,3 @@
# Any Github changes require admin approval
/.github/** @GTNewHorizons/admin
+6 -1
View File
@@ -21,9 +21,14 @@ Originally created by TecTech authors, taken with permission. It's under MIT lic
}
```
Replace `master-SNAPSHOT` with a commit hash or a tag name (if any) of your choice to prevent unexpected upstream changes.
2. Add `required-after:structurelib;` to your `@Mod(dependencies = "..")` tring.
2. Add `required-after:structurelib;` to your `@Mod(dependencies = "..")` tring.
3. Add `structurelib` to your `mcmod.info`.
## Contribution
The project is developed using IDEA. Please manually configure the build to not delegate to gradle for textures to show up in dev.
## Current license
Due to me adding in ItemStackMap, which was derived from code from CodeChicken's LGPL mod NEI. This mod is effectively considered
licensed under LGPL in its entirety since commit ebef584b8c518f8202cfe0984fb28a6d23e52b3d or any squash commit containing it, e.g.
609e50428ee4e293f65f67f554a70b9fcfae97c4.
+24
View File
@@ -0,0 +1,24 @@
javadoc {
inputs.file "src/main/javadoc/overview.html"
include "com/gtnewhorizon/structurelib/alignment/**"
include "com/gtnewhorizon/structurelib/constructable/**"
include "com/gtnewhorizon/structurelib/structure/**"
include "com/gtnewhorizon/structurelib/util/**"
include "com/gtnewhorizon/structurelib/StructureLibAPI.java"
options {
overview "src/main/javadoc/overview.html"
links 'https://docs.oracle.com/javase/8/docs/api/'
links 'https://skmedix.github.io/ForgeJavaDocs/javadoc/forge/1.7.10-10.13.4.1614/'
}
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
}
+570 -145
View File
@@ -1,48 +1,77 @@
//version: ffe7b130f98fdfa1ac7c6ba4bd34722a55ab28d4
//version: 1659977784
/*
DO NOT CHANGE THIS FILE!
Also, you may replace this file at any time if there is an update available.
Please check https://github.com/SinTh0r4s/ExampleMod1.7.10/blob/main/build.gradle for updates.
DO NOT CHANGE THIS FILE!
Also, you may replace this file at any time if there is an update available.
Please check https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/build.gradle for updates.
*/
import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.internal.logging.text.StyledTextOutput.Style
import org.gradle.internal.logging.text.StyledTextOutputFactory
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
buildscript {
repositories {
mavenCentral()
maven {
name = "forge"
url = "https://maven.minecraftforge.net"
name 'forge'
url 'https://maven.minecraftforge.net'
}
maven {
name = "sonatype"
url = "https://oss.sonatype.org/content/repositories/snapshots/"
name 'sonatype'
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
maven {
name = "Scala CI dependencies"
url = "https://repo1.maven.org/maven2/"
name 'Scala CI dependencies'
url 'https://repo1.maven.org/maven2/'
}
maven {
name = "jitpack"
url = "https://jitpack.io"
name 'jitpack'
url 'https://jitpack.io'
}
}
dependencies {
classpath 'com.github.GTNewHorizons:ForgeGradle:1.2.4'
classpath 'com.github.GTNewHorizons:ForgeGradle:1.2.7'
}
}
plugins {
id 'java-library'
id 'idea'
id 'eclipse'
id 'scala'
id("org.ajoberstar.grgit") version("3.1.1")
id("com.github.johnrengelman.shadow") version("4.0.4")
id("com.palantir.git-version") version("0.12.3")
id 'maven-publish'
id 'org.jetbrains.kotlin.jvm' version '1.5.30' apply false
id 'org.jetbrains.kotlin.kapt' version '1.5.30' apply false
id 'com.google.devtools.ksp' version '1.5.30-1.0.0' apply false
id 'org.ajoberstar.grgit' version '4.1.1'
id 'com.github.johnrengelman.shadow' version '4.0.4'
id 'com.palantir.git-version' version '0.13.0' apply false
id 'de.undercouch.download' version '5.0.1'
id 'com.github.gmazzo.buildconfig' version '3.0.3' apply false
id 'com.diffplug.spotless' version '6.7.2' apply false
}
verifySettingsGradle()
dependencies {
implementation 'com.diffplug:blowdryer:1.6.0'
}
apply plugin: 'com.diffplug.blowdryer'
if (project.file('.git/HEAD').isFile()) {
apply plugin: 'com.palantir.git-version'
}
def out = services.get(StyledTextOutputFactory).create('an-output')
apply plugin: 'forge'
@@ -62,6 +91,13 @@ idea {
}
}
boolean disableSpotless = project.hasProperty("disableSpotless") ? project.disableSpotless.toBoolean() : false
if (!disableSpotless) {
apply plugin: 'com.diffplug.spotless'
apply from: Blowdryer.file('spotless.gradle')
}
if(JavaVersion.current() != JavaVersion.VERSION_1_8) {
throw new GradleException("This project requires Java 8, but it's running on " + JavaVersion.current())
}
@@ -87,33 +123,34 @@ checkPropertyExists("containsMixinsAndOrCoreModOnly")
checkPropertyExists("usesShadowedDependencies")
checkPropertyExists("developmentEnvironmentUserName")
def checkPropertyExists(String propertyName) {
if (project.hasProperty(propertyName) == false) {
throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/SinTh0r4s/ExampleMod1.7.10/blob/main/gradle.properties")
}
}
boolean noPublishedSources = project.hasProperty("noPublishedSources") ? project.noPublishedSources.toBoolean() : false
boolean usesMixinDebug = project.hasProperty('usesMixinDebug') ?: project.usesMixins.toBoolean()
boolean forceEnableMixins = project.hasProperty('forceEnableMixins') ? project.forceEnableMixins.toBoolean() : false
String channel = project.hasProperty('channel') ? project.channel : 'stable'
String mappingsVersion = project.hasProperty('mappingsVersion') ? project.mappingsVersion : '12'
String javaSourceDir = "src/main/java/"
String scalaSourceDir = "src/main/scala/"
String kotlinSourceDir = "src/main/kotlin/"
String targetPackageJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/")
String targetPackageScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/")
if((new File(targetPackageJava).exists() || new File(targetPackageScala).exists()) == false) {
throw new GradleException("Could not resolve \"modGroup\"! Could not find " + targetPackageJava + " or " + targetPackageScala)
String targetPackageKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/")
if(!(getFile(targetPackageJava).exists() || getFile(targetPackageScala).exists() || getFile(targetPackageKotlin).exists())) {
throw new GradleException("Could not resolve \"modGroup\"! Could not find " + targetPackageJava + " or " + targetPackageScala + " or " + targetPackageKotlin)
}
if(apiPackage) {
targetPackageJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/")
targetPackageScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/")
if((new File(targetPackageJava).exists() || new File(targetPackageScala).exists()) == false) {
throw new GradleException("Could not resolve \"apiPackage\"! Could not find " + targetPackageJava + " or " + targetPackageScala)
targetPackageKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/")
if(!(getFile(targetPackageJava).exists() || getFile(targetPackageScala).exists() || getFile(targetPackageKotlin).exists())) {
throw new GradleException("Could not resolve \"apiPackage\"! Could not find " + targetPackageJava + " or " + targetPackageScala + " or " + targetPackageKotlin)
}
}
if(accessTransformersFile) {
String targetFile = "src/main/resources/META-INF/" + accessTransformersFile
if(new File(targetFile).exists() == false) {
if(!getFile(targetFile).exists()) {
throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile)
}
}
@@ -125,15 +162,17 @@ if(usesMixins.toBoolean()) {
targetPackageJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinsPackage.toString().replaceAll("\\.", "/")
targetPackageScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinsPackage.toString().replaceAll("\\.", "/")
if((new File(targetPackageJava).exists() || new File(targetPackageScala).exists()) == false) {
throw new GradleException("Could not resolve \"mixinsPackage\"! Could not find " + targetPackageJava + " or " + targetPackageScala)
targetPackageKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinsPackage.toString().replaceAll("\\.", "/")
if(!(getFile(targetPackageJava).exists() || getFile(targetPackageScala).exists() || getFile(targetPackageKotlin).exists())) {
throw new GradleException("Could not resolve \"mixinsPackage\"! Could not find " + targetPackageJava + " or " + targetPackageScala + " or " + targetPackageKotlin)
}
String targetFileJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".java"
String targetFileScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".scala"
String targetFileScalaJava = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".java"
if((new File(targetFileJava).exists() || new File(targetFileScala).exists() || new File(targetFileScalaJava).exists()) == false) {
throw new GradleException("Could not resolve \"mixinPlugin\"! Could not find " + targetFileJava + " or " + targetFileScala + " or " + targetFileScalaJava)
String targetFileKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".kt"
if(!(getFile(targetFileJava).exists() || getFile(targetFileScala).exists() || getFile(targetFileScalaJava).exists() || getFile(targetFileKotlin).exists())) {
throw new GradleException("Could not resolve \"mixinPlugin\"! Could not find " + targetFileJava + " or " + targetFileScala + " or " + targetFileScalaJava + " or " + targetFileKotlin)
}
}
@@ -141,8 +180,9 @@ if(coreModClass) {
String targetFileJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".java"
String targetFileScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".scala"
String targetFileScalaJava = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".java"
if((new File(targetFileJava).exists() || new File(targetFileScala).exists() || new File(targetFileScalaJava).exists()) == false) {
throw new GradleException("Could not resolve \"coreModClass\"! Could not find " + targetFileJava + " or " + targetFileScala + " or " + targetFileScalaJava)
String targetFileKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".kt"
if(!(getFile(targetFileJava).exists() || getFile(targetFileScala).exists() || getFile(targetFileScalaJava).exists() || getFile(targetFileKotlin).exists())) {
throw new GradleException("Could not resolve \"coreModClass\"! Could not find " + targetFileJava + " or " + targetFileScala + " or " + targetFileScalaJava + " or " + targetFileKotlin)
}
}
@@ -155,20 +195,64 @@ configurations.all {
}
// Fix Jenkins' Git: chmod a file should not be detected as a change and append a '.dirty' to the version
'git config core.fileMode false'.execute()
// Pulls version from git tag
try {
version = minecraftVersion + "-" + gitVersion()
'git config core.fileMode false'.execute()
}
catch (Exception e) {
throw new IllegalStateException("This mod must be version controlled by Git AND the repository must provide at least one tag!");
catch (Exception ignored) {
out.style(Style.Failure).println("git isn't installed at all")
}
// Pulls version first from the VERSION env and then git tag
String identifiedVersion
String versionOverride = System.getenv("VERSION") ?: null
try {
identifiedVersion = versionOverride == null ? gitVersion() : versionOverride
}
catch (Exception ignored) {
out.style(Style.Failure).text(
'This mod must be version controlled by Git AND the repository must provide at least one tag,\n' +
'or the VERSION override must be set! ').style(Style.SuccessHeader).text('(Do NOT download from GitHub using the ZIP option, instead\n' +
'clone the repository, see ').style(Style.Info).text('https://gtnh.miraheze.org/wiki/Development').style(Style.SuccessHeader).println(' for details.)'
)
versionOverride = 'NO-GIT-TAG-SET'
identifiedVersion = versionOverride
}
version = minecraftVersion + '-' + identifiedVersion
ext {
modVersion = identifiedVersion
}
if(identifiedVersion == versionOverride) {
out.style(Style.Failure).text('Override version to ').style(Style.Identifier).text(modVersion).style(Style.Failure).println('!\7')
}
group = modGroup
archivesBaseName = modId
if(project.hasProperty("customArchiveBaseName") && customArchiveBaseName) {
archivesBaseName = customArchiveBaseName
}
else {
archivesBaseName = modId
}
def arguments = []
def jvmArguments = []
if (usesMixins.toBoolean() || forceEnableMixins) {
arguments += [
"--tweakClass org.spongepowered.asm.launch.MixinTweaker"
]
if (usesMixinDebug.toBoolean()) {
jvmArguments += [
"-Dmixin.debug.countInjections=true",
"-Dmixin.debug.verbose=true",
"-Dmixin.debug.export=true"
]
}
}
minecraft {
version = minecraftVersion + "-" + forgeVersion + "-" + minecraftVersion
runDir = "run"
version = minecraftVersion + '-' + forgeVersion + '-' + minecraftVersion
runDir = 'run'
if (replaceGradleTokenInFile) {
replaceIn replaceGradleTokenInFile
@@ -179,78 +263,101 @@ minecraft {
replace gradleTokenModName, modName
}
if(gradleTokenVersion) {
replace gradleTokenVersion, versionDetails().lastTag
replace gradleTokenVersion, modVersion
}
if(gradleTokenGroupName) {
replace gradleTokenGroupName, modGroup
}
}
clientIntellijRun {
args(arguments)
jvmArgs(jvmArguments)
if(developmentEnvironmentUserName) {
args("--username", developmentEnvironmentUserName)
}
}
serverIntellijRun {
args(arguments)
jvmArgs(jvmArguments)
}
}
if(file("addon.gradle").exists()) {
apply from: "addon.gradle"
if(file('addon.gradle').exists()) {
apply from: 'addon.gradle'
}
apply from: 'repositories.gradle'
configurations {
implementation.extendsFrom(shadowImplementation)
implementation.extendsFrom(shadowImplementation) // TODO: remove after all uses are refactored
implementation.extendsFrom(shadowCompile)
implementation.extendsFrom(shadeCompile)
}
repositories {
maven {
name = "Overmind forge repo mirror"
url = "https://gregtech.overminddl1.com/"
name 'Overmind forge repo mirror'
url 'https://gregtech.overminddl1.com/'
}
if(usesMixins.toBoolean()) {
if(usesMixins.toBoolean() || forceEnableMixins) {
maven {
name = "sponge"
url = "https://repo.spongepowered.org/repository/maven-public"
name 'sponge'
url 'https://repo.spongepowered.org/repository/maven-public'
}
maven {
url = "https://jitpack.io"
url 'https://jitpack.io'
}
}
}
dependencies {
if(usesMixins.toBoolean()) {
annotationProcessor("org.ow2.asm:asm-debug-all:5.0.3")
annotationProcessor("com.google.guava:guava:24.1.1-jre")
annotationProcessor("com.google.code.gson:gson:2.8.6")
annotationProcessor("org.spongepowered:mixin:0.8-SNAPSHOT")
annotationProcessor('org.ow2.asm:asm-debug-all:5.0.3')
annotationProcessor('com.google.guava:guava:24.1.1-jre')
annotationProcessor('com.google.code.gson:gson:2.8.6')
annotationProcessor('org.spongepowered:mixin:0.8-SNAPSHOT')
}
if(usesMixins.toBoolean() || forceEnableMixins) {
// using 0.8 to workaround a issue in 0.7 which fails mixin application
compile("org.spongepowered:mixin:0.7.11-SNAPSHOT") {
compile('com.github.GTNewHorizons:SpongePoweredMixin:0.7.12-GTNH') {
// Mixin includes a lot of dependencies that are too up-to-date
exclude module: "launchwrapper"
exclude module: "guava"
exclude module: "gson"
exclude module: "commons-io"
exclude module: "log4j-core"
exclude module: 'launchwrapper'
exclude module: 'guava'
exclude module: 'gson'
exclude module: 'commons-io'
exclude module: 'log4j-core'
}
compile("com.github.GTNewHorizons:SpongeMixins:1.3.3:dev")
compile('com.github.GTNewHorizons:SpongeMixins:1.5.0')
}
}
apply from: 'dependencies.gradle'
def mixingConfigRefMap = "mixins." + modId + ".refmap.json"
def mixingConfigRefMap = 'mixins.' + modId + '.refmap.json'
def refMap = "${tasks.compileJava.temporaryDir}" + File.separator + mixingConfigRefMap
def mixinSrg = "${tasks.reobf.temporaryDir}" + File.separator + "mixins.srg"
task generateAssets {
if(usesMixins.toBoolean()) {
new File(projectDir.toString() + "/src/main/resources/", "mixins." + modId + ".json").text = """{
if (usesMixins.toBoolean()) {
def mixinConfigFile = getFile("/src/main/resources/mixins." + modId + ".json");
if (!mixinConfigFile.exists()) {
mixinConfigFile.text = """{
"required": true,
"minVersion": "0.7.11",
"package": "${modGroup}.${mixinsPackage}",
"plugin": "${modGroup}.${mixinPlugin}",
"refmap": "${mixingConfigRefMap}",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8"
"compatibilityLevel": "JAVA_8",
"mixins": [],
"client": [],
"server": []
}
"""
}
}
}
@@ -260,16 +367,31 @@ task relocateShadowJar(type: ConfigureShadowRelocation) {
}
shadowJar {
project.configurations.shadeCompile.each { dep ->
from(project.zipTree(dep)) {
exclude 'META-INF', 'META-INF/**'
}
}
manifest {
attributes(getManifestAttributes())
}
minimize() // This will only allow shading for actually used classes
configurations = [project.configurations.shadowImplementation]
configurations = [
project.configurations.shadowImplementation,
project.configurations.shadowCompile
]
dependsOn(relocateShadowJar)
}
jar {
project.configurations.shadeCompile.each { dep ->
from(project.zipTree(dep)) {
exclude 'META-INF', 'META-INF/**'
}
}
manifest {
attributes(getManifestAttributes())
}
@@ -290,48 +412,32 @@ afterEvaluate {
if(usesMixins.toBoolean()) {
tasks.compileJava {
options.compilerArgs += [
"-AreobfSrgFile=${tasks.reobf.srg}",
"-AoutSrgFile=${mixinSrg}",
"-AoutRefMapFile=${refMap}",
// Elan: from what I understand they are just some linter configs so you get some warning on how to properly code
"-XDenableSunApiLintControl",
"-XDignore.symbol.file"
"-AreobfSrgFile=${tasks.reobf.srg}",
"-AoutSrgFile=${mixinSrg}",
"-AoutRefMapFile=${refMap}",
// Elan: from what I understand they are just some linter configs so you get some warning on how to properly code
"-XDenableSunApiLintControl",
"-XDignore.symbol.file"
]
}
}
}
runClient {
def arguments = []
if(usesMixins.toBoolean()) {
arguments += [
"--mods=../build/libs/$modId-${version}.jar",
"--tweakClass org.spongepowered.asm.launch.MixinTweaker"
]
}
if(developmentEnvironmentUserName) {
arguments += [
"--username",
developmentEnvironmentUserName
"--username",
developmentEnvironmentUserName
]
}
args(arguments)
jvmArgs(jvmArguments)
}
runServer {
def arguments = []
if (usesMixins.toBoolean()) {
arguments += [
"--mods=../build/libs/$modId-${version}.jar",
"--tweakClass org.spongepowered.asm.launch.MixinTweaker"
]
}
args(arguments)
jvmArgs(jvmArguments)
}
tasks.withType(JavaExec).configureEach {
@@ -339,39 +445,40 @@ tasks.withType(JavaExec).configureEach {
javaToolchains.launcherFor {
languageVersion = projectJavaVersion
}
)
)
}
processResources
{
// this will ensure that this task is redone when the versions change.
inputs.property "version", project.version
inputs.property "mcversion", project.minecraft.version
processResources {
// this will ensure that this task is redone when the versions change.
inputs.property "version", project.version
inputs.property "mcversion", project.minecraft.version
exclude("spotless.gradle")
// replace stuff in mcmod.info, nothing else
from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info'
// replace stuff in mcmod.info, nothing else
from(sourceSets.main.resources.srcDirs) {
include 'mcmod.info'
// replace version and mcversion
expand "minecraftVersion": project.minecraft.version,
"modVersion": versionDetails().lastTag,
"modId": modId,
"modName": modName
}
// replace modVersion and minecraftVersion
expand "minecraftVersion": project.minecraft.version,
"modVersion": modVersion,
"modId": modId,
"modName": modName
}
if(usesMixins.toBoolean()) {
from refMap
}
if(usesMixins.toBoolean()) {
from refMap
}
// copy everything else, thats not the mcmod.info
from(sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
}
}
// copy everything else that's not the mcmod.info
from(sourceSets.main.resources.srcDirs) {
exclude 'mcmod.info'
exclude 'spotless.gradle'
}
}
def getManifestAttributes() {
def manifestAttributes = [:]
if(containsMixinsAndOrCoreModOnly.toBoolean() == false && (usesMixins.toBoolean() || coreModClass)) {
if(!containsMixinsAndOrCoreModOnly.toBoolean() && (usesMixins.toBoolean() || coreModClass)) {
manifestAttributes += ["FMLCorePluginContainsFMLMod": true]
}
@@ -385,21 +492,27 @@ def getManifestAttributes() {
if(usesMixins.toBoolean()) {
manifestAttributes += [
"TweakClass" : "org.spongepowered.asm.launch.MixinTweaker",
"MixinConfigs" : "mixins." + modId + ".json",
"ForceLoadAsMod" : containsMixinsAndOrCoreModOnly.toBoolean() == false
"TweakClass" : "org.spongepowered.asm.launch.MixinTweaker",
"MixinConfigs" : "mixins." + modId + ".json",
"ForceLoadAsMod" : !containsMixinsAndOrCoreModOnly.toBoolean()
]
}
return manifestAttributes
}
task sourcesJar(type: Jar) {
from (sourceSets.main.allJava)
from (sourceSets.main.allSource)
from (file("$projectDir/LICENSE"))
getArchiveClassifier().set('sources')
}
task shadowDevJar(type: ShadowJar) {
project.configurations.shadeCompile.each { dep ->
from(project.zipTree(dep)) {
exclude 'META-INF', 'META-INF/**'
}
}
from sourceSets.main.output
getArchiveClassifier().set("dev")
@@ -408,7 +521,10 @@ task shadowDevJar(type: ShadowJar) {
}
minimize() // This will only allow shading for actually used classes
configurations = [project.configurations.shadowImplementation]
configurations = [
project.configurations.shadowImplementation,
project.configurations.shadowCompile
]
}
task relocateShadowDevJar(type: ConfigureShadowRelocation) {
@@ -423,6 +539,12 @@ task circularResolverJar(type: Jar) {
}
task devJar(type: Jar) {
project.configurations.shadeCompile.each { dep ->
from(project.zipTree(dep)) {
exclude 'META-INF', 'META-INF/**'
}
}
from sourceSets.main.output
getArchiveClassifier().set("dev")
@@ -437,12 +559,12 @@ task devJar(type: Jar) {
}
task apiJar(type: Jar) {
from (sourceSets.main.allJava) {
include modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString() + '/**'
from (sourceSets.main.allSource) {
include modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/") + '/**'
}
from (sourceSets.main.output) {
include modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString() + '/**'
include modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/") + '/**'
}
from (sourceSets.main.resources.srcDirs) {
@@ -453,48 +575,128 @@ task apiJar(type: Jar) {
}
artifacts {
archives sourcesJar
if(!noPublishedSources) {
archives sourcesJar
}
archives devJar
if(apiPackage) {
archives apiJar
}
}
// Updating
task updateBuildScript {
doLast {
if (updateBuildScript()) return
// The gradle metadata includes all of the additional deps that we disabled from POM generation (including forgeBin with no groupID),
// and isn't strictly needed with the POM so just disable it.
tasks.withType(GenerateModuleMetadata) {
enabled = false
}
print("Build script already up-to-date!")
// workaround variable hiding in pom processing
def projectConfigs = project.configurations
publishing {
publications {
maven(MavenPublication) {
from components.java
if(usesShadowedDependencies.toBoolean()) {
artifact source: shadowJar, classifier: ""
}
if(!noPublishedSources) {
artifact source: sourcesJar, classifier: "sources"
}
artifact source: usesShadowedDependencies.toBoolean() ? shadowDevJar : devJar, classifier: "dev"
if (apiPackage) {
artifact source: apiJar, classifier: "api"
}
groupId = System.getenv("ARTIFACT_GROUP_ID") ?: "com.github.GTNewHorizons"
artifactId = System.getenv("ARTIFACT_ID") ?: project.name
// Using the identified version, not project.version as it has the prepended 1.7.10
version = System.getenv("RELEASE_VERSION") ?: identifiedVersion
// remove extra garbage from minecraft and minecraftDeps configuration
pom.withXml {
def badArtifacts = [:].withDefault {[] as Set<String>}
for (configuration in [
projectConfigs.minecraft,
projectConfigs.minecraftDeps
]) {
for (dependency in configuration.allDependencies) {
badArtifacts[dependency.group == null ? "" : dependency.group] += dependency.name
}
}
// example for specifying extra stuff to ignore
// badArtifacts["org.example.group"] += "artifactName"
Node pomNode = asNode()
pomNode.dependencies.'*'.findAll() {
badArtifacts[it.groupId.text()].contains(it.artifactId.text())
}.each() {
it.parent().remove(it)
}
}
}
}
repositories {
maven {
url = "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases"
credentials {
username = System.getenv("MAVEN_USER") ?: "NONE"
password = System.getenv("MAVEN_PASSWORD") ?: "NONE"
}
}
}
}
if (isNewBuildScriptVersionAvailable()) {
// Updating
task updateBuildScript {
doLast {
if (performBuildScriptUpdate(projectDir.toString())) return
print("Build script already up-to-date!")
}
}
if (!project.getGradle().startParameter.isOffline() && isNewBuildScriptVersionAvailable(projectDir.toString())) {
if (autoUpdateBuildScript.toBoolean()) {
updateBuildScript()
performBuildScriptUpdate(projectDir.toString())
} else {
println("Build script update available! Run 'gradle updateBuildScript'")
out.style(Style.SuccessHeader).println("Build script update available! Run 'gradle updateBuildScript'")
}
}
static URL availableBuildScriptUrl() {
new URL("https://raw.githubusercontent.com/SinTh0r4s/ExampleMod1.7.10/main/build.gradle")
new URL("https://raw.githubusercontent.com/GTNewHorizons/ExampleMod1.7.10/main/build.gradle")
}
static URL exampleSettingsGradleUrl() {
new URL("https://raw.githubusercontent.com/GTNewHorizons/ExampleMod1.7.10/main/settings.gradle.example")
}
static boolean updateBuildScript() {
if (isNewBuildScriptVersionAvailable()) {
def buildscriptFile = new File("build.gradle")
def verifySettingsGradle() {
def settingsFile = getFile("settings.gradle")
if (!settingsFile.exists()) {
println("Downloading default settings.gradle")
exampleSettingsGradleUrl().withInputStream { i -> settingsFile.withOutputStream { it << i } }
throw new GradleException("Settings.gradle has been updated, please re-run task.")
}
}
boolean performBuildScriptUpdate(String projectDir) {
if (isNewBuildScriptVersionAvailable(projectDir)) {
def buildscriptFile = getFile("build.gradle")
availableBuildScriptUrl().withInputStream { i -> buildscriptFile.withOutputStream { it << i } }
print("Build script updated. Please REIMPORT the project or RESTART your IDE!")
out.style(Style.Success).print("Build script updated. Please REIMPORT the project or RESTART your IDE!")
verifySettingsGradle()
return true
}
return false
}
static boolean isNewBuildScriptVersionAvailable() {
boolean isNewBuildScriptVersionAvailable(String projectDir) {
Map parameters = ["connectTimeout": 2000, "readTimeout": 2000]
String currentBuildScript = new File("build.gradle").getText()
String currentBuildScript = getFile("build.gradle").getText()
String currentBuildScriptHash = getVersionHash(currentBuildScript)
String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText()
String availableBuildScriptHash = getVersionHash(availableBuildScript)
@@ -515,3 +717,226 @@ configure(updateBuildScript) {
group = 'forgegradle'
description = 'Updates the build script to the latest version'
}
// Parameter Deobfuscation
task deobfParams {
doLast {
String mcpDir = "$project.gradle.gradleUserHomeDir/caches/minecraft/de/oceanlabs/mcp/mcp_$channel/$mappingsVersion"
String mcpZIP = "$mcpDir/mcp_$channel-$mappingsVersion-${minecraftVersion}.zip"
String paramsCSV = "$mcpDir/params.csv"
download.run {
src "https://maven.minecraftforge.net/de/oceanlabs/mcp/mcp_$channel/$mappingsVersion-$minecraftVersion/mcp_$channel-$mappingsVersion-${minecraftVersion}.zip"
dest mcpZIP
overwrite false
}
if(!file(paramsCSV).exists()) {
println("Extracting MCP archive ...")
unzip(mcpZIP, mcpDir)
}
println("Parsing params.csv ...")
Map<String, String> params = new HashMap<>()
Files.lines(Paths.get(paramsCSV)).forEach{line ->
String[] cells = line.split(",")
if(cells.length > 2 && cells[0].matches("p_i?\\d+_\\d+_")) {
params.put(cells[0], cells[1])
}
}
out.style(Style.Success).println("Modified ${replaceParams(file("$projectDir/src/main/java"), params)} files!")
out.style(Style.Failure).println("Don't forget to verify that the code still works as before!\n It could be broken due to duplicate variables existing now\n or parameters taking priority over other variables.")
}
}
static int replaceParams(File file, Map<String, String> params) {
int fileCount = 0
if(file.isDirectory()) {
for(File f : file.listFiles()) {
fileCount += replaceParams(f, params)
}
return fileCount
}
println("Visiting ${file.getName()} ...")
try {
String content = new String(Files.readAllBytes(file.toPath()))
int hash = content.hashCode()
params.forEach{key, value ->
content = content.replaceAll(key, value)
}
if(hash != content.hashCode()) {
Files.write(file.toPath(), content.getBytes("UTF-8"))
return 1
}
} catch(Exception e) {
e.printStackTrace()
}
return 0
}
// Credit: bitsnaps (https://gist.github.com/bitsnaps/00947f2dce66f4bbdabc67d7e7b33681)
static unzip(String zipFileName, String outputDir) {
byte[] buffer = new byte[16384]
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName))
ZipEntry zipEntry = zis.getNextEntry()
while (zipEntry != null) {
File newFile = new File(outputDir + File.separator, zipEntry.name)
if (zipEntry.isDirectory()) {
if (!newFile.isDirectory() && !newFile.mkdirs()) {
throw new IOException("Failed to create directory $newFile")
}
} else {
// fix for Windows-created archives
File parent = newFile.parentFile
if (!parent.isDirectory() && !parent.mkdirs()) {
throw new IOException("Failed to create directory $parent")
}
// write file content
FileOutputStream fos = new FileOutputStream(newFile)
int len = 0
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len)
}
fos.close()
}
zipEntry = zis.getNextEntry()
}
zis.closeEntry()
zis.close()
}
configure(deobfParams) {
group = 'forgegradle'
description = 'Rename all obfuscated parameter names inherited from Minecraft classes'
}
// Dependency Deobfuscation
def deobf(String sourceURL) {
try {
URL url = new URL(sourceURL)
String fileName = url.getFile()
//get rid of directories:
int lastSlash = fileName.lastIndexOf("/")
if(lastSlash > 0) {
fileName = fileName.substring(lastSlash + 1)
}
//get rid of extension:
if(fileName.endsWith(".jar") || fileName.endsWith(".litemod")) {
fileName = fileName.substring(0, fileName.lastIndexOf("."))
}
String hostName = url.getHost()
if(hostName.startsWith("www.")) {
hostName = hostName.substring(4)
}
List parts = Arrays.asList(hostName.split("\\."))
Collections.reverse(parts)
hostName = String.join(".", parts)
return deobf(sourceURL, "$hostName/$fileName")
} catch(Exception e) {
return deobf(sourceURL, "deobf/${sourceURL.hashCode()}")
}
}
// The method above is to be preferred. Use this method if the filename is not at the end of the URL.
def deobf(String sourceURL, String rawFileName) {
String bon2Version = "2.5.1"
String fileName = URLDecoder.decode(rawFileName, "UTF-8")
String cacheDir = "$project.gradle.gradleUserHomeDir/caches"
String bon2Dir = "$cacheDir/forge_gradle/deobf"
String bon2File = "$bon2Dir/BON2-${bon2Version}.jar"
String obfFile = "$cacheDir/modules-2/files-2.1/${fileName}.jar"
String deobfFile = "$cacheDir/modules-2/files-2.1/${fileName}-deobf.jar"
if(file(deobfFile).exists()) {
return files(deobfFile)
}
String mappingsVer
String remoteMappings = project.hasProperty('remoteMappings') ? project.remoteMappings : 'https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/'
if(remoteMappings) {
String id = "${forgeVersion.split("\\.")[3]}-$minecraftVersion"
String mappingsZIP = "$cacheDir/forge_gradle/maven_downloader/de/oceanlabs/mcp/mcp_snapshot_nodoc/$id/mcp_snapshot_nodoc-${id}.zip"
zipMappings(mappingsZIP, remoteMappings, bon2Dir)
mappingsVer = "snapshot_$id"
} else {
mappingsVer = "${channel}_$mappingsVersion"
}
download.run {
src "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases/com/github/parker8283/BON2/$bon2Version-CUSTOM/BON2-$bon2Version-CUSTOM-all.jar"
dest bon2File
quiet true
overwrite false
}
download.run {
src sourceURL
dest obfFile
quiet true
overwrite false
}
exec {
commandLine 'java', '-jar', bon2File, '--inputJar', obfFile, '--outputJar', deobfFile, '--mcVer', minecraftVersion, '--mappingsVer', mappingsVer, '--notch'
workingDir bon2Dir
standardOutput = new FileOutputStream("${deobfFile}.log")
}
return files(deobfFile)
}
def zipMappings(String zipPath, String url, String bon2Dir) {
File zipFile = new File(zipPath)
if(zipFile.exists()) {
return
}
String fieldsCache = "$bon2Dir/data/fields.csv"
String methodsCache = "$bon2Dir/data/methods.csv"
download.run {
src "${url}fields.csv"
dest fieldsCache
quiet true
}
download.run {
src "${url}methods.csv"
dest methodsCache
quiet true
}
zipFile.getParentFile().mkdirs()
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))
zos.putNextEntry(new ZipEntry("fields.csv"))
Files.copy(Paths.get(fieldsCache), zos)
zos.closeEntry()
zos.putNextEntry(new ZipEntry("methods.csv"))
Files.copy(Paths.get(methodsCache), zos)
zos.closeEntry()
zos.close()
}
// Helper methods
def checkPropertyExists(String propertyName) {
if (!project.hasProperty(propertyName)) {
throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/gradle.properties")
}
}
def getFile(String relativePath) {
return new File(projectDir, relativePath)
}
+2 -1
View File
@@ -1,5 +1,6 @@
// Add your dependencies here
dependencies {
compile("com.github.GTNewHorizons:GTNHLib:0.0.1:dev")
runtime("com.github.GTNewHorizons:NotEnoughItems:2.2.20-GTNH:dev")
}
+4 -1
View File
@@ -1,5 +1,8 @@
// Add any additional repositiroes for your dependencies here
repositories {
maven {
name = "GTNH"
url = "http://jenkins.usrv.eu:8081/nexus/content/groups/public/"
}
}
+10
View File
@@ -0,0 +1,10 @@
plugins {
id 'com.diffplug.blowdryerSetup' version '1.6.0'
}
apply plugin: 'com.diffplug.blowdryerSetup'
blowdryerSetup {
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.1.4')
//devLocal '.' // Use this when testing config updates locally
}
@@ -0,0 +1,521 @@
package com.gtnewhorizon.structurelib;
import static com.gtnewhorizon.structurelib.StructureLib.RANDOM;
import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX;
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import java.util.*;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.culling.Frustrum;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.profiler.Profiler;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.lwjgl.opengl.GL11;
public class ClientProxy extends CommonProxy {
private static final short[] RGBA_NO_TINT = {255, 255, 255, 255};
private static final short[] RGBA_RED_TINT = {255, 128, 128, 0};
private static final Map<HintParticleInfo, HintGroup> allHints = new HashMap<>();
/**
* All batches of hints.
*/
private static final List<HintGroup> allGroups = new ArrayList<>();
public static int HOLOGRAM_LIFETIME = 200;
/**
* Current batch of hints. Belongs to the same logical multiblock.
*/
private static HintGroup currentHints;
/**
* Collection of all hints. Sorted from farthest (index 0) to nearest (index size - 1)
* <p>
* 10k should be more than enough, if not a bit wasteful
*/
private static final List<HintParticleInfo> allHintsForRender = new ArrayList<>(10000);
/**
* If the diff to current player position is too great sort the allHints.
* Initial value is very far off below y=0 but not huge enough to make a NaN.
* <p>
* We are using this supposedly immutable object as a mutable object here.
* So be aware.
*/
private static final Vec3 lastPlayerPos = Vec3.createVectorHelper(0, -1e30, 0);
/**
* if true rebuild the allHints list from all hintOwners (and sort it)
*/
private static boolean allHintsDirty = false;
/**
* counter keeping track of how many renderThrough hint particle we have. enable optimization if no renderThrough
*/
private static int renderThrough;
/**
* A throttle map for local player. Integrated server will use the throttle map in super class instead.
*/
private static final Map<Object, Long> localThrottleMap = new HashMap<>();
@Override
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
ensureHinting();
HintParticleInfo info = new HintParticleInfo(w, x, y, z, icons, RGBa);
// check and remove colliding holograms
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
HintGroup dupe = allHints.get(info);
if (dupe != null && dupe != currentHints) {
allGroups.remove(dupe);
removeGroup(dupe);
}
}
allHints.put(info, currentHints);
currentHints.getHints().add(info);
allHintsDirty = true;
EntityFX particle = new WeightlessParticleFX(
w,
x + RANDOM.nextFloat() * 0.5F,
y + RANDOM.nextFloat() * 0.5F,
z + RANDOM.nextFloat() * 0.5F,
0,
0,
0);
particle.setRBGColorF(0, 0.6F * RANDOM.nextFloat(), 0.8f);
Minecraft.getMinecraft().effectRenderer.addEffect(particle);
}
@Override
public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), RGBa);
}
@Override
public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
hintParticleTinted(w, x, y, z, icons, RGBA_NO_TINT);
}
@Override
public void hintParticle(World w, int x, int y, int z, Block block, int meta) {
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), RGBA_NO_TINT);
}
@Override
public boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] rgBa) {
if (player instanceof EntityPlayerMP) return super.updateHintParticleTint(player, w, x, y, z, rgBa);
if (player != getCurrentPlayer()) return false; // how?
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
if (hint == null) {
return false;
}
hint.setTint(rgBa);
return true;
}
private static HintParticleInfo getHintParticleInfo(World w, int x, int y, int z) {
HintParticleInfo info = new HintParticleInfo(w, x, y, z, null, null);
HintGroup existing = allHints.get(info);
if (existing != null) {
for (HintParticleInfo hint : existing.getHints()) {
if (hint.equals(info)) {
return hint;
}
}
}
return null;
}
@Override
public boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
if (player instanceof EntityPlayerMP) return super.markHintParticleError(player, w, x, y, z);
if (player != getCurrentPlayer()) return false; // how?
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
if (hint == null) {
return false;
}
hint.setTint(RGBA_RED_TINT);
hint.setRenderThrough();
return true;
}
@Override
public void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend) {
if (player instanceof EntityPlayerMP)
super.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend);
else if (player != null)
addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, localThrottleMap);
}
static void removeGroup(HintGroup group) {
for (HintParticleInfo hintParticleInfo : group.getHints()) {
allHints.remove(hintParticleInfo);
if (hintParticleInfo.renderThrough) renderThrough--;
}
allHintsDirty = true;
}
private static IIcon[] createIIconFromBlock(Block block, int meta) {
IIcon[] ret = new IIcon[6];
for (int i = 0; i < 6; i++) {
ret[i] = block.getIcon(i, meta);
}
return ret;
}
@Override
public void addClientSideChatMessages(String... messages) {
GuiNewChat chat = Minecraft.getMinecraft().ingameGUI.getChatGUI();
for (String s : messages) {
chat.printChatMessage(new ChatComponentText(s));
}
}
@Override
public EntityPlayer getCurrentPlayer() {
return Minecraft.getMinecraft().thePlayer;
}
@Override
public boolean isCurrentPlayer(EntityPlayer player) {
return player == Minecraft.getMinecraft().thePlayer;
}
@Override
public void startHinting(World w) {
if (!w.isRemote) return;
if (currentHints != null) endHinting(w);
currentHints = new HintGroup();
}
private void ensureHinting() {
if (currentHints == null) currentHints = new HintGroup();
}
@Override
public void endHinting(World w) {
if (!w.isRemote || currentHints == null) return;
while (!allGroups.isEmpty() && allGroups.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
allGroups.remove(0);
}
if (!currentHints.getHints().isEmpty()) allGroups.add(currentHints);
// we use the player existence time here as some worlds don't really advance the time ticker
currentHints.setCreationTime(getCurrentPlayer().ticksExisted);
currentHints = null;
}
@Override
public long getOverworldTime() {
// there is no overworld, better just hope current world time is ok...
return Minecraft.getMinecraft().theWorld.getTotalWorldTime();
}
@Override
public void uploadChannels(ItemStack trigger) {
StructureLib.net.sendToServer(new SetChannelDataMessage(trigger));
}
@Override
public void preInit(FMLPreInitializationEvent e) {
FMLCommonHandler.instance().bus().register(new FMLEventHandler());
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
}
static void markTextureUsed(IIcon icon) {
if (StructureLib.COMPAT instanceof IStructureCompat)
((IStructureCompat) StructureLib.COMPAT).markTextureUsed(icon);
}
private static class HintGroup {
private final List<HintParticleInfo> hints = new LinkedList<>();
private int creationTime = -1;
public List<HintParticleInfo> getHints() {
return hints;
}
public void setCreationTime(int creationTime) {
this.creationTime = creationTime;
}
public int getCreationTime() {
return creationTime;
}
}
private static class HintParticleInfo {
private final World w;
// these are the block coordinate for e.g. w.getBlock()
private final int x, y, z;
// these are the lower bounds for rendering. the upper bound would be X + size
// currently size is fixed to be 0.5
// it is not made into a constants because this will allow for easy changing during debug
private final double X, Y, Z;
private final IIcon[] icons;
private short[] tint;
private boolean renderThrough;
private final long creationTime = System.currentTimeMillis(); // use tick time instead maybe
public HintParticleInfo(World w, int x, int y, int z, IIcon[] icons, short[] tint) {
this.w = w;
this.x = x;
this.y = y;
this.z = z;
X = x + 0.25;
Y = y + 0.25;
Z = z + 0.25;
this.icons = icons;
this.tint = tint;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof HintParticleInfo)) return false;
HintParticleInfo that = (HintParticleInfo) o;
return x == that.x && y == that.y && z == that.z;
}
public void setTint(short[] tint) {
this.tint = tint;
}
public void setRenderThrough() {
if (!this.renderThrough) {
ClientProxy.renderThrough += 1;
allHintsDirty = true;
}
this.renderThrough = true;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
result = 31 * result + z;
return result;
}
public boolean isInFrustrum(Frustrum frustrum) {
return frustrum.isBoxInFrustum(X, Y, Z, X + 0.5, Y + 0.5, Z + 0.5);
}
public void draw(Tessellator tes) {
double size = 0.5;
int brightness = w.blockExists(x, 0, z) ? w.getLightBrightnessForSkyBlocks(x, y, z, 0) : 0;
tes.setBrightness(brightness);
tes.setColorRGBA(
(int) (tint[0] * .9F),
(int) (tint[1] * .95F),
(int) (tint[2] * 1F),
ConfigurationHandler.INSTANCE.getHintTransparency());
for (int i = 0; i < 6; i++) {
if (icons[i] == null) continue;
markTextureUsed(icons[i]);
double u = icons[i].getMinU();
double U = icons[i].getMaxU();
double v = icons[i].getMinV();
double V = icons[i].getMaxV();
switch (i) { // {DOWN, UP, NORTH, SOUTH, WEST, EAST}
case 0:
tes.addVertexWithUV(X, Y, Z + size, u, V);
tes.addVertexWithUV(X, Y, Z, u, v);
tes.addVertexWithUV(X + size, Y, Z, U, v);
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
break;
case 1:
tes.addVertexWithUV(X, Y + size, Z, u, v);
tes.addVertexWithUV(X, Y + size, Z + size, u, V);
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
break;
case 2:
tes.addVertexWithUV(X, Y, Z, U, V);
tes.addVertexWithUV(X, Y + size, Z, U, v);
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
tes.addVertexWithUV(X + size, Y, Z, u, V);
break;
case 3:
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
tes.addVertexWithUV(X + size, Y + size, Z + size, U, v);
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
tes.addVertexWithUV(X, Y, Z + size, u, V);
break;
case 4:
tes.addVertexWithUV(X, Y, Z + size, U, V);
tes.addVertexWithUV(X, Y + size, Z + size, U, v);
tes.addVertexWithUV(X, Y + size, Z, u, v);
tes.addVertexWithUV(X, Y, Z, u, V);
break;
case 5:
tes.addVertexWithUV(X + size, Y, Z, U, V);
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
break;
}
}
}
public long getCreationTime() {
return creationTime;
}
public double getSquareDistanceTo(Vec3 point) {
return point.squareDistanceTo(X, Y, Z);
}
}
public static class FMLEventHandler {
private void resetPlayerLocation() {
lastPlayerPos.xCoord = Minecraft.getMinecraft().thePlayer.posX;
lastPlayerPos.yCoord = Minecraft.getMinecraft().thePlayer.posY;
lastPlayerPos.zCoord = Minecraft.getMinecraft().thePlayer.posZ;
}
@SubscribeEvent
public void onClientTick(ClientTickEvent e) {
if (e.phase == Phase.END && Minecraft.getMinecraft().thePlayer != null) {
Vec3 playerPos = Minecraft.getMinecraft().thePlayer.getPosition(1);
boolean sortRequired = false;
// The allGroups is implicitly sorted by creationTime
// here we exploit this ordering to reduce iteration size
int deadline = Minecraft.getMinecraft().thePlayer.ticksExisted
- ConfigurationHandler.INSTANCE.getHintLifespan();
int i;
for (i = 0; i < allGroups.size(); i++) {
if (allGroups.get(i).getCreationTime() > deadline) {
break;
}
}
if (i != 0) {
sortRequired = true;
// clear this block and go no further
List<HintGroup> toRemove = allGroups.subList(0, i);
toRemove.forEach(ClientProxy::removeGroup);
toRemove.clear();
}
if (allHintsDirty) {
allHintsForRender.clear();
// we need greatly efficient sequential access and mildly efficient sorting
// rebuild is sort of rare, and it's expected to lag anyway
// so no min-heap here
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
sortRequired = true;
}
if (renderThrough > 0 && (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2)) {
// only sort if there is anything that need depth test disabled
// only redo sort if player moved some distance
// default is 0.1 block
// if there was a full rebuild, go sort it as well
allHintsForRender.sort(Comparator.comparingDouble(info -> info.getSquareDistanceTo(playerPos)));
resetPlayerLocation();
}
}
}
}
public static class ForgeEventHandler {
@SubscribeEvent
public void onWorldLoad(WorldEvent.Load e) {
if (e.world.isRemote) {
// flush hints. we are in a different world now.
allHintsForRender.clear();
allGroups.clear();
lastPlayerPos.yCoord = -1e30;
renderThrough = 0;
// clear throttles. hopefully a world switch is enough long as a cool down.
localThrottleMap.clear();
}
}
@SubscribeEvent
public void onRenderWorldLast(RenderWorldLastEvent e) {
if (allHintsForRender.isEmpty()) return;
// seriously, I'm not a OpenGL expert, so I'm probably doing a lot of very stupid stuff here.
// Please consider contributing patches if you find something to optimize.
Profiler p = Minecraft.getMinecraft().mcProfiler;
p.startSection("HintParticle");
p.startSection("Prepare");
Frustrum frustrum = new Frustrum();
Entity entitylivingbase = Minecraft.getMinecraft().renderViewEntity;
double d0 = entitylivingbase.lastTickPosX
+ (entitylivingbase.posX - entitylivingbase.lastTickPosX) * e.partialTicks;
double d1 = entitylivingbase.lastTickPosY
+ (entitylivingbase.posY - entitylivingbase.lastTickPosY) * e.partialTicks;
double d2 = entitylivingbase.lastTickPosZ
+ (entitylivingbase.posZ - entitylivingbase.lastTickPosZ) * e.partialTicks;
frustrum.setPosition(d0, d1, d2);
GL11.glPushMatrix();
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT); // TODO figure out original states
// we need the back facing rendered because the thing is transparent
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glEnable(GL11.GL_BLEND); // enable blend so it is transparent
GL11.glBlendFunc(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA);
// depth test begin as enabled
boolean renderThrough = false;
Minecraft.getMinecraft().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
GL11.glTranslated(-RenderManager.renderPosX, -RenderManager.renderPosY, -RenderManager.renderPosZ);
Tessellator tes = Tessellator.instance;
tes.startDrawingQuads();
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(); i < allHintsForRenderSize; i++) {
HintParticleInfo hint = allHintsForRender.get(i);
if (!hint.isInFrustrum(frustrum)) continue;
if (renderThrough != hint.renderThrough) {
if (i > 0) {
p.endStartSection("Draw");
tes.draw();
tes.startDrawingQuads();
p.endStartSection("Prepare");
}
if (hint.renderThrough) GL11.glDisable(GL11.GL_DEPTH_TEST);
else GL11.glEnable(GL11.GL_DEPTH_TEST);
renderThrough = hint.renderThrough;
}
hint.draw(tes);
}
p.endStartSection("Draw");
tes.draw();
p.endSection();
GL11.glPopAttrib();
GL11.glPopMatrix();
p.endSection();
}
}
}
@@ -0,0 +1,102 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
public class CommonProxy {
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {}
public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {}
public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {}
public void hintParticle(World w, int x, int y, int z, Block block, int meta) {}
public boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] rgBa) {
if (player instanceof EntityPlayerMP) { // just in case
StructureLib.net.sendTo(
new UpdateHintParticleMessage(x, (short) y, z, rgBa[0], rgBa[1], rgBa[2], rgBa[3]),
(EntityPlayerMP) player);
return true;
} else {
return false;
}
}
public EntityPlayer getCurrentPlayer() {
return null;
}
public boolean isCurrentPlayer(EntityPlayer player) {
return false;
}
public void addClientSideChatMessages(String... messages) {}
public void startHinting(World w) {}
public void endHinting(World w) {}
public void preInit(FMLPreInitializationEvent e) {}
public long getOverworldTime() {
return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime();
}
public void uploadChannels(ItemStack trigger) {}
public boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
if (player instanceof EntityPlayerMP) { // just in case
StructureLib.net.sendTo(new ErrorHintParticleMessage(x, (short) y, z), (EntityPlayerMP) player);
return true;
} else {
return false;
}
}
private final Map<EntityPlayerMP, Map<Object, Long>> throttleMap = new WeakHashMap<>();
public void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend) {
if (player instanceof EntityPlayerMP) {
Map<Object, Long> submap = throttleMap.computeIfAbsent((EntityPlayerMP) player, p -> new HashMap<>());
addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, submap);
}
}
protected static void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend,
Map<Object, Long> submap) {
long now = System.currentTimeMillis();
Long old;
if (forceUpdateLastSend) {
old = submap.put(throttleKey, now);
} else {
old = submap.get(throttleKey);
}
if (old == null || now - old >= intervalRequired) {
player.addChatComponentMessage(text);
if (!forceUpdateLastSend) submap.put(throttleKey, now);
}
}
}
@@ -3,19 +3,22 @@ package com.gtnewhorizon.structurelib;
import cpw.mods.fml.client.event.ConfigChangedEvent;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import java.io.File;
import java.util.Map;
import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import java.io.File;
import java.util.Map;
public enum ConfigurationHandler {
INSTANCE;
private Configuration config;
private int maxCoexistingHologram;
private boolean removeCollidingHologram;
private int hintLifespan;
private int hintTransparency;
private int autoPlaceBudget;
private int autoPlaceInterval;
ConfigurationHandler() {
FMLCommonHandler.instance().bus().register(this);
@@ -39,8 +42,46 @@ public enum ConfigurationHandler {
}
private void loadConfig() {
maxCoexistingHologram = config.getInt("maxCoexisting", "client.hologram", 1, 1, 100, "An attempt will be made to prune old holograms when a new hologram is about to be projected");
removeCollidingHologram = config.getBoolean("removeColliding", "client.hologram", true, "An attempt will be made to remove an existing hologram if it collides with a new hologram.");
maxCoexistingHologram = config.getInt(
"maxCoexisting",
"client.hologram",
1,
1,
100,
"An attempt will be made to prune old holograms when a new hologram is about to be projected");
removeCollidingHologram = config.getBoolean(
"removeColliding",
"client.hologram",
true,
"An attempt will be made to remove an existing hologram if it collides with a new hologram.");
hintLifespan =
config.getInt("hintLifespan", "client.hologram", 400, 1, 20000, "Ticks before a hologram disappears.");
hintTransparency = config.getInt(
"hintTransparency",
"client.hologram",
192,
1,
255,
"Alpha value of hologram particles. Higher the value, the more \"ghostly\" the hologram will appear to be.");
autoPlaceBudget = config.getInt(
"autoPlaceBudget",
"common.hologram",
25,
1,
200,
"Max number of elements can be placed in one round of auto place.\n"
+ "As expected, server side settings will overrides client settings.\n"
+ "Certain larger multi might increase these values beyond this configured value.");
autoPlaceInterval = config.getInt(
"autoPlaceInterval",
"common.hologram",
300,
0,
20000,
"Unit: millisecond. Minimal interval between two auto place round.\n"
+ "As expected, server side settings will overrides client settings.\n"
+ "Note this relates to the wall clock, not in game ticks.\n"
+ "Value smaller than default is likely to be perceived as no minimal interval whatsoever.");
if (config.hasChanged()) {
config.save();
@@ -62,6 +103,22 @@ public enum ConfigurationHandler {
return removeCollidingHologram;
}
public int getHintLifespan() {
return hintLifespan;
}
public int getHintTransparency() {
return hintTransparency;
}
public int getAutoPlaceBudget() {
return autoPlaceBudget;
}
public int getAutoPlaceInterval() {
return autoPlaceInterval;
}
Configuration getConfig() {
return config;
}
@@ -1,16 +1,14 @@
package com.gtnewhorizon.structurelib;
import net.minecraftforge.common.config.Configuration;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.Nullable;
import net.minecraftforge.common.config.Configuration;
enum ConfigurationVersion {
V1 {
@Override
protected void step(Configuration c) {
}
protected void step(Configuration c) {}
};
private static final ConfigurationVersion[] VALUES = values();
@@ -32,7 +30,10 @@ enum ConfigurationVersion {
}
public static ConfigurationVersion identify(Configuration c) {
return Arrays.stream(VALUES).filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker())).findFirst().orElse(V1);
return Arrays.stream(VALUES)
.filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker()))
.findFirst()
.orElse(V1);
}
@Nullable
@@ -1,15 +1,13 @@
package com.gtnewhorizon.structurelib;
import cpw.mods.fml.client.IModGuiFactory;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import java.util.Set;
public class GuiFactory implements IModGuiFactory {
@Override
public void initialize(Minecraft minecraftInstance) {
}
public void initialize(Minecraft minecraftInstance) {}
@Override
public Class<? extends GuiScreen> mainConfigGuiClass() {
@@ -25,4 +23,4 @@ public class GuiFactory implements IModGuiFactory {
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) {
return null;
}
}
}
@@ -0,0 +1,23 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.gui.GuiScreenConfigureChannels;
import cpw.mods.fml.common.network.IGuiHandler;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
public class GuiHandler implements IGuiHandler {
@Override
public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
return null;
}
@Override
public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
switch (ID) {
case 0:
return new GuiScreenConfigureChannels(player.inventory.getStackInSlot(x));
default:
return null;
}
}
}
@@ -2,16 +2,22 @@ package com.gtnewhorizon.structurelib;
import cpw.mods.fml.client.config.GuiConfig;
import cpw.mods.fml.client.config.IConfigElement;
import java.util.List;
import java.util.stream.Collectors;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.common.config.ConfigElement;
import net.minecraftforge.common.config.Configuration;
import java.util.List;
import java.util.stream.Collectors;
public class GuiModConfig extends GuiConfig {
public GuiModConfig(GuiScreen guiScreen) {
super(guiScreen, getConfigElements(), StructureLibAPI.MOD_ID, false, false, GuiConfig.getAbridgedConfigPath(ConfigurationHandler.INSTANCE.getConfig().toString()));
super(
guiScreen,
getConfigElements(),
StructureLibAPI.MOD_ID,
false,
false,
GuiConfig.getAbridgedConfigPath(
ConfigurationHandler.INSTANCE.getConfig().toString()));
}
@SuppressWarnings("rawtypes")
@@ -0,0 +1,7 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.util.IIcon;
public interface IStructureCompat {
void markTextureUsed(IIcon o);
}
@@ -1,15 +1,20 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.block.BlockHint;
import com.gtnewhorizon.structurelib.command.CommandConfigureChannels;
import com.gtnewhorizon.structurelib.item.ItemBlockHint;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool;
import com.gtnewhorizon.structurelib.proxy.CommonProxy;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import com.gtnewhorizon.structurelib.util.XSTR;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerStartingEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import cpw.mods.fml.common.registry.GameRegistry;
@@ -20,59 +25,115 @@ import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* This class does not contain a stable API. Refrain from using this class.
*/
@Mod(modid = StructureLibAPI.MOD_ID, name = "StructureLib", version = "GRADLETOKEN_VERSION", acceptableRemoteVersions = "*", guiFactory = "com.gtnewhorizon.structurelib.GuiFactory")
@Mod(
modid = StructureLibAPI.MOD_ID,
name = "StructureLib",
version = "GRADLETOKEN_VERSION",
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizon.structurelib.GuiFactory")
public class StructureLib {
public static boolean DEBUG_MODE = Boolean.getBoolean("structurelib.debug");
public static Logger LOGGER = LogManager.getLogger("StructureLib");
private static final String STRUCTURECOMPAT_MODID = "structurecompat";
public static boolean DEBUG_MODE;
public static boolean PANIC_MODE = Boolean.getBoolean("structurelib.panic");
public static final Logger LOGGER = LogManager.getLogger("StructureLib");
@SidedProxy(serverSide = "com.gtnewhorizon.structurelib.proxy.CommonProxy", clientSide = "com.gtnewhorizon.structurelib.proxy.ClientProxy")
static CommonProxy proxy;
static SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
@SidedProxy(
serverSide = "com.gtnewhorizon.structurelib.CommonProxy",
clientSide = "com.gtnewhorizon.structurelib.ClientProxy")
static CommonProxy proxy;
static {
net.registerMessage(AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER);
net.registerMessage(AlignmentMessage.ClientHandler.class, AlignmentMessage.AlignmentData.class, 1, Side.CLIENT);
}
static final SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
public static final XSTR RANDOM = new XSTR();
static {
net.registerMessage(
AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER);
net.registerMessage(AlignmentMessage.ClientHandler.class, AlignmentMessage.AlignmentData.class, 1, Side.CLIENT);
net.registerMessage(UpdateHintParticleMessage.Handler.class, UpdateHintParticleMessage.class, 2, Side.CLIENT);
net.registerMessage(SetChannelDataMessage.Handler.class, SetChannelDataMessage.class, 3, Side.SERVER);
net.registerMessage(ErrorHintParticleMessage.Handler.class, ErrorHintParticleMessage.class, 4, Side.CLIENT);
static Block blockHint;
static Item itemBlockHint;
static Item itemFrontRotationTool;
static Item itemConstructableTrigger;
public static final CreativeTabs creativeTab = new CreativeTabs("structurelib") {
@Override
@SideOnly(Side.CLIENT)
public Item getTabIconItem() {
return StructureLibAPI.getItemBlockHint();
}
};
try {
DEBUG_MODE = Boolean.parseBoolean(System.getProperty("structurelib.debug"));
} catch (IllegalArgumentException | NullPointerException e) {
// turn on debug by default in dev mode
// this will be overridden if above property is present and set to false
DEBUG_MODE = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
}
}
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e) {
ConfigurationHandler.INSTANCE.init(e.getSuggestedConfigurationFile());
GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint");
itemBlockHint = ItemBlock.getItemFromBlock(StructureLibAPI.getBlockHint());
GameRegistry.registerItem(itemFrontRotationTool = new ItemFrontRotationTool(), itemFrontRotationTool.getUnlocalizedName());
GameRegistry.registerItem(itemConstructableTrigger = new ItemConstructableTrigger(), itemConstructableTrigger.getUnlocalizedName());
proxy.preInit(e);
}
public static final XSTR RANDOM = new XSTR();
public static void addClientSideChatMessages(String... messages) {
proxy.addClientSideChatMessages(messages);
}
@Mod.Instance
static StructureLib INSTANCE;
public static EntityPlayer getCurrentPlayer() {
return proxy.getCurrentPlayer();
}
static Object COMPAT;
public static boolean isCurrentPlayer(EntityPlayer player) {
return proxy.isCurrentPlayer(player);
}
static Block blockHint;
static Item itemBlockHint;
static Item itemFrontRotationTool;
static Item itemConstructableTrigger;
public static final CreativeTabs creativeTab = new CreativeTabs("structurelib") {
@Override
@SideOnly(Side.CLIENT)
public Item getTabIconItem() {
return StructureLibAPI.getItemBlockHint();
}
};
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e) {
ConfigurationHandler.INSTANCE.init(e.getSuggestedConfigurationFile());
GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint");
itemBlockHint = ItemBlock.getItemFromBlock(StructureLibAPI.getBlockHint());
GameRegistry.registerItem(
itemFrontRotationTool = new ItemFrontRotationTool(), itemFrontRotationTool.getUnlocalizedName());
GameRegistry.registerItem(
itemConstructableTrigger = new ItemConstructableTrigger(),
itemConstructableTrigger.getUnlocalizedName());
proxy.preInit(e);
NetworkRegistry.INSTANCE.registerGuiHandler(instance(), new GuiHandler());
if (Loader.isModLoaded(STRUCTURECOMPAT_MODID)) {
COMPAT = Loader.instance()
.getIndexedModList()
.get(STRUCTURECOMPAT_MODID)
.getMod();
}
}
@Mod.EventHandler
public void serverStarting(FMLServerStartingEvent e) {
e.registerServerCommand(new CommandConfigureChannels());
}
public static void addClientSideChatMessages(String... messages) {
proxy.addClientSideChatMessages(messages);
}
public static EntityPlayer getCurrentPlayer() {
return proxy.getCurrentPlayer();
}
public static boolean isCurrentPlayer(EntityPlayer player) {
return proxy.isCurrentPlayer(player);
}
public static long getOverworldTime() {
return proxy.getOverworldTime();
}
public static StructureLib instance() {
return INSTANCE;
}
public CommonProxy proxy() {
return proxy;
}
}
@@ -1,131 +1,262 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import static com.gtnewhorizon.structurelib.StructureLib.proxy;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import cpw.mods.fml.common.network.NetworkRegistry;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import static com.gtnewhorizon.structurelib.StructureLib.proxy;
/**
* A stable interface into the structure lib's internals. Backwards compatibility is maintained to the maximum extend possible.
*/
public class StructureLibAPI {
public static final String MOD_ID = "structurelib";
public static final int HINT_BLOCK_META_GENERIC_0 = 0;
public static final int HINT_BLOCK_META_GENERIC_1 = 1;
public static final int HINT_BLOCK_META_GENERIC_2 = 2;
public static final int HINT_BLOCK_META_GENERIC_3 = 3;
public static final int HINT_BLOCK_META_GENERIC_4 = 4;
public static final int HINT_BLOCK_META_GENERIC_5 = 5;
public static final int HINT_BLOCK_META_GENERIC_6 = 6;
public static final int HINT_BLOCK_META_GENERIC_7 = 7;
public static final int HINT_BLOCK_META_GENERIC_8 = 8;
public static final int HINT_BLOCK_META_GENERIC_9 = 9;
public static final int HINT_BLOCK_META_GENERIC_10 = 10;
public static final int HINT_BLOCK_META_GENERIC_11 = 11;
public static final int HINT_BLOCK_META_DEFAULT = 12;
public static final int HINT_BLOCK_META_AIR = 13;
public static final int HINT_BLOCK_META_NOT_AIR = 14;
public static final int HINT_BLOCK_META_ERROR = 15;
public static final String MOD_ID = "structurelib";
public static final int HINT_BLOCK_META_GENERIC_0 = 0;
public static final int HINT_BLOCK_META_GENERIC_1 = 1;
public static final int HINT_BLOCK_META_GENERIC_2 = 2;
public static final int HINT_BLOCK_META_GENERIC_3 = 3;
public static final int HINT_BLOCK_META_GENERIC_4 = 4;
public static final int HINT_BLOCK_META_GENERIC_5 = 5;
public static final int HINT_BLOCK_META_GENERIC_6 = 6;
public static final int HINT_BLOCK_META_GENERIC_7 = 7;
public static final int HINT_BLOCK_META_GENERIC_8 = 8;
public static final int HINT_BLOCK_META_GENERIC_9 = 9;
public static final int HINT_BLOCK_META_GENERIC_10 = 10;
public static final int HINT_BLOCK_META_GENERIC_11 = 11;
public static final int HINT_BLOCK_META_DEFAULT = 12;
public static final int HINT_BLOCK_META_AIR = 13;
public static final int HINT_BLOCK_META_NOT_AIR = 14;
public static final int HINT_BLOCK_META_ERROR = 15;
/**
* Start a batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
*
* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
*/
public static void startHinting(World w) {
proxy.startHinting(w);
}
/**
* Start a batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
* <p>
* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
*/
public static void startHinting(World w) {
proxy.startHinting(w);
}
/**
* Start current batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
*
* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
*/
public static void endHinting(World w) {
proxy.endHinting(w);
}
/**
* End current batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
* <p>
* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
*/
public static void endHinting(World w) {
proxy.endHinting(w);
}
public static void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
proxy.hintParticleTinted(w, x, y, z, icons, RGBa);
}
/**
* Generate a new hint particle on client side at given location using given textures with a tint.
* @param w World to spawn. Usually the client world.
* @param x x coord
* @param y y coord
* @param z z coord
* @param icons 6 texture. in forge direction order.
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this later on.
*/
public static void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
proxy.hintParticleTinted(w, x, y, z, icons, RGBa);
}
public static void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {
proxy.hintParticleTinted(w, x, y, z, block, meta, RGBa);
}
/**
* Generate a new hint particle on client side at given location using textures from given block with a tint.
* @param w World to spawn. Usually the client world.
* @param x x coord
* @param y y coord
* @param z z coord
* @param block block to take texture from
* @param meta the meta of block to take texture from
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this later on.
*/
public static void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {
proxy.hintParticleTinted(w, x, y, z, block, meta, RGBa);
}
public static void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
proxy.hintParticle(w, x, y, z, icons);
}
/**
* Generate a new hint particle on client side at given location using given textures.
* @param w World to spawn. Usually the client world.
* @param x x coord
* @param y y coord
* @param z z coord
* @param icons 6 texture. in forge direction order.
*/
public static void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
proxy.hintParticle(w, x, y, z, icons);
}
public static void hintParticle(World w, int x, int y, int z, Block block, int meta) {
proxy.hintParticle(w, x, y, z, block, meta);
}
/**
* Generate a new hint particle on client side at given location using textures from given block.
* @param w World to spawn. Usually the client world.
* @param x x coord
* @param y y coord
* @param z z coord
* @param block block to take texture from
* @param meta the meta of block to take texture from
*/
public static void hintParticle(World w, int x, int y, int z, Block block, int meta) {
proxy.hintParticle(w, x, y, z, block, meta);
}
/**
* Query the ExtendedFacing of this tile entity.
* The ExtendedFacing will later be set onto given tile entity via {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)}
* upon arrival of server reply.
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void queryAlignment(IAlignmentProvider provider) {
StructureLib.net.sendToServer(new AlignmentMessage.AlignmentQuery(provider));
}
/**
* Update the tint of given hint particle. Do nothing if particle not found.
*
* Can be called on either client side or server side.
* Server side will schedule a network message to update it for this player only.
* Will do nothing if no hint particle is found at given location.
*
* @return false if nothing updated. true if updated or update instruction sent.
*/
public static boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
return proxy.markHintParticleError(player, w, x, y, z);
}
/**
* Send the ExtendedFacing of this Tile Entity to all players. Can be called on server side only.
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider) {
StructureLib.net.sendToAll(new AlignmentMessage.AlignmentData(provider));
}
/**
* Update the tint of given hint particle. Do nothing if particle not found.
*
* @return false if nothing updated. true if updated.
*/
public static boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] RGBa) {
return proxy.updateHintParticleTint(player, w, x, y, z, RGBa);
}
/**
* Send the ExtendedFacing of this Tile Entity to given player. Can be called on server side only.
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider, EntityPlayerMP player) {
StructureLib.net.sendTo(new AlignmentMessage.AlignmentData(provider), player);
}
/**
* Query the ExtendedFacing of this tile entity from client side. Can be sent only on client side.
* The ExtendedFacing will later be set onto given tile entity via {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)}
* upon arrival of server reply.
* <p>
* The server side will query the {@link ExtendedFacing} of this provider using {@link IAlignment#getExtendedFacing()}
*
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void queryAlignment(IAlignmentProvider provider) {
StructureLib.net.sendToServer(new AlignmentMessage.AlignmentQuery(provider));
}
/**
* Send the ExtendedFacing of this Tile Entity to all players around target point. Can be called on server side only.
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider, NetworkRegistry.TargetPoint targetPoint) {
StructureLib.net.sendToAllAround(new AlignmentMessage.AlignmentData(provider), targetPoint);
}
/**
* Send the ExtendedFacing of this Tile Entity to all players. Can be called on server side only.
* <p>
* The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
*
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider) {
StructureLib.net.sendToAll(new AlignmentMessage.AlignmentData(provider));
}
/**
* Send the ExtendedFacing of this Tile Entity to given player. Can be called on server side only.
* <p>
* The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
*
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider, EntityPlayerMP player) {
StructureLib.net.sendTo(new AlignmentMessage.AlignmentData(provider), player);
}
/**
* Send the ExtendedFacing of this Tile Entity to all players in that dimension. Can be called on server side only.
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider, World dimension) {
StructureLib.net.sendToDimension(new AlignmentMessage.AlignmentData(provider), dimension.provider.dimensionId);
}
/**
* Send the ExtendedFacing of this Tile Entity to all players around target point. Can be called on server side only.
* <p>
* The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
*
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider, NetworkRegistry.TargetPoint targetPoint) {
StructureLib.net.sendToAllAround(new AlignmentMessage.AlignmentData(provider), targetPoint);
}
public static Block getBlockHint() {
return StructureLib.blockHint;
}
/**
* Send the ExtendedFacing of this Tile Entity to all players in that dimension. Can be called on server side only.
* <p>
* The receiving tile entity will receive the {@link ExtendedFacing} via its {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
*
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
*/
public static void sendAlignment(IAlignmentProvider provider, World dimension) {
StructureLib.net.sendToDimension(new AlignmentMessage.AlignmentData(provider), dimension.provider.dimensionId);
}
public static Item getItemBlockHint() {
return StructureLib.itemBlockHint;
}
/**
* Get the Block for StructureLib supplied hint block.
*/
public static Block getBlockHint() {
return StructureLib.blockHint;
}
public static boolean isDebugEnabled() {
return StructureLib.DEBUG_MODE;
}
/**
* Get the ItemBlock for StructureLib supplied hint block.
*/
public static Item getItemBlockHint() {
return StructureLib.itemBlockHint;
}
public static void setDebugEnabled(boolean enabled) {
StructureLib.DEBUG_MODE = enabled;
}
/**
* Check if structure debug mode is on.
*
* @return true if debug mode is on
*/
public static boolean isDebugEnabled() {
return StructureLib.DEBUG_MODE;
}
public static void setDebugEnabled(boolean enabled) {
StructureLib.DEBUG_MODE = enabled;
}
/**
* Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and
* will be changed, but the general idea will always stay the same.
* <p>
* Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
*/
public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayerMP actor) {
// TODO extend this function a bit
Block block = w.getBlock(x, y, z);
return block.isAir(w, x, y, z) || block.isReplaceable(w, x, y, z);
}
/**
* Send chat to player, but throttled.
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
* to handle value equality.
* @param player player to send chat to
* @param text chat to send
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
* we purposefully chose to not use a bigger data type to limit how long this interval can be
*/
public static void addThrottledChat(
Object throttleKey, EntityPlayer player, IChatComponent text, short intervalRequired) {
addThrottledChat(throttleKey, player, text, intervalRequired, false);
}
/**
* Send chat to player, but throttled.
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and {@link Object#equals(Object)}
* to handle value equality.
* @param player player to send chat to
* @param text chat to send
* @param intervalRequired interval required before last recorded time to actually send the message. unit in millisecond.
* we purposefully chose to not use a bigger data type to limit how long this interval can be
* @param forceUpdateLastSend if true, always update the last send time, even if the message isn't actually sent.
*/
public static void addThrottledChat(
Object throttleKey,
EntityPlayer player,
IChatComponent text,
short intervalRequired,
boolean forceUpdateLastSend) {
proxy.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend);
}
}
@@ -7,10 +7,9 @@ import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
public class AlignmentUtility {
private AlignmentUtility(){
}
private AlignmentUtility() {}
public static boolean handle(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ){
public static boolean handle(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ) {
TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ);
if (tTileEntity == null || aPlayer instanceof FakePlayer) {
return aPlayer instanceof EntityPlayerMP;
@@ -6,61 +6,67 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
import net.minecraftforge.common.util.ForgeDirection;
public interface IAlignment extends IAlignmentLimits,IAlignmentProvider {
int DIRECTIONS_COUNT= Direction.VALUES.length;
int ROTATIONS_COUNT= Rotation.VALUES.length;
int FLIPS_COUNT= Flip.VALUES.length;
public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
int DIRECTIONS_COUNT = Direction.VALUES.length;
int ROTATIONS_COUNT = Rotation.VALUES.length;
int FLIPS_COUNT = Flip.VALUES.length;
int STATES_COUNT = ExtendedFacing.STATES_COUNT;
ExtendedFacing getExtendedFacing();
/**
* Set the facing without additional checks.
* Tools altering facing should use {@link #toolSetExtendedFacing(ExtendedFacing)} instead.
*/
void setExtendedFacing(ExtendedFacing alignment);
IAlignmentLimits getAlignmentLimits();
@Override
default IAlignment getAlignment(){
default IAlignment getAlignment() {
return this;
}
static int getAlignmentIndex(ForgeDirection direction, Rotation rotation, Flip flip){
return (direction.ordinal()*ROTATIONS_COUNT+rotation.getIndex())*FLIPS_COUNT+flip.getIndex();
static int getAlignmentIndex(ForgeDirection direction, Rotation rotation, Flip flip) {
return (direction.ordinal() * ROTATIONS_COUNT + rotation.getIndex()) * FLIPS_COUNT + flip.getIndex();
}
default ForgeDirection getDirection(){
default ForgeDirection getDirection() {
return getExtendedFacing().getDirection();
}
default void setDirection(ForgeDirection direction){
default void setDirection(ForgeDirection direction) {
setExtendedFacing(getExtendedFacing().with(direction));
}
default Rotation getRotation(){
default Rotation getRotation() {
return getExtendedFacing().getRotation();
}
default void setRotation(Rotation rotation){
default void setRotation(Rotation rotation) {
setExtendedFacing(getExtendedFacing().with(rotation));
}
default Flip getFlip(){
default Flip getFlip() {
return getExtendedFacing().getFlip();
}
default void setFlip(Flip flip){
default void setFlip(Flip flip) {
setExtendedFacing(getExtendedFacing().with(flip));
}
default boolean toolSetDirection(ForgeDirection direction){
if(direction==null || direction==ForgeDirection.UNKNOWN){
for (int i = 0,j=getDirection().ordinal()+1, valuesLength = Direction.VALUES.length; i < valuesLength; i++) {
default boolean toolSetDirection(ForgeDirection direction) {
if (direction == null || direction == ForgeDirection.UNKNOWN) {
for (int i = 0, j = getDirection().ordinal() + 1, valuesLength = Direction.VALUES.length;
i < valuesLength;
i++) {
if (toolSetDirection(Direction.VALUES[(j + i) % valuesLength].getForgeDirection())) {
return true;
}
}
}else {
} else {
for (ExtendedFacing extendedFacing : ExtendedFacing.FOR_FACING.get(direction)) {
if(checkedSetExtendedFacing(extendedFacing)){
if (checkedSetExtendedFacing(extendedFacing)) {
return true;
}
}
@@ -68,17 +74,17 @@ public interface IAlignment extends IAlignmentLimits,IAlignmentProvider {
return false;
}
default boolean checkedSetDirection(ForgeDirection direction){
if (isNewDirectionValid(direction)){
default boolean checkedSetDirection(ForgeDirection direction) {
if (isNewDirectionValid(direction)) {
setDirection(direction);
return true;
}
return false;
}
default boolean canSetToDirectionAny(ForgeDirection direction){
default boolean canSetToDirectionAny(ForgeDirection direction) {
for (ExtendedFacing extendedFacing : ExtendedFacing.FOR_FACING.get(direction)) {
if(isNewExtendedFacingValid(extendedFacing)){
if (isNewExtendedFacingValid(extendedFacing)) {
return true;
}
}
@@ -86,66 +92,69 @@ public interface IAlignment extends IAlignmentLimits,IAlignmentProvider {
}
default boolean toolSetRotation(Rotation rotation) {
if(rotation==null){
if (rotation == null) {
int flips = Flip.VALUES.length;
int rotations = Rotation.VALUES.length;
for (int ii = 0,jj=getFlip().ordinal(); ii < flips; ii++) {
for (int ii = 0, jj = getFlip().ordinal(); ii < flips; ii++) {
for (int i = 1, j = getRotation().ordinal(); i < rotations; i++) {
if (checkedSetExtendedFacing(ExtendedFacing.of(getDirection(), Rotation.VALUES[(j + i) % rotations], Flip.VALUES[(jj + ii) % flips]))) {
if (checkedSetExtendedFacing(ExtendedFacing.of(
getDirection(), Rotation.VALUES[(j + i) % rotations], Flip.VALUES[(jj + ii) % flips]))) {
return true;
}
}
}
return false;
}else {
} else {
return checkedSetRotation(rotation);
}
}
default boolean checkedSetRotation(Rotation rotation){
if (isNewRotationValid(rotation)){
default boolean checkedSetRotation(Rotation rotation) {
if (isNewRotationValid(rotation)) {
setRotation(rotation);
return true;
}
return false;
}
default boolean toolSetFlip(Flip flip){
if(flip==null){
default boolean toolSetFlip(Flip flip) {
if (flip == null) {
for (int i = 1, j = getFlip().ordinal(), valuesLength = Flip.VALUES.length; i < valuesLength; i++) {
if (checkedSetFlip(Flip.VALUES[(j + i) % valuesLength])) {
return true;
}
}
return false;
}else {
} else {
return checkedSetFlip(flip);
}
}
default boolean checkedSetFlip(Flip flip){
if (isNewFlipValid(flip)){
default boolean checkedSetFlip(Flip flip) {
if (isNewFlipValid(flip)) {
setFlip(flip);
return true;
}
return false;
}
default boolean toolSetExtendedFacing(ExtendedFacing extendedFacing){
if(extendedFacing==null){
for (int i = 0,j=getExtendedFacing().ordinal()+1, valuesLength = ExtendedFacing.VALUES.length; i < valuesLength; i++) {
if (checkedSetExtendedFacing(ExtendedFacing.VALUES[(j + i)%valuesLength])){
default boolean toolSetExtendedFacing(ExtendedFacing extendedFacing) {
if (extendedFacing == null) {
for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length;
i < valuesLength;
i++) {
if (checkedSetExtendedFacing(ExtendedFacing.VALUES[(j + i) % valuesLength])) {
return true;
}
}
return false;
}else {
} else {
return checkedSetExtendedFacing(extendedFacing);
}
}
default boolean checkedSetExtendedFacing(ExtendedFacing alignment){
if (isNewExtendedFacingValid(alignment)){
default boolean checkedSetExtendedFacing(ExtendedFacing alignment) {
if (isNewExtendedFacingValid(alignment)) {
setExtendedFacing(alignment);
return true;
}
@@ -153,31 +162,29 @@ public interface IAlignment extends IAlignmentLimits,IAlignmentProvider {
}
default boolean isNewDirectionValid(ForgeDirection direction) {
return isNewExtendedFacingValid(direction,getRotation(),getFlip());
return isNewExtendedFacingValid(direction, getRotation(), getFlip());
}
default boolean isNewRotationValid(Rotation rotation){
return isNewExtendedFacingValid(getDirection(),rotation,getFlip());
default boolean isNewRotationValid(Rotation rotation) {
return isNewExtendedFacingValid(getDirection(), rotation, getFlip());
}
default boolean isNewFlipValid(Flip flip){
return isNewExtendedFacingValid(getDirection(),getRotation(),flip);
default boolean isNewFlipValid(Flip flip) {
return isNewExtendedFacingValid(getDirection(), getRotation(), flip);
}
default boolean isExtendedFacingValid() {
return isNewExtendedFacingValid(getDirection(),getRotation(),getFlip());
return isNewExtendedFacingValid(getDirection(), getRotation(), getFlip());
}
@Override
default boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip){
default boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip) {
return getAlignmentLimits().isNewExtendedFacingValid(direction, rotation, flip);
}
@Override
default boolean isNewExtendedFacingValid(ExtendedFacing alignment){
return getAlignmentLimits().isNewExtendedFacingValid(
alignment.getDirection(),
alignment.getRotation(),
alignment.getFlip());
default boolean isNewExtendedFacingValid(ExtendedFacing alignment) {
return getAlignmentLimits()
.isNewExtendedFacingValid(alignment.getDirection(), alignment.getRotation(), alignment.getFlip());
}
}
}
@@ -1,14 +1,14 @@
package com.gtnewhorizon.structurelib.alignment;
import static com.gtnewhorizon.structurelib.alignment.IAlignment.STATES_COUNT;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
import net.minecraftforge.common.util.ForgeDirection;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import static com.gtnewhorizon.structurelib.alignment.IAlignment.STATES_COUNT;
import net.minecraftforge.common.util.ForgeDirection;
public interface IAlignmentLimits {
@@ -17,10 +17,7 @@ public interface IAlignmentLimits {
boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip);
default boolean isNewExtendedFacingValid(ExtendedFacing alignment) {
return isNewExtendedFacingValid(
alignment.getDirection(),
alignment.getRotation(),
alignment.getFlip());
return isNewExtendedFacingValid(alignment.getDirection(), alignment.getRotation(), alignment.getFlip());
}
static IAlignmentLimits allowOnly(ExtendedFacing... allowedFacings) {
@@ -57,12 +54,16 @@ public interface IAlignmentLimits {
}
public Builder deny(ForgeDirection fd) {
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = false);
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = false);
return this;
}
public Builder allow(ForgeDirection fd) {
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = true);
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = true);
return this;
}
@@ -77,22 +78,30 @@ public interface IAlignmentLimits {
}
public Builder deny(Rotation fd) {
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = false);
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = false);
return this;
}
public Builder allow(Rotation fd) {
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = true);
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = true);
return this;
}
public Builder deny(Flip fd) {
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = false);
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = false);
return this;
}
public Builder allow(Flip fd) {
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = true);
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = true);
return this;
}
@@ -113,7 +122,7 @@ public interface IAlignmentLimits {
}
/**
* Prefers rotation over flip, so both flip will get translated to opposite rotation and no flip
* Prefers rotation to flip, so both flip will get translated to opposite rotation and no flip
*
* @param flip the preferred flip to be used Horizontal or vertical
* @return this
@@ -5,4 +5,4 @@ import javax.annotation.Nullable;
public interface IAlignmentProvider {
@Nullable
IAlignment getAlignment();
}
}
@@ -1,12 +1,12 @@
package com.gtnewhorizon.structurelib.alignment;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import static java.lang.Math.abs;
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import net.minecraft.util.Vec3;
import net.minecraftforge.common.util.ForgeDirection;
import static java.lang.Math.abs;
public class IntegerAxisSwap {
private final Vec3Impl forFirstAxis;
private final Vec3Impl forSecondAxis;
@@ -16,69 +16,89 @@ public class IntegerAxisSwap {
this.forFirstAxis = Direction.getAxisVector(forFirstAxis);
this.forSecondAxis = Direction.getAxisVector(forSecondAxis);
this.forThirdAxis = Direction.getAxisVector(forThirdAxis);
if(abs(this.forFirstAxis.get0())+abs(this.forSecondAxis.get0())+abs(this.forThirdAxis.get0())!=1 ||
abs(this.forFirstAxis.get1())+abs(this.forSecondAxis.get1())+abs(this.forThirdAxis.get1())!=1 ||
abs(this.forFirstAxis.get2())+abs(this.forSecondAxis.get2())+abs(this.forThirdAxis.get2())!=1){
throw new IllegalArgumentException("Axis are overlapping/missing! "+
forFirstAxis.name()+" "+
forSecondAxis.name()+" "+
forThirdAxis.name());
if (abs(this.forFirstAxis.get0()) + abs(this.forSecondAxis.get0()) + abs(this.forThirdAxis.get0()) != 1
|| abs(this.forFirstAxis.get1()) + abs(this.forSecondAxis.get1()) + abs(this.forThirdAxis.get1()) != 1
|| abs(this.forFirstAxis.get2()) + abs(this.forSecondAxis.get2()) + abs(this.forThirdAxis.get2())
!= 1) {
throw new IllegalArgumentException("Axis are overlapping/missing! " + forFirstAxis.name()
+ " " + forSecondAxis.name()
+ " " + forThirdAxis.name());
}
}
public Vec3Impl translate(Vec3Impl point){
public Vec3Impl translate(Vec3Impl point) {
return new Vec3Impl(
forFirstAxis.get0()*point.get0() +forFirstAxis.get1()*point.get1() +forFirstAxis.get2()*point.get2(),
forSecondAxis.get0()*point.get0()+forSecondAxis.get1()*point.get1()+forSecondAxis.get2()*point.get2(),
forThirdAxis.get0()*point.get0() +forThirdAxis.get1()*point.get1() +forThirdAxis.get2()*point.get2()
);
forFirstAxis.get0() * point.get0()
+ forFirstAxis.get1() * point.get1()
+ forFirstAxis.get2() * point.get2(),
forSecondAxis.get0() * point.get0()
+ forSecondAxis.get1() * point.get1()
+ forSecondAxis.get2() * point.get2(),
forThirdAxis.get0() * point.get0()
+ forThirdAxis.get1() * point.get1()
+ forThirdAxis.get2() * point.get2());
}
public Vec3Impl inverseTranslate(Vec3Impl point){
public Vec3Impl inverseTranslate(Vec3Impl point) {
return new Vec3Impl(
forFirstAxis.get0()*point.get0()+forSecondAxis.get0()*point.get1()+forThirdAxis.get0()*point.get2(),
forFirstAxis.get1()*point.get0()+forSecondAxis.get1()*point.get1()+forThirdAxis.get1()*point.get2(),
forFirstAxis.get2()*point.get0()+forSecondAxis.get2()*point.get1()+forThirdAxis.get2()*point.get2()
);
forFirstAxis.get0() * point.get0()
+ forSecondAxis.get0() * point.get1()
+ forThirdAxis.get0() * point.get2(),
forFirstAxis.get1() * point.get0()
+ forSecondAxis.get1() * point.get1()
+ forThirdAxis.get1() * point.get2(),
forFirstAxis.get2() * point.get0()
+ forSecondAxis.get2() * point.get1()
+ forThirdAxis.get2() * point.get2());
}
public Vec3 translate(Vec3 point){
public Vec3 translate(Vec3 point) {
return Vec3.createVectorHelper(
forFirstAxis.get0()*point.xCoord +forFirstAxis.get1()*point.yCoord +forFirstAxis.get2()*point.zCoord,
forSecondAxis.get0()*point.xCoord+forSecondAxis.get1()*point.yCoord+forSecondAxis.get2()*point.zCoord,
forThirdAxis.get0()*point.xCoord +forThirdAxis.get1()*point.yCoord +forThirdAxis.get2()*point.zCoord
);
forFirstAxis.get0() * point.xCoord
+ forFirstAxis.get1() * point.yCoord
+ forFirstAxis.get2() * point.zCoord,
forSecondAxis.get0() * point.xCoord
+ forSecondAxis.get1() * point.yCoord
+ forSecondAxis.get2() * point.zCoord,
forThirdAxis.get0() * point.xCoord
+ forThirdAxis.get1() * point.yCoord
+ forThirdAxis.get2() * point.zCoord);
}
public Vec3 inverseTranslate(Vec3 point){
public Vec3 inverseTranslate(Vec3 point) {
return Vec3.createVectorHelper(
forFirstAxis.get0()*point.xCoord+forSecondAxis.get0()*point.yCoord+forThirdAxis.get0()*point.zCoord,
forFirstAxis.get1()*point.xCoord+forSecondAxis.get1()*point.yCoord+forThirdAxis.get1()*point.zCoord,
forFirstAxis.get2()*point.xCoord+forSecondAxis.get2()*point.yCoord+forThirdAxis.get2()*point.zCoord
);
}
public void translate(int[] point,int[] out){
out[0]=forFirstAxis.get0()*point[0] +forFirstAxis.get1()*point[1] +forFirstAxis.get2()*point[2];
out[1]=forSecondAxis.get0()*point[0]+forSecondAxis.get1()*point[1]+forSecondAxis.get2()*point[2];
out[2]=forThirdAxis.get0()*point[0] +forThirdAxis.get1()*point[1] +forThirdAxis.get2()*point[2];
forFirstAxis.get0() * point.xCoord
+ forSecondAxis.get0() * point.yCoord
+ forThirdAxis.get0() * point.zCoord,
forFirstAxis.get1() * point.xCoord
+ forSecondAxis.get1() * point.yCoord
+ forThirdAxis.get1() * point.zCoord,
forFirstAxis.get2() * point.xCoord
+ forSecondAxis.get2() * point.yCoord
+ forThirdAxis.get2() * point.zCoord);
}
public void inverseTranslate(int[] point,int[] out){
out[0]=forFirstAxis.get0()*point[0]+forSecondAxis.get0()*point[1]+forThirdAxis.get0()*point[2];
out[1]=forFirstAxis.get1()*point[0]+forSecondAxis.get1()*point[1]+forThirdAxis.get1()*point[2];
out[2]=forFirstAxis.get2()*point[0]+forSecondAxis.get2()*point[1]+forThirdAxis.get2()*point[2];
public void translate(int[] point, int[] out) {
out[0] = forFirstAxis.get0() * point[0] + forFirstAxis.get1() * point[1] + forFirstAxis.get2() * point[2];
out[1] = forSecondAxis.get0() * point[0] + forSecondAxis.get1() * point[1] + forSecondAxis.get2() * point[2];
out[2] = forThirdAxis.get0() * point[0] + forThirdAxis.get1() * point[1] + forThirdAxis.get2() * point[2];
}
public void translate(double[] point,double[] out){
out[0]=forFirstAxis.get0()*point[0] +forFirstAxis.get1()*point[1] +forFirstAxis.get2()*point[2];
out[1]=forSecondAxis.get0()*point[0]+forSecondAxis.get1()*point[1]+forSecondAxis.get2()*point[2];
out[2]=forThirdAxis.get0()*point[0] +forThirdAxis.get1()*point[1] +forThirdAxis.get2()*point[2];
public void inverseTranslate(int[] point, int[] out) {
out[0] = forFirstAxis.get0() * point[0] + forSecondAxis.get0() * point[1] + forThirdAxis.get0() * point[2];
out[1] = forFirstAxis.get1() * point[0] + forSecondAxis.get1() * point[1] + forThirdAxis.get1() * point[2];
out[2] = forFirstAxis.get2() * point[0] + forSecondAxis.get2() * point[1] + forThirdAxis.get2() * point[2];
}
public void inverseTranslate(double[] point,double[] out){
out[0]=forFirstAxis.get0()*point[0]+forSecondAxis.get0()*point[1]+forThirdAxis.get0()*point[2];
out[1]=forFirstAxis.get1()*point[0]+forSecondAxis.get1()*point[1]+forThirdAxis.get1()*point[2];
out[2]=forFirstAxis.get2()*point[0]+forSecondAxis.get2()*point[1]+forThirdAxis.get2()*point[2];
public void translate(double[] point, double[] out) {
out[0] = forFirstAxis.get0() * point[0] + forFirstAxis.get1() * point[1] + forFirstAxis.get2() * point[2];
out[1] = forSecondAxis.get0() * point[0] + forSecondAxis.get1() * point[1] + forSecondAxis.get2() * point[2];
out[2] = forThirdAxis.get0() * point[0] + forThirdAxis.get1() * point[1] + forThirdAxis.get2() * point[2];
}
public void inverseTranslate(double[] point, double[] out) {
out[0] = forFirstAxis.get0() * point[0] + forSecondAxis.get0() * point[1] + forThirdAxis.get0() * point[2];
out[1] = forFirstAxis.get1() * point[0] + forSecondAxis.get1() * point[1] + forThirdAxis.get1() * point[2];
out[2] = forFirstAxis.get2() * point[0] + forSecondAxis.get2() * point[1] + forThirdAxis.get2() * point[2];
}
}
@@ -0,0 +1,184 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import static com.gtnewhorizon.structurelib.util.MiscUtils.getTagKeys;
import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.stream.Stream;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.Constants.NBT;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
/**
* Provide accessor methods for channel data from trigger item.
*
* <a href="{@docRoot}/overview-summary.html#channels">Channels section on overview</a>
*/
public class ChannelDataAccessor {
private static final String SECONDARY_HINT_TAG = "channels";
private ChannelDataAccessor() {}
/**
* Return a trigger item that
* <ul>
* <li>Still carry all the subchannel in some unspecified ways</li>
* <li>Have its master channel data replaced with specified subchannel's data</li>
* </ul>
*
* If given subchannel does not exist, masterStack may or may not be returned to save performance.
* Code within StructureLib can depend on this behavior, you cannot.
*
* @param masterStack trigger item. can be the return value of another {@link #withChannel(ItemStack, String)} call
* @param channel subchannel to use. will use current master channel data instead if this channel does not exist
* Note: all channel identifiers are supposed to be lower case and not empty.
* @return a trigger item with new master channel
*/
public static ItemStack withChannel(ItemStack masterStack, String channel) {
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
if (StructureLibAPI.isDebugEnabled()
&& !channel.toLowerCase(Locale.ROOT).equals(channel))
throw new IllegalArgumentException("Channel name can be lower case ONLY");
if (!masterStack.hasTagCompound()
|| !masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
|| !masterStack
.stackTagCompound
.getCompoundTag(SECONDARY_HINT_TAG)
.hasKey(channel, NBT.TAG_INT)) return masterStack;
ItemStack ret = new ItemStack(
masterStack.getItem(),
masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).getInteger(channel),
Items.feather.getDamage(masterStack));
ret.setTagCompound(masterStack.stackTagCompound);
return ret;
}
/**
* Check if given trigger item contains any subchannel
* @param masterStack trigger stack to check
* @return true if contains any subchannel
*/
public static boolean hasSubChannel(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
return masterStack.hasTagCompound() && masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND);
}
/**
* Check if given trigger item contains specified subchannel
* @param masterStack trigger stack to check
* @param channel channel identifier.
* Note: all channel identifiers are supposed to be lower case and not empty.
* @return true if contains specified subchannel
*/
public static boolean hasSubChannel(ItemStack masterStack, String channel) {
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
if (StructureLibAPI.isDebugEnabled()
&& !channel.toLowerCase(Locale.ROOT).equals(channel))
throw new IllegalArgumentException("Channel name can be lower case ONLY");
return !channel.isEmpty()
&& masterStack.hasTagCompound()
&& masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
&& masterStack
.stackTagCompound
.getCompoundTag(SECONDARY_HINT_TAG)
.hasKey(channel, NBT.TAG_INT);
}
/**
* Get the subchannel data from given trigger item. Will use master channel instead if not present.
* @param masterStack trigger stack to query from
* @param channel channel identifier.
* Note: all channel identifiers are supposed to be lower case and not empty.
* @return channel data
*/
public static int getChannelData(ItemStack masterStack, String channel) {
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
if (StructureLibAPI.isDebugEnabled()
&& !channel.toLowerCase(Locale.ROOT).equals(channel))
throw new IllegalArgumentException("Channel name can be lower case ONLY");
if (!masterStack.hasTagCompound()
|| !masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
|| !masterStack
.stackTagCompound
.getCompoundTag(SECONDARY_HINT_TAG)
.hasKey(channel, NBT.TAG_INT)) return masterStack.stackSize;
return masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).getInteger(channel);
}
/**
* Set the subchannel data on given trigger item
* @param masterStack trigger stack to check
* @param channel channel identifier.
* Note: all channel identifiers are supposed to be lower case and not empty.
* @param data subchannel data. should always be a positive value
*/
public static void setChannelData(ItemStack masterStack, String channel, int data) {
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
if (StructureLibAPI.isDebugEnabled()
&& !channel.toLowerCase(Locale.ROOT).equals(channel))
throw new IllegalArgumentException("Channel name can be lower case ONLY");
if (data <= 0) throw new IllegalArgumentException();
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
NBTTagCompound main = masterStack.stackTagCompound;
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
main.getCompoundTag(SECONDARY_HINT_TAG).setInteger(channel, data);
}
/**
* Clear the given subchannel from given trigger item, if it exists
* @param masterStack trigger stack to unset
* @param channel channel identifier.
* Note: all channel identifiers are supposed to be lower case and not empty.
*/
public static void unsetChannelData(ItemStack masterStack, String channel) {
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
if (StructureLibAPI.isDebugEnabled()
&& !channel.toLowerCase(Locale.ROOT).equals(channel))
throw new IllegalArgumentException("Channel name can be lower case ONLY");
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
NBTTagCompound main = masterStack.stackTagCompound;
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
NBTTagCompound tag = main.getCompoundTag(SECONDARY_HINT_TAG);
tag.removeTag(channel);
if (tag.hasNoTags()) main.removeTag(SECONDARY_HINT_TAG);
if (main.hasNoTags()) masterStack.stackTagCompound = null;
}
/**
* Wipe all subchannel data on given trigger item
* @param masterStack trigger stack to wipe
*/
public static void wipeChannelData(ItemStack masterStack) {
if (masterStack == null) throw new IllegalArgumentException();
if (masterStack.stackTagCompound != null) masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
}
/**
* Iterate over all subchannel data on trigger stack. Does not include master channel!!
* @param masterStack trigger stack to check
* @return A java8 stream of pairs. Key is channel identifier and value is channel data. Pairs do not support
* mutation nor removal. Can still cause {@link java.util.ConcurrentModificationException} if underlying channel
* data is modified while stream is working.
*/
public static Stream<Entry<String, Integer>> iterateChannelData(ItemStack masterStack) {
if (!hasSubChannel(masterStack)) return Stream.empty();
NBTTagCompound tag = masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG);
return getTagKeys(tag).stream().map(s -> new ImmutablePair<>(s, tag.getInteger(s)));
}
/**
* Get the number of subchannels present on given trigger item. Does not include master channel.
* @param masterStack trigger stack to query from
* @return subchannel count
*/
public static int countChannelData(ItemStack masterStack) {
if (!hasSubChannel(masterStack)) return 0;
NBTTagCompound tag = masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG);
return tag.func_150296_c().size();
}
}
@@ -1,82 +1,116 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import com.gtnewhorizon.structurelib.ConfigurationHandler;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import java.util.WeakHashMap;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.util.ForgeDirection;
public class ConstructableUtility {
private ConstructableUtility() {
}
private static final WeakHashMap<EntityPlayerMP, Long> lastUse = new WeakHashMap<>();
private static long clientSideLastUse = 0;
public static boolean handle(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
private ConstructableUtility() {}
public static boolean handle(
ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
StructureLibAPI.startHinting(aWorld);
boolean ret = handle0(aStack, aPlayer, aWorld, aX, aY, aZ, aSide);
StructureLibAPI.endHinting(aWorld);
return ret;
}
private static boolean handle0(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
private static boolean handle0(
ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ);
if (tTileEntity == null || aPlayer instanceof FakePlayer) {
return aPlayer instanceof EntityPlayerMP;
}
if (aPlayer instanceof EntityPlayerMP) {
//struct gen
if (aPlayer.isSneaking() && aPlayer.capabilities.isCreativeMode) {
if (tTileEntity instanceof IConstructableProvider) {
IConstructable constructable = ((IConstructableProvider) tTileEntity).getConstructable();
if (constructable != null) {
constructable.construct(aStack, false);
}
} else if (tTileEntity instanceof IConstructable) {
((IConstructable) tTileEntity).construct(aStack, false);
} else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) {
IMultiblockInfoContainer<TileEntity> iMultiblockInfoContainer = IMultiblockInfoContainer.get(tTileEntity.getClass());
if (tTileEntity instanceof IAlignment) {
iMultiblockInfoContainer.construct(aStack, false, tTileEntity,
((IAlignment) tTileEntity).getExtendedFacing());
} else {
iMultiblockInfoContainer.construct(aStack, false, tTileEntity,
ExtendedFacing.of(ForgeDirection.getOrientation(aSide)));
}
// not sneaking - client side will generate hologram. nothing to do on server side
if (!aPlayer.isSneaking()) return true;
long timePast = System.currentTimeMillis() - getLastUseMilis(aPlayer);
if (timePast < ConfigurationHandler.INSTANCE.getAutoPlaceInterval()) {
aPlayer.addChatComponentMessage(new ChatComponentTranslation(
"item.structurelib.constructableTrigger.too_fast",
ConfigurationHandler.INSTANCE.getAutoPlaceInterval() - timePast));
return true;
}
} else if (!StructureLib.isCurrentPlayer(aPlayer)) {
return false;
}
IConstructable constructable = null;
if (tTileEntity instanceof IConstructableProvider)
constructable = ((IConstructableProvider) tTileEntity).getConstructable();
else if (tTileEntity instanceof IConstructable) {
constructable = (IConstructable) tTileEntity;
} else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) {
ExtendedFacing facing = tTileEntity instanceof IAlignment
? ((IAlignment) tTileEntity).getExtendedFacing()
: ExtendedFacing.of(ForgeDirection.getOrientation(aSide));
constructable = IMultiblockInfoContainer.<TileEntity>get(tTileEntity.getClass())
.toConstructable(tTileEntity, facing);
}
if (constructable == null) return false;
if (aPlayer instanceof EntityPlayerMP) {
EntityPlayerMP playerMP = (EntityPlayerMP) aPlayer;
// server side and sneaking (already checked above)
// do construct
if (aPlayer.capabilities.isCreativeMode) {
constructable.construct(aStack, false);
} else if (constructable instanceof ISurvivalConstructable) {
int built = ((ISurvivalConstructable) constructable)
.survivalConstruct(
aStack,
ConfigurationHandler.INSTANCE.getAutoPlaceBudget(),
IItemSource.fromPlayer(playerMP),
playerMP);
if (built > 0) {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.built_stat", built));
} else if (built == -1) {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.complete"));
}
setLastUseMilis(aPlayer);
} else {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.error.not_enabled"));
}
return true;
} else if (StructureLib.isCurrentPlayer(aPlayer)) {//particles and text client side
//if ((!aPlayer.isSneaking() || !aPlayer.capabilities.isCreativeMode)) {
if (tTileEntity instanceof IConstructableProvider) {
IConstructable constructable = ((IConstructableProvider) tTileEntity).getConstructable();
if (constructable != null) {
constructable.construct(aStack, true);
StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack));
}
} else if (tTileEntity instanceof IConstructable) {
IConstructable constructable = (IConstructable) tTileEntity;
constructable.construct(aStack, true);
StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack));
return false;
} else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) {
IMultiblockInfoContainer<TileEntity> iMultiblockInfoContainer = IMultiblockInfoContainer.get(tTileEntity.getClass());
if (tTileEntity instanceof IAlignment) {
iMultiblockInfoContainer.construct(aStack, true, tTileEntity,
((IAlignment) tTileEntity).getExtendedFacing());
} else {
iMultiblockInfoContainer.construct(aStack, true, tTileEntity,
ExtendedFacing.of(ForgeDirection.getOrientation(aSide)));
}
StructureLib.addClientSideChatMessages(IMultiblockInfoContainer.get(tTileEntity.getClass()).getDescription(aStack));
return false;
}
}
// client side
// particles and text
constructable.construct(aStack, true);
if (System.currentTimeMillis() - getLastUseMilis(aPlayer) >= 300)
StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack));
setLastUseMilis(aPlayer);
return false;
}
private static void setLastUseMilis(EntityPlayer aPlayer) {
if (!(aPlayer instanceof EntityPlayerMP))
// assume client side
clientSideLastUse = System.currentTimeMillis();
else lastUse.put((EntityPlayerMP) aPlayer, System.currentTimeMillis());
}
private static long getLastUseMilis(EntityPlayer aPlayer) {
if (!(aPlayer instanceof EntityPlayerMP))
// assume client side
return clientSideLastUse;
return lastUse.getOrDefault(aPlayer, 0L);
}
}
@@ -13,4 +13,3 @@ public interface IConstructable {
@SideOnly(Side.CLIENT)
String[] getStructureDescription(ItemStack stackSize);
}
@@ -6,9 +6,9 @@ import javax.annotation.Nullable;
* Implement this interface if this tile entity MIGHT be constructable
*/
public interface IConstructableProvider {
/**
* @return null if not constructable, an instance otherwise.
*/
@Nullable
IConstructable getConstructable();
/**
* @return null if not constructable, an instance otherwise.
*/
@Nullable
IConstructable getConstructable();
}
@@ -1,12 +1,14 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.HashMap;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import java.util.HashMap;
import net.minecraft.world.World;
/**
* To implement IConstructable on not own TileEntities
@@ -33,6 +35,59 @@ public interface IMultiblockInfoContainer<T> {
void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide);
/**
* Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
* @return -1 if done, a helping pointer
*/
default int survivalConstruct(
ItemStack stackSize,
int elementBudge,
IItemSource source,
EntityPlayerMP actorProfile,
T tileEntity,
ExtendedFacing aSide) {
return -1;
}
@SideOnly(Side.CLIENT)
String[] getDescription(ItemStack stackSize);
/**
* Override this and return {@link #toConstructable(IMultiblockInfoContainer, Object, ExtendedFacing)} if
* {@link #survivalConstruct(ItemStack, int, IItemSource, EntityPlayerMP, Object, ExtendedFacing)} isn't a stub
*/
default IConstructable toConstructable(T tileEntity, ExtendedFacing aSide) {
return new IConstructable() {
@Override
public void construct(ItemStack stackSize, boolean hintsOnly) {
IMultiblockInfoContainer.this.construct(stackSize, hintsOnly, tileEntity, aSide);
}
@Override
public String[] getStructureDescription(ItemStack stackSize) {
return IMultiblockInfoContainer.this.getDescription(stackSize);
}
};
}
static <T> ISurvivalConstructable toConstructable(
IMultiblockInfoContainer<T> thiz, T tileEntity, ExtendedFacing aSide) {
return new ISurvivalConstructable() {
@Override
public int survivalConstruct(
ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor) {
return thiz.survivalConstruct(stackSize, elementBudget, source, actor, tileEntity, aSide);
}
@Override
public void construct(ItemStack stackSize, boolean hintsOnly) {
thiz.construct(stackSize, hintsOnly, tileEntity, aSide);
}
@Override
public String[] getStructureDescription(ItemStack stackSize) {
return thiz.getDescription(stackSize);
}
};
}
}
@@ -0,0 +1,17 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
public interface ISurvivalConstructable extends IConstructable {
/**
* Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
*
* @param elementBudget The server configured element budget. You can tune this up a bit if the structure is too big, but
* generally should not be a 4 digits number to not overwhelm the server
* @return -1 if done, otherwise number of elements placed this round
*/
int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor);
}
@@ -0,0 +1,4 @@
/**
* This package contains interfaces and utility methods for autobuilding a structure.
*/
package com.gtnewhorizon.structurelib.alignment.constructable;
@@ -13,11 +13,11 @@ public enum Direction {
private final ForgeDirection forgeDirection;
private final Vec3Impl axisVector;
public static final Direction[] VALUES=values();
public static final Direction[] VALUES = values();
Direction(ForgeDirection forgeDirection) {
this.forgeDirection = forgeDirection;
axisVector=new Vec3Impl(forgeDirection.offsetX,forgeDirection.offsetY,forgeDirection.offsetZ);
axisVector = new Vec3Impl(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ);
}
public ForgeDirection getForgeDirection() {
@@ -28,7 +28,7 @@ public enum Direction {
return axisVector;
}
public static Vec3Impl getAxisVector(ForgeDirection forgeDirection){
public static Vec3Impl getAxisVector(ForgeDirection forgeDirection) {
return VALUES[forgeDirection.ordinal()].axisVector;
}
}
@@ -1,20 +1,20 @@
package com.gtnewhorizon.structurelib.alignment.enumerable;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import net.minecraft.util.Vec3;
import net.minecraftforge.common.util.ForgeDirection;
import java.util.*;
import static com.gtnewhorizon.structurelib.alignment.IAlignment.FLIPS_COUNT;
import static com.gtnewhorizon.structurelib.alignment.IAlignment.ROTATIONS_COUNT;
import static java.lang.Math.abs;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.*;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import java.util.*;
import net.minecraft.util.StatCollector;
import net.minecraft.util.Vec3;
import net.minecraftforge.common.util.ForgeDirection;
public enum ExtendedFacing {
DOWN_NORMAL_NONE("down normal none"),
DOWN_NORMAL_HORIZONTAL("down normal horizontal"),
@@ -113,25 +113,40 @@ public enum ExtendedFacing {
EAST_COUNTER_CLOCKWISE_VERTICAL("east counter clockwise vertical"),
EAST_COUNTER_CLOCKWISE_BOTH("east counter clockwise both");
public static final ExtendedFacing DEFAULT=NORTH_NORMAL_NONE;
public static final ExtendedFacing DEFAULT = NORTH_NORMAL_NONE;
public static final ExtendedFacing[] VALUES = values();
public static final Map<ForgeDirection, List<ExtendedFacing>> FOR_FACING=new HashMap<>();
public static final Map<ForgeDirection, List<ExtendedFacing>> FOR_FACING = new HashMap<>();
public static final int STATES_COUNT = VALUES.length;
static {
stream(values()).forEach(extendedFacing ->
FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> {
if (extendedFacings == null) {
extendedFacings = new ArrayList<>();
}
extendedFacings.add(extendedFacing);
return extendedFacings;
})));
stream(values())
.forEach(extendedFacing ->
FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> {
if (extendedFacings == null) {
extendedFacings = new ArrayList<>();
}
extendedFacings.add(extendedFacing);
return extendedFacings;
})));
}
private static final Map<String, ExtendedFacing> NAME_LOOKUP = stream(VALUES).collect(toMap(ExtendedFacing::getName2, (extendedFacing) -> extendedFacing));
private static final EnumMap<ForgeDirection, ImmutableSet<ExtendedFacing>> LOOKUP_BY_DIRECTION = stream(VALUES).collect(groupingBy(ExtendedFacing::getDirection, () -> new EnumMap<>(ForgeDirection.class), collectingAndThen(toSet(), ImmutableSet::copyOf)));
private static final EnumMap<Rotation, ImmutableSet<ExtendedFacing>> LOOKUP_BY_ROTATION = stream(VALUES).collect(groupingBy(ExtendedFacing::getRotation, () -> new EnumMap<>(Rotation.class), collectingAndThen(toSet(), ImmutableSet::copyOf)));
private static final EnumMap<Flip, ImmutableSet<ExtendedFacing>> LOOKUP_BY_FLIP = stream(VALUES).collect(groupingBy(ExtendedFacing::getFlip, () -> new EnumMap<>(Flip.class), collectingAndThen(toSet(), ImmutableSet::copyOf)));
private static final Map<String, ExtendedFacing> NAME_LOOKUP =
stream(VALUES).collect(toMap(ExtendedFacing::getName2, (extendedFacing) -> extendedFacing));
private static final EnumMap<ForgeDirection, ImmutableSet<ExtendedFacing>> LOOKUP_BY_DIRECTION = stream(VALUES)
.collect(groupingBy(
ExtendedFacing::getDirection,
() -> new EnumMap<>(ForgeDirection.class),
collectingAndThen(toSet(), ImmutableSet::copyOf)));
private static final EnumMap<Rotation, ImmutableSet<ExtendedFacing>> LOOKUP_BY_ROTATION = stream(VALUES)
.collect(groupingBy(
ExtendedFacing::getRotation,
() -> new EnumMap<>(Rotation.class),
collectingAndThen(toSet(), ImmutableSet::copyOf)));
private static final EnumMap<Flip, ImmutableSet<ExtendedFacing>> LOOKUP_BY_FLIP = stream(VALUES)
.collect(groupingBy(
ExtendedFacing::getFlip,
() -> new EnumMap<>(Flip.class),
collectingAndThen(toSet(), ImmutableSet::copyOf)));
private final ForgeDirection direction;
private final ForgeDirection a, b, c;
private final Rotation rotation;
@@ -142,133 +157,146 @@ public enum ExtendedFacing {
ExtendedFacing(String name) {
this.name = name;
direction= Direction.VALUES[ordinal()/(ROTATIONS_COUNT*FLIPS_COUNT)].getForgeDirection();
rotation=Rotation.VALUES[ordinal()/FLIPS_COUNT-direction.ordinal()*ROTATIONS_COUNT];
flip=Flip.VALUES[ordinal()%FLIPS_COUNT];
ForgeDirection a,b,c;
switch (direction){
direction = Direction.VALUES[ordinal() / (ROTATIONS_COUNT * FLIPS_COUNT)].getForgeDirection();
rotation = Rotation.VALUES[ordinal() / FLIPS_COUNT - direction.ordinal() * ROTATIONS_COUNT];
flip = Flip.VALUES[ordinal() % FLIPS_COUNT];
ForgeDirection a, b, c;
switch (direction) {
case DOWN:
a= ForgeDirection.WEST;
b= ForgeDirection.SOUTH;
c= ForgeDirection.UP;
a = ForgeDirection.WEST;
b = ForgeDirection.SOUTH;
c = ForgeDirection.UP;
break;
case UP:
a= ForgeDirection.EAST;
b= ForgeDirection.SOUTH;
c= ForgeDirection.DOWN;
a = ForgeDirection.EAST;
b = ForgeDirection.SOUTH;
c = ForgeDirection.DOWN;
break;
case NORTH:
a= ForgeDirection.WEST;
b= ForgeDirection.DOWN;
c= ForgeDirection.SOUTH;
a = ForgeDirection.WEST;
b = ForgeDirection.DOWN;
c = ForgeDirection.SOUTH;
break;
case SOUTH:
a= ForgeDirection.EAST;
b= ForgeDirection.DOWN;
c= ForgeDirection.NORTH;
a = ForgeDirection.EAST;
b = ForgeDirection.DOWN;
c = ForgeDirection.NORTH;
break;
case WEST:
a= ForgeDirection.SOUTH;
b= ForgeDirection.DOWN;
c= ForgeDirection.EAST;
a = ForgeDirection.SOUTH;
b = ForgeDirection.DOWN;
c = ForgeDirection.EAST;
break;
case EAST:
a= ForgeDirection.NORTH;
b= ForgeDirection.DOWN;
c= ForgeDirection.WEST;
a = ForgeDirection.NORTH;
b = ForgeDirection.DOWN;
c = ForgeDirection.WEST;
break;
default:throw new RuntimeException("Is impossible...");
default:
throw new RuntimeException("Is impossible...");
}
switch (flip){//This duplicates some axis swaps since flip boolean would do, but seems more convenient to use
switch (flip) { // This duplicates some axis swaps since flip boolean would do, but seems more convenient to use
case HORIZONTAL:
a=a.getOpposite();
a = a.getOpposite();
break;
case BOTH:
a=a.getOpposite();
a = a.getOpposite();
case VERTICAL:
b=b.getOpposite();
b = b.getOpposite();
break;
case NONE: break;
default:throw new RuntimeException("Even more impossible...");
case NONE:
break;
default:
throw new RuntimeException("Even more impossible...");
}
switch (rotation) {
case CLOCKWISE: {
ForgeDirection _a=a;
a =b;
b =_a.getOpposite();
ForgeDirection _a = a;
a = b;
b = _a.getOpposite();
break;
}
case UPSIDE_DOWN:
a=a.getOpposite();
b=b.getOpposite();
a = a.getOpposite();
b = b.getOpposite();
break;
case COUNTER_CLOCKWISE: {
ForgeDirection _a=a;
a =b.getOpposite();
b =_a;
ForgeDirection _a = a;
a = b.getOpposite();
b = _a;
break;
}
case NORMAL: break;
case NORMAL:
break;
default:
throw new RuntimeException("More impossible...");
}
this.a=a;
this.b=b;
this.c=c;
integerAxisSwap =new IntegerAxisSwap(a,b,c);
this.a = a;
this.b = b;
this.c = c;
integerAxisSwap = new IntegerAxisSwap(a, b, c);
}
public static ExtendedFacing of(ForgeDirection direction, Rotation rotation, Flip flip){
if(direction==ForgeDirection.UNKNOWN){
public static ExtendedFacing of(ForgeDirection direction, Rotation rotation, Flip flip) {
if (direction == ForgeDirection.UNKNOWN) {
return VALUES[IAlignment.getAlignmentIndex(ForgeDirection.NORTH, rotation, flip)];
}
return VALUES[IAlignment.getAlignmentIndex(direction, rotation, flip)];
}
public static ExtendedFacing of(ForgeDirection direction){
if(direction==ForgeDirection.UNKNOWN){
public static ExtendedFacing of(ForgeDirection direction) {
if (direction == ForgeDirection.UNKNOWN) {
return DEFAULT;
}
return VALUES[IAlignment.getAlignmentIndex(direction, Rotation.NORMAL, Flip.NONE)];
}
public static ImmutableSet<ExtendedFacing> getAllWith(ForgeDirection direction) { return LOOKUP_BY_DIRECTION.get(direction);}
public static ImmutableSet<ExtendedFacing> getAllWith(Rotation rotation) { return LOOKUP_BY_ROTATION.get(rotation);}
public static ImmutableSet<ExtendedFacing> getAllWith(Flip flip) { return LOOKUP_BY_FLIP.get(flip);}
public ExtendedFacing with(ForgeDirection direction){
return of(direction,rotation,flip);
public static ImmutableSet<ExtendedFacing> getAllWith(ForgeDirection direction) {
return LOOKUP_BY_DIRECTION.get(direction);
}
public ExtendedFacing with(Rotation rotation){
return of(direction,rotation,flip);
public static ImmutableSet<ExtendedFacing> getAllWith(Rotation rotation) {
return LOOKUP_BY_ROTATION.get(rotation);
}
public ExtendedFacing with(Flip flip){
return of(direction,rotation,flip);
public static ImmutableSet<ExtendedFacing> getAllWith(Flip flip) {
return LOOKUP_BY_FLIP.get(flip);
}
public ExtendedFacing with(ForgeDirection direction) {
return of(direction, rotation, flip);
}
public ExtendedFacing with(Rotation rotation) {
return of(direction, rotation, flip);
}
public ExtendedFacing with(Flip flip) {
return of(direction, rotation, flip);
}
public ExtendedFacing getOppositeDirection() {
return of(direction.getOpposite(),rotation,flip);
return of(direction.getOpposite(), rotation, flip);
}
public ExtendedFacing getOppositeRotation() {
return of(direction,rotation.getOpposite(),flip);
return of(direction, rotation.getOpposite(), flip);
}
public ExtendedFacing getOppositeFlip() {
return of(direction,rotation,flip.getOpposite());
return of(direction, rotation, flip.getOpposite());
}
/**
* Gets the same effective facing achieved by different rot/flip combo
*
* @return same effective facing, but different enum value
*/
public ExtendedFacing getDuplicate(){
return of(direction,rotation.getOpposite(),flip.getOpposite());
public ExtendedFacing getDuplicate() {
return of(direction, rotation.getOpposite(), flip.getOpposite());
}
public int getIndex(){
public int getIndex() {
return ordinal();
}
@@ -276,6 +304,10 @@ public enum ExtendedFacing {
return this.name;
}
public String getLocalizedName() {
return StatCollector.translateToLocal("structurelib.facing." + getIndex());
}
public static ExtendedFacing byName(String name) {
return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT));
}
@@ -302,39 +334,46 @@ public enum ExtendedFacing {
/**
* Translates relative to front facing offset to world offset
* @param abcOffset A,B,C offset (facing relative L-->R,U-->D,F-->B)
* @return X,Y,Z offset in world
*
* @param abcOffset A,B,C offset (facing relative {@code L-->R,U-->D,F-->B})
* @return X, Y, Z offset in world
*/
public Vec3 getWorldOffset(Vec3 abcOffset) {
return integerAxisSwap.inverseTranslate(abcOffset);
}
public Vec3Impl getWorldOffset(Vec3Impl abcOffset) {
return integerAxisSwap.inverseTranslate(abcOffset);
}
public void getWorldOffset(int[] point,int[] out){
integerAxisSwap.inverseTranslate(point,out);
}
public void getWorldOffset(double[] point,double[] out){
integerAxisSwap.inverseTranslate(point,out);
public void getWorldOffset(int[] point, int[] out) {
integerAxisSwap.inverseTranslate(point, out);
}
public void getWorldOffset(double[] point, double[] out) {
integerAxisSwap.inverseTranslate(point, out);
}
/**
* Translates world offset to relative front facing offset
*
* @param xyzOffset X,Y,Z offset in world
* @return A,B,C offset (facing relative L-->R,U-->D,F-->B)
* @return A, B, C offset (facing relative {@code L-->R,U-->D,F-->B})
*/
public Vec3 getOffsetABC(Vec3 xyzOffset){
public Vec3 getOffsetABC(Vec3 xyzOffset) {
return integerAxisSwap.translate(xyzOffset);
}
public Vec3Impl getOffsetABC(Vec3Impl xyzOffset){
public Vec3Impl getOffsetABC(Vec3Impl xyzOffset) {
return integerAxisSwap.translate(xyzOffset);
}
public void getOffsetABC(int[] point,int[] out){
integerAxisSwap.translate(point,out);
public void getOffsetABC(int[] point, int[] out) {
integerAxisSwap.translate(point, out);
}
public void getOffsetABC(double[] point,double[] out){
integerAxisSwap.translate(point,out);
public void getOffsetABC(double[] point, double[] out) {
integerAxisSwap.translate(point, out);
}
public IntegerAxisSwap getIntegerAxisSwap() {
@@ -1,14 +1,14 @@
package com.gtnewhorizon.structurelib.alignment.enumerable;
import javax.annotation.Nonnull;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import static java.lang.Math.abs;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import javax.annotation.Nonnull;
public enum Flip {
NONE(3, "none"),
HORIZONTAL(2, "horizontal"),
@@ -26,7 +26,7 @@ public enum Flip {
this.name = nameIn;
}
public int getIndex(){
public int getIndex() {
return ordinal();
}
@@ -58,19 +58,19 @@ public enum Flip {
return this.name;
}
public boolean isNotFlipped(){
return this==NONE;
public boolean isNotFlipped() {
return this == NONE;
}
public boolean isBothFlipped(){
return this==BOTH;
public boolean isBothFlipped() {
return this == BOTH;
}
public boolean isHorizontallyFlipped() {
return this==HORIZONTAL || isBothFlipped();
return this == HORIZONTAL || isBothFlipped();
}
public boolean isVerticallyFliped() {
return this==VERTICAL || isBothFlipped();
return this == VERTICAL || isBothFlipped();
}
}
@@ -1,14 +1,14 @@
package com.gtnewhorizon.structurelib.alignment.enumerable;
import javax.annotation.Nonnull;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import static java.lang.Math.abs;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import javax.annotation.Nonnull;
public enum Rotation {
NORMAL(2, "normal"),
CLOCKWISE(3, "clockwise"),
@@ -19,14 +19,15 @@ public enum Rotation {
private final String name;
public static final Rotation[] VALUES = values();
private static final Map<String, Rotation> NAME_LOOKUP = stream(VALUES).collect(toMap(Rotation::getName2, (rotation) -> rotation));
private static final Map<String, Rotation> NAME_LOOKUP =
stream(VALUES).collect(toMap(Rotation::getName2, (rotation) -> rotation));
Rotation(int oppositeIn, String nameIn) {
this.opposite = oppositeIn;
this.name = nameIn;
}
public int getIndex(){
public int getIndex() {
return ordinal();
}
@@ -58,19 +59,19 @@ public enum Rotation {
return this.name;
}
public boolean isNotRotated(){
return this==NORMAL;
public boolean isNotRotated() {
return this == NORMAL;
}
public boolean isClockwise(){
return this==CLOCKWISE;
public boolean isClockwise() {
return this == CLOCKWISE;
}
public boolean isCounterClockwise(){
return this==COUNTER_CLOCKWISE;
public boolean isCounterClockwise() {
return this == COUNTER_CLOCKWISE;
}
public boolean isUpsideDown(){
return this==UPSIDE_DOWN;
public boolean isUpsideDown() {
return this == UPSIDE_DOWN;
}
}
@@ -1,5 +1,7 @@
/**
* This package provide a framework for rotating a given block, or a multiblock structure.
* <p>
* All public stuff in this package and its subpackages are considered part of the public API.
* Backwards compatibility is maintained to the maximum extend possible.
*/
package com.gtnewhorizon.structurelib.alignment;
package com.gtnewhorizon.structurelib.alignment;
@@ -4,6 +4,7 @@ import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.texture.IIconRegister;
@@ -13,55 +14,53 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import java.util.List;
public class BlockHint extends Block {
private static final IIcon[] hint = new IIcon[16];
private static final IIcon[] hint = new IIcon[16];
public BlockHint() {
super(Material.iron);
setBlockName("structurelib.blockhint");
setCreativeTab(StructureLib.creativeTab);
}
public BlockHint() {
super(Material.iron);
setBlockName("structurelib.blockhint");
setCreativeTab(StructureLib.creativeTab);
}
@Override
public void registerBlockIcons(IIconRegister aIconRegister) {
//super.registerBlockIcons(aIconRegister);
hint[0] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_0");
hint[1] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_1");
hint[2] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_2");
hint[3] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_3");
hint[4] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_4");
hint[5] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_5");
hint[6] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_6");
hint[7] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_7");
hint[8] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_8");
hint[9] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_9");
hint[10] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_10");
hint[11] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_11");
hint[12] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_DEFAULT");
hint[13] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_AIR");
hint[14] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_NOAIR");
hint[15] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_ERROR");
}
@Override
public void registerBlockIcons(IIconRegister aIconRegister) {
// super.registerBlockIcons(aIconRegister);
hint[0] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_0");
hint[1] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_1");
hint[2] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_2");
hint[3] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_3");
hint[4] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_4");
hint[5] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_5");
hint[6] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_6");
hint[7] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_7");
hint[8] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_8");
hint[9] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_9");
hint[10] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_10");
hint[11] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_11");
hint[12] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_DEFAULT");
hint[13] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_AIR");
hint[14] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_NOAIR");
hint[15] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_ERROR");
}
@Override
public IIcon getIcon(int aSide, int aMeta) {
return hint[aMeta];
}
@Override
public IIcon getIcon(int aSide, int aMeta) {
return hint[aMeta];
}
@Override
@SideOnly(Side.CLIENT)
public IIcon getIcon(IBlockAccess aWorld, int xCoord, int yCoord, int zCoord, int aSide) {
int tMeta = aWorld.getBlockMetadata(xCoord, yCoord, zCoord);
return getIcon(aSide, tMeta);
}
@Override
@SideOnly(Side.CLIENT)
public IIcon getIcon(IBlockAccess aWorld, int xCoord, int yCoord, int zCoord, int aSide) {
int tMeta = aWorld.getBlockMetadata(xCoord, yCoord, zCoord);
return getIcon(aSide, tMeta);
}
@Override
@SuppressWarnings("unchecked")
public void getSubBlocks(Item aItem, CreativeTabs par2CreativeTabs, List aList) {
for (int i = 0; i <= 15; i++) {
aList.add(new ItemStack(aItem, 1, i));
}
}
@Override
@SuppressWarnings("unchecked")
public void getSubBlocks(Item aItem, CreativeTabs par2CreativeTabs, List aList) {
for (int i = 0; i <= 15; i++) {
aList.add(new ItemStack(aItem, 1, i));
}
}
}
@@ -0,0 +1,121 @@
package com.gtnewhorizon.structurelib.command;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.WrongUsageException;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.ChatComponentTranslation;
public class CommandConfigureChannels extends CommandBase {
@Override
public String getCommandName() {
return "sl_channel";
}
@Override
public String getCommandUsage(ICommandSender p_71518_1_) {
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
+ "/sl_channel <set> <channel name> <value>\n"
+ "<channel name> can have space in it. <value> must be positive integer";
}
@Override
public int getRequiredPermissionLevel() {
return 0;
}
@Override
public List<?> addTabCompletionOptions(ICommandSender p_71516_1_, String[] p_71516_2_) {
if (p_71516_2_.length == 1)
return getListOfStringsMatchingLastWord(p_71516_2_, "get", "set", "unset", "wipe", "getall");
return null;
}
@Override
public void processCommand(ICommandSender sender, String[] args) {
if (args.length == 0) throw new WrongUsageException(getCommandUsage(sender));
if (!(sender instanceof EntityPlayerMP)) throw new WrongUsageException("must be a player");
EntityPlayerMP player = (EntityPlayerMP) sender;
ItemStack heldItem = player.getHeldItem();
if (heldItem == null || !(heldItem.getItem() instanceof ItemConstructableTrigger)) {
throw new WrongUsageException("must hold a hologram");
}
String channel;
switch (args[0]) {
case "getall":
if (!ChannelDataAccessor.hasSubChannel(heldItem)) {
sender.addChatMessage(new ChatComponentText("No subchannel"));
} else {
sender.addChatMessage(new ChatComponentTranslation(
"item.structurelib.constructableTrigger.desc.lshift.0",
ChannelDataAccessor.countChannelData(heldItem)));
ChannelDataAccessor.iterateChannelData(heldItem)
.map(e -> e.getKey() + ": " + e.getValue())
.map(ChatComponentText::new)
.forEach(player::addChatMessage);
}
return;
case "wipe":
ChannelDataAccessor.wipeChannelData(heldItem);
return;
case "get":
case "unset":
if (args.length < 2) throw new WrongUsageException(getCommandUsage(sender));
channel =
args.length == 2 ? args[1] : Arrays.stream(args).skip(1).collect(Collectors.joining());
break;
case "set":
if (args.length < 3) throw new WrongUsageException(getCommandUsage(sender));
channel = args.length == 3
? args[1]
: Arrays.stream(args).skip(1).limit(args.length - 2).collect(Collectors.joining());
break;
default:
throw new WrongUsageException(getCommandUsage(sender));
}
switch (args[0]) {
case "get":
if (ChannelDataAccessor.hasSubChannel(heldItem, channel))
sender.addChatMessage(new ChatComponentText(
channel + " value: " + ChannelDataAccessor.getChannelData(heldItem, channel)));
else sender.addChatMessage(new ChatComponentText(channel + " value: N/A"));
break;
case "clear":
if (ChannelDataAccessor.hasSubChannel(heldItem, channel))
sender.addChatMessage(new ChatComponentText(channel + " no value"));
else {
ChannelDataAccessor.unsetChannelData(heldItem, channel);
sender.addChatMessage(new ChatComponentText(channel + " cleared"));
player.inventoryContainer.detectAndSendChanges();
}
break;
case "set":
int value;
try {
value = Integer.parseInt(args[args.length - 1]);
} catch (NumberFormatException e) {
throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
}
if (value <= 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
ChannelDataAccessor.setChannelData(heldItem, channel, value);
player.inventoryContainer.detectAndSendChanges();
sender.addChatMessage(new ChatComponentText(channel + " value: " + value));
break;
}
}
}
@@ -1,140 +0,0 @@
package com.gtnewhorizon.structurelib.entity.fx;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.proxy.ClientProxy;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.init.Blocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
@SideOnly(Side.CLIENT)
public class EntityFXBlockHint extends EntityFX {
private IIcon[] icons;
private short[] mRGBa = {255,255,255,0};
public EntityFXBlockHint(World world){
this(world,0,0,0, Blocks.stone,0);
}
/**
*
* @param world
* @param x
* @param y
* @param z
* @param icons DOWN, UP, NORTH, SOUTH, WEST, EAST
*/
public EntityFXBlockHint(World world, int x, int y, int z, IIcon[] icons) {
super(world, x + .25, y + .5, z + .25);
particleGravity = 0;
prevPosX = posX;
prevPosY = posY;
prevPosZ = posZ;
noClip = true;
particleMaxAge = 2000 + StructureLib.RANDOM.nextInt(200);
this.icons=icons;
}
public EntityFXBlockHint(World world, int x, int y, int z, Block block, int meta) {
super(world, x+.25, y+.5, z+.25);
particleGravity = 0;
prevPosX = posX;
prevPosY = posY;
prevPosZ = posZ;
noClip = true;
particleMaxAge = 2000 + StructureLib.RANDOM.nextInt(200);
icons = new IIcon[6];
for (int i = 0; i < 6; i++) {
icons[i] = block.getIcon(i, meta);
}
}
public EntityFXBlockHint withColorTint(short[] coloure){
this.mRGBa =coloure;
return this;
}
@Override
public void renderParticle(Tessellator tes, float subTickTime, float p_70539_3_, float p_70539_4_, float p_70539_5_, float p_70539_6_, float p_70539_7_) {
float size = .5f;
float X = (float) (prevPosX + (posX - prevPosX) * (double) subTickTime - EntityFX.interpPosX);
float Y = (float) (prevPosY + (posY - prevPosY) * (double) subTickTime - EntityFX.interpPosY) - size / 2;
float Z = (float) (prevPosZ + (posZ - prevPosZ) * (double) subTickTime - EntityFX.interpPosZ);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glDepthMask(false);
tes.setColorRGBA((int) (mRGBa[0] * .9F), (int) (mRGBa[1] * .95F), (int) (mRGBa[2] * 1F), 192);
//var8, var9 - X U
//var 10, var 11 - Y V
for(int i=0;i<6;i++){
if(icons[i]==null) {
continue;
}
double u=icons[i].getMinU();
double U=icons[i].getMaxU();
double v=icons[i].getMinV();
double V=icons[i].getMaxV();
switch (i){//{DOWN, UP, NORTH, SOUTH, WEST, EAST}
case 0:
tes.addVertexWithUV(X, Y, Z + size, u, V);
tes.addVertexWithUV(X, Y, Z, u, v);
tes.addVertexWithUV(X + size, Y, Z, U, v);
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
break;
case 1:
tes.addVertexWithUV(X, Y + size, Z, u, v);
tes.addVertexWithUV(X, Y + size, Z + size, u, V);
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
break;
case 2:
tes.addVertexWithUV(X, Y, Z, U, V);
tes.addVertexWithUV(X, Y + size, Z, U, v);
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
tes.addVertexWithUV(X + size, Y, Z, u, V);
break;
case 3:
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
tes.addVertexWithUV(X + size, Y + size, Z + size, U, v);
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
tes.addVertexWithUV(X, Y, Z + size, u, V);
break;
case 4:
tes.addVertexWithUV(X, Y, Z + size, U, V);
tes.addVertexWithUV(X, Y + size, Z + size, U, v);
tes.addVertexWithUV(X, Y + size, Z, u, v);
tes.addVertexWithUV(X, Y, Z, u, V);
break;
case 5:
tes.addVertexWithUV(X + size, Y, Z, U, V);
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
break;
}
}
GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glDepthMask(true);
}
@Override
public int getFXLayer() {
return 1;
}
@Override
public boolean shouldRenderInPass(int pass) {
return pass==2;
}
@Override
public void setDead() {
super.setDead();
ClientProxy.onHintDead(this);
}
}
@@ -6,36 +6,39 @@ import net.minecraft.client.particle.EntityFX;
import net.minecraft.world.World;
@SideOnly(Side.CLIENT)
public class WeightlessParticleFX extends EntityFX
{
public WeightlessParticleFX(World p_i1205_1_, double p_i1205_2_, double p_i1205_4_, double p_i1205_6_, double p_i1205_8_, double p_i1205_10_, double p_i1205_12_)
{
public class WeightlessParticleFX extends EntityFX {
public WeightlessParticleFX(
World p_i1205_1_,
double p_i1205_2_,
double p_i1205_4_,
double p_i1205_6_,
double p_i1205_8_,
double p_i1205_10_,
double p_i1205_12_) {
super(p_i1205_1_, p_i1205_2_, p_i1205_4_, p_i1205_6_, p_i1205_8_, p_i1205_10_, p_i1205_12_);
this.motionX = p_i1205_8_ + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.05F);
this.motionY = p_i1205_10_ + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.05F);
this.motionZ = p_i1205_12_ + (double)((float)(Math.random() * 2.0D - 1.0D) * 0.05F);
this.motionX = p_i1205_8_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F);
this.motionY = p_i1205_10_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F);
this.motionZ = p_i1205_12_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F);
this.particleRed = this.particleGreen = this.particleBlue = this.rand.nextFloat() * 0.3F + 0.7F;
this.particleScale = this.rand.nextFloat() * this.rand.nextFloat() * 6.0F + 1.0F;
this.particleMaxAge = (int)(16.0D / ((double)this.rand.nextFloat() * 0.8D + 0.2D)) + 2;
this.noClip=true;
this.particleMaxAge = (int) (16.0D / ((double) this.rand.nextFloat() * 0.8D + 0.2D)) + 2;
this.noClip = true;
}
/**
* Called to update the entity's position/logic.
*/
public void onUpdate()
{
public void onUpdate() {
this.prevPosX = this.posX;
this.prevPosY = this.posY;
this.prevPosZ = this.posZ;
if (this.particleAge++ >= this.particleMaxAge)
{
if (this.particleAge++ >= this.particleMaxAge) {
this.setDead();
}
this.setParticleTextureIndex(7 - this.particleAge * 8 / this.particleMaxAge);
//this.motionY += 0.004D;
// this.motionY += 0.004D;
this.moveEntity(this.motionX, this.motionY, this.motionZ);
this.motionX *= 0.8999999761581421D;
this.motionY *= 0.8999999761581421D;
@@ -0,0 +1,400 @@
package com.gtnewhorizon.structurelib.gui;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.gui.GuiScrollableList.IGuiScreen;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.lang3.StringUtils;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen {
private static final int KEY_MAX_WIDTH = 50;
private final ItemStack trigger;
private final GuiChannelsList list;
private GuiTextField key, value;
protected int guiTop, guiLeft;
public GuiScreenConfigureChannels(ItemStack trigger) {
this.trigger = trigger;
list = new GuiChannelsList(152, 100, 12, 12, 14);
list.addSelectionListener((list, selectedIndex) -> {
Entry<String, Integer> e = list.getElementAt(selectedIndex);
key.setText(e.getKey());
value.setText(e.getValue().toString());
updateButtons();
});
}
@Override
public void initGui() {
super.initGui();
// so you can keep holding backspace to delete a long sequence of chars
Keyboard.enableRepeatEvents(true);
guiLeft = (this.width - this.getXSize()) / 2;
guiTop = (this.height - this.getYSize()) / 2;
key = new GuiTextField(fontRendererObj, guiLeft + 45, guiTop + 119, 151 + 12 - 45, 12) {
@Override
public void writeText(String text) {
// force lower case
super.writeText(text.toLowerCase(Locale.ROOT));
updateButtons();
}
@Override
public void mouseClicked(int p_146192_1_, int p_146192_2_, int p_146192_3_) {
super.mouseClicked(p_146192_1_, p_146192_2_, p_146192_3_);
boolean flag = p_146192_1_ >= this.xPosition
&& p_146192_1_ < this.xPosition + this.width
&& p_146192_2_ >= this.yPosition
&& p_146192_2_ < this.yPosition + this.height;
if (flag && p_146192_3_ == 1) {
key.setText("");
value.setText("");
}
}
@Override
public void setText(String p_146180_1_) {
super.setText(p_146180_1_);
updateButtons();
}
};
value = new GuiTextField(fontRendererObj, guiLeft + 45, guiTop + 139, 151 + 12 - 45, 12) {
@Override
public void writeText(String text) {
// ignore write requests containing non digit characters.
// we don't accept decimals or negative numbers, so we can safely use isDigit instead of something more
// advanced
// use codePoints() in case someone pasted an emoji by mistake
if (text != null && text.codePoints().allMatch(Character::isDigit)) {
super.writeText(text);
updateButtons();
}
}
@Override
public void setFocused(boolean p_146195_1_) {
if (!p_146195_1_ && isFocused() && !StringUtils.isBlank(getText())) {
int result;
try {
result = Math.max(Integer.parseInt(getText()), 1);
} catch (NumberFormatException e) {
result = 1;
}
setText(String.valueOf(result));
}
super.setFocused(p_146195_1_);
}
@Override
public void mouseClicked(int p_146192_1_, int p_146192_2_, int p_146192_3_) {
super.mouseClicked(p_146192_1_, p_146192_2_, p_146192_3_);
boolean flag = p_146192_1_ >= this.xPosition
&& p_146192_1_ < this.xPosition + this.width
&& p_146192_2_ >= this.yPosition
&& p_146192_2_ < this.yPosition + this.height;
if (flag && p_146192_3_ == 1) {
setText("");
}
}
@Override
public void setText(String p_146180_1_) {
super.setText(p_146180_1_);
updateButtons();
}
};
list.onGuiInit(this);
addButton(new GuiButton(
0, guiLeft + 12, guiTop + 157, 47, 20, I18n.format("item.structurelib.constructableTrigger.gui.add")));
addButton(new GuiButton(
1,
guiLeft + 65,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.unset")));
addButton(new GuiButton(
2,
guiLeft + 118,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.wipe")));
updateButtons();
}
@Override
public int getGuiLeft() {
return guiLeft;
}
@Override
public int getGuiTop() {
return guiTop;
}
@Override
public int getXSize() {
return 176;
}
@Override
public int getYSize() {
return 188;
}
@Override
public void addButton(GuiButton button) {
getButtonList().add(button);
}
@Override
public void removeButton(GuiButton button) {
getButtonList().remove(button);
}
@Override
public int getOverlayOffsetX() {
return 0;
}
@Override
public void doActionPerformed(GuiButton but) {
actionPerformed(but);
}
@SuppressWarnings("unchecked")
private List<GuiButton> getButtonList() {
return buttonList;
}
@Override
public void handleMouseInput() {
int delta = Mouse.getEventDWheel();
if (delta != 0) list.handleDWheel(delta);
super.handleMouseInput();
}
@Override
protected void mouseClicked(int mX, int mY, int button) {
key.mouseClicked(mX, mY, button);
value.mouseClicked(mX, mY, button);
super.mouseClicked(mX, mY, button);
}
@Override
protected void keyTyped(char aChar, int aKey) {
switch (aKey) {
case Keyboard.KEY_TAB:
if (key.isFocused()) {
key.setFocused(false);
value.setFocused(true);
} else {
key.setFocused(true);
value.setFocused(false);
}
return;
case Keyboard.KEY_RETURN:
case Keyboard.KEY_NUMPADENTER:
GuiButton add = getButtonList().get(0);
if (add.enabled) doActionPerformed(add);
return;
case Keyboard.KEY_UP:
if (list.selectedIndex > 0) list.setSelection(list.selectedIndex - 1);
return;
case Keyboard.KEY_DOWN:
if (list.selectedIndex < list.getNumElements() - 1) list.setSelection(list.selectedIndex + 1);
return;
}
if (key.textboxKeyTyped(aChar, aKey)) {
updateButtons();
return;
}
if (value.textboxKeyTyped(aChar, aKey)) return;
super.keyTyped(aChar, aKey);
}
private void updateButtons() {
// this will be called from setText of key and value. NEVER UPDATE THE VALUE OF THESE HERE OR GET A
// STACKOVERFLOW!
boolean existing = ChannelDataAccessor.hasSubChannel(trigger, key.getText());
getButtonList().get(0).displayString = existing
? I18n.format("item.structurelib.constructableTrigger.gui.set")
: I18n.format("item.structurelib.constructableTrigger.gui.add");
getButtonList().get(0).enabled = !StringUtils.isBlank(value.getText());
getButtonList().get(1).enabled = existing && !StringUtils.isBlank(value.getText());
}
private int getValue() {
try {
return Integer.parseInt(value.getText());
} catch (NumberFormatException e) {
return -1;
}
}
@Override
protected void actionPerformed(GuiButton btn) {
if (btn == null) return;
switch (btn.id) {
case 0:
int value = getValue();
if (value <= 0) return;
ChannelDataAccessor.setChannelData(trigger, key.getText(), value);
break;
case 1:
ChannelDataAccessor.unsetChannelData(trigger, key.getText());
break;
case 2:
ChannelDataAccessor.wipeChannelData(trigger);
break;
}
super.actionPerformed(btn);
}
@Override
public void onGuiClosed() {
super.onGuiClosed();
Keyboard.enableRepeatEvents(false);
StructureLib.instance().proxy().uploadChannels(trigger);
}
@Override
public void updateScreen() {
super.updateScreen();
key.updateCursorCounter();
value.updateCursorCounter();
}
@Override
public boolean doesGuiPauseGame() {
return false;
}
@Override
public void drawScreen(int mX, int mY, float partialTick) {
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/channels.png"));
int topLeftX = (this.width - this.getXSize()) / 2;
int topLeftY = (this.height - this.getYSize()) / 2;
drawTexturedModalRect(topLeftX, topLeftY, 0, 0, getXSize(), getYSize());
super.drawScreen(mX, mY, partialTick);
list.drawScreen(mX, mY, partialTick);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.key"), guiLeft + 12, guiTop + 122, 0);
key.drawTextBox();
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.value"), guiLeft + 12, guiTop + 142, 0);
value.drawTextBox();
}
private class GuiChannelsList extends GuiScrollableList<Entry<String, Integer>> {
private List<Entry<String, Integer>> cache;
public GuiChannelsList(int width, int height, int originX, int originY, int slotHeight) {
super(width, height, originX, originY, slotHeight);
}
@Override
public Entry<String, Integer> getElementAt(int index) {
return ChannelDataAccessor.iterateChannelData(trigger)
.sorted()
.skip(index)
.findFirst()
.orElse(null);
}
@Override
public int getNumElements() {
return ChannelDataAccessor.countChannelData(trigger);
}
@Override
protected boolean elementClicked(int elementIndex, boolean doubleClick, int mXRelative, int mYRelative) {
if (mXRelative >= margin + 1
&& mXRelative <= margin + 5
&& mYRelative >= margin / 2 + 1
&& mYRelative <= margin / 2 + 5) {
Entry<String, Integer> e = getElementAt(elementIndex);
if (e != null) {
ChannelDataAccessor.unsetChannelData(trigger, e.getKey());
return false;
}
}
return super.elementClicked(elementIndex, doubleClick, mXRelative, mYRelative);
}
@Override
protected void prepareDrawElements() {
cache = ChannelDataAccessor.iterateChannelData(trigger).sorted().collect(Collectors.toList());
}
@Override
protected void drawElement(
int elementIndex, int x, int y, int height, Tessellator tessellator, boolean isHovering) {
if (elementIndex < 0 || elementIndex >= cache.size()) {
return;
}
Entry<String, Integer> e = cache.get(elementIndex);
if (e == null) return;
if (elementIndex > 0) {
// args x1, x2, y1, color
drawHorizontalLine(minX + 1, maxX - 1, y - 2, 0xffaaaaaa);
}
int keyMaxWidth = KEY_MAX_WIDTH * 2;
if (isHovering) {
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/channels.png"));
GL11.glColor4f(1, 1, 1, 1);
drawTexturedModalRect(x + margin + 1, y + margin / 2 + 1, 251, 251, 5, 5);
}
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, 0xffffff);
int valueX = x + margin + keyMaxWidth + margin;
fontRendererObj.drawString(trim(e.getValue().toString(), maxX - valueX), valueX, y + margin / 2, 0xffffff);
}
@Override
public void drawScreen(int mX, int mY, float partialTick) {
super.drawScreen(mX, mY, partialTick);
// args x1, y1, y2, color
drawVerticalLine(minX + margin + margin / 2 + KEY_MAX_WIDTH * 2, minY + 1, maxY - 1, 0xffaaaaaa);
}
public String trim(String e, int keyMaxWidth) {
// TODO optimize this to not create a billion string, or maybe cache this
String s = fontRendererObj.trimStringToWidth(e, keyMaxWidth);
if (s.length() != e.length()) {
StringBuilder buffer =
new StringBuilder(s).deleteCharAt(s.length() - 1).append("...");
while ((s = fontRendererObj.trimStringToWidth(buffer.toString(), keyMaxWidth)).length()
!= buffer.length())
// drop last original char
buffer.deleteCharAt(s.length() - 4);
do {
buffer.append('.');
} while (fontRendererObj.getStringWidth(buffer.toString()) <= keyMaxWidth);
s = buffer.deleteCharAt(s.length() - 1)
.insert(s.length() - 3, EnumChatFormatting.GRAY)
.toString();
}
return s;
}
}
}
@@ -0,0 +1,443 @@
package com.gtnewhorizon.structurelib.gui;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
/**
* Shamelessly stolen from EnderCore, with some amount of edit to fit in one file. Original code from CrazyPants.
* <p>
* As the code has been released into public domain, it is now LGPL as part of this library as a whole.
* You can go to EnderCore's repository to obtain a public domain copy though
*
* Major edits
* <ul>
* <li>Separate dwheel handling to handleDWheel. The next event calls often cause the mouse click event to
* not fire on other widgets</li>
* <li>scrollbar are now rendered within width</li>
* <li>added prepareDrawElements</li>
* </ul>
*
* @author CrazyPants
* @author glee8e
*/
public abstract class GuiScrollableList<T> {
protected final int slotHeight;
private final Minecraft mc = Minecraft.getMinecraft();
protected final int originX;
protected final int originY;
protected final int width;
protected final int height;
protected int minY;
protected int maxY;
protected int minX;
protected int maxX;
protected int mouseX;
protected int mouseY;
protected int selectedIndex = -1;
protected int margin = 4;
protected final List<ListSelectionListener<T>> listeners = new CopyOnWriteArrayList<>();
private int scrollUpButtonID;
private int scrollDownButtonID;
private float initialClickY = -2.0F;
private float scrollMultiplier;
private float amountScrolled;
private long lastClickedTime;
private boolean showSelectionBox = true;
public GuiScrollableList(int width, int height, int originX, int originY, int slotHeight) {
this.width = width;
this.height = height;
this.originX = originX;
this.originY = originY;
this.slotHeight = slotHeight;
minY = originY;
maxY = minY + height;
minX = originX;
maxX = minX + width;
}
public void onGuiInit(IGuiScreen gui) {
minY = originY + gui.getGuiTop();
maxY = minY + height;
minX = originX + gui.getGuiLeft();
maxX = minX + width;
}
public void addSelectionListener(ListSelectionListener<T> listener) {
listeners.add(listener);
}
public void removeSelectionListener(ListSelectionListener<T> listener) {
listeners.remove(listener);
}
public T getSelectedElement() {
return getElementAt(selectedIndex);
}
public void setSelection(T selection) {
setSelection(getIndexOf(selection));
}
public void setSelection(int index) {
if (index == selectedIndex) {
return;
}
selectedIndex = index;
for (ListSelectionListener<T> listener : listeners) {
listener.selectionChanged(this, selectedIndex);
}
}
public int getIndexOf(T element) {
if (element == null) {
return -1;
}
for (int i = 0; i < getNumElements(); i++) {
if (element.equals(getElementAt(i))) {
return i;
}
}
return -1;
}
public abstract T getElementAt(int index);
public abstract int getNumElements();
protected abstract void drawElement(
int elementIndex, int x, int y, int height, Tessellator tessellator, boolean isHovering);
protected boolean elementClicked(int elementIndex, boolean doubleClick, int mXRelative, int mYRelative) {
return true;
}
public void setShowSelectionBox(boolean val) {
showSelectionBox = val;
}
protected int getContentHeight() {
return getNumElements() * slotHeight;
}
public void setScrollButtonIds(int scrollUpButtonID, int scrollDownButtonID) {
this.scrollUpButtonID = scrollUpButtonID;
this.scrollDownButtonID = scrollDownButtonID;
}
private void clampScrollToBounds() {
int i = getContentOverhang();
if (i < 0) {
i *= -1;
}
if (amountScrolled < 0.0F) {
amountScrolled = 0.0F;
}
if (amountScrolled > i) {
amountScrolled = i;
}
}
public int getContentOverhang() {
return getContentHeight() - (height - margin);
}
public void actionPerformed(GuiButton b) {
if (b.enabled) {
if (b.id == scrollUpButtonID) {
amountScrolled -= slotHeight * 2f / 3;
initialClickY = -2.0F;
clampScrollToBounds();
} else if (b.id == scrollDownButtonID) {
amountScrolled += slotHeight * 2f / 3;
initialClickY = -2.0F;
clampScrollToBounds();
}
}
}
/**
* draws the slot to the screen, pass in mouse's current x and y and partial
* ticks
*/
public void drawScreen(int mX, int mY, float partialTick) {
this.mouseX = mX;
this.mouseY = mY;
processMouseEvents();
if (getContentOverhang() > 0) maxX = minX + width - 6;
else maxX = minX + width;
clampScrollToBounds();
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_FOG);
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int sx = minX * sr.getScaleFactor();
int sw = width * sr.getScaleFactor();
int sy = mc.displayHeight - (maxY * sr.getScaleFactor());
int sh = height * sr.getScaleFactor();
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(sx, sy, sw, sh);
Tessellator tessellator = Tessellator.instance;
drawContainerBackground(tessellator);
int amountScrolled = (int) this.amountScrolled;
int contentYOffset = this.minY + margin - amountScrolled;
boolean hoveringPrecondition = mX >= minX && mX <= maxX && mY >= minY && mY <= maxY;
prepareDrawElements();
for (int i = 0; i < getNumElements(); ++i) {
int elementY = contentYOffset + i * this.slotHeight;
int slotHeight = this.slotHeight - margin;
if (elementY <= maxY && elementY + slotHeight >= minY) {
if (showSelectionBox && i == selectedIndex) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_TEXTURE_2D);
tessellator.startDrawingQuads();
tessellator.setColorOpaque_I(8421504);
tessellator.addVertexWithUV(minX, elementY + slotHeight + 2, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(maxX, elementY + slotHeight + 2, 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(maxX, elementY - 2, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(minX, elementY - 2, 0.0D, 0.0D, 0.0D);
tessellator.setColorOpaque_I(0);
tessellator.addVertexWithUV(minX + 1, elementY + slotHeight + 1, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(maxX - 1, elementY + slotHeight + 1, 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(maxX - 1, elementY - 1, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(minX + 1, elementY - 1, 0.0D, 0.0D, 0.0D);
tessellator.draw();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
boolean isHovering = hoveringPrecondition && elementY - 2 <= mY && mY < elementY + slotHeight + 2;
drawElement(i, minX, elementY, slotHeight, tessellator, isHovering);
}
}
GL11.glDisable(GL11.GL_SCISSOR_TEST);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_ALPHA_TEST);
GL11.glShadeModel(GL11.GL_SMOOTH);
GL11.glDisable(GL11.GL_TEXTURE_2D);
tessellator.startDrawingQuads();
tessellator.setColorRGBA_I(0, 0);
tessellator.addVertexWithUV(this.minX, this.minY + margin, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(this.maxX, this.minY + margin, 0.0D, 1.0D, 1.0D);
tessellator.setColorRGBA_I(0, 255);
tessellator.addVertexWithUV(this.maxX, this.minY, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(this.minX, this.minY, 0.0D, 0.0D, 0.0D);
tessellator.draw();
tessellator.startDrawingQuads();
tessellator.setColorRGBA_I(0, 255);
tessellator.addVertexWithUV(this.minX, this.maxY, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(this.maxX, this.maxY, 0.0D, 1.0D, 1.0D);
tessellator.setColorRGBA_I(0, 0);
tessellator.addVertexWithUV(this.maxX, this.maxY - margin, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(this.minX, this.maxY - margin, 0.0D, 0.0D, 0.0D);
tessellator.draw();
renderScrollBar(tessellator);
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glShadeModel(GL11.GL_FLAT);
GL11.glEnable(GL11.GL_ALPHA_TEST);
GL11.glDisable(GL11.GL_BLEND);
}
protected void prepareDrawElements() {}
protected void renderScrollBar(Tessellator tessellator) {
int contentHeightOverBounds = getContentOverhang();
if (contentHeightOverBounds > 0) {
int clear = (maxY - minY) * (maxY - minY) / getContentHeight();
if (clear < 32) {
clear = 32;
}
if (clear > maxY - minY - 8) {
clear = maxY - minY - 8;
}
int y = (int) this.amountScrolled * (maxY - minY - clear) / contentHeightOverBounds + minY;
if (y < minY) {
y = minY;
}
GL11.glDisable(GL11.GL_TEXTURE_2D);
int scrollBarMinX = getScrollBarX();
int scrollBarMaxX = scrollBarMinX + 6;
tessellator.startDrawingQuads();
tessellator.setColorRGBA_I(0, 255);
tessellator.addVertexWithUV(scrollBarMinX, maxY, 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, maxY, 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, minY, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(scrollBarMinX, minY, 0.0D, 0.0D, 0.0D);
tessellator.setColorRGBA_F(0.3f, 0.3f, 0.3f, 1);
tessellator.addVertexWithUV(scrollBarMinX, (y + clear), 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, (y + clear), 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV(scrollBarMaxX, y, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(scrollBarMinX, y, 0.0D, 0.0D, 0.0D);
tessellator.setColorRGBA_F(0.7f, 0.7f, 0.7f, 1);
tessellator.addVertexWithUV(scrollBarMinX, (y + clear - 1), 0.0D, 0.0D, 1.0D);
tessellator.addVertexWithUV((scrollBarMaxX - 1), (y + clear - 1), 0.0D, 1.0D, 1.0D);
tessellator.addVertexWithUV((scrollBarMaxX - 1), y, 0.0D, 1.0D, 0.0D);
tessellator.addVertexWithUV(scrollBarMinX, y, 0.0D, 0.0D, 0.0D);
tessellator.draw();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
}
private void processMouseEvents() {
if (Mouse.isButtonDown(0)) {
processMouseBown();
} else {
initialClickY = -1.0F;
}
}
public void handleDWheel(int mouseWheelDelta) {
if (!Mouse.isButtonDown(0)) {
if (mouseWheelDelta > 0) {
amountScrolled -= slotHeight / 2f;
} else /* if (mouseWheelDelta < 0) */ {
amountScrolled += slotHeight / 2f;
}
}
}
private void processMouseBown() {
int contentHeightOverBounds;
if (initialClickY == -1.0F) {
if (mouseY >= minY && mouseY <= maxY && mouseX >= minX && mouseX <= maxX + 6) {
boolean clickInBounds = true;
int y = mouseY - minY + (int) amountScrolled - margin;
int mouseOverElement = y / slotHeight;
if (mouseX >= minX
&& mouseX <= maxX
&& mouseOverElement >= 0
&& y >= 0
&& mouseOverElement < getNumElements()) {
boolean doubleClick =
mouseOverElement == selectedIndex && Minecraft.getSystemTime() - lastClickedTime < 250L;
if (elementClicked(
mouseOverElement, doubleClick, mouseX - minX, y - slotHeight * mouseOverElement)) {
setSelection(mouseOverElement);
lastClickedTime = Minecraft.getSystemTime();
}
} else if (mouseX >= minX && mouseX <= maxX && y < 0) {
clickInBounds = false;
}
int scrollBarMinX = getScrollBarX();
int scrollBarMaxX = scrollBarMinX + 6;
if (mouseX >= scrollBarMinX && mouseX <= scrollBarMaxX) {
scrollMultiplier = -1.0F;
contentHeightOverBounds = getContentOverhang();
if (contentHeightOverBounds < 1) {
contentHeightOverBounds = 1;
}
int empty = (int) ((float) ((maxY - minY) * (maxY - minY)) / (float) getContentHeight());
if (empty < 32) {
empty = 32;
}
if (empty > maxY - minY - 8) {
empty = maxY - minY - 8;
}
scrollMultiplier /= (float) (maxY - minY - empty) / (float) contentHeightOverBounds;
} else {
scrollMultiplier = 1.0F;
}
if (clickInBounds) {
initialClickY = mouseY;
} else {
initialClickY = -2.0F;
}
} else {
initialClickY = -2.0F;
}
} else if (initialClickY >= 0.0F) {
// Scrolling
amountScrolled -= (mouseY - initialClickY) * scrollMultiplier;
initialClickY = mouseY;
}
}
protected int getScrollBarX() {
return maxX;
}
protected void drawContainerBackground(Tessellator tess) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GL11.glDisable(GL11.GL_TEXTURE_2D);
tess.startDrawingQuads();
tess.setColorOpaque_I(2105376);
tess.addVertex(minX, maxY, 0.0D);
tess.addVertex(maxX, maxY, 0.0D);
tess.addVertex(maxX, minY, 0.0D);
tess.addVertex(minX, minY, 0.0D);
tess.draw();
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
public interface IGuiScreen {
int getGuiLeft();
int getGuiTop();
int getXSize();
int getYSize();
void addButton(GuiButton button);
void removeButton(GuiButton button);
int getOverlayOffsetX();
void doActionPerformed(GuiButton but);
}
public interface ListSelectionListener<T> {
void selectionChanged(GuiScrollableList<T> list, int selectedIndex);
}
}
@@ -1,5 +1,8 @@
package com.gtnewhorizon.structurelib.item;
import static net.minecraft.util.StatCollector.translateToLocal;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
@@ -7,60 +10,66 @@ import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import java.util.List;
import static net.minecraft.util.StatCollector.translateToLocal;
public class ItemBlockHint extends ItemBlock {
public ItemBlockHint(Block p_i45328_1_) {
super(p_i45328_1_);
setMaxDamage(0);
setHasSubtypes(true);
setCreativeTab(CreativeTabs.tabBlock);
}
public ItemBlockHint(Block p_i45328_1_) {
super(p_i45328_1_);
setMaxDamage(0);
setHasSubtypes(true);
setCreativeTab(CreativeTabs.tabBlock);
}
@Override
@SuppressWarnings("unchecked")
public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List aList, boolean aF3_H) {
aList.add(translateToLocal("structurelib.blockhint.desc.0"));//Helps while building
switch (aStack.getItemDamage()) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.1"));//Placeholder for a certain group.
break;
case 12:
aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.2"));//General placeholder.
break;
case 13:
aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.3"));//Make sure it contains Air material.
break;
case 14:
aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.4"));//Make sure it does not contain Air material.
break;
case 15:
aList.add(EnumChatFormatting.BLUE.toString() + translateToLocal("structurelib.blockhint.desc.5"));//ERROR, what did u expect?
break;
default://WTF?
aList.add("Damn son where did you get that!?");
aList.add(EnumChatFormatting.BLUE.toString() + "From outer space... I guess...");
}
}
@Override
@SuppressWarnings("unchecked")
public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List aList, boolean aF3_H) {
aList.add(translateToLocal("structurelib.blockhint.desc.0")); // Helps while building
switch (aStack.getItemDamage()) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
aList.add(EnumChatFormatting.AQUA.toString()
+ EnumChatFormatting.BOLD
+ translateToLocal("structurelib.blockhint.desc.1")); // Placeholder for a certain group.
break;
case 12:
aList.add(EnumChatFormatting.AQUA.toString()
+ EnumChatFormatting.BOLD
+ translateToLocal("structurelib.blockhint.desc.2")); // General placeholder.
break;
case 13:
aList.add(EnumChatFormatting.AQUA.toString()
+ EnumChatFormatting.BOLD
+ translateToLocal("structurelib.blockhint.desc.3")); // Make sure it contains Air material.
break;
case 14:
aList.add(EnumChatFormatting.AQUA.toString()
+ EnumChatFormatting.BOLD
+ translateToLocal(
"structurelib.blockhint.desc.4")); // Make sure it does not contain Air material.
break;
case 15:
aList.add(EnumChatFormatting.BLUE
+ translateToLocal("structurelib.blockhint.desc.5")); // ERROR, what did u expect?
break;
default: // WTF?
aList.add("Damn son where did you get that!?");
aList.add(EnumChatFormatting.BLUE + "From outer space... I guess...");
}
}
public int getMetadata(int aMeta) {
return aMeta;
}
public int getMetadata(int aMeta) {
return aMeta;
}
public String getUnlocalizedName(ItemStack aStack) {
return this.field_150939_a.getUnlocalizedName() + "." + getDamage(aStack);
}
public String getUnlocalizedName(ItemStack aStack) {
return this.field_150939_a.getUnlocalizedName() + "." + getDamage(aStack);
}
}
@@ -1,17 +1,19 @@
package com.gtnewhorizon.structurelib.item;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import java.util.List;
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
import static net.minecraft.util.EnumChatFormatting.BLUE;
import static net.minecraft.util.StatCollector.translateToLocal;
import static net.minecraft.util.StatCollector.translateToLocalFormatted;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
import java.util.List;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import org.lwjgl.input.Keyboard;
public class ItemConstructableTrigger extends Item {
public ItemConstructableTrigger() {
@@ -21,17 +23,54 @@ public class ItemConstructableTrigger extends Item {
}
@Override
public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ) {
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
if (world.isRemote && getMovingObjectPositionFromPlayer(world, player, true) == null)
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
return super.onItemRightClick(stack, world, player);
}
@Override
public boolean onItemUseFirst(
ItemStack stack,
EntityPlayer player,
World world,
int x,
int y,
int z,
int side,
float hitX,
float hitY,
float hitZ) {
return ConstructableUtility.handle(stack, player, world, x, y, z, side);
}
@SuppressWarnings("unchecked")
@Override
public void addInformation(ItemStack aStack, EntityPlayer ep, List aList, boolean boo) {
aList.add(translateToLocal("item.structurelib.constructableTrigger.desc.0"));//Triggers Constructable Interface
aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.1"));//Shows multiblock construction details,
aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.2"));//just Use on a multiblock controller.
aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.3"));//(Sneak Use in creative to build)
aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.4"));//Quantity affects tier/mode/type
if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
aList.add(translateToLocalFormatted(
"item.structurelib.constructableTrigger.desc.lshift.0",
ChannelDataAccessor.countChannelData(aStack)));
ChannelDataAccessor.iterateChannelData(aStack)
.map(e -> e.getKey() + ": " + e.getValue())
.forEach(aList::add);
} else {
aList.add(translateToLocal(
"item.structurelib.constructableTrigger.desc.0")); // Triggers Constructable Interface
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.1")); // Shows multiblock construction details,
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.2")); // just Use on a multiblock controller.
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.3")); // (Sneak Use in creative to build)
aList.add(BLUE
+ translateToLocal(
"item.structurelib.constructableTrigger.desc.4")); // Quantity affects tier/mode/type
if (ChannelDataAccessor.hasSubChannel(aStack))
aList.add(translateToLocal("item.structurelib.constructableTrigger.desc.5"));
}
}
}
@@ -1,38 +1,49 @@
package com.gtnewhorizon.structurelib.item;
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
import static net.minecraft.util.StatCollector.translateToLocal;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.AlignmentUtility;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.util.List;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.world.World;
import java.util.List;
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
import static net.minecraft.util.StatCollector.translateToLocal;
public class ItemFrontRotationTool extends Item {
public ItemFrontRotationTool() {
setMaxStackSize(1);
setUnlocalizedName("structurelib.frontRotationTool");
setTextureName(MOD_ID + ":itemFrontRotationTool");
setCreativeTab(StructureLib.creativeTab);
}
public ItemFrontRotationTool() {
setMaxStackSize(1);
setUnlocalizedName("structurelib.frontRotationTool");
setTextureName(MOD_ID + ":itemFrontRotationTool");
setCreativeTab(StructureLib.creativeTab);
}
@Override
public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ) {
return AlignmentUtility.handle(player, world, x, y, z);
}
@Override
public boolean onItemUseFirst(
ItemStack stack,
EntityPlayer player,
World world,
int x,
int y,
int z,
int side,
float hitX,
float hitY,
float hitZ) {
return AlignmentUtility.handle(player, world, x, y, z);
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List aList, boolean p_77624_4_) {
aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0"));//Triggers Front Rotation Interface
aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.1"));//Rotates only the front panel,
aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.2"));//which allows structure rotation.
}
@Override
@SideOnly(Side.CLIENT)
public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List aList, boolean p_77624_4_) {
aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0")); // Triggers Front Rotation Interface
aList.add(EnumChatFormatting.BLUE
+ translateToLocal("item.structurelib.frontRotationTool.desc.1")); // Rotates only the front panel,
aList.add(EnumChatFormatting.BLUE
+ translateToLocal("item.structurelib.frontRotationTool.desc.2")); // which allows structure rotation.
}
}
@@ -21,8 +21,7 @@ public abstract class AlignmentMessage implements IMessage {
int mPosD;
int mAlign;
public AlignmentMessage() {
}
public AlignmentMessage() {}
@Override
public void fromBytes(ByteBuf pBuffer) {
@@ -47,11 +46,9 @@ public abstract class AlignmentMessage implements IMessage {
}
private AlignmentMessage(IAlignmentProvider provider) {
if (!(provider instanceof TileEntity))
throw new IllegalArgumentException("Provider must be a TileEntity");
if (!(provider instanceof TileEntity)) throw new IllegalArgumentException("Provider must be a TileEntity");
IAlignment alignment = provider.getAlignment();
if (alignment == null)
throw new IllegalArgumentException("Passed in provider does not provide an alignment!");
if (alignment == null) throw new IllegalArgumentException("Passed in provider does not provide an alignment!");
TileEntity base = (TileEntity) provider;
mPosX = base.xCoord;
mPosY = base.yCoord;
@@ -69,8 +66,7 @@ public abstract class AlignmentMessage implements IMessage {
}
public static class AlignmentQuery extends AlignmentMessage {
public AlignmentQuery() {
}
public AlignmentQuery() {}
public AlignmentQuery(IAlignmentProvider provider) {
super(provider);
@@ -82,8 +78,7 @@ public abstract class AlignmentMessage implements IMessage {
}
public static class AlignmentData extends AlignmentMessage {
public AlignmentData() {
}
public AlignmentData() {}
private AlignmentData(AlignmentQuery query) {
mPosX = query.mPosX;
@@ -106,7 +101,9 @@ public abstract class AlignmentMessage implements IMessage {
@Override
public IMessage onMessage(AlignmentData pMessage, MessageContext pCtx) {
if (StructureLib.getCurrentPlayer().worldObj.provider.dimensionId == pMessage.mPosD) {
TileEntity te = StructureLib.getCurrentPlayer().worldObj.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ);
TileEntity te = StructureLib.getCurrentPlayer()
.worldObj
.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ);
if (te instanceof IAlignmentProvider) {
IAlignment alignment = ((IAlignmentProvider) te).getAlignment();
if (alignment != null) {
@@ -126,8 +123,7 @@ public abstract class AlignmentMessage implements IMessage {
TileEntity te = world.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ);
if (te instanceof IAlignmentProvider) {
IAlignment alignment = ((IAlignmentProvider) te).getAlignment();
if (alignment == null)
return null;
if (alignment == null) return null;
pMessage.mAlign = alignment.getExtendedFacing().getIndex();
return new AlignmentData(pMessage);
}
@@ -135,4 +131,4 @@ public abstract class AlignmentMessage implements IMessage {
return null;
}
}
}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizon.structurelib.net;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
import io.netty.buffer.ByteBuf;
public class ErrorHintParticleMessage implements IMessage {
private int x;
private short y;
private int z;
public ErrorHintParticleMessage() {}
public ErrorHintParticleMessage(int x, short y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public void fromBytes(ByteBuf buf) {
x = buf.readInt();
y = buf.readShort();
z = buf.readInt();
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeInt(x);
buf.writeShort(y);
buf.writeInt(z);
}
public static class Handler implements IMessageHandler<ErrorHintParticleMessage, IMessage> {
@Override
public IMessage onMessage(ErrorHintParticleMessage msg, MessageContext ctx) {
boolean updateResult = StructureLibAPI.markHintParticleError(
StructureLib.getCurrentPlayer(), StructureLib.getCurrentPlayer().worldObj, msg.x, msg.y, msg.z);
if (StructureLibAPI.isDebugEnabled())
LOGGER.debug(
"Server instructed to mark hint particle at ({}, {}, {}) error, result {}!",
msg.x,
msg.y,
msg.z,
updateResult);
return null;
}
}
}
@@ -0,0 +1,67 @@
package com.gtnewhorizon.structurelib.net;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import cpw.mods.fml.common.network.ByteBufUtils;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import org.apache.commons.lang3.tuple.Pair;
public class SetChannelDataMessage implements IMessage {
private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
public SetChannelDataMessage() {}
public SetChannelDataMessage(ItemStack trigger) {
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
}
@Override
public void fromBytes(ByteBuf buf) {
data.clear();
int size = ByteBufUtils.readVarShort(buf);
for (int i = 0; i < size; i++) {
data.add(Pair.of(ByteBufUtils.readUTF8String(buf), ByteBufUtils.readVarInt(buf, 4)));
}
}
@Override
public void toBytes(ByteBuf buf) {
ByteBufUtils.writeVarShort(buf, data.size());
for (Entry<String, Integer> e : data) {
ByteBufUtils.writeUTF8String(buf, e.getKey());
ByteBufUtils.writeVarInt(buf, e.getValue(), 4);
}
}
public static class Handler implements IMessageHandler<SetChannelDataMessage, IMessage> {
@Override
public IMessage onMessage(SetChannelDataMessage message, MessageContext ctx) {
EntityPlayerMP player = ctx.getServerHandler().playerEntity;
ItemStack heldItem = player.getHeldItem();
if (heldItem != null && heldItem.getItem() instanceof ItemConstructableTrigger) {
ChannelDataAccessor.wipeChannelData(heldItem);
for (Entry<String, Integer> e : message.data) {
ChannelDataAccessor.setChannelData(heldItem, e.getKey(), e.getValue());
}
// since this is a set all channel request from the client, we would assume client already know
// what this would look like on the client, so no sync
} else {
StructureLib.LOGGER.warn(
"{} trying to set channel data on {}, which is not a hologram projector!",
player.getUniqueID(),
heldItem);
}
return null;
}
}
}
@@ -0,0 +1,77 @@
package com.gtnewhorizon.structurelib.net;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
import io.netty.buffer.ByteBuf;
public class UpdateHintParticleMessage implements IMessage {
private int x;
private short y;
private int z;
private short r;
private short g;
private short b;
private short a;
public UpdateHintParticleMessage() {}
public UpdateHintParticleMessage(int x, short y, int z, short r, short g, short b, short a) {
this.x = x;
this.y = y;
this.z = z;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
@Override
public void fromBytes(ByteBuf buf) {
x = buf.readInt();
y = buf.readShort();
z = buf.readInt();
r = buf.readUnsignedByte();
g = buf.readUnsignedByte();
b = buf.readUnsignedByte();
a = buf.readUnsignedByte();
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeInt(x);
buf.writeShort(y);
buf.writeInt(z);
buf.writeByte(r);
buf.writeByte(g);
buf.writeByte(b);
buf.writeByte(a);
}
public static class Handler implements IMessageHandler<UpdateHintParticleMessage, IMessage> {
@Override
public IMessage onMessage(UpdateHintParticleMessage msg, MessageContext ctx) {
boolean updateResult = StructureLibAPI.updateHintParticleTint(
StructureLib.getCurrentPlayer(),
StructureLib.getCurrentPlayer().worldObj,
msg.x,
msg.y,
msg.z,
new short[] {
msg.r, msg.g, msg.b, msg.a,
});
if (StructureLibAPI.isDebugEnabled())
LOGGER.debug(
"Server instructed to update hint particle at ({}, {}, {}), result {}!",
msg.x,
msg.y,
msg.z,
updateResult);
return null;
}
}
}
@@ -1,187 +0,0 @@
package com.gtnewhorizon.structurelib.proxy;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.gtnewhorizon.structurelib.ConfigurationHandler;
import com.gtnewhorizon.structurelib.entity.fx.EntityFXBlockHint;
import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import static com.gtnewhorizon.structurelib.StructureLib.RANDOM;
public class ClientProxy extends CommonProxy {
@Override
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
EntityFXBlockHint hint = new EntityFXBlockHint(w, x, y, z, icons).withColorTint(RGBa);
Minecraft.getMinecraft().effectRenderer.addEffect(hint);
ensureHinting();
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
HintParticleInfo info = new HintParticleInfo(x, y, z, currentHints);
EntityFXBlockHint dupe = allHints.inverse().get(info);
if (dupe != null) {
List<EntityFXBlockHint> owner = allHints.get(dupe).owner;
// in case someone forget to call endHinting
if (owner != currentHints) {
hintOwners.remove(owner);
owner.forEach(EntityFXBlockHint::setDead);
owner.clear();
}
}
allHints.forcePut(hint, info);
}
currentHints.add(hint);
EntityFX particle = new WeightlessParticleFX(w, x + RANDOM.nextFloat() * 0.5F, y + RANDOM.nextFloat() * 0.5F, z + RANDOM.nextFloat() * 0.5F, 0, 0, 0);
particle.setRBGColorF(0, 0.6F * RANDOM.nextFloat(), 0.8f);
Minecraft.getMinecraft().effectRenderer.addEffect(particle);
}
@Override
public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), RGBa);
}
@Override
public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
hintParticleTinted(w, x, y, z, icons, new short[]{255, 255, 255, 0});
}
@Override
public void hintParticle(World w, int x, int y, int z, Block block, int meta) {
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), new short[]{255, 255, 255, 0});
}
private static IIcon[] createIIconFromBlock(Block block, int meta) {
IIcon[] ret = new IIcon[6];
for (int i = 0; i < 6; i++) {
ret[i] = block.getIcon(i, meta);
}
return ret;
}
@Override
public void addClientSideChatMessages(String... messages) {
GuiNewChat chat = Minecraft.getMinecraft().ingameGUI.getChatGUI();
for (String s : messages) {
chat.printChatMessage(new ChatComponentText(s));
}
}
@Override
public EntityPlayer getCurrentPlayer() {
return Minecraft.getMinecraft().thePlayer;
}
@Override
public boolean isCurrentPlayer(EntityPlayer player) {
return player == Minecraft.getMinecraft().thePlayer;
}
private static final BiMap<EntityFXBlockHint, HintParticleInfo> allHints = HashBiMap.create();
private static final List<List<EntityFXBlockHint>> hintOwners = new ArrayList<>();
private static List<EntityFXBlockHint> currentHints;
public static void onHintDead(EntityFXBlockHint fx) {
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram())
allHints.remove(fx);
for (Iterator<List<EntityFXBlockHint>> iterator = hintOwners.iterator(); iterator.hasNext(); ) {
List<EntityFXBlockHint> list = iterator.next();
if (list.remove(fx)) {
if (list.isEmpty())
iterator.remove();
break;
}
}
}
@Override
public void startHinting(World w) {
if (!w.isRemote)
return;
if (currentHints != null)
hintOwners.add(currentHints);
currentHints = new LinkedList<>();
}
private void ensureHinting() {
if (currentHints == null)
currentHints = new LinkedList<>();
}
@Override
public void endHinting(World w) {
if (!w.isRemote)
return;
while (!hintOwners.isEmpty() && hintOwners.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
List<EntityFXBlockHint> list = hintOwners.remove(0);
list.forEach(EntityFXBlockHint::setDead);
list.clear();
}
if (!currentHints.isEmpty())
hintOwners.add(currentHints);
currentHints = null;
}
@Override
public void preInit(FMLPreInitializationEvent e) {
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
}
private static class HintParticleInfo {
private final int x, y, z;
private final List<EntityFXBlockHint> owner;
public HintParticleInfo(int x, int y, int z, List<EntityFXBlockHint> owner) {
this.x = x;
this.y = y;
this.z = z;
this.owner = owner;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HintParticleInfo that = (HintParticleInfo) o;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
result = 31 * result + z;
return result;
}
}
public static class ForgeEventHandler {
@SubscribeEvent
public void onWorldLoad(WorldEvent.Load e) {
if (e.world.isRemote) {
allHints.clear();
hintOwners.clear();
}
}
}
}
@@ -1,26 +0,0 @@
package com.gtnewhorizon.structurelib.proxy;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
public class CommonProxy {
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa){}
public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa){}
public void hintParticle(World w, int x, int y, int z, IIcon[] icons){}
public void hintParticle(World w, int x, int y, int z, Block block, int meta){}
public EntityPlayer getCurrentPlayer() {
return null;
}
public boolean isCurrentPlayer(EntityPlayer player) {
return false;
}
public void addClientSideChatMessages(String... messages){
}
public void startHinting(World w) {}
public void endHinting(World w) {}
public void preInit(FMLPreInitializationEvent e) {}
}
@@ -0,0 +1,30 @@
package com.gtnewhorizon.structurelib.structure;
class ChatThrottleKey {
static class NoExplicitChannel {
private static final int hashOffset = NoExplicitChannel.class.hashCode();
private final String subchannel;
public NoExplicitChannel(String subchannel) {
this.subchannel = subchannel;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NoExplicitChannel)) return false;
NoExplicitChannel that = (NoExplicitChannel) o;
return subchannel.equals(that.subchannel);
}
@Override
public int hashCode() {
// as this one will be placed within a map along with instances of different classes, it'd be best to add
// an offset based off class type to avoid collision
return subchannel.hashCode() + hashOffset;
}
}
}
@@ -3,5 +3,5 @@ package com.gtnewhorizon.structurelib.structure;
import net.minecraft.world.World;
public interface IBlockPosConsumer {
void consume(World world, int x, int y, int z);
void consume(World world, int x, int y, int z);
}
@@ -4,15 +4,15 @@ import net.minecraft.block.Block;
import net.minecraft.world.World;
public interface ICustomBlockSetting {
/**
* Default block setting calls {@link World#setBlock(int x, int y, int z, Block block, int meta, int updateType)} like:
* {@code world.setBlock(x,y,z,this/block,meta,2)} where updateType 2 means to update lighting and stuff
*
* @param world world that should be affected
* @param x x position to set
* @param y y position to set
* @param z z position to set
* @param meta required meta
*/
void setBlock(World world, int x, int y, int z, int meta);
/**
* Default block setting calls {@link World#setBlock(int x, int y, int z, Block block, int meta, int updateType)} like:
* {@code world.setBlock(x,y,z,this/block,meta,2)} where updateType 2 means to update lighting and stuff
*
* @param world world that should be affected
* @param x x position to set
* @param y y position to set
* @param z z position to set
* @param meta required meta
*/
void setBlock(World world, int x, int y, int z, int meta);
}
@@ -0,0 +1,111 @@
package com.gtnewhorizon.structurelib.structure;
import com.gtnewhorizon.structurelib.util.InventoryIterable;
import com.gtnewhorizon.structurelib.util.InventoryUtility;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
import java.util.Map;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
/**
* Represent an item source. Take only, cannot be placed back.
*/
public interface IItemSource {
/**
* We are probably not going to need to get a lot of items from this method,
* so I don't care about the overhead of storing all fetched items even if downstream code doesn't need it
*/
@Nonnull
Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count);
/**
* Take exactly one item that matches the predicate.
*
* @return item stack, or null if none matches
*/
default ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
Map<ItemStack, Integer> take = take(predicate, simulate, 1);
return take.isEmpty() ? null : take.keySet().iterator().next();
}
/**
* Take exactly count amount of items matching predicate.
*
* @return true if at least count amount of items can be/is taken.
*/
default boolean takeAll(Predicate<ItemStack> predicate, boolean simulate, int count) {
// fast path for 1 item take requests
if (count == 1) return takeOne(predicate, simulate) != null;
Map<ItemStack, Integer> have = take(predicate, true, count);
if (have.values().stream().mapToInt(Integer::intValue).sum() < count) return false;
take(predicate, simulate, count);
return true;
}
/**
* Take exactly one item. ItemStack given must have a stack size of 1.
*
* @return true if found. false otherwise
*/
default boolean takeOne(ItemStack stack, boolean simulate) {
if (stack == null || stack.getItem() == null || stack.stackSize != 1) throw new IllegalArgumentException();
ItemStack took = takeOne(ItemStackPredicate.from(stack, NBTMode.EXACT), simulate);
return took == null || took.stackSize <= 0;
}
/**
* Take some item. Will not take item if it cannot find enough items to take.
*
* @return true if enough item can be/has been extracted. false otherwise
*/
default boolean takeAll(ItemStack stack, boolean simulate) {
// fast path for 1 item take requests
if (stack.stackSize == 1) return takeOne(stack, simulate);
ItemStackPredicate predicate = ItemStackPredicate.from(stack, NBTMode.EXACT);
Map<ItemStack, Integer> have = take(predicate, true, stack.stackSize);
if (have.values().stream().mapToInt(Integer::intValue).sum() < stack.stackSize) return false;
if (simulate) return true;
take(predicate, false, stack.stackSize);
return true;
}
/**
* Construct an IItemSource from inventories associated with given player.
* <p>
* This will be backed by {@link InventoryUtility}
*/
static IItemSource fromPlayer(EntityPlayerMP player) {
return new IItemSource() {
@Nonnull
@Override
public Map<ItemStack, Integer> take(Predicate<ItemStack> p, boolean s, int c) {
return InventoryUtility.takeFromInventory(player, p, s, c);
}
@Override
public boolean takeOne(ItemStack stack, boolean simulate) {
if (stack == null || stack.getItem() == null || stack.stackSize != 1)
throw new IllegalArgumentException();
return InventoryUtility.takeFromInventory(player, stack, simulate) == 1;
}
@Override
public boolean takeAll(ItemStack stack, boolean simulate) {
return InventoryUtility.takeFromInventory(player, stack, simulate) == stack.stackSize;
}
};
}
/**
* Construct an IItemSource from given inventory
* <p>
* This will be backed by {@link InventoryUtility}
*/
static IItemSource fromInventory(IInventory inv) {
return (p, s, c) -> InventoryUtility.takeFromInventory(new InventoryIterable<>(inv), p, s, c, true);
}
}
@@ -0,0 +1,7 @@
package com.gtnewhorizon.structurelib.structure;
import net.minecraft.block.Block;
public interface ISpecialItemBlock {
int getItemMetaFromBlockMeta(Block block, int blockMeta);
}
@@ -1,175 +1,410 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import java.util.function.Function;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import java.util.Arrays;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
/**
* This is the structure definition of your multi. You will have one of these for each multi.
*
* <h2>Cache the instances of this class</h2>
* <p>
* You will construct this definition once per multi, either at class load or at first call to your variant of {@code getStructureDefinition()}.
* You can use a {@link ClassValue ClassValue&lt;IStructureDefinition&lt;?>>} if you have a template for subclass.
* You can also use {@link StructureUtility#defer(Function) defer()} or its overloads for simpler cases, but it is
* slightly less performant as it will allocate new objects on each structure check, whereas {@link ClassValue} will
* allocate once and cache it onwards.
* <p>
* <h2>Constructing</h2>
* <p>
* {@linkplain IStructureDefinition} is constructed using {@link IStructureDefinition#builder()}
* <p>
* Builder has two main methods:
* <ul>
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()}
* is a 2d string array describing the layout of structure. You can optionally use `StructureUtility#transpose()`
* to transpose the array to make the structure look nicer in source code.
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addElement(Character, IStructureElement) addElement()}. It takes a char identifier (mapped to a char in any of {@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()} calls) and a {@link IStructureElement}.
* </ul>
* <h2>Position A, B and C</h2>
*
* Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
* <ul>
* <li>The "A" position would be the number of blocks on the left side of the controller, not counting controller itself.</li>
* <li>The "B" position would be the number of blocks on the top side of the controller, not counting controller itself.</li>
* <li>The "C" position would be the number of blocks between you and controller, not counting controller itself.</li>
* </ul>
* @param <T> Type of the context object. Usually this will be the multiblock controller.
*/
public interface IStructureDefinition<T> {
/**
* Used internally
*
* Might, but is not required, to throw {@link java.util.NoSuchElementException} if the given structure piece is not found.
*
* @param name same name as for other methods here
* @return the array of elements to process
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly matches
*/
IStructureElement<T>[] getStructureFor(String name);
/**
* Used internally
* <p>
* Might, but is not required, to throw {@link java.util.NoSuchElementException} if the given structure piece is not found.
*
* @param name same name as for other methods here
* @return the array of elements to process
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly matches
*/
IStructureElement<T>[] getStructureFor(String name);
default boolean check(T object, String piece, World world, ExtendedFacing extendedFacing,
int basePositionX, int basePositionY, int basePositionZ,
int basePositionA, int basePositionB, int basePositionC,
boolean forceCheckAllBlocks) {
return iterate(object, null, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ,
basePositionA, basePositionB, basePositionC, false, forceCheckAllBlocks);
}
/**
* Run a structure check.
* @param object context object. usually multiblock controller.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param forceCheckAllBlocks check all location even if chunk not currently loaded.
* @return true if successful, false otherwise
*/
default boolean check(
T object,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
int basePositionC,
boolean forceCheckAllBlocks) {
return iterate(
object,
null,
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
false,
forceCheckAllBlocks);
}
default boolean hints(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
int basePositionX, int basePositionY, int basePositionZ,
int basePositionA, int basePositionB, int basePositionC) {
return iterate(object, trigger, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ,
basePositionA, basePositionB, basePositionC, true, null);
}
/**
* Spawn hint particles. Should not be called on server side.
* @param object context object. usually multiblock controller.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @return true if successful, false otherwise
*/
default boolean hints(
T object,
ItemStack trigger,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
int basePositionC) {
return iterate(
object,
trigger,
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
true,
null);
}
default boolean build(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
int basePositionX, int basePositionY, int basePositionZ,
int basePositionA, int basePositionB, int basePositionC) {
return iterate(object, trigger, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ,
basePositionA, basePositionB, basePositionC, false, null);
}
/**
* Build the multi in creative mode on a best-effort basis. Does not guarantee the structure is correct after a
* successful call.
*
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @return true if sucessful, false otherwise
*/
default boolean build(
T object,
ItemStack trigger,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
int basePositionC) {
return iterate(
object,
trigger,
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
false,
null);
}
default boolean buildOrHints(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
int basePositionX, int basePositionY, int basePositionZ,
int basePositionA, int basePositionB, int basePositionC,
boolean hintsOnly) {
return iterate(object, trigger, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ,
basePositionA, basePositionB, basePositionC, hintsOnly, null);
}
/**
* Call {@link #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} if {@code hintsOnly},
* call {@link #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} otherwise.
*
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param hintsOnly whether to spawn hints or do creative build.
* @return true if sucessful, false otherwise
* @see #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
* @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
*/
default boolean buildOrHints(
T object,
ItemStack trigger,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
int basePositionC,
boolean hintsOnly) {
return iterate(
object,
trigger,
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
hintsOnly,
null);
}
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<T>[] elements, World world, ExtendedFacing extendedFacing,
int basePositionX, int basePositionY, int basePositionZ,
int basePositionA, int basePositionB, int basePositionC,
boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
if (world.isRemote ^ hintsOnly) {
return false;
}
/**
* Cause a survival build.
*
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param piece the structure piece's string identifier.
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param elementBudget build up to this many elements
* @param source from where to drain resource
* @param actor source of action
* @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if anything
* would be placed. use with caution.
* if in doubt, call {@link #check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}
* after this call and set this parameter to false
* @return number of elements built, or -1 if structure done
* @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
*/
default int survivalBuild(
T object,
ItemStack trigger,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
int basePositionC,
int elementBudget,
IItemSource source,
EntityPlayerMP actor,
boolean check) {
SurvivalBuildStructureWalker<T> walker =
new SurvivalBuildStructureWalker<>(object, trigger, source, actor, elementBudget, check);
StructureUtility.iterateV2(
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
walker,
"survivalBuild");
return walker.getBuilt();
}
//change base position to base offset
basePositionA = -basePositionA;
basePositionB = -basePositionB;
basePositionC = -basePositionC;
/**
* Low level utility.
* @param object context object. usually multiblock controller.
* @param trigger The trigger item that contains channel data.
* @param elements the structure piece
* @param world the world object this check takes place in.
* @param extendedFacing the current structure's orientation.
* @param basePositionX X location of the structure
* @param basePositionY Y location of the structure
* @param basePositionZ Z location of the structure
* @param basePositionA see class javadoc
* @param basePositionB see class javadoc
* @param basePositionC see class javadoc
* @param hintsOnly whether to spawn hints or do creative build. no effect if {@code checkBlocksIfNotNullForceCheckAllIfTrue} is not null.
* @param checkBlocksIfNotNullForceCheckAllIfTrue creative build or spawn hints if null. Force check not loaded chunk
* if true. Check only loaded chunk is false.
* @return true if iteration completed successfully.
* @param <T> type of context object.
*/
static <T> boolean iterate(
T object,
ItemStack trigger,
IStructureElement<T>[] elements,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
int basePositionC,
boolean hintsOnly,
Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
if (!world.isRemote && hintsOnly) {
return false;
}
int[] abc = new int[]{basePositionA, basePositionB, basePositionC};
int[] xyz = new int[3];
if (checkBlocksIfNotNullForceCheckAllIfTrue != null) {
boolean success;
if (checkBlocksIfNotNullForceCheckAllIfTrue) {
success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
(e, w, x, y, z) -> e.check(object, w, x, y, z),
"check");
} else {
success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
skipBlockUnloaded((e, w, x, y, z) -> e.check(object, w, x, y, z)),
"check force");
}
if (StructureLibAPI.isDebugEnabled() && success) {
StructureLib.LOGGER.info(
"Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
}
return success;
} else {
if (hintsOnly) {
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z) -> {
e.spawnHint(object, world, x, y, z, trigger);
return true;
}),
"spawnHint");
} else {
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z) -> {
e.placeBlock(object, world, x, y, z, trigger);
return true;
}),
"placeBlock");
}
}
return true;
}
if (checkBlocksIfNotNullForceCheckAllIfTrue != null) {
if (checkBlocksIfNotNullForceCheckAllIfTrue) {
for (IStructureElement<T> element : elements) {
if (element.isNavigating()) {
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
} else {
extendedFacing.getWorldOffset(abc, xyz);
xyz[0] += basePositionX;
xyz[1] += basePositionY;
xyz[2] += basePositionZ;
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) {
if (DEBUG_MODE) {
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] failed @ " +
Arrays.toString(xyz) + " " + Arrays.toString(abc));
}
return false;
}
} else {
if (DEBUG_MODE) {
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] !blockExists @ " +
Arrays.toString(xyz) + " " + Arrays.toString(abc));
}
return false;
}
abc[0] += 1;
}
}
} else {
for (IStructureElement<T> element : elements) {
if (element.isNavigating()) {
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
} else {
extendedFacing.getWorldOffset(abc, xyz);
xyz[0] += basePositionX;
xyz[1] += basePositionY;
xyz[2] += basePositionZ;
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) {
if (DEBUG_MODE) {
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] failed @ " +
Arrays.toString(xyz) + " " + Arrays.toString(abc));
}
return false;
}
} else {
if (DEBUG_MODE) {
StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] !blockExists @ " +
Arrays.toString(xyz) + " " + Arrays.toString(abc));
}
}
abc[0] += 1;
}
}
}
} else {
if (hintsOnly) {
for (IStructureElement<T> element : elements) {
if (element.isNavigating()) {
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
} else {
extendedFacing.getWorldOffset(abc, xyz);
xyz[0] += basePositionX;
xyz[1] += basePositionY;
xyz[2] += basePositionZ;
element.spawnHint(object, world, xyz[0], xyz[1], xyz[2], trigger);
abc[0] += 1;
}
}
} else {
for (IStructureElement<T> element : elements) {
if (element.isNavigating()) {
abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA();
abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB();
abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC();
} else {
extendedFacing.getWorldOffset(abc, xyz);
xyz[0] += basePositionX;
xyz[1] += basePositionY;
xyz[2] += basePositionZ;
if (world.blockExists(xyz[0], xyz[1], xyz[2])) {
element.placeBlock(object, world, xyz[0], xyz[1], xyz[2], trigger);
}
abc[0] += 1;
}
}
}
}
return true;
}
static <T> StructureDefinition.Builder<T> builder() {
return StructureDefinition.builder();
}
/**
* Create a new instance of builder.
* @param <T> type of context object
*/
static <T> StructureDefinition.Builder<T> builder() {
return StructureDefinition.builder();
}
}
@@ -1,43 +1,129 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import java.util.function.Consumer;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
/**
* Use StructureUtility to instantiate
*/
public interface IStructureElement<T> {
boolean check(T t, World world, int x, int y, int z);
boolean check(T t, World world, int x, int y, int z);
boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger);
boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger);
boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger);
boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger);
default int getStepA() {
return 1;
}
/**
* Try place the block by taking resource from given IItemSource.
* <p>
* You might want to use {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, EntityPlayerMP)}
* or its overloads to implement this.
*
* @param trigger trigger item
* @param s drain resources from this place
* @param actor source of action. for very critical errors you can also just send the error messages here, bypassing
* any filter that chatter might have.
* @param chatter send error messages here. Caller will choose an appropriate way to forward it to player if the
* other fallbacks also fails.
*/
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit");
if (StructureLibAPI.isDebugEnabled())
LOGGER.error(
"Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
getClass().getName());
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
return PlaceResult.SKIP;
}
default int getStepB() {
return 0;
}
/**
* Forget the messed up class dependency graph for now. this is just so convenient.
*/
default IStructureElementNoPlacement<T> noPlacement() {
return new IStructureElementNoPlacement<T>() {
default int getStepC() {
return 0;
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
return IStructureElement.this.check(t, world, x, y, z);
}
default boolean resetA() {
return false;
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return IStructureElement.this.spawnHint(t, world, x, y, z, trigger);
}
};
}
default boolean resetB() {
return false;
}
default int getStepA() {
return 1;
}
default boolean resetC() {
return false;
}
default int getStepB() {
return 0;
}
default boolean isNavigating() {
return false;
}
default int getStepC() {
return 0;
}
default boolean resetA() {
return false;
}
default boolean resetB() {
return false;
}
default boolean resetC() {
return false;
}
default boolean isNavigating() {
return false;
}
enum PlaceResult {
/**
* This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or
* some other unforeseen situations.
* TODO this definition doesn't seem right. Should we separate SKIP from EXISTS, or SKIP from ERROR?
*/
SKIP,
/**
* This element's space is occupied by other stuff and require player attention, or player missing required resource,
* or the block cannot be placed due to obscure mechanisms, or some other unforeseen situations.
*/
REJECT,
/**
* Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait for next round.
*/
STOP,
/**
* Element placed successfully.
*/
ACCEPT,
/**
* Combination of ACCEPT and STOP.
* <p>
* Element placed successfully. To proceed further would require stopping the placement and wait for next round.
*/
ACCEPT_STOP;
}
}
@@ -1,41 +1,77 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
/**
* Use StructureUtility to instantiate
*/
public interface IStructureElementChain<T> extends IStructureElement<T> {
IStructureElement<T>[] fallbacks();
IStructureElement<T>[] fallbacks();
@Override
default boolean check(T t, World world, int x, int y, int z) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.check(t, world, x, y, z)) {
return true;
}
}
return false;
}
@Override
default boolean check(T t, World world, int x, int y, int z) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.check(t, world, x, y, z)) {
return true;
}
}
return false;
}
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.spawnHint(t, world, x, y, z, trigger)) {
return true;
}
}
return false;
}
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.spawnHint(t, world, x, y, z, trigger)) {
return true;
}
}
return false;
}
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.placeBlock(t, world, x, y, z, trigger)) {
return true;
}
}
return false;
}
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
for (IStructureElement<T> fallback : fallbacks()) {
if (fallback.placeBlock(t, world, x, y, z, trigger)) {
return true;
}
}
return false;
}
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
boolean haveSkip = false;
List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add);
switch (result) {
case REJECT:
break;
case SKIP:
haveSkip = true;
break;
default:
return result;
}
}
// dump all that noise back into upstream
bufferedNoise.forEach(chatter);
// TODO need reconsider to ensure this is the right course of action
return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT;
}
}
@@ -1,16 +1,33 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.function.Consumer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
public interface IStructureElementCheckOnly<T> extends IStructureElement<T> {
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return false;
}
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return false;
}
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return false;
}
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return false;
}
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
return PlaceResult.SKIP;
}
}
@@ -3,5 +3,4 @@ package com.gtnewhorizon.structurelib.structure;
/**
* Use StructureUtility to instantiate
*/
public interface IStructureElementDeferred<T> extends IStructureElement<T> {
}
public interface IStructureElementDeferred<T> extends IStructureElement<T> {}
@@ -1,11 +1,33 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.function.Consumer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
public interface IStructureElementNoPlacement<T> extends IStructureElement<T> {
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return false;
}
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return false;
}
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
return PlaceResult.REJECT;
}
@Override
default IStructureElementNoPlacement<T> noPlacement() {
return this;
}
}
@@ -1,28 +1,45 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.function.Consumer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
/**
* Use StructureUtility to instantiate
*/
interface IStructureNavigate<T> extends IStructureElement<T> {
@Override
default boolean check(T t, World world, int x, int y, int z) {
return true;
}
@Override
default boolean check(T t, World world, int x, int y, int z) {
return true;
}
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
@Override
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return true;
}
default boolean isNavigating() {
return true;
}
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
return PlaceResult.SKIP;
}
default boolean isNavigating() {
return true;
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizon.structurelib.structure;
import net.minecraft.world.World;
public interface IStructureWalker<T> {
boolean visit(IStructureElement<T> element, World world, int x, int y, int z);
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
return false;
}
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
return new IStructureWalker<T>() {
@Override
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
return walker.visit(element, world, x, y, z);
}
@Override
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
return walker.visit(element, world, x, y, z);
}
};
}
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
return new IStructureWalker<T>() {
@Override
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
return walker.visit(element, world, x, y, z);
}
@Override
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
return true;
}
};
}
}
@@ -4,6 +4,7 @@ import net.minecraft.block.Block;
/**
* a function to extract tier info from a block.
*
* @param <TIER>
*/
@FunctionalInterface
@@ -3,10 +3,11 @@ package com.gtnewhorizon.structurelib.structure;
/**
* Implement this and use {@link StructureUtility#withContext(IStructureElement)} in case you need to record some
* temporary states that don't need to live in your main context, which would usually live for the whole lifetime of the
* structure than just during structure check. Usually just save some few bytes, but becomes non trivial if you need to
* structure than just during structure check. Usually just save some few bytes, but becomes non-trivial if you need to
* record too much stuff.
*
* <p>
* You will have to reset the context yourself though. The API can't guess when the old context becomes invalid.
*
* @param <CTX> the temporary state context
*/
public interface IWithExtendedContext<CTX> {
@@ -1,38 +1,54 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.function.Consumer;
import java.util.function.Function;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
import java.util.function.Function;
class LazyStructureElement<T> implements IStructureElementDeferred<T> {
private Function<T, IStructureElement<T>> to;
private IStructureElement<T> elem;
private Function<T, IStructureElement<T>> to;
private IStructureElement<T> elem;
public LazyStructureElement(Function<T, IStructureElement<T>> to) {
this.to = to;
}
public LazyStructureElement(Function<T, IStructureElement<T>> to) {
this.to = to;
}
private IStructureElement<T> get(T t) {
if (to != null) {
elem = to.apply(t);
to = null;
}
return elem;
}
private IStructureElement<T> get(T t) {
if (to != null) {
elem = to.apply(t);
to = null;
}
return elem;
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
return get(t).check(t, world, x, y, z);
}
@Override
public boolean check(T t, World world, int x, int y, int z) {
return get(t).check(t, world, x, y, z);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return get(t).placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
return get(t).placeBlock(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return get(t).spawnHint(t, world, x, y, z, trigger);
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
return get(t).spawnHint(t, world, x, y, z, trigger);
}
@Override
public PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
}
@@ -1,201 +1,197 @@
package com.gtnewhorizon.structurelib.structure;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.*;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import java.util.*;
import java.util.stream.Collectors;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.*;
public class StructureDefinition<T> implements IStructureDefinition<T> {
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private final Map<String, IStructureElement<T>[]> structures;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private final Map<String, IStructureElement<T>[]> structures;
public static <B> Builder<B> builder() {
return new Builder<>();
}
public static <B> Builder<B> builder() {
return new Builder<>();
}
private StructureDefinition(
Map<Character, IStructureElement<T>> elements,
Map<String, String> shapes,
Map<String, IStructureElement<T>[]> structures) {
this.elements = elements;
this.shapes = shapes;
this.structures = structures;
}
private StructureDefinition(
Map<Character, IStructureElement<T>> elements,
Map<String, String> shapes,
Map<String, IStructureElement<T>[]> structures) {
this.elements = elements;
this.shapes = shapes;
this.structures = structures;
}
public static class Builder<T> {
private static final char A = '\uA000';
private static final char B = '\uB000';
private static final char C = '\uC000';
private char d = '\uD000';
private final Map<Vec3Impl, Character> navigates;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
public static class Builder<T> {
private static final char A = '\uA000';
private static final char B = '\uB000';
private static final char C = '\uC000';
private char d = '\uD000';
private final Map<Vec3Impl, Character> navigates;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private Builder() {
navigates = new HashMap<>();
elements = new HashMap<>();
shapes = new HashMap<>();
}
private Builder() {
navigates = new HashMap<>();
elements = new HashMap<>();
shapes = new HashMap<>();
}
public Map<Character, IStructureElement<T>> getElements() {
return elements;
}
public Map<Character, IStructureElement<T>> getElements() {
return elements;
}
public Map<String, String> getShapes() {
return shapes;
}
public Map<String, String> getShapes() {
return shapes;
}
/**
* Adds shape
* + is anything but air
* - is air checks
* space bar is skip
* ~ is also skip (but marks controller position, optional and logically it is a space...)
* rest needs to be defined
* <p>
* next char is next block(a)
* next string is next line(b)
* next string[] is next slice(c)
* <p>
* char A000-FFFF range is reserved for generated skips
*
* @param name unlocalized/code name
* @param structurePiece generated or written struct - DO NOT STORE IT ANYWHERE, or at least set them to null afterwards
* @return this builder
*/
public Builder<T> addShape(String name, String[][] structurePiece) {
StringBuilder builder = new StringBuilder();
if (structurePiece.length > 0) {
for (String[] strings : structurePiece) {
if (strings.length > 0) {
for (String string : strings) {
builder.append(string).append(B);
}
builder.setLength(builder.length() - 1);
}
builder.append(C);
}
builder.setLength(builder.length() - 1);
}
int a = 0, b = 0, c = 0;
for (int i = 0; i < builder.length(); i++) {
char ch = builder.charAt(i);
if (ch == ' ' || ch == '~') {
builder.setCharAt(i, A);
ch = A;
}
if (ch == A) {
a++;
} else if (ch == B) {
a = 0;
b++;
} else if (ch == C) {
a = 0;
b = 0;
c++;
} else if (a != 0 || b != 0 || c != 0) {
Vec3Impl vec3 = new Vec3Impl(a, b, c);
Character navigate = navigates.get(vec3);
if (navigate == null) {
navigate = d++;
navigates.put(vec3, navigate);
addElement(navigate, step(vec3));
}
builder.setCharAt(i - 1, navigate);
a = 0;
b = 0;
c = 0;
}
}
/**
* Adds shape
* + is anything but air
* - is air checks
* space bar is skip
* ~ is also skip (but marks controller position, optional. implementation wise it is a space...)
* rest needs to be defined
* <p>
* next char is next block(a)
* next string is next line(b)
* next string[] is next slice(c)
* <p>
* char A000-FFFF range is reserved for generated skips
*
* @param name unlocalized/code name
* @param structurePiece generated or written struct - DO NOT STORE IT ANYWHERE, or at least set them to null afterwards
* @return this builder
*/
public Builder<T> addShape(String name, String[][] structurePiece) {
StringBuilder builder = new StringBuilder();
if (structurePiece.length > 0) {
for (String[] strings : structurePiece) {
if (strings.length > 0) {
for (String string : strings) {
builder.append(string).append(B);
}
builder.setLength(builder.length() - 1);
}
builder.append(C);
}
builder.setLength(builder.length() - 1);
}
int a = 0, b = 0, c = 0;
for (int i = 0; i < builder.length(); i++) {
char ch = builder.charAt(i);
if (ch == ' ' || ch == '~') {
builder.setCharAt(i, A);
ch = A;
}
if (ch == A) {
a++;
} else if (ch == B) {
a = 0;
b++;
} else if (ch == C) {
a = 0;
b = 0;
c++;
} else if (a != 0 || b != 0 || c != 0) {
Vec3Impl vec3 = new Vec3Impl(a, b, c);
Character navigate = navigates.get(vec3);
if (navigate == null) {
navigate = d++;
navigates.put(vec3, navigate);
addElement(navigate, step(vec3));
}
builder.setCharAt(i - 1, navigate);
a = 0;
b = 0;
c = 0;
}
}
String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", "");
String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", "");
if (built.contains("+")) {
addElement('+', notAir());
}
if (built.contains("-")) {
addElement('-', isAir());
}
shapes.put(name, built);
return this;
}
if (built.contains("+")) {
addElement('+', notAir());
}
if (built.contains("-")) {
addElement('-', isAir());
}
shapes.put(name, built);
return this;
}
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public IStructureDefinition<T> build() {
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
if (DEBUG_MODE) {
return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures);
} else {
return structures::get;
}
}
public IStructureDefinition<T> build() {
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
if (StructureLibAPI.isDebugEnabled()) {
return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures);
} else {
return structures::get;
}
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().chars()
.mapToObj(c -> (char) c)
.distinct()
.map(elements::get)
.toArray(IStructureElement[]::new)
));
} else {
throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " +
Arrays.toString(missing.toArray()));
}
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
.chars()
.mapToObj(c -> (char) c)
.distinct()
.map(elements::get)
.toArray(IStructureElement[]::new)));
} else {
throw new RuntimeException("Missing Structure Element bindings for (chars as integers): "
+ Arrays.toString(missing.toArray()));
}
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<T>[]> compileStructureMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().chars().mapToObj(c -> elements.get((char) c)).toArray(IStructureElement[]::new)
));
} else {
throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " +
Arrays.toString(missing.toArray()));
}
}
@SuppressWarnings("unchecked")
private Map<String, IStructureElement<T>[]> compileStructureMap() {
Set<Integer> missing = findMissing();
if (missing.isEmpty()) {
return shapes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
.chars()
.mapToObj(c -> elements.get((char) c))
.toArray(IStructureElement[]::new)));
} else {
throw new RuntimeException("Missing Structure Element bindings for (chars as integers): "
+ Arrays.toString(missing.toArray()));
}
}
private Set<Integer> findMissing() {
return shapes.values().stream()
.flatMapToInt(CharSequence::chars)
.filter(i -> !elements.containsKey((char) i))
.boxed()
.collect(Collectors.toSet());
}
}
private Set<Integer> findMissing() {
return shapes.values().stream()
.flatMapToInt(CharSequence::chars)
.filter(i -> !elements.containsKey((char) i))
.boxed()
.collect(Collectors.toSet());
}
}
public Map<Character, IStructureElement<T>> getElements() {
return elements;
}
public Map<Character, IStructureElement<T>> getElements() {
return elements;
}
public Map<String, String> getShapes() {
return shapes;
}
public Map<String, String> getShapes() {
return shapes;
}
public Map<String, IStructureElement<T>[]> getStructures() {
return structures;
}
public Map<String, IStructureElement<T>[]> getStructures() {
return structures;
}
@Override
public IStructureElement<T>[] getStructureFor(String name) {
IStructureElement<T>[] elements = structures.get(name);
if (elements == null)
throw new NoSuchElementException(name);
return elements;
}
}
@Override
public IStructureElement<T>[] getStructureFor(String name) {
IStructureElement<T>[] elements = structures.get(name);
if (elements == null) throw new NoSuchElementException(name);
return elements;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,56 @@
package com.gtnewhorizon.structurelib.structure;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
private final T object;
private final ItemStack trigger;
private final IItemSource source;
private final EntityPlayerMP actor;
private final int elementBudget;
private final boolean check;
private int built = -1;
public SurvivalBuildStructureWalker(
T object, ItemStack trigger, IItemSource source, EntityPlayerMP actor, int elementBudget, boolean check) {
this.object = object;
this.trigger = trigger;
this.source = source;
this.actor = actor;
this.elementBudget = elementBudget;
this.check = check;
}
@Override
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
PlaceResult placeResult = element.survivalPlaceBlock(
object, world, x, y, z, trigger, source, actor, actor::addChatComponentMessage);
if (placeResult != PlaceResult.SKIP && built == -1) built = 0;
switch (placeResult) {
case SKIP:
return true;
case ACCEPT:
if (check) element.check(object, world, x, y, z);
return ++built < elementBudget;
case REJECT:
StructureLibAPI.markHintParticleError(actor, world, x, y, z);
return false;
case ACCEPT_STOP:
if (check) element.check(object, world, x, y, z);
built += 1;
// intentional fallthrough
case STOP:
return false;
default:
throw new NullPointerException();
}
}
public int getBuilt() {
return built;
}
}
@@ -1,15 +1,14 @@
package com.gtnewhorizon.structurelib.structure.adders;
import net.minecraft.block.Block;
public interface IBlockAdder<T> {
/**
* Callback on block added, needs to check if block is valid (and add it)
*
* @param block block attempted to add
* @param meta meta of block attempted to add
* @return is structure still valid
*/
boolean apply(T t, Block block, int meta);
/**
* Callback on block added, needs to check if block is valid (and add it)
*
* @param block block attempted to add
* @param meta meta of block attempted to add
* @return is structure still valid
*/
boolean apply(T t, Block block, int meta);
}
@@ -3,11 +3,11 @@ package com.gtnewhorizon.structurelib.structure.adders;
import net.minecraft.tileentity.TileEntity;
public interface ITileAdder<T> {
/**
* Callback to add hatch, needs to check if tile is valid (and add it)
*
* @param tileEntity tile
* @return managed to add hatch (structure still valid)
*/
boolean apply(T t, TileEntity tileEntity);
/**
* Callback to add hatch, needs to check if tile is valid (and add it)
*
* @param tileEntity tile
* @return managed to add hatch (structure still valid)
*/
boolean apply(T t, TileEntity tileEntity);
}
@@ -0,0 +1,4 @@
/**
* Adder interfaces used in various standard structure elements.
*/
package com.gtnewhorizon.structurelib.structure.adders;
@@ -1,5 +1,7 @@
/**
* This package provide support for multiblock structure checking.
* <p>
* All public stuff in this package and its subpackages are considered part of the public API.
* Backwards compatibility is maintained to the maximum extend possible.
*/
package com.gtnewhorizon.structurelib.structure;
package com.gtnewhorizon.structurelib.structure;
@@ -0,0 +1,47 @@
package com.gtnewhorizon.structurelib.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
public class InventoryIterable<Inv extends IInventory> implements Iterable<ItemStack> {
private final Inv inv;
private final int maxSlot;
public InventoryIterable(Inv inv) {
this(inv, -1);
}
InventoryIterable(Inv inv, int maxSlot) {
this.inv = inv;
this.maxSlot = maxSlot;
}
public Inv getInventory() {
return inv;
}
@Override
public Iterator<ItemStack> iterator() {
return new Iterator<ItemStack>() {
private int ptr = 0;
@Override
public boolean hasNext() {
return ptr < inv.getSizeInventory() && (maxSlot == -1 || ptr < maxSlot);
}
@Override
public ItemStack next() {
if (!hasNext()) throw new NoSuchElementException();
return inv.getStackInSlot(ptr++);
}
@Override
public void remove() {
inv.setInventorySlotContents(ptr - 1, null);
}
};
}
}
@@ -0,0 +1,391 @@
/**
* Copyright (c) 2022, glee8e
* This file is part of StructureLib.
* <p>
* StructureLib is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* <p>
* StructureLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* <p>
* You should have received a copy of the GNU Lesser General Public License
* along with Foobar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.gtnewhorizon.structurelib.util;
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
import com.gtnewhorizon.structurelib.util.InventoryUtility.ItemStackExtractor.APIType;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.init.Items;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryEnderChest;
import net.minecraft.item.ItemStack;
/**
* This class is part of API, but is not stable. Use at your own risk.
*/
public class InventoryUtility {
private static final SortedRegistry<ItemStackExtractor> stackExtractors = new SortedRegistry<>();
private static final List<Predicate<? super EntityPlayerMP>> enableEnder = new CopyOnWriteArrayList<>();
/**
* The remove() of the Iterable returned must be implemented!
*/
private static final SortedRegistry<InventoryProvider<?>> inventoryProviders = new SortedRegistry<>();
static {
inventoryProviders.register("5000-main-inventory", new InventoryProvider<InventoryIterable<InventoryPlayer>>() {
@Override
public InventoryIterable<InventoryPlayer> getInventory(EntityPlayerMP player) {
return new InventoryIterable<>(player.inventory, player.inventory.mainInventory.length);
}
@Override
public void markDirty(InventoryIterable<InventoryPlayer> inv) {
// player save its content using means other than inv.markDirty()
// here we only need to sync it to client
inv.getInventory().player.inventoryContainer.detectAndSendChanges();
}
});
inventoryProviders.register(
"7000-ender-inventory", new InventoryProvider<InventoryIterable<InventoryEnderChest>>() {
@Override
public InventoryIterable<InventoryEnderChest> getInventory(EntityPlayerMP player) {
if (enableEnder.stream().anyMatch(p -> p.test(player)))
return new InventoryIterable<>(player.getInventoryEnderChest());
return null;
}
@Override
public void markDirty(InventoryIterable<InventoryEnderChest> inv) {
// inv.getInventory().markDirty();
// TODO this seems to be a noop
}
});
}
public static void registerEnableEnderCondition(Predicate<? super EntityPlayerMP> predicate) {
enableEnder.add(predicate);
}
public static void registerStackExtractor(String key, ItemStackExtractor val) {
if (Arrays.stream(APIType.values()).noneMatch(val::isAPIImplemented))
throw new IllegalArgumentException("Must implement at least one API");
stackExtractors.register(key, val);
}
public static <Inv extends IInventory> void registerStackExtractor(
String key, Function<ItemStack, ? extends Inv> extractor) {
registerStackExtractor(key, newItemStackProvider(extractor));
}
public static void registerInventoryProvider(String key, InventoryProvider<?> val) {
inventoryProviders.register(key, val);
}
public static <Inv extends IInventory> void registerInventoryProvider(
String key, Function<EntityPlayerMP, ? extends Inv> extractor) {
registerInventoryProvider(key, newInventoryProvider(extractor));
}
public static Iterator<? extends ItemStackExtractor> getStackExtractors() {
return stackExtractors.iterator();
}
public static <Inv extends IInventory> InventoryProvider<InventoryIterable<Inv>> newInventoryProvider(
Function<EntityPlayerMP, ? extends Inv> extractor) {
return new InventoryProvider<InventoryIterable<Inv>>() {
@Override
public InventoryIterable<Inv> getInventory(EntityPlayerMP player) {
Inv inv = extractor.apply(player);
return inv != null ? new InventoryIterable<>(inv) : null;
}
@Override
public void markDirty(InventoryIterable<Inv> inv) {
inv.getInventory().markDirty();
}
};
}
public static ItemStackExtractor newItemStackProvider(Function<ItemStack, ? extends IInventory> extractor) {
return new ItemStackExtractor() {
@Override
public boolean isAPIImplemented(APIType type) {
return type == APIType.MAIN;
}
@Override
public int takeFromStack(
Predicate<ItemStack> predicate,
boolean simulate,
int count,
ItemStackCounter store,
ItemStack stack,
ItemStack filter,
EntityPlayerMP player) {
IInventory inv = extractor.apply(stack);
if (inv == null) return 0;
int found = takeFromInventory(
new InventoryIterable<>(inv), predicate, simulate, count, store, filter, player, false);
if (found > 0) inv.markDirty();
return found;
}
};
}
/**
* take count amount of stacks that matches given filter. might take partial amount of stuff, so simulation is suggested
* if you ever need to take more than one.
*
* @param player source of stacks
* @param predicate item stack filter
* @param simulate whether to do removal
* @param count let's hope int size is enough...
* @return amount taken. never negative nor bigger than count...
*/
public static Map<ItemStack, Integer> takeFromInventory(
EntityPlayerMP player, Predicate<ItemStack> predicate, boolean simulate, int count) {
ItemStackCounterImpl store = new ItemStackCounterImpl();
int sum = 0;
for (InventoryProvider<?> provider : inventoryProviders) {
sum += takeFromPlayer(player, predicate, simulate, count - sum, store, provider, null);
if (sum >= count) return store.getStore();
}
return store.getStore();
}
/**
* take count amount of stacks that matches given filter. might take partial amount of stuff, so simulation is suggested
* if you ever need to take more than one.
*
* @param player source of stacks
* @param filter the precise type of item to extract. stackSize matters
* @param simulate whether to do removal
* @return amount taken. never negative nor bigger than count...
*/
public static int takeFromInventory(EntityPlayerMP player, ItemStack filter, boolean simulate) {
int sum = 0;
int count = filter.stackSize;
ItemStackPredicate predicate = ItemStackPredicate.from(filter, NBTMode.EXACT);
ItemStackCounterImpl store = new ItemStackCounterImpl();
for (InventoryProvider<?> provider : inventoryProviders) {
sum += takeFromPlayer(player, predicate, simulate, count - sum, store, provider, filter);
if (sum >= count) return sum;
}
return sum;
}
// workaround java generics issue
private static <R extends Iterable<ItemStack>> int takeFromPlayer(
EntityPlayerMP player,
Predicate<ItemStack> predicate,
boolean simulate,
int count,
ItemStackCounterImpl store,
InventoryProvider<R> provider,
ItemStack filter) {
R inv = provider.getInventory(player);
if (inv == null) return 0;
int taken = takeFromInventory(inv, predicate, simulate, count, store, filter, player, true);
if (taken > 0) provider.markDirty(inv);
return taken;
}
/**
* take count amount of stacks that matches given filter. Will do 1 level of recursion to try find more stacks....
* e.g. from backpacks...
*
* @param inv source of stacks
* @param predicate item stack filter
* @param simulate whether to do removal
* @param count let's hope int size is enough...
* @param recursive do recursive lookup using {@link #getStackExtractors() stack extractors}
* @return amount taken. never negative nor bigger than count...
*/
public static Map<ItemStack, Integer> takeFromInventory(
Iterable<ItemStack> inv, Predicate<ItemStack> predicate, boolean simulate, int count, boolean recursive) {
ItemStackCounterImpl store = new ItemStackCounterImpl();
takeFromInventory(inv, predicate, simulate, count, store, null, null, recursive);
return store.getStore();
}
private static int takeFromInventory(
@Nonnull Iterable<ItemStack> inv,
@Nonnull Predicate<ItemStack> predicate,
boolean simulate,
int count,
@Nonnull ItemStackCounter store,
@Nullable ItemStack filter,
@Nullable EntityPlayerMP player,
boolean recursive) {
int found = 0;
ItemStack copiedFilter = null;
if (filter != null) {
copiedFilter = new ItemStack(filter.getItem(), filter.stackSize, Items.feather.getDamage(filter));
copiedFilter.setTagCompound(filter.stackTagCompound);
}
for (Iterator<ItemStack> iterator = inv.iterator(); iterator.hasNext(); ) {
ItemStack stack = iterator.next();
// invalid stack
if (stack == null || stack.getItem() == null || stack.stackSize <= 0) continue;
if (predicate.test(stack)) {
found += stack.stackSize;
if (found > count) {
int surplus = found - count;
store.add(stack, stack.stackSize - surplus);
if (!simulate) {
// leave the surplus in its place
stack.stackSize = surplus;
}
return count;
}
store.add(stack, stack.stackSize);
if (!simulate) iterator.remove();
if (found == count) return count;
}
if (!recursive) continue;
for (ItemStackExtractor f : stackExtractors) {
if (filter != null && f.isAPIImplemented(APIType.EXTRACT_ONE_STACK)) {
copiedFilter.stackSize = count - found;
found += f.getItem(stack, copiedFilter, simulate, player);
} else {
found += f.takeFromStack(predicate, simulate, count - found, store, stack, filter, player);
}
if (found >= count) return found;
}
}
return found;
}
public interface OptimizedExtractor {
/**
* Extract a particular type of item. The extractor can choose to not return all items contained within this item
* as long as it makes sense, but the author should inform the player of this.
* <p>
* Whether optimized extractor do recursive extraction is at the discretion of implementor.
*
* @param source from where an extraction should be attempted
* @param toExtract stack to extract. should not be mutated! match the NBT tag using EXACT mode.
* @param simulate true if only query but does not actually remove
* @param player executor of extraction, or null if from a machine
* @return amount extracted
*/
int extract(ItemStack source, ItemStack toExtract, boolean simulate, EntityPlayerMP player);
}
public interface InventoryProvider<R extends Iterable<ItemStack>> {
R getInventory(EntityPlayerMP player);
void markDirty(R inv);
}
public interface ItemStackExtractor {
enum APIType {
MAIN,
EXTRACT_ONE_STACK,
}
boolean isAPIImplemented(APIType type);
/**
* Extract a particular type of item. The extractor can choose to not return all items contained within this item
* as long as it makes sense, but the author should inform the player of this.
* <p>
* Whether this method does recursive extraction is at the discretion of implementor.
*
* @param predicate the main filtering predicate. never null. It's assumed predicate always returns true on filter,
* if that is not null
* @param store where to store extracted items. Should increment the
* @param source from where an extraction should be attempted
* @param filter stack to extract. should not be mutated! match the NBT tag using EXACT mode. might be null.
* @param simulate true if only query but does not actually remove
* @param player executor of extraction, or null if from a machine
* @return amount extracted, or -1 if this is not implemented
*/
int takeFromStack(
Predicate<ItemStack> predicate,
boolean simulate,
int count,
ItemStackCounter store,
ItemStack source,
ItemStack filter,
EntityPlayerMP player);
/**
* Extract a particular type of item. The extractor can choose to not return all items contained within this item
* as long as it makes sense, but the author should inform the player of this.
* <p>
* Whether this method does recursive extraction is at the discretion of implementor.
*
* @param source from where an extraction should be attempted
* @param toExtract stack to extract. should not be mutated! match the NBT tag using EXACT mode.
* @param simulate true if only query but does not actually remove
* @param player executor of extraction, or null if from a machine
* @return amount extracted, or -1 if this is not implemented
*/
default int getItem(ItemStack source, ItemStack toExtract, boolean simulate, EntityPlayerMP player) {
return -1;
}
static ItemStackExtractor createOnlyOptimized(@Nonnull OptimizedExtractor optimizedExtractor) {
return new ItemStackExtractor() {
@Override
public boolean isAPIImplemented(APIType type) {
return type == APIType.EXTRACT_ONE_STACK;
}
@Override
public int takeFromStack(
Predicate<ItemStack> predicate,
boolean simulate,
int count,
ItemStackCounter store,
ItemStack stack,
ItemStack filter,
EntityPlayerMP player) {
return 0;
}
@Override
public int getItem(ItemStack source, ItemStack toExtract, boolean simulate, EntityPlayerMP player) {
return optimizedExtractor.extract(source, toExtract, simulate, player);
}
};
}
}
public interface ItemStackCounter {
/**
* Add some amount of stack. Note stackSize is used instead of stack.stackSize.
*/
void add(ItemStack stack, int stackSize);
}
private static class ItemStackCounterImpl implements ItemStackCounter {
private final Map<ItemStack, Integer> store = new ItemStackMap<>(true);
@Override
public void add(ItemStack stack, int stackSize) {
if (stack == null || stack.getItem() == null || stackSize <= 0) throw new IllegalArgumentException();
store.merge(stack, stackSize, Integer::sum);
}
public Map<ItemStack, Integer> getStore() {
return store;
}
}
}
@@ -0,0 +1,38 @@
package com.gtnewhorizon.structurelib.util;
import java.util.Iterator;
import net.minecraft.item.ItemStack;
public class ItemStackArrayIterable implements Iterable<ItemStack> {
private final ItemStack[] stacks;
public ItemStackArrayIterable(ItemStack[] stacks) {
this.stacks = stacks;
}
public ItemStack[] getStacks() {
return stacks;
}
@Override
public Iterator<ItemStack> iterator() {
return new Iterator<ItemStack>() {
private int ptr = 0;
@Override
public boolean hasNext() {
return ptr < stacks.length;
}
@Override
public ItemStack next() {
return stacks[ptr++];
}
@Override
public void remove() {
stacks[ptr - 1] = null;
}
};
}
}
@@ -0,0 +1,112 @@
package com.gtnewhorizon.structurelib.util;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import net.minecraft.block.Block;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
public final class ItemStackPredicate implements Predicate<ItemStack> {
public static ItemStackPredicate from(Item item) {
return new ItemStackPredicate(item);
}
public static ItemStackPredicate from(ItemStack itemStack) {
return new ItemStackPredicate(itemStack.getItem()).setMeta(Items.feather.getDamage(itemStack));
}
public static ItemStackPredicate from(ItemStack itemStack, NBTMode mode) {
return new ItemStackPredicate(itemStack.getItem())
.setMeta(Items.feather.getDamage(itemStack))
.setTag(mode, itemStack.stackTagCompound);
}
public static ItemStackPredicate from(Block block) {
return new ItemStackPredicate(Item.getItemFromBlock(block));
}
private final Item item;
private int meta = -1;
private NBTTagCompound tag;
private NBTMode mode = NBTMode.IGNORE;
private ItemStackPredicate(Item item) {
this.item = item;
}
public ItemStackPredicate ignoreMeta() {
this.meta = -1;
return this;
}
public ItemStackPredicate setMeta(int meta) {
this.meta = meta;
return this;
}
public ItemStackPredicate setTag(NBTMode mode, NBTTagCompound tag) {
this.mode = mode;
this.tag = tag;
return this;
}
@Override
public boolean test(ItemStack itemStack) {
if (item != null) if (itemStack.getItem() != item) return false;
if (meta != -1) if (Items.feather.getDamage(itemStack) != meta) return false;
return mode.test(tag, itemStack.stackTagCompound);
}
public enum NBTMode implements BiPredicate<NBTTagCompound, NBTTagCompound> {
IGNORE {
@Override
public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) {
return true;
}
},
IN {
@Override
public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) {
if (lhs == null || lhs.hasNoTags()) return true;
if (rhs == null || rhs.hasNoTags()) return false;
for (String key : MiscUtils.getTagKeys(lhs)) {
if (!rhs.hasKey(key, lhs.func_150299_b(key))) return false;
NBTBase tag = lhs.getTag(key);
if (tag instanceof NBTTagCompound) {
if (!test((NBTTagCompound) tag, rhs.getCompoundTag(key))) return false;
} else {
if (!tag.equals(rhs.getTag(key))) return false;
}
}
return true;
}
},
EXACT {
@Override
public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) {
if (lhs != null && lhs.hasNoTags()) lhs = null;
if (rhs != null && rhs.hasNoTags()) rhs = null;
return Objects.equals(lhs, rhs);
}
},
IGNORE_KNOWN_INSIGNIFICANT_TAGS {
@Override
public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) {
// fast path for empty tags
if (rhs == null || rhs.hasNoTags()) return lhs == null || lhs.hasNoTags();
// TODO make an implementation without copying a potentially huge tag
rhs = (NBTTagCompound) rhs.copy();
for (String s : KNOWN_INSIGNIFICANT_TAGS) rhs.removeTag(s);
return EXACT.test(lhs, rhs);
}
};
private static final String[] KNOWN_INSIGNIFICANT_TAGS = {
"display", // TODO expand/refine this
};
}
}
@@ -0,0 +1,16 @@
package com.gtnewhorizon.structurelib.util;
import java.util.Set;
import net.minecraft.nbt.NBTTagCompound;
/**
* not related to a certain mod!
*/
public class MiscUtils {
private MiscUtils() {}
@SuppressWarnings("unchecked")
public static Set<String> getTagKeys(NBTTagCompound tag) {
return tag.func_150296_c();
}
}
@@ -0,0 +1,57 @@
package com.gtnewhorizon.structurelib.util;
import java.util.*;
import java.util.function.Consumer;
public class SortedRegistry<V> implements Iterable<V> {
private final NavigableMap<String, V> store = new TreeMap<>();
/**
* Store stuff in an array for faster iteration access than a red black tree
*/
private List<V> baked = Collections.emptyList();
public void register(String key, V val) {
if (key == null || val == null) throw new NullPointerException();
V old = store.putIfAbsent(key, val);
if (old != null) {
throw new IllegalArgumentException("Duplicate key: " + key);
}
// TODO allow a user to edit the order with a configuration file
baked = new ArrayList<>(store.values());
}
public int size() {
return store.size();
}
public boolean isEmpty() {
return store.isEmpty();
}
public boolean containsKey(String key) {
return store.containsKey(key);
}
public boolean containsValue(V value) {
return store.containsValue(value);
}
public V get(String key) {
return store.get(key);
}
@Override
public Iterator<V> iterator() {
return baked.iterator();
}
@Override
public void forEach(Consumer<? super V> action) {
baked.forEach(action);
}
@Override
public Spliterator<V> spliterator() {
return baked.spliterator();
}
}
@@ -23,11 +23,15 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
* Gets the coordinate.
*/
public int get(int index) {
switch (index){
case 0: return val0;
case 1: return val1;
case 2: return val2;
default: return 0;
switch (index) {
case 0:
return val0;
case 1:
return val1;
case 2:
return val2;
default:
return 0;
}
}
@@ -53,7 +57,9 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
}
public Vec3Impl offset(ForgeDirection facing, int n) {
return n == 0 ? this : new Vec3Impl(val0 + facing.offsetX * n, val1 + facing.offsetY * n, val2 + facing.offsetZ * n);
return n == 0
? this
: new Vec3Impl(val0 + facing.offsetX * n, val1 + facing.offsetY * n, val2 + facing.offsetZ * n);
}
public Vec3Impl add(Vec3Impl pos) {
@@ -64,16 +70,18 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
return new Vec3Impl(val0 - pos.val0, val1 - pos.val1, val2 - pos.val2);
}
public Vec3Impl add(int pos0,int pos1,int pos2) {
public Vec3Impl add(int pos0, int pos1, int pos2) {
return new Vec3Impl(val0 + pos0, val1 + pos1, val2 + pos2);
}
public Vec3Impl sub(int pos0,int pos1,int pos2) {
public Vec3Impl sub(int pos0, int pos1, int pos2) {
return new Vec3Impl(val0 - pos0, val1 - pos1, val2 - pos2);
}
public Vec3Impl crossProduct(Vec3Impl vec) {
return new Vec3Impl(val1 * vec.val2 - val2 * vec.val1, val2 * vec.val0 - val0 * vec.val2,
return new Vec3Impl(
val1 * vec.val2 - val2 * vec.val1,
val2 * vec.val0 - val0 * vec.val2,
val0 * vec.val1 - val1 * vec.val0);
}
@@ -95,17 +103,17 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
public double distanceSq(double x, double y, double z, boolean useCenter) {
double d0 = useCenter ? 0.5D : 0.0D;
double d1 = (double)val0 + d0 - x;
double d2 = (double)val1 + d0 - y;
double d3 = (double)val2 + d0 - z;
double d1 = (double) val0 + d0 - x;
double d2 = (double) val1 + d0 - y;
double d3 = (double) val2 + d0 - z;
return d1 * d1 + d2 * d2 + d3 * d3;
}
public int manhattanDistance(Vec3Impl to) {
float f = (float)Math.abs(to.val0 - val0);
float f1 = (float)Math.abs(to.val1 - val1);
float f2 = (float)Math.abs(to.val2 - val2);
return (int)(f + f1 + f2);
float f = (float) Math.abs(to.val0 - val0);
float f1 = (float) Math.abs(to.val1 - val1);
float f2 = (float) Math.abs(to.val2 - val2);
return (int) (f + f1 + f2);
}
@Override
@@ -114,14 +122,14 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
}
public Vec3Impl abs() {
return new Vec3Impl(Math.abs(val0),Math.abs(val1),Math.abs(val2));
return new Vec3Impl(Math.abs(val0), Math.abs(val1), Math.abs(val2));
}
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o instanceof Vec3Impl) {
Vec3Impl vec3i = (Vec3Impl)o;
Vec3Impl vec3i = (Vec3Impl) o;
return val0 == vec3i.val0 && val1 == vec3i.val1 && val2 == vec3i.val2;
}
return false;
@@ -130,4 +138,4 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
public int hashCode() {
return (val1 + val2 * 31) * 31 + val0;
}
}
}
@@ -1,28 +1,28 @@
package com.gtnewhorizon.structurelib.util;
/*
A subclass of java.util.random that implements the Xorshift random number
generator
<p>
- it is 30% faster than the generator from Java's library - it produces
random sequences of higher quality than java.util.Random - this class also
provides a clone() function
<p>
Usage: XSRandom rand = new XSRandom(); //Instantiation x = rand.nextInt();
//pull a random number
<p>
To use the class in legacy code, you may also instantiate an XSRandom object
and assign it to a java.util.Random object: java.util.Random rand = new
XSRandom();
<p>
for an explanation of the algorithm, see
http://demesos.blogspot.com/2011/09/pseudo-random-number-generators.html
A subclass of java.util.random that implements the Xorshift random number
generator
<p>
- it is 30% faster than the generator from Java's library - it produces
random sequences of higher quality than java.util.Random - this class also
provides a clone() function
<p>
Usage: XSRandom rand = new XSRandom(); //Instantiation x = rand.nextInt();
//pull a random number
<p>
To use the class in legacy code, you may also instantiate an XSRandom object
and assign it to a java.util.Random object: java.util.Random rand = new
XSRandom();
<p>
for an explanation of the algorithm, see
http://demesos.blogspot.com/2011/09/pseudo-random-number-generators.html
@author Wilfried Elmenreich University of Klagenfurt/Lakeside Labs
* http://www.elmenreich.tk
* <p>
* This code is released under the GNU Lesser General Public License Version 3
* http://www.gnu.org/licenses/lgpl-3.0.txt
*/
@author Wilfried Elmenreich University of Klagenfurt/Lakeside Labs
* http://www.elmenreich.tk
* <p>
* This code is released under the GNU Lesser General Public License Version 3
* http://www.gnu.org/licenses/lgpl-3.0.txt
*/
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
@@ -40,22 +40,22 @@ public class XSTR extends Random {
private static final long GAMMA = 0x9e3779b97f4a7c15L;
private static final int PROBE_INCREMENT = 0x9e3779b9;
private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL;
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0f / (1 << 24)
private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
public static final XSTR XSTR_INSTANCE=new XSTR(){
public static final XSTR XSTR_INSTANCE = new XSTR() {
@Override
public synchronized void setSeed(long seed) {
if(!Thread.currentThread().getStackTrace()[2].getClassName().equals(Random.class.getName())) {
if (!Thread.currentThread().getStackTrace()[2].getClassName().equals(Random.class.getName())) {
throw new NoSuchMethodError("This is meant to be shared!, leave seed state alone!");
}
}
};
/*
MODIFIED BY: Robotia
Modification: Implemented Random class seed generator
*/
MODIFIED BY: Robotia
Modification: Implemented Random class seed generator
*/
/**
* Creates a new pseudo random number generator. The seed is initialized to
@@ -226,9 +226,9 @@ public class XSTR extends Random {
*/
@Override
public int nextInt(int bound) {
//if (bound <= 0) {
//throw new RuntimeException("BadBound");
//}
// if (bound <= 0) {
// throw new RuntimeException("BadBound");
// }
/*int r = next(31);
int m = bound - 1;
@@ -242,7 +242,7 @@ public class XSTR extends Random {
;
}
return r;*/
//speedup, new nextInt ~+40%
// speedup, new nextInt ~+40%
long last = seed ^ seed << 21;
last ^= last >>> 35;
last ^= last << 4;
@@ -270,9 +270,11 @@ public class XSTR extends Random {
@Override
public void nextBytes(byte[] bytes_arr) {
for (int iba = 0, lenba = bytes_arr.length; iba < lenba; ) {
for (int rndba = nextInt(), nba = Math.min(lenba - iba, Integer.SIZE / Byte.SIZE); nba-- > 0; rndba >>= Byte.SIZE) {
for (int rndba = nextInt(), nba = Math.min(lenba - iba, Integer.SIZE / Byte.SIZE);
nba-- > 0;
rndba >>= Byte.SIZE) {
bytes_arr[iba++] = (byte) rndba;
}
}
}
}
}
+235
View File
@@ -0,0 +1,235 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>API Overview</TITLE>
</HEAD>
<BODY>
StructureLib API documentation.
<h1>Key API class outline</h1>
<p>
Here we outline a few key API classes/interfaces.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition}</h2>
<p>
This is the structure definition of your multi.
You will have one `IStructureDefinition` for each multi.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.IStructureElement}</h2>
<p>
These are how you define which block will be accepted.
</p>
<h2> {@link com.gtnewhorizon.structurelib.structure.StructureUtility}</h2>
<p>
This is the standard library of {@link com.gtnewhorizon.structurelib.structure.IStructureElement
IStructureElement}s.
The Standard Library contains structure elements that handle basic block element and elements that allow composing
these basic elements.
</p>
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable}</h2>
<p>
This interface should be implemented by tile entities that support creative autoplacing and hint particle emitting.
You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition
IStructureDefinition}
and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#build(Object, ItemStack, String,
World, ExtendedFacing, int, int, int, int, int, int) build()}
or {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#hints(Object, ItemStack, String, World,
ExtendedFacing, int, int, int, int, int, int) hints()} method here.
</p>
<h2> {@link com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable}</h2>
<p>
This interface is an enhancement over {@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable
IConstructable}.
If your tile entities also support survival autoplacing then you should implement this.
You will usually want to have a backing {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition
IStructureDefinition}
and call its {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack,
String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean)
survivalBuild()}.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructableProvider}</h2>
<p>
This interface should be implemented on tile entities that will delegate all constructable or survival construable
functionality to some other class.
This is mostly only to support gregtech meta tile entity system.
</p>
<p>
Return null if the delegate does not exist.
</p>
<h2>{@link com.gtnewhorizon.structurelib.util.InventoryUtility}</h2>
<p>
This class backs the standard implementations of {@link com.gtnewhorizon.structurelib.structure.IItemSource}.
You might want to register new inventory provider or inventory extractor there to facilitate survival auto place.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.IAlignmentProvider}, {@link
com.gtnewhorizon.structurelib.alignment.IAlignment} and {@link
com.gtnewhorizon.structurelib.alignment.IAlignmentLimits}</h2>
<p>
These interfaces will be used to implement rotation.
Provide a framework to rotate the block.
You can skip this if you store ExtendedFacing and do rotation in other means.
</p>
<h2>{@link com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor}</h2>
<p>
This class contains the accessors for channel data held on a trigger item. See <a href="#channels">below</a> for more
information.
</p>
<h2>{@link com.gtnewhorizon.structurelib.StructureLibAPI}</h2>
<p>
This class contains the low level utility used for implementing new {@link
com.gtnewhorizon.structurelib.structure.IStructureElement}.
You will also need methods in this class to while implementing rotation.
</p>
<p>
<b>IF THERE IS A SAME NAMED METHOD IN OTHER CLASS, ONLY USE METHODS FOUND IN THIS CLASS, NOT OTHER CLASS.</b>
This abstraction is necessary so we can change how underlying code work (e.g. how hint particles are rendered)
without
breaking client code.
</p>
<h1>Important Concepts</h1>
<h2>Context Object</h2>
<p>
Context Object are what you pass to {@link
com.gtnewhorizon.structurelib.structure.IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int,
int, int, int, int, boolean)} and alike as first parameter. It will hold the necessary states for {@link
com.gtnewhorizon.structurelib.structure.IStructureElement}. It's usually, but not necessarily, the multiblock controller.
</p>
<p>
Standard structure element library offers method to switch context objects if needed.
</p>
<h2>Trigger Item</h2>
<p>
Trigger Item is the item used to trigger hint spawn, creative build and survival auto place.
The trigger item will be passed to relevant classes as an indicator of structure tier.
The item type and stack tag compound of this item stack are opaque objects that should not be parsed.
</p>
<p>
StructureLib comes with a trigger item implementation.
It does register it to the game registries, but does not come with a recipe.
Usually there is no reason to reimplement this on your own.
</p>
<h2><a href="#channels" id="channels">Channels</a></h2>
<p>
Trigger item carries the player's choice of structure tier by its stack size.
When one multi have multiple tiered component that can have interleaving tier, this becomes an obnoxious
restriction, as one ItemStack cannot have two stack sizes at the same time.
Here we use the <i>channels</i> to bypass this restriction.
</p>
<p>
Basically, channels are information stored on the trigger item's NBT tags.
One trigger item can carry multiple channels.
Each channel can be identified with <b>lower case</b> strings and has the value being one positive Java int.
There will be one and only one master channel and 0 or more sub channel.
When a non-existent sub channel is queried, master channel will be used instead to fill its place.
</p>
<p>
Structure Definition Author and/or Structure Element Author can choose whether they use a channel and which channel
to use.
</p>
<p>
Structure Element Author will usually not need to consider channels, i.e. proceed as if this concept does not exist,
unless it must take input from multiple channels.
</p>
<p>
Structure Definition Author will consider how to map each channel to each tiered component.
He will be using `com.gtnewhorizon.structurelib.structure.StructureUtility.withChannel` to direct structure element
on which channel to use.
Conceptually, it will return an ItemStack with the master channel being the value of selected channel.
In implementation, withChannel will return a new ItemStack with same item, nbt but different stack size.
</p>
<p>
It's worth mentioning that the returned new ItemStack will still have all the channel data on it, so a multi-channel
structure element can still query the channel it wants just fine. It is however impossible to recover the original
master channel.
</p>
<h3>why is this called channel?</h3>
Just give me a better name and I'd do the renaming.
<h3>Examples</h3>
<h4>For Structure Definition Author</h4>
<p>GT++ Chemical Plant is a multi with 4 distinctly tiered component.</p>
<p>It will use
<ul>
<li>master channel for its outer shell</li>
<li>channel `"casing"` for machine casings</li>
<li>channel `"pipe"` for pipe casings</li>
<li>channel `"coil"` for heating coils</li>
</ul>
Its structure code will then look like
<pre>
.addElement('a', shellStructureElement())
.addElement('b', withChannel("casing", machineCasingStructureElement()))
.addElement('c', withChannel("pipe", pipeCasingStructureElement()))
.addElement('d', withChannel("coil", coilStructureElement()))
</pre>
whereas all these structure elements are designed without considering channels whatsoever
<h4> Multi channel input Structure Element</h4>
<p>
Suppose your hatch element has a required facing and can accept arbitrary meta tile entity, so it will need both
meta id
and facing input.
</p>
<p>
It will use
<ul>
<li>master channel for its facing</li>
<li>channel `"id"` for meta tile entity id</li>
</ul>
<p>
Its code will then look like
<pre>
import com.gtnewhorizon.structurelib.structure.IStructureElement;
public class MyHatchElement&lt;T> implements IStructureElement&lt;T> {
//...
@Override
public PlaceResult survivalPlaceBlock(
Object o,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer chatter) {
if (shouldSkip(o, world, x, y, z)) return PlaceResult.SKIP;
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
ItemStack taken = s.takeOne(filterByID(ChannelDataAccessor.getChannelData(trigger, "id")).andThen(filterByFacing(trigger.stackSize)));
if (taken == null) return PlaceResult.REJECT;
return StructureUtility.survivalPlaceBlock(taken, ItemStackPredicate.NBTMode.IGNORE, null, true, world, x, y, z, s, actor);
}
}
</pre>
The code for placeBlock() and spawnHint() will be left as an exercise for the reader.
</BODY>
</HTML>
@@ -22,10 +22,26 @@ item.structurelib.frontRotationTool.desc.2=which allows structure rotation.
item.structurelib.constructableTrigger.name=Multiblock Structure Hologram Projector
item.structurelib.constructableTrigger.desc.0=Triggers Constructable Interface
item.structurelib.constructableTrigger.desc.1=Shows multiblock construction details,
item.structurelib.constructableTrigger.desc.2=just Use on a multiblock controller.
item.structurelib.constructableTrigger.desc.3=(Sneak Use in creative to build)
item.structurelib.constructableTrigger.desc.4=Quantity affects tier/mode/type
item.structurelib.constructableTrigger.desc.1=Shows multiblock construction schematic,
item.structurelib.constructableTrigger.desc.2=use on the multiblock controller.
item.structurelib.constructableTrigger.desc.3=(Sneak use on controller to build)
item.structurelib.constructableTrigger.desc.4=Quantity of schematics in item stack affects tier/mode/type
item.structurelib.constructableTrigger.desc.5=Hold §l[LSHIFT]§r to display channel data
item.structurelib.constructableTrigger.desc.lshift.0=Contain %d sub channel.
item.structurelib.constructableTrigger.too_fast=Please wait %d milliseconds before another autoplace...
item.structurelib.constructableTrigger.gui.key=Key:
item.structurelib.constructableTrigger.gui.value=Value:
item.structurelib.constructableTrigger.gui.add=Add
item.structurelib.constructableTrigger.gui.set=Set
item.structurelib.constructableTrigger.gui.unset=Unset
item.structurelib.constructableTrigger.gui.wipe=§4Wipe
structurelib.autoplace.built_stat=Built %s blocks
structurelib.autoplace.complete=§aThis multiblock has completed!
structurelib.autoplace.error.not_enabled=§cThis multiblock does not support autoplace in survival!
structurelib.autoplace.error.no_simple_block=§cSuggested to place §r%s§c but none was found
structurelib.autoplace.error.no_item_stack=§cSuggested to place block by using §r%s§c but none was found
structurelib.autoplace.warning.no_explicit_channel=§6This structure depends on nonexistent sub channel §r%s§6. Using master channel instead.
structurelib.blockhint.desc.0=Helps while building
structurelib.blockhint.0.name=Hint 1 dot
@@ -51,6 +67,103 @@ structurelib.blockhint.15.name=Hint error
structurelib.blockhint.desc.5=ERROR, what did u expect?
itemGroup.structurelib=StructureLib
structurelib.facing.0=down normal none
structurelib.facing.1=down normal horizontal
structurelib.facing.2=down normal vertical
structurelib.facing.3=down normal both
structurelib.facing.4=down clockwise none
structurelib.facing.5=down clockwise horizontal
structurelib.facing.6=down clockwise vertical
structurelib.facing.7=down clockwise both
structurelib.facing.8=down upside down
structurelib.facing.9=down upside down
structurelib.facing.10=down upside down
structurelib.facing.11=down upside down
structurelib.facing.12=down counter clockwise
structurelib.facing.13=down counter clockwise
structurelib.facing.14=down counter clockwise
structurelib.facing.15=down counter clockwise
structurelib.facing.16=up normal none
structurelib.facing.17=up normal horizontal
structurelib.facing.18=up normal vertical
structurelib.facing.19=up normal both
structurelib.facing.20=up clockwise none
structurelib.facing.21=up clockwise horizontal
structurelib.facing.22=up clockwise vertical
structurelib.facing.23=up clockwise both
structurelib.facing.24=up upside down
structurelib.facing.25=up upside down
structurelib.facing.26=up upside down
structurelib.facing.27=up upside down
structurelib.facing.28=up counter clockwise
structurelib.facing.29=up counter clockwise
structurelib.facing.30=up counter clockwise
structurelib.facing.31=up counter clockwise
structurelib.facing.32=north normal none
structurelib.facing.33=north normal horizontal
structurelib.facing.34=north normal vertical
structurelib.facing.35=north normal both
structurelib.facing.36=north clockwise none
structurelib.facing.37=north clockwise horizontal
structurelib.facing.38=north clockwise vertical
structurelib.facing.39=north clockwise both
structurelib.facing.40=north upside down
structurelib.facing.41=north upside down
structurelib.facing.42=north upside down
structurelib.facing.43=north upside down
structurelib.facing.44=north counter clockwise
structurelib.facing.45=north counter clockwise
structurelib.facing.46=north counter clockwise
structurelib.facing.47=north counter clockwise
structurelib.facing.48=south normal none
structurelib.facing.49=south normal horizontal
structurelib.facing.50=south normal vertical
structurelib.facing.51=south normal both
structurelib.facing.52=south clockwise none
structurelib.facing.53=south clockwise horizontal
structurelib.facing.54=south clockwise vertical
structurelib.facing.55=south clockwise both
structurelib.facing.56=south upside down
structurelib.facing.57=south upside down
structurelib.facing.58=south upside down
structurelib.facing.59=south upside down
structurelib.facing.60=south counter clockwise
structurelib.facing.61=south counter clockwise
structurelib.facing.62=south counter clockwise
structurelib.facing.63=south counter clockwise
structurelib.facing.64=west normal none
structurelib.facing.65=west normal horizontal
structurelib.facing.66=west normal vertical
structurelib.facing.67=west normal both
structurelib.facing.68=west clockwise none
structurelib.facing.69=west clockwise horizontal
structurelib.facing.70=west clockwise vertical
structurelib.facing.71=west clockwise both
structurelib.facing.72=west upside down
structurelib.facing.73=west upside down
structurelib.facing.74=west upside down
structurelib.facing.75=west upside down
structurelib.facing.76=west counter clockwise
structurelib.facing.77=west counter clockwise
structurelib.facing.78=west counter clockwise
structurelib.facing.79=west counter clockwise
structurelib.facing.80=east normal none
structurelib.facing.81=east normal horizontal
structurelib.facing.82=east normal vertical
structurelib.facing.83=east normal both
structurelib.facing.84=east clockwise none
structurelib.facing.85=east clockwise horizontal
structurelib.facing.86=east clockwise vertical
structurelib.facing.87=east clockwise both
structurelib.facing.88=east upside down
structurelib.facing.89=east upside down
structurelib.facing.90=east upside down
structurelib.facing.91=east upside down
structurelib.facing.92=east counter clockwise
structurelib.facing.93=east counter clockwise
structurelib.facing.94=east counter clockwise
structurelib.facing.95=east counter clockwise
structurelib.config.client=Client Side Configurations
structurelib.config.client.tooltip=Configurations that only changes how the game looks like, but not how it functions.
structurelib.config.client.hologram=Hologram Configurations
@@ -23,9 +23,25 @@ item.structurelib.frontRotationTool.desc.2=多方块主机一起旋转/翻转。
item.structurelib.constructableTrigger.name=多方块结构全息投影仪
item.structurelib.constructableTrigger.desc.0=可以显示多方块机器的结构
item.structurelib.constructableTrigger.desc.1=只能在多方块主机上使用,将会显示机器
item.structurelib.constructableTrigger.desc.2=结构的细节。 (译者注:请搭高使用)
item.structurelib.constructableTrigger.desc.3= (创造模式下潜行右键可以直接完成搭建)
item.structurelib.constructableTrigger.desc.2=结构的示意图。 (译者注:请搭高使用)
item.structurelib.constructableTrigger.desc.3= (潜行右键可以直接完成搭建)
item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显示的机器等级/模式/类型
item.structurelib.constructableTrigger.desc.5=按下 §l[左SHIFT]§r 以显示子信道
item.structurelib.constructableTrigger.desc.lshift.0=包含 %d 个子信道
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待 %d 毫秒...
item.structurelib.constructableTrigger.gui.key=信道名:
item.structurelib.constructableTrigger.gui.value=值:
item.structurelib.constructableTrigger.gui.add=添加
item.structurelib.constructableTrigger.gui.set=替换
item.structurelib.constructableTrigger.gui.unset=删除
item.structurelib.constructableTrigger.gui.wipe=§4全部清空
structurelib.autoplace.built_stat=已放置 %s 个方块
structurelib.autoplace.complete=§a这个多方块结构已经完成自动搭建!
structurelib.autoplace.error.not_enabled=§c这个多方块结构不支持在生存模式下自动搭建!
structurelib.autoplace.error.no_simple_block=§c建议放置 §r%s§c ,但没有找到它
structurelib.autoplace.error.no_item_stack=§c建议使用 §r%s§c 放置方块,但没有找到它
structurelib.autoplace.warning.no_explicit_channel=§6这个结构依赖于不存在的子信道 §r%s§6 。已使用主信道值替代。
structurelib.blockhint.desc.0=帮助你搭建机器
structurelib.blockhint.0.name=提示方块:1号
@@ -51,6 +67,103 @@ structurelib.blockhint.15.name=提示方块:错误
structurelib.blockhint.desc.5=ERROR,你想要做什么?
itemGroup.structurelib=StructureLib
structurelib.facing.0=面朝下方
structurelib.facing.1=面朝下方,并水平翻转
structurelib.facing.2=面朝下方,并垂直翻转
structurelib.facing.3=面朝下方,并旋转180度
structurelib.facing.4=面朝下方,并顺时针旋转90度
structurelib.facing.5=面朝下方,顺时针旋转90度并水平翻转
structurelib.facing.6=面朝下方,顺时针旋转90度并垂直翻转
structurelib.facing.7=面朝下方,并逆时针旋转90度
structurelib.facing.8=面朝下方,并旋转180度
structurelib.facing.9=面朝下方,并垂直翻转
structurelib.facing.10=面朝下方,并水平翻转
structurelib.facing.11=面朝下方
structurelib.facing.12=面朝下方,并逆时针旋转90度
structurelib.facing.13=面朝下方,逆时针旋转90度并水平翻转
structurelib.facing.14=面朝下方,逆时针旋转90度并垂直翻转
structurelib.facing.15=面朝下方,并顺时针旋转90度
structurelib.facing.16=面朝上方
structurelib.facing.17=面朝上方,并水平翻转
structurelib.facing.18=面朝上方,并垂直翻转
structurelib.facing.19=面朝上方,并旋转180度
structurelib.facing.20=面朝上方,并顺时针旋转90度
structurelib.facing.21=面朝上方,顺时针旋转90度并水平翻转
structurelib.facing.22=面朝上方,顺时针旋转90度并垂直翻转
structurelib.facing.23=面朝上方,并逆时针旋转90度
structurelib.facing.24=面朝上方,并旋转180度
structurelib.facing.25=面朝上方,并垂直翻转
structurelib.facing.26=面朝上方,并水平翻转
structurelib.facing.27=面朝上方
structurelib.facing.28=面朝上方,并逆时针旋转90度
structurelib.facing.29=面朝上方,逆时针旋转90度并水平翻转
structurelib.facing.30=面朝上方,逆时针旋转90度并垂直翻转
structurelib.facing.31=面朝上方,并顺时针旋转90度
structurelib.facing.32=面朝北方
structurelib.facing.33=面朝北方,并水平翻转
structurelib.facing.34=面朝北方,并垂直翻转
structurelib.facing.35=面朝北方,并旋转180度
structurelib.facing.36=面朝北方,并顺时针旋转90度
structurelib.facing.37=面朝北方,顺时针旋转90度并水平翻转
structurelib.facing.38=面朝北方,顺时针旋转90度并垂直翻转
structurelib.facing.39=面朝北方,并逆时针旋转90度
structurelib.facing.40=面朝北方,并旋转180度
structurelib.facing.41=面朝北方,并垂直翻转
structurelib.facing.42=面朝北方,并水平翻转
structurelib.facing.43=面朝北方
structurelib.facing.44=面朝北方,并逆时针旋转90度
structurelib.facing.45=面朝北方,逆时针旋转90度并水平翻转
structurelib.facing.46=面朝北方,逆时针旋转90度并垂直翻转
structurelib.facing.47=面朝北方,并顺时针旋转90度
structurelib.facing.48=面朝南方
structurelib.facing.49=面朝南方,并水平翻转
structurelib.facing.50=面朝南方,并垂直翻转
structurelib.facing.51=面朝南方,并旋转180度
structurelib.facing.52=面朝南方,并顺时针旋转90度
structurelib.facing.53=面朝南方,顺时针旋转90度并水平翻转
structurelib.facing.54=面朝南方,顺时针旋转90度并垂直翻转
structurelib.facing.55=面朝南方,并逆时针旋转90度
structurelib.facing.56=面朝南方,并旋转180度
structurelib.facing.57=面朝南方,并垂直翻转
structurelib.facing.58=面朝南方,并水平翻转
structurelib.facing.59=面朝南方
structurelib.facing.60=面朝南方,并逆时针旋转90度
structurelib.facing.61=面朝南方,逆时针旋转90度并水平翻转
structurelib.facing.62=面朝南方,逆时针旋转90度并垂直翻转
structurelib.facing.63=面朝南方,并顺时针旋转90度
structurelib.facing.64=面朝西方
structurelib.facing.65=面朝西方,并水平翻转
structurelib.facing.66=面朝西方,并垂直翻转
structurelib.facing.67=面朝西方,并旋转180度
structurelib.facing.68=面朝西方,并顺时针旋转90度
structurelib.facing.69=面朝西方,顺时针旋转90度并水平翻转
structurelib.facing.70=面朝西方,顺时针旋转90度并垂直翻转
structurelib.facing.71=面朝西方,并逆时针旋转90度
structurelib.facing.72=面朝西方,并旋转180度
structurelib.facing.73=面朝西方,并垂直翻转
structurelib.facing.74=面朝西方,并水平翻转
structurelib.facing.75=面朝西方
structurelib.facing.76=面朝西方,并逆时针旋转90度
structurelib.facing.77=面朝西方,逆时针旋转90度并水平翻转
structurelib.facing.78=面朝西方,逆时针旋转90度并垂直翻转
structurelib.facing.79=面朝西方,并顺时针旋转90度
structurelib.facing.80=面朝东方
structurelib.facing.81=面朝东方,并水平翻转
structurelib.facing.82=面朝东方,并垂直翻转
structurelib.facing.83=面朝东方,并旋转180度
structurelib.facing.84=面朝东方,并顺时针旋转90度
structurelib.facing.85=面朝东方,顺时针旋转90度并水平翻转
structurelib.facing.86=面朝东方,顺时针旋转90度并垂直翻转
structurelib.facing.87=面朝东方,并逆时针旋转90度
structurelib.facing.88=面朝东方,并旋转180度
structurelib.facing.89=面朝东方,并垂直翻转
structurelib.facing.90=面朝东方,并水平翻转
structurelib.facing.91=面朝东方
structurelib.facing.92=面朝东方,并逆时针旋转90度
structurelib.facing.93=面朝东方,逆时针旋转90度并水平翻转
structurelib.facing.94=面朝东方,逆时针旋转90度并垂直翻转
structurelib.facing.95=面朝东方,并顺时针旋转90度
structurelib.config.client=客户端-配置
structurelib.config.client.tooltip=修改此配置只会改变游戏中的显示效果,而不会改变实际工作模式。
structurelib.config.client.hologram=全息投影仪-配置
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB