Compare commits

..
69 Commits
Author SHA1 Message Date
Glease 0ff38c2da9 revise javadoc publish (#27)
* fix deprecation

* add cleanup
2024-01-09 22:52:41 +01:00
chochem 307bb1714c Fix broken mod (#26)
* clear out repositories

* update bs

* update deps

* spotless
2024-01-09 11:42:10 +08:00
Raven Szewczyk 7d8e6a0ea5 [ci skip] upgraded build system 2024-01-08 21:29:25 +00:00
Glease b6d2af2170 add new api to check for rotation/flip changes (#24)
* add new api to check for rotation/flip changes

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* add a bit javadoc

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2023-09-13 14:29:39 +02:00
Glease 8309567076 fix cube faces rendering order (#22)
* fix cube faces rendering order

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* spotless

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* address review

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2023-07-13 22:05:35 +02:00
kstvr32 23312198a6 Allow autoplace channels to work in survival mode (#20)
* allow autoplace channels to work in survival mode

* bump NEI dependency version
2023-05-04 17:40:03 +02:00
Glease fa5e2ae4ca flush buffer to ensure vertexes in same trig being drawn in same batch (#19)
* flush buffer to ensure vertexes in same trig being drawn in same batch

* off by one
2023-03-08 12:23:32 +01:00
Glease a8a1547845 remove unnecessary shim code for IMultiblockInfoContainer
nobody implement these in practice, so we should be safe
2023-02-28 12:37:18 +08:00
Raven Szewczyk 113e05b7e6 Java11 javadoc generator (#18) 2023-02-28 01:30:57 +08:00
Glease 080785ad05 spotless 2023-02-27 20:36:44 +08:00
Glease 6c14ca0a1d fix hologram rendering at large coordinates
Inspired by https://github.com/GTNewHorizons/NotEnoughItems/pull/353. Thanks @eigenraven!
2023-02-27 19:57:51 +08:00
Glease ea38c4e1d6 add API to get hologram item 2023-02-11 01:26:46 +08:00
Glease 50e8c97aa3 fix javadoc 2023-02-11 01:26:46 +08:00
Raven Szewczyk 9a82239f2c [ci skip] upgraded build system 2023-01-31 09:56:09 +00:00
Raven Szewczyk 8ed2be38eb Update dependencies, fix javadoc generation 2023-01-30 13:00:51 +00:00
Raven Szewczyk 582f59e73d [ci skip] Add git-blame-ignore-revs for spotlessApply 2023-01-30 12:52:27 +00:00
Raven Szewczyk 464e44de85 [ci skip] spotlessApply with the new settings 2023-01-30 12:52:27 +00:00
Raven Szewczyk 392d2ace8a [ci skip] Update buildscript to RetroFuturaGradle 2023-01-30 12:52:24 +00:00
Glease 978938d450 more documentation efforts 2022-09-16 18:57:04 +08:00
Glease 7a2328c294 push members from StructureEvent down to concrete class 2022-09-16 18:57:04 +08:00
Glease d2cd45e07b drop MP 2022-09-16 18:57:04 +08:00
Glease 8a24900623 Spotless 2022-09-16 18:57:04 +08:00
Glease 15a0034e03 make API compatibility statement 2022-09-16 18:57:04 +08:00
Glease 7aae6dc31c more documentation efforts... 2022-09-16 18:57:04 +08:00
Glease 875bc1990e Add instrumenting API 2022-09-16 18:57:04 +08:00
Glease 74aefd22be remove unused imports 2022-09-16 18:57:04 +08:00
Glease 72ff51d093 expose what IStructureDefinition this constructable is using 2022-09-16 18:57:04 +08:00
Glease 7d74a65ec0 expose what blocks this element might place 2022-09-16 18:57:04 +08:00
Glease 0ac8908b37 Improve documentation on IItemSource 2022-09-16 18:57:04 +08:00
Glease 2dd4349e54 fix default implementation for IItemSource having inverted output 2022-09-16 18:57:04 +08:00
Glease f813104ec4 fix tests 2022-09-16 18:57:04 +08:00
github-actions[bot]GleaseGitHub GTNH Actions
4fe9eb6ea5 Spotless apply for branch feature/survival-autoplace-argument-object for #13 (#14)
Co-authored-by: Glease <4586901+Glease@users.noreply.github.com>
Co-authored-by: GitHub GTNH Actions <>
2022-09-16 18:57:04 +08:00
Glease c0814274ec Use argument object for survival autoplace 2022-09-16 18:57:04 +08:00
Glease 23605fc22d fix particles not sorted when rendering
some experimental code left in during initial rework...
2022-09-09 19:48:12 +08:00
Glease 8ee90a7338 fix crash upon opening hologram gui 2022-09-09 18:09:59 +08:00
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
98 changed files with 7900 additions and 3231 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# Ignore spotlessApply reformat
464e44de8541a46833f50c3bd363d50e1677a685
+42 -20
View File
@@ -1,22 +1,44 @@
# Auto detect text files and perform LF normalization
* text=auto
* text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
*.[jJ][aA][rR] binary
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.[pP][nN][gG] binary
*.[jJ][pP][gG] binary
*.[jJ][pP][eE][gG] binary
*.[gG][iI][fF] binary
*.[tT][iI][fF] binary
*.[tT][iI][fF][fF] binary
*.[iI][cC][oO] binary
*.[sS][vV][gG] text
*.[eE][pP][sS] binary
*.[xX][cC][fF] binary
*.[kK][aA][rR] binary
*.[mM]4[aA] binary
*.[mM][iI][dD] binary
*.[mM][iI][dD][iI] binary
*.[mM][pP]3 binary
*.[oO][gG][gG] binary
*.[rR][aA] binary
*.7[zZ] binary
*.[gG][zZ] binary
*.[tT][aA][rR] binary
*.[tT][gG][zZ] binary
*.[zZ][iI][pP] binary
*.[tT][cC][nN] binary
*.[sS][oO] binary
*.[dD][lL][lL] binary
*.[dD][yY][lL][iI][bB] binary
*.[pP][sS][dD] binary
*.[tT][tT][fF] binary
*.[oO][tT][fF] binary
*.[pP][aA][tT][cC][hH] -text
*.[bB][aA][tT] text eol=crlf
*.[cC][mM][dD] text eol=crlf
*.[pP][sS]1 text eol=crlf
*[aA][uU][tT][oO][gG][eE][nN][eE][rR][aA][tT][eE][dD]* binary
+97
View File
@@ -0,0 +1,97 @@
name: Deploy Javadoc
on:
push:
branches:
- master
- feature/doc
tags:
- '*'
delete:
permissions:
contents: write
pages: write
id-token: write
jobs:
prune:
if: github.event_name == 'delete'
concurrency: javadoc-publish
runs-on: ubuntu-latest
outputs:
result_hash: ${{ steps.deploy.outputs.hash }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: gh-pages
- name: Deploy 🚀
id: deploy
shell: bash
run: |
if git rm -r $TARGET_FOLDER ; then
echo Nothing to clean up
fi
find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' | sort --version-sort --reverse > index.txt
git add index.txt
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR_ID}-${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Clean up javadoc after ref $TARGET_FOLDER is deleted"
git push
echo "hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
env:
TARGET_FOLDER: ${{ github.ref_name }}
javadoc:
if: github.event_name == 'push'
concurrency: javadoc-publish
runs-on: ubuntu-latest
outputs:
result_hash: ${{ steps.deploy.outputs.hash }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-java@v3
with:
java-version: |
8
11
17
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#' | sort --version-sort --reverse > index.txt
git add index.txt
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR_ID}-${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Deploy javadoc from ref $TARGET_FOLDER"
git push
echo "hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
env:
TARGET_FOLDER: ${{ github.ref_name }}
+165
View File
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
+2
View File
@@ -32,3 +32,5 @@ The project is developed using IDEA. Please manually configure the build to not
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.
Note while the LICENSE.txt is added by a much later commit, all code on previous commits are covered by this as well.
+42
View File
@@ -0,0 +1,42 @@
javadoc {
javadocTool = javaToolchains.javadocToolFor {
// Last version with frames, but it also has search
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.ADOPTIUM
}
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"
include "com/gtnewhorizon/structurelib/StructureEvent.java"
classpath += sourceSets.patchedMc.output
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/'
addBooleanOption("-frames", true)
}
}
tasks.register('javadocJar', Jar) {
dependsOn javadoc
archiveClassifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
}
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
+1268 -599
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -1,5 +1,9 @@
// Add your dependencies here
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.0.13:dev")
runtimeOnly("com.github.GTNewHorizons:NotEnoughItems:2.5.3-GTNH:dev")
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
}
+3
View File
@@ -58,3 +58,6 @@ containsMixinsAndOrCoreModOnly = false
# If enabled, you may use 'shadowImplementation' for dependencies. They will be integrated in your jar. It is your
# responsibility check the licence and request permission for distribution, if required.
usesShadowedDependencies = false
# TODO: Remove when you modify this repository, it's been put in for automatic buildscript updates
disableCheckstyle = true
Binary file not shown.
+3 -1
View File
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Regular → Executable
+178 -117
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,78 +17,111 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+15 -23
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,10 +25,14 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -37,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,38 +65,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
-1
View File
@@ -1,5 +1,4 @@
// Add any additional repositiroes for your dependencies here
repositories {
}
+21 -4
View File
@@ -1,10 +1,27 @@
plugins {
id 'com.diffplug.blowdryerSetup' version '1.6.0'
pluginManagement {
repositories {
maven {
// RetroFuturaGradle
name "GTNH Maven"
url "https://nexus.gtnewhorizons.com/repository/public/"
mavenContent {
includeGroupByRegex("com\\.gtnewhorizons\\..+")
}
}
gradlePluginPortal()
mavenCentral()
mavenLocal()
}
}
apply plugin: 'com.diffplug.blowdryerSetup'
plugins {
id 'com.diffplug.blowdryerSetup' version '1.7.1'
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' // Provides java toolchains
}
blowdryerSetup {
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.1.4')
repoSubfolder 'gtnhShared'
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.2.0')
//devLocal '.' // Use this when testing config updates locally
}
@@ -1,49 +1,108 @@
package com.gtnewhorizon.structurelib;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.gtnewhorizon.structurelib.entity.fx.EntityFXBlockHint;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import static com.gtnewhorizon.structurelib.StructureLib.RANDOM;
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.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;
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;
public class ClientProxy extends CommonProxy {
private static final short[] NO_TINT = {255, 255, 255, 0};
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<>();
/**
* 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) {
EntityFXBlockHint hint = new EntityFXBlockHint(w, x, y, z, icons).withColorTint(RGBa);
Minecraft.getMinecraft().effectRenderer.addEffect(hint);
ensureHinting();
HintParticleInfo info = new HintParticleInfo(w, x, y, z, icons, RGBa);
// check and remove colliding holograms
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();
}
HintGroup dupe = allHints.get(info);
if (dupe != null && dupe != currentHints) {
allGroups.remove(dupe);
removeGroup(dupe);
}
allHints.forcePut(hint, info);
}
currentHints.add(hint);
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
@@ -53,25 +112,67 @@ public class ClientProxy extends CommonProxy {
@Override
public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
hintParticleTinted(w, x, y, z, icons, NO_TINT);
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), NO_TINT);
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 info = new HintParticleInfo(x, y, z, null);
EntityFXBlockHint existing = allHints.inverse().get(info);
if (existing != null) {
existing.withColorTint(rgBa);
return true;
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
if (hint == null) {
return false;
}
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) {
@@ -100,76 +201,109 @@ public class ClientProxy extends CommonProxy {
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<>();
if (currentHints != null) endHinting(w);
currentHints = new HintGroup();
}
private void ensureHinting() {
if (currentHints == null) currentHints = new LinkedList<>();
if (currentHints == null) currentHints = new HintGroup();
}
@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 (!w.isRemote || currentHints == null) return;
while (!allGroups.isEmpty() && allGroups.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
allGroups.remove(0);
}
if (!currentHints.isEmpty()) hintOwners.add(currentHints);
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 void preInit(FMLPreInitializationEvent e) {
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
}
@Override
public long getOverworldTime() {
// there is no overworld, better just hope current world time is ok...
return Minecraft.getMinecraft().theWorld.getTotalWorldTime();
}
private static class HintParticleInfo {
private final int x, y, z;
private final List<EntityFXBlockHint> owner;
@Override
public void uploadChannels(ItemStack trigger) {
StructureLib.net.sendToServer(new SetChannelDataMessage(trigger));
}
public HintParticleInfo(int x, int y, int z, List<EntityFXBlockHint> owner) {
@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;
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;
this.owner = owner;
this.icons = icons;
this.tint = tint;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof HintParticleInfo)) return false;
HintParticleInfo that = (HintParticleInfo) o;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
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
@@ -179,15 +313,257 @@ public class ClientProxy extends CommonProxy {
result = 31 * result + z;
return result;
}
public boolean isInFrustrum(Frustrum frustrum) {
return frustrum.isBoxInFrustum(x + 0.25, y + 0.25, z + 0.25, x + 0.75, y + 0.75, z + 0.75);
}
public void draw(Tessellator tes, double eyeX, double eyeY, double eyeZ, int eyeXint, int eyeYint,
int eyeZint) {
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());
double X = (x - eyeXint) + 0.25;
double Y = (y - eyeYint) + 0.25;
double Z = (z - eyeZint) + 0.25;
double worldX = x + 0.25;
double worldY = y + 0.25;
double worldZ = z + 0.25;
// this rendering code is independently written by glee8e on July 10th, 2023
// and is released as part of StructureLib under LGPL terms, just like everything else in this project
// cube is a very special model. its facings can be rendered correctly by viewer distance without using
// surface normals and view vector
// here we do a 2 pass render.
// first pass we draw obstructed faces (i.e. faces that are further away from player)
// second pass we draw unobstructed faces
for (int j = 0; j < 2; j++) {
boolean unobstructedPass = j == 1;
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:
// all these ifs is in form if ((is face unobstructed) != (is in unobstructred pass))
if ((worldY >= eyeY) != unobstructedPass) continue;
tes.setNormal(0, -1, 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, U, v);
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
tes.addVertexWithUV(X, Y, Z + size, u, V);
break;
case 1:
if ((worldY + size <= eyeY) != unobstructedPass) continue;
tes.setNormal(0, 1, 0);
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 + size, U, V);
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
tes.addVertexWithUV(X, Y + size, Z, u, v);
break;
case 2:
if ((worldZ >= eyeZ) != unobstructedPass) continue;
tes.setNormal(0, 0, -1);
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 + size, Z, u, v);
tes.addVertexWithUV(X + size, Y, Z, u, V);
tes.addVertexWithUV(X, Y, Z, U, V);
break;
case 3:
if ((worldZ + size <= eyeZ) != unobstructedPass) continue;
tes.setNormal(0, 0, 1);
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 + size, Z + size, u, v);
tes.addVertexWithUV(X, Y, Z + size, u, V);
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
break;
case 4:
if ((worldX >= eyeX) != unobstructedPass) continue;
tes.setNormal(-1, 0, 0);
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 + size, Z, u, v);
tes.addVertexWithUV(X, Y, Z, u, V);
tes.addVertexWithUV(X, Y, Z + size, U, V);
break;
case 5:
if ((worldX + size <= eyeX) != unobstructedPass) continue;
tes.setNormal(1, 0, 0);
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 + size, Z + size, u, v);
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
tes.addVertexWithUV(X + size, Y, Z, U, V);
break;
}
}
}
}
public long getCreationTime() {
return creationTime;
}
public double getSquareDistanceTo(Vec3 point) {
return point.squareDistanceTo(x + 0.5, y + 0.5, z + 0.5);
}
}
public static class ForgeEventHandler {
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 onWorldLoad(WorldEvent.Load e) {
if (e.world.isRemote) {
allHints.clear();
hintOwners.clear();
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 (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2) {
// 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;
int i0 = (int) d0, i1 = (int) d1, i2 = (int) d2;
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.glDisable(GL11.GL_ALPHA_TEST);
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(-d0 + i0, -d1 + i1, -d2 + i2);
Tessellator tes = Tessellator.instance;
tes.startDrawing(GL11.GL_TRIANGLES);
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(), cubeDrawn = 0; i
< allHintsForRenderSize; i++, cubeDrawn++) {
HintParticleInfo hint = allHintsForRender.get(i);
if (!hint.isInFrustrum(frustrum)) continue;
if (renderThrough != hint.renderThrough) {
if (i > 0) {
p.endStartSection("Draw");
tes.draw();
tes.startDrawing(GL11.GL_TRIANGLES);
cubeDrawn = 0;
p.endStartSection("Prepare");
}
if (hint.renderThrough) GL11.glDisable(GL11.GL_DEPTH_TEST);
else GL11.glEnable(GL11.GL_DEPTH_TEST);
renderThrough = hint.renderThrough;
}
// Minecraft Tessellator draw in batch of 4096. This plays nicely with quads, but for trigs
// it might occasionally end up with one half in first batch and the other half in later batch.
// so we manually flush the buffer here if that would happen.
// 6 faces per particle, 2 trig per face, 3 vertex per trig
if ((cubeDrawn + 1) >= 4096 / (6 * 2 * 3)) {
tes.draw();
tes.startDrawing(GL11.GL_TRIANGLES);
}
hint.draw(tes, d0, d1, d2, i0, i1, i2);
}
p.endStartSection("Draw");
tes.draw();
p.endSection();
GL11.glPopAttrib();
GL11.glPopMatrix();
p.endSection();
}
}
}
@@ -1,15 +1,25 @@
package com.gtnewhorizon.structurelib;
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;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
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) {}
@@ -48,4 +58,40 @@ public class CommonProxy {
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);
}
}
}
@@ -1,20 +1,27 @@
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 cpw.mods.fml.client.event.ConfigChangedEvent;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
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);
@@ -50,6 +57,34 @@ public enum ConfigurationHandler {
"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();
@@ -71,6 +106,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;
}
@@ -2,11 +2,15 @@ package com.gtnewhorizon.structurelib;
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) {}
};
@@ -30,10 +34,8 @@ 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,11 +1,14 @@
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 cpw.mods.fml.client.IModGuiFactory;
public class GuiFactory implements IModGuiFactory {
@Override
public void initialize(Minecraft minecraftInstance) {}
@@ -0,0 +1,26 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.gui.GuiScreenConfigureChannels;
import cpw.mods.fml.common.network.IGuiHandler;
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;
}
}
}
@@ -1,14 +1,17 @@
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 cpw.mods.fml.client.config.GuiConfig;
import cpw.mods.fml.client.config.IConfigElement;
public class GuiModConfig extends GuiConfig {
public GuiModConfig(GuiScreen guiScreen) {
super(
guiScreen,
@@ -16,16 +19,13 @@ public class GuiModConfig extends GuiConfig {
StructureLibAPI.MOD_ID,
false,
false,
GuiConfig.getAbridgedConfigPath(
ConfigurationHandler.INSTANCE.getConfig().toString()));
GuiConfig.getAbridgedConfigPath(ConfigurationHandler.INSTANCE.getConfig().toString()));
}
@SuppressWarnings("rawtypes")
private static List<IConfigElement> getConfigElements() {
final Configuration config = ConfigurationHandler.INSTANCE.getConfig();
return config.getCategoryNames().stream()
.filter(name -> name.indexOf('.') == -1)
.map(name -> new ConfigElement(config.getCategory(name)))
.collect(Collectors.toList());
return config.getCategoryNames().stream().filter(name -> name.indexOf('.') == -1)
.map(name -> new ConfigElement(config.getCategory(name))).collect(Collectors.toList());
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.util.IIcon;
public interface IStructureCompat {
void markTextureUsed(IIcon o);
}
@@ -0,0 +1,160 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import com.gtnewhorizon.structurelib.structure.IStructureElement;
import cpw.mods.fml.common.eventhandler.Event;
/**
* Subscribers are <b>required</b> to not keep reference to these events, nor to depend on values of these events after
* the method call returns.
* <p>
* All of these events will be fired on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}, if they are
* enabled by {@link StructureLibAPI#enableInstrument(Object)}.
* <p>
* All public stuff in this class and its enclosed classes are considered part of the public API. Backwards
* compatibility is maintained to the maximum extend possible.
*/
public abstract class StructureEvent extends Event {
/*
* In case the event is passed to another thread for processing.
*/
private final Object identifier = StructureLibAPI.instrument.get();
StructureEvent() {}
/**
* The instrument identifier. Subscribers should filter by these identifiers to prevent listening to a structure
* event for other people.
*/
public Object getInstrumentIdentifier() {
return identifier;
}
/**
* Fired <b>just before</b> any structure element is visited.
*/
public static final class StructureElementVisitedEvent extends StructureEvent {
private final World world;
private final int x, y, z;
private final int a, b, c;
private final IStructureElement<?> element;
/**
* Fire this event on {@link MinecraftForge#EVENT_BUS} by populating it with given arguments. Do nothing if
* instrumenting isn't enabled for current thread.
* <p>
* Any {@link Exception} thrown by event listeners will be logged and ignored. {@link Error} will be propagated
* as is.
*/
public static void fireEvent(World world, int x, int y, int z, int a, int b, int c,
IStructureElement<?> element) {
if (StructureLibAPI.isInstrumentEnabled()) {
try {
MinecraftForge.EVENT_BUS.post(new StructureElementVisitedEvent(world, x, y, z, a, b, c, element));
} catch (Exception e) {
// logging is done by event bus, we just ignore the consequence here
}
}
}
StructureElementVisitedEvent(World world, int x, int y, int z, int a, int b, int c,
IStructureElement<?> element) {
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.a = a;
this.b = b;
this.c = c;
this.element = element;
}
/**
* A position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
* <p>
* Submit a feature request if you need the baseOffset.
*/
public int getA() {
return a;
}
/**
* B position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
* <p>
* Submit a feature request if you need the baseOffset.
*/
public int getB() {
return b;
}
/**
* C position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
* <p>
* Submit a feature request if you need the baseOffset.
*/
public int getC() {
return c;
}
/**
* Element being visited.
*/
public IStructureElement<?> getElement() {
return element;
}
@Override
public String toString() {
return "StructureElementVisitedEvent{" + "a="
+ a
+ ", b="
+ b
+ ", c="
+ c
+ ", element="
+ element
+ ", world="
+ getWorld()
+ ", x="
+ getX()
+ ", y="
+ getY()
+ ", z="
+ getZ()
+ '}';
}
/**
* Location of this event.
*/
public World getWorld() {
return world;
}
/**
* Location of this event. Absolute world coordinate.
*/
public int getX() {
return x;
}
/**
* Location of this event. Absolute world coordinate.
*/
public int getY() {
return y;
}
/**
* Location of this event. Absolute world coordinate.
*/
public int getZ() {
return z;
}
}
}
@@ -1,28 +1,37 @@
package com.gtnewhorizon.structurelib;
import com.gtnewhorizon.structurelib.block.BlockHint;
import com.gtnewhorizon.structurelib.item.ItemBlockHint;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import com.gtnewhorizon.structurelib.util.XSTR;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import cpw.mods.fml.common.registry.GameRegistry;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
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;
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.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;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/**
* This class does not contain a stable API. Refrain from using this class.
*/
@@ -33,31 +42,52 @@ import org.apache.logging.log4j.Logger;
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizon.structurelib.GuiFactory")
public class StructureLib {
public static boolean DEBUG_MODE = Boolean.getBoolean("structurelib.debug");
private static final String STRUCTURECOMPAT_MODID = "structurecompat";
public static boolean DEBUG_MODE;
public static boolean PANIC_MODE = Boolean.getBoolean("structurelib.panic");
public static Logger LOGGER = LogManager.getLogger("StructureLib");
public static final Logger LOGGER = LogManager.getLogger("StructureLib");
@SidedProxy(
serverSide = "com.gtnewhorizon.structurelib.CommonProxy",
clientSide = "com.gtnewhorizon.structurelib.ClientProxy")
static CommonProxy proxy;
static SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
static final SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
static {
net.registerMessage(
AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER);
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);
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");
}
}
public static final XSTR RANDOM = new XSTR();
@Mod.Instance
static StructureLib INSTANCE;
static Object COMPAT;
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() {
@@ -71,11 +101,22 @@ public class StructureLib {
GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint");
itemBlockHint = ItemBlock.getItemFromBlock(StructureLibAPI.getBlockHint());
GameRegistry.registerItem(
itemFrontRotationTool = new ItemFrontRotationTool(), itemFrontRotationTool.getUnlocalizedName());
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) {
@@ -93,4 +134,12 @@ public class StructureLib {
public static long getOverworldTime() {
return proxy.getOverworldTime();
}
public static StructureLib instance() {
return INSTANCE;
}
public CommonProxy proxy() {
return proxy;
}
}
@@ -2,23 +2,30 @@ package com.gtnewhorizon.structurelib;
import static com.gtnewhorizon.structurelib.StructureLib.proxy;
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 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.AutoPlaceEnvironment;
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import cpw.mods.fml.common.network.NetworkRegistry;
/**
* A stable interface into the structure lib's internals. Backwards compatibility is maintained to the maximum extend possible.
* 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;
@@ -36,43 +43,152 @@ public class StructureLibAPI {
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;
static final ThreadLocal<Object> instrument = new ThreadLocal<>();
/**
* Start a batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
* Start instrumenting <b>for the current thread only</b>. When instrumentation is enabled,
* {@link IStructureDefinition}'s builtin iteration methods will send {@link StructureEvent} and its subclasses when
* appropriate. Otherwise, these events will not be sent at all, regardless of whether a listener for these events
* are registered.
* <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)}
* Identifiers are required to be value-comparable, i.e. overrides {@link Object#equals(Object)}. Suggested
* identifier includes
* <ul>
* <li>Mod Instance or {@link cpw.mods.fml.common.ModContainer}</li>
* <li>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable} and its friends, if you are
* triggering structure check via this interface.</li>
* <li>{@link net.minecraft.util.ResourceLocation}</li>
* </ul>
* <p>
* It's an API abuse for normal {@link IStructureDefinition} users to depend on this feature. They have full control
* and full knowledge of the {@link IStructureDefinition} they are using. Using events to instrument the structure
* check/autoplace is completely unnecessary and does nothing but add overhead.
* <p>
* Instruments are required to call {@link #disableInstrument()} whenever it is done with its business, as there can
* be only one instrument for any given thread.
*
* @param identifier an identifier sent along the actual event.
* @throws IllegalStateException if instrument has already been enabled.
*/
public static void enableInstrument(Object identifier) {
if (isInstrumentEnabled()) throw new IllegalStateException();
instrument.set(instrument);
}
/**
* Stop instrumenting.
*
* @throws IllegalStateException if instrumenting hasn't been enabled.
*/
public static void disableInstrument() {
if (!isInstrumentEnabled()) throw new IllegalStateException();
instrument.set(null);
}
/**
* Check if instrumenting is enabled.
*
* @return true if enabled, false otherwise
*/
public static boolean isInstrumentEnabled() {
return instrument.get() != null;
}
/**
* 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);
}
/**
* End current batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
* 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)}
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* Update the tint of given hint particle. Do nothing if particle not found.
* <p>
* 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);
}
/**
* 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) {
@@ -80,9 +196,13 @@ public class StructureLibAPI {
}
/**
* 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.
* 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
*/
@@ -92,6 +212,9 @@ public class StructureLibAPI {
/**
* 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
*/
@@ -101,6 +224,9 @@ public class StructureLibAPI {
/**
* 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
*/
@@ -109,7 +235,11 @@ public class StructureLibAPI {
}
/**
* Send the ExtendedFacing of this Tile Entity to all players around target point. Can be called on server side only.
* 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
*/
@@ -119,6 +249,9 @@ public class StructureLibAPI {
/**
* 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
*/
@@ -126,14 +259,32 @@ public class StructureLibAPI {
StructureLib.net.sendToDimension(new AlignmentMessage.AlignmentData(provider), dimension.provider.dimensionId);
}
/**
* Get the Block for StructureLib supplied hint block.
*/
public static Block getBlockHint() {
return StructureLib.blockHint;
}
/**
* Get the ItemBlock for StructureLib supplied hint block.
*/
public static Item getItemBlockHint() {
return StructureLib.itemBlockHint;
}
/**
* Get the hologram item.
*/
public static Item getDefaultHologramItem() {
return StructureLib.itemConstructableTrigger;
}
/**
* Check if structure debug mode is on.
*
* @return true if debug mode is on
*/
public static boolean isDebugEnabled() {
return StructureLib.DEBUG_MODE;
}
@@ -143,13 +294,60 @@ public class StructureLibAPI {
}
/**
* 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.
*
* Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
* 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, AutoPlaceEnvironment)}
*/
@Deprecated
public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayerMP actor) {
return isBlockTriviallyReplaceable(w, x, y, z, (EntityPlayer) actor);
}
/**
* 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, AutoPlaceEnvironment)}
*/
public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayer actor) {
// TODO extend this function a bit
return w.getBlock(x, y, z).isReplaceable(w, x, y, z);
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);
}
}
@@ -1,8 +1,9 @@
package com.gtnewhorizon.structurelib.alignment;
import net.minecraftforge.common.util.ForgeDirection;
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
import net.minecraftforge.common.util.ForgeDirection;
class AlignmentLimits implements IAlignmentLimits {
@@ -7,6 +7,7 @@ import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
public class AlignmentUtility {
private AlignmentUtility() {}
public static boolean handle(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ) {
@@ -1,12 +1,14 @@
package com.gtnewhorizon.structurelib.alignment;
import net.minecraftforge.common.util.ForgeDirection;
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
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;
public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
int DIRECTIONS_COUNT = Direction.VALUES.length;
int ROTATIONS_COUNT = Rotation.VALUES.length;
int FLIPS_COUNT = Flip.VALUES.length;
@@ -15,8 +17,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
ExtendedFacing getExtendedFacing();
/**
* Set the facing without additional checks.
* Tools altering facing should use {@link #toolSetExtendedFacing(ExtendedFacing)} instead.
* Set the facing without additional checks. Tools altering facing should use
* {@link #toolSetExtendedFacing(ExtendedFacing)} instead.
*/
void setExtendedFacing(ExtendedFacing alignment);
@@ -57,9 +59,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
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++) {
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;
}
@@ -97,8 +98,11 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
int rotations = Rotation.VALUES.length;
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;
}
}
@@ -140,9 +144,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
default boolean toolSetExtendedFacing(ExtendedFacing extendedFacing) {
if (extendedFacing == null) {
for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length;
i < valuesLength;
i++) {
for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length; i
< valuesLength; i++) {
if (checkedSetExtendedFacing(ExtendedFacing.VALUES[(j + i) % valuesLength])) {
return true;
}
@@ -187,4 +190,28 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
return getAlignmentLimits()
.isNewExtendedFacingValid(alignment.getDirection(), alignment.getRotation(), alignment.getFlip());
}
/**
* Check if this object support a flip change, assuming both direction and rotation is not changed.
*/
default boolean isFlipChangeAllowed() {
ExtendedFacing facing = getExtendedFacing();
for (Flip flip : Flip.VALUES) {
if (flip == getFlip()) continue;
if (isNewExtendedFacingValid(facing.with(flip))) return true;
}
return false;
}
/**
* Check if this object support a rotation change, assuming both direction and flip is not changed.
*/
default boolean isRotationChangeAllowed() {
ExtendedFacing facing = getExtendedFacing();
for (Rotation rotation : Rotation.VALUES) {
if (rotation == getRotation()) continue;
if (isNewExtendedFacingValid(facing.with(rotation))) return true;
}
return false;
}
}
@@ -2,14 +2,16 @@ 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 java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;
import net.minecraftforge.common.util.ForgeDirection;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
public interface IAlignmentLimits {
IAlignmentLimits UNLIMITED = (direction, rotation, flip) -> true;
@@ -37,6 +39,7 @@ public interface IAlignmentLimits {
}
class Builder {
protected final boolean[] validStates = new boolean[STATES_COUNT];
private Builder() {}
@@ -54,15 +57,13 @@ public interface IAlignmentLimits {
}
public Builder deny(ForgeDirection fd) {
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
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)
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = true);
return this;
}
@@ -78,29 +79,25 @@ public interface IAlignmentLimits {
}
public Builder deny(Rotation fd) {
ExtendedFacing.getAllWith(fd).stream()
.mapToInt(ExtendedFacing::getIndex)
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)
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)
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)
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
.forEach(v -> validStates[v] = true);
return this;
}
@@ -122,7 +119,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
@@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib.alignment;
import javax.annotation.Nullable;
public interface IAlignmentProvider {
@Nullable
IAlignment getAlignment();
}
@@ -2,12 +2,14 @@ package com.gtnewhorizon.structurelib.alignment;
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 com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
public class IntegerAxisSwap {
private final Vec3Impl forFirstAxis;
private final Vec3Impl forSecondAxis;
private final Vec3Impl forThirdAxis;
@@ -20,61 +22,49 @@ public class IntegerAxisSwap {
|| 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());
throw new IllegalArgumentException(
"Axis are overlapping/missing! " + forFirstAxis
.name() + " " + forSecondAxis.name() + " " + forThirdAxis.name());
}
}
public Vec3Impl translate(Vec3Impl point) {
return new Vec3Impl(
forFirstAxis.get0() * point.get0()
+ forFirstAxis.get1() * point.get1()
forFirstAxis.get0() * point.get0() + forFirstAxis.get1() * point.get1()
+ forFirstAxis.get2() * point.get2(),
forSecondAxis.get0() * point.get0()
+ forSecondAxis.get1() * point.get1()
forSecondAxis.get0() * point.get0() + forSecondAxis.get1() * point.get1()
+ forSecondAxis.get2() * point.get2(),
forThirdAxis.get0() * point.get0()
+ forThirdAxis.get1() * point.get1()
forThirdAxis.get0() * point.get0() + forThirdAxis.get1() * point.get1()
+ forThirdAxis.get2() * point.get2());
}
public Vec3Impl inverseTranslate(Vec3Impl point) {
return new Vec3Impl(
forFirstAxis.get0() * point.get0()
+ forSecondAxis.get0() * point.get1()
forFirstAxis.get0() * point.get0() + forSecondAxis.get0() * point.get1()
+ forThirdAxis.get0() * point.get2(),
forFirstAxis.get1() * point.get0()
+ forSecondAxis.get1() * point.get1()
forFirstAxis.get1() * point.get0() + forSecondAxis.get1() * point.get1()
+ forThirdAxis.get1() * point.get2(),
forFirstAxis.get2() * point.get0()
+ forSecondAxis.get2() * point.get1()
forFirstAxis.get2() * point.get0() + forSecondAxis.get2() * point.get1()
+ forThirdAxis.get2() * point.get2());
}
public Vec3 translate(Vec3 point) {
return Vec3.createVectorHelper(
forFirstAxis.get0() * point.xCoord
+ forFirstAxis.get1() * point.yCoord
forFirstAxis.get0() * point.xCoord + forFirstAxis.get1() * point.yCoord
+ forFirstAxis.get2() * point.zCoord,
forSecondAxis.get0() * point.xCoord
+ forSecondAxis.get1() * point.yCoord
forSecondAxis.get0() * point.xCoord + forSecondAxis.get1() * point.yCoord
+ forSecondAxis.get2() * point.zCoord,
forThirdAxis.get0() * point.xCoord
+ forThirdAxis.get1() * point.yCoord
forThirdAxis.get0() * point.xCoord + forThirdAxis.get1() * point.yCoord
+ forThirdAxis.get2() * point.zCoord);
}
public Vec3 inverseTranslate(Vec3 point) {
return Vec3.createVectorHelper(
forFirstAxis.get0() * point.xCoord
+ forSecondAxis.get0() * point.yCoord
forFirstAxis.get0() * point.xCoord + forSecondAxis.get0() * point.yCoord
+ forThirdAxis.get0() * point.zCoord,
forFirstAxis.get1() * point.xCoord
+ forSecondAxis.get1() * point.yCoord
forFirstAxis.get1() * point.xCoord + forSecondAxis.get1() * point.yCoord
+ forThirdAxis.get1() * point.zCoord,
forFirstAxis.get2() * point.xCoord
+ forSecondAxis.get2() * point.yCoord
forFirstAxis.get2() * point.xCoord + forSecondAxis.get2() * point.yCoord
+ forThirdAxis.get2() * point.zCoord);
}
@@ -0,0 +1,177 @@
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 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;
import com.gtnewhorizon.structurelib.StructureLibAPI;
/**
* 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,39 +1,41 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
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.nbt.NBTTagCompound;
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;
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 com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
public class ConstructableUtility {
// TODO make these configurable and expose this to external world
public static final int COOLDOWN = 5;
private static final int BUDGET = 25;
public static boolean redBrokenOne = true; // TODO this is horrible
private static final WeakHashMap<EntityPlayerMP, Long> lastUse = new WeakHashMap<>();
private static long clientSideLastUse = 0;
private ConstructableUtility() {}
public static boolean handle(
ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
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;
@@ -42,10 +44,12 @@ public class ConstructableUtility {
// not sneaking - client side will generate hologram. nothing to do on server side
if (!aPlayer.isSneaking()) return true;
long timePast = StructureLib.getOverworldTime() - getLastUseTick(aStack);
if (timePast < COOLDOWN) {
aPlayer.addChatComponentMessage(new ChatComponentTranslation(
"item.structurelib.constructableTrigger.too_fast", COOLDOWN - timePast));
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)) {
@@ -58,8 +62,7 @@ public class ConstructableUtility {
else if (tTileEntity instanceof IConstructable) {
constructable = (IConstructable) tTileEntity;
} else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) {
ExtendedFacing facing = tTileEntity instanceof IAlignment
? ((IAlignment) tTileEntity).getExtendedFacing()
ExtendedFacing facing = tTileEntity instanceof IAlignment ? ((IAlignment) tTileEntity).getExtendedFacing()
: ExtendedFacing.of(ForgeDirection.getOrientation(aSide));
constructable = IMultiblockInfoContainer.<TileEntity>get(tTileEntity.getClass())
.toConstructable(tTileEntity, facing);
@@ -74,16 +77,16 @@ public class ConstructableUtility {
if (aPlayer.capabilities.isCreativeMode) {
constructable.construct(aStack, false);
} else if (constructable instanceof ISurvivalConstructable) {
int built = ((ISurvivalConstructable) constructable)
.survivalConstruct(aStack, BUDGET, IItemSource.fromPlayer(playerMP), playerMP);
if (built > 0)
// TODO somehow notify extensions that their inventory might have been modified and need to be
// synced to client or saved
playerMP.inventory.markDirty();
else if (built == -1) {
int built = ((ISurvivalConstructable) constructable).survivalConstruct(
aStack,
ConfigurationHandler.INSTANCE.getAutoPlaceBudget(),
ISurvivalBuildEnvironment.create(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"));
}
setLastUseTickToStackTag(aStack);
setLastUseMilis(aPlayer);
} else {
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.error.not_enabled"));
}
@@ -92,19 +95,23 @@ public class ConstructableUtility {
// client side
// particles and text
constructable.construct(aStack, true);
if (getLastUseTick(aStack) == 0)
if (System.currentTimeMillis() - getLastUseMilis(aPlayer) >= 300)
StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack));
setLastUseMilis(aPlayer);
return false;
}
private static long getLastUseTick(ItemStack aStack) {
return aStack.hasTagCompound() ? aStack.getTagCompound().getLong("LastUse") : 0;
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 void setLastUseTickToStackTag(ItemStack aStack) {
NBTTagCompound tag = aStack.stackTagCompound;
if (tag == null) tag = aStack.stackTagCompound = new NBTTagCompound();
// here we force use the overworld tick time, in case the current world is over
tag.setLong("LastUse", StructureLib.getOverworldTime());
private static long getLastUseMilis(EntityPlayer aPlayer) {
if (!(aPlayer instanceof EntityPlayerMP))
// assume client side
return clientSideLastUse;
return lastUse.getOrDefault(aPlayer, 0L);
}
}
@@ -1,15 +1,26 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import net.minecraft.item.ItemStack;
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.item.ItemStack;
/**
* Created by Tec on 24.03.2017.
*/
public interface IConstructable {
void construct(ItemStack stackSize, boolean hintsOnly);
/**
* Get the structure definition used for this constructable. Can be null if this constructable is not backed by one.
*/
default IStructureDefinition<?> getStructureDefinition() {
return null;
}
@SideOnly(Side.CLIENT)
String[] getStructureDescription(ItemStack stackSize);
}
@@ -6,6 +6,7 @@ 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.
*/
@@ -1,19 +1,25 @@
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 net.minecraft.world.World;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
/**
* To implement IConstructable on not own TileEntities
*/
public interface IMultiblockInfoContainer<T> {
HashMap<String, IMultiblockInfoContainer<?>> MULTIBLOCK_MAP = new HashMap<>();
/**
@@ -36,28 +42,37 @@ 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)}
* Construct the structure using
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
*
* @return -1 if done, a helping pointer
*/
default int survivalConstruct(
ItemStack stackSize,
int elementBudge,
IItemSource source,
EntityPlayerMP actorProfile,
T tileEntity,
ExtendedFacing aSide) {
return -1;
}
int survivalConstruct(ItemStack stackSize, int elementBudge, ISurvivalBuildEnvironment env, T tileEntity,
ExtendedFacing aSide);
@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() {
default ISurvivalConstructable toConstructable(T tileEntity, ExtendedFacing aSide) {
return new ISurvivalConstructable() {
@Override
public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
return IMultiblockInfoContainer.this
.survivalConstruct(stackSize, elementBudget, env, tileEntity, aSide);
}
@Override
public int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source,
EntityPlayerMP actor) {
return IMultiblockInfoContainer.this.survivalConstruct(
stackSize,
elementBudget,
ISurvivalBuildEnvironment.create(source, actor),
tileEntity,
aSide);
}
@Override
public void construct(ItemStack stackSize, boolean hintsOnly) {
IMultiblockInfoContainer.this.construct(stackSize, hintsOnly, tileEntity, aSide);
@@ -69,25 +84,4 @@ public interface IMultiblockInfoContainer<T> {
}
};
}
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);
}
};
}
}
@@ -1,17 +1,85 @@
package com.gtnewhorizon.structurelib.alignment.constructable;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
/**
* An extension to {@link IConstructable}. Tile Entities (or delegates returned by
* {@link IConstructableProvider#getConstructable()} should implement this interface to allow third party tools and
* StructureLib builtin constructable tools to trigger autoplace in survival mode.
* <p>
* While not originally planned, a creative player can also use this interface to trigger autoplace by using an
* {@link IItemSource} with every possible items in the game.
*/
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)}
* End player might want to disable this hack if it turns out to be too crashy
*/
boolean DISABLE_HACKY_MIGRATION_CODE = Boolean.getBoolean("structurelib.disable_isc_migration_hack");
/**
* Construct the structure using
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
* or
* {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean)}
*
* @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
* @param elementBudget The server configured element budget. The implementor can choose to 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);
@Deprecated
default int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor) {
return survivalConstruct(stackSize, elementBudget, ISurvivalBuildEnvironment.create(source, actor));
}
/**
* Construct the structure using
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
* or
* {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)}
*
* @param elementBudget The server configured element budget. The implementor can choose to 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, -2 if not supported (e.g. due to an incompatible change in the API of env, otherwise number
* of elements placed this round
*/
default int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
EntityPlayer actor = env.getActor();
if (actor instanceof EntityPlayerMP)
return survivalConstruct(stackSize, elementBudget, env.getSource(), (EntityPlayerMP) actor);
else if (!DISABLE_HACKY_MIGRATION_CODE && __get_player() == null) {
// as far as I know, no implementor would actually do anything to actor beyond passing it down to
// IStructureDefinition, so it's probably fine to proceed like this
GlobalStates.entityPlayer.set(env.getActor());
try {
return survivalConstruct(stackSize, elementBudget, env.getSource(), null);
} finally {
GlobalStates.entityPlayer.remove();
}
}
return -2;
}
/**
* Internal hack. Do not use.
*/
@Deprecated
static EntityPlayer __get_player() {
return GlobalStates.entityPlayer.get();
}
}
class GlobalStates {
public static final ThreadLocal<EntityPlayer> entityPlayer = new ThreadLocal<>();
}
@@ -0,0 +1,4 @@
/**
* This package contains interfaces and utility methods for autobuilding a structure.
*/
package com.gtnewhorizon.structurelib.alignment.constructable;
@@ -1,9 +1,16 @@
package com.gtnewhorizon.structurelib.alignment.enumerable;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.minecraftforge.common.util.ForgeDirection;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
public enum Direction {
DOWN(ForgeDirection.DOWN),
UP(ForgeDirection.UP),
NORTH(ForgeDirection.NORTH),
@@ -14,10 +21,12 @@ public enum Direction {
private final ForgeDirection forgeDirection;
private final Vec3Impl axisVector;
public static final Direction[] VALUES = values();
private static final Map<Vec3Impl, Direction> reverseMap = Arrays.stream(VALUES)
.collect(Collectors.toMap(d -> d.axisVector, Function.identity()));
Direction(ForgeDirection forgeDirection) {
this.forgeDirection = forgeDirection;
axisVector = new Vec3Impl(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ);
axisVector = Vec3Impl.getFromPool(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ);
}
public ForgeDirection getForgeDirection() {
@@ -31,4 +40,8 @@ public enum Direction {
public static Vec3Impl getAxisVector(ForgeDirection forgeDirection) {
return VALUES[forgeDirection.ordinal()].axisVector;
}
public static Direction getByAxisVector(Vec3Impl vec3) {
return reverseMap.get(vec3);
}
}
@@ -6,16 +6,19 @@ 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;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
public enum ExtendedFacing {
DOWN_NORMAL_NONE("down normal none"),
DOWN_NORMAL_HORIZONTAL("down normal horizontal"),
DOWN_NORMAL_VERTICAL("down normal vertical"),
@@ -119,31 +122,31 @@ public enum ExtendedFacing {
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 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(
.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(
private static final EnumMap<Flip, ImmutableSet<ExtendedFacing>> LOOKUP_BY_FLIP = stream(VALUES).collect(
groupingBy(
ExtendedFacing::getFlip,
() -> new EnumMap<>(Flip.class),
collectingAndThen(toSet(), ImmutableSet::copyOf)));
@@ -333,9 +336,9 @@ public enum ExtendedFacing {
}
/**
* Translates relative to front facing offset to world offset
* Translates relative to front facing offset to world offset
*
* @param abcOffset A,B,C offset (facing relative L-->R,U-->D,F-->B)
* @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) {
@@ -355,10 +358,32 @@ public enum ExtendedFacing {
}
/**
* Translates world offset to relative front facing offset
* do note the ABC coordinate system is (X, -Y, Z).
*/
public ForgeDirection getWorldDirection(ForgeDirection abcDirection) {
switch (abcDirection) {
case NORTH:
return getRelativeForwardInWorld();
case SOUTH:
return getRelativeBackInWorld();
case UP:
return getRelativeDownInWorld();
case DOWN:
return getRelativeUpInWorld();
case EAST:
return getRelativeLeftInWorld();
case WEST:
return getRelativeRightInWorld();
default:
return ForgeDirection.UNKNOWN;
}
}
/**
* 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) {
return integerAxisSwap.translate(xyzOffset);
@@ -7,9 +7,11 @@ 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"),
VERTICAL(1, "vertical"),
@@ -7,9 +7,11 @@ 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"),
UPSIDE_DOWN(0, "upside down"),
@@ -19,8 +21,8 @@ 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;
@@ -1,5 +1,7 @@
/**
* 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.
* 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;
@@ -1,10 +1,7 @@
package com.gtnewhorizon.structurelib.block;
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;
@@ -14,7 +11,14 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public class BlockHint extends Block {
private static final IIcon[] hint = new IIcon[16];
public BlockHint() {
@@ -0,0 +1,121 @@
package com.gtnewhorizon.structurelib.command;
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;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
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,146 +0,0 @@
package com.gtnewhorizon.structurelib.entity.fx;
import com.gtnewhorizon.structurelib.ClientProxy;
import com.gtnewhorizon.structurelib.StructureLib;
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);
}
}
@@ -1,20 +1,16 @@
package com.gtnewhorizon.structurelib.entity.fx;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.world.World;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@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 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);
@@ -0,0 +1,411 @@
package com.gtnewhorizon.structurelib.gui;
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;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.gui.GuiScrollableList.IGuiScreen;
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!
String keyText = key.getText();
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
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,447 @@
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);
}
}
@@ -3,6 +3,7 @@ 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;
@@ -11,6 +12,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
public class ItemBlockHint extends ItemBlock {
public ItemBlockHint(Block p_i45328_1_) {
super(p_i45328_1_);
setMaxDamage(0);
@@ -35,33 +37,35 @@ public class ItemBlockHint extends ItemBlock {
case 9:
case 10:
case 11:
aList.add(EnumChatFormatting.AQUA.toString()
+ EnumChatFormatting.BOLD
+ translateToLocal("structurelib.blockhint.desc.1")); // Placeholder for a certain group.
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.
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.
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.
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?
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.toString() + "From outer space... I guess...");
aList.add(EnumChatFormatting.BLUE + "From outer space... I guess...");
}
}
@@ -3,18 +3,23 @@ package com.gtnewhorizon.structurelib.item;
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.ConstructableUtility;
import java.util.List;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import org.lwjgl.input.Keyboard;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
public class ItemConstructableTrigger extends Item {
public ItemConstructableTrigger() {
setUnlocalizedName("structurelib.constructableTrigger");
setTextureName(MOD_ID + ":itemConstructableTrigger");
@@ -22,51 +27,43 @@ 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
}
@Override
public void onUpdate(
ItemStack p_77663_1_, World p_77663_2_, Entity p_77663_3_, int p_77663_4_, boolean p_77663_5_) {
// remove LastUse tags if it times out,
// so it can stack with other trigger item stacks
// don't remove it too soon though, to prevent chat spam
// this might be perceived as a hack, but ok this is simple enough to implement
if (p_77663_1_.hasTagCompound()
&& p_77663_1_.getTagCompound().getLong("LastUse") + ConstructableUtility.COOLDOWN * 2
< StructureLib.getOverworldTime()) {
NBTTagCompound tag = p_77663_1_.getTagCompound();
tag.removeTag("LastUse");
if (tag.hasNoTags()) p_77663_1_.setTagCompound(null);
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"));
}
}
}
@@ -3,18 +3,22 @@ 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 com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.AlignmentUtility;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public class ItemFrontRotationTool extends Item {
public ItemFrontRotationTool() {
setMaxStackSize(1);
setUnlocalizedName("structurelib.frontRotationTool");
@@ -23,17 +27,8 @@ public class ItemFrontRotationTool 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 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);
}
@@ -41,9 +36,13 @@ public class ItemFrontRotationTool extends Item {
@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.
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.
}
}
@@ -1,20 +1,23 @@
package com.gtnewhorizon.structurelib.net;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
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 net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.DimensionManager;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
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;
public abstract class AlignmentMessage implements IMessage {
int mPosX;
int mPosY;
int mPosZ;
@@ -66,6 +69,7 @@ public abstract class AlignmentMessage implements IMessage {
}
public static class AlignmentQuery extends AlignmentMessage {
public AlignmentQuery() {}
public AlignmentQuery(IAlignmentProvider provider) {
@@ -78,6 +82,7 @@ public abstract class AlignmentMessage implements IMessage {
}
public static class AlignmentData extends AlignmentMessage {
public AlignmentData() {}
private AlignmentData(AlignmentQuery query) {
@@ -98,11 +103,11 @@ public abstract class AlignmentMessage implements IMessage {
}
public static class ClientHandler implements IMessageHandler<AlignmentData, IMessage> {
@Override
public IMessage onMessage(AlignmentData pMessage, MessageContext pCtx) {
if (StructureLib.getCurrentPlayer().worldObj.provider.dimensionId == pMessage.mPosD) {
TileEntity te = StructureLib.getCurrentPlayer()
.worldObj
TileEntity te = StructureLib.getCurrentPlayer().worldObj
.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ);
if (te instanceof IAlignmentProvider) {
IAlignment alignment = ((IAlignmentProvider) te).getAlignment();
@@ -116,6 +121,7 @@ public abstract class AlignmentMessage implements IMessage {
}
public static class ServerHandler implements IMessageHandler<AlignmentQuery, AlignmentData> {
@Override
public AlignmentData onMessage(AlignmentQuery pMessage, MessageContext pCtx) {
World world = DimensionManager.getWorld(pMessage.mPosD);
@@ -0,0 +1,60 @@
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,73 @@
package com.gtnewhorizon.structurelib.net;
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;
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;
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;
}
}
}
@@ -4,12 +4,14 @@ 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;
@@ -53,6 +55,7 @@ public class UpdateHintParticleMessage implements IMessage {
}
public static class Handler implements IMessageHandler<UpdateHintParticleMessage, IMessage> {
@Override
public IMessage onMessage(UpdateHintParticleMessage msg, MessageContext ctx) {
boolean updateResult = StructureLibAPI.updateHintParticleTint(
@@ -61,16 +64,13 @@ public class UpdateHintParticleMessage implements IMessage {
msg.x,
msg.y,
msg.z,
new short[] {
msg.r, msg.g, msg.b, msg.a,
});
if (StructureLib.DEBUG_MODE)
LOGGER.debug(
"Server instructed to update hint particle at ({}, {}, {}), result {}!",
msg.x,
msg.y,
msg.z,
updateResult);
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;
}
}
@@ -0,0 +1,231 @@
package com.gtnewhorizon.structurelib.structure;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
/**
* Represent the environment in which autoplace of a single element took place.
*/
public class AutoPlaceEnvironment {
private IItemSource source;
private final EntityPlayer actor;
private final Consumer<IChatComponent> chatter;
private final IStructureDefinition<?> definition;
private final String piece;
private final ExtendedFacing facing;
final int[] offsetABC;
private final int[] baseOffsetABC;
public static AutoPlaceEnvironment fromLegacy(IItemSource source, EntityPlayer actor,
Consumer<IChatComponent> chatter) {
if (source instanceof WrappedIItemSource) {
AutoPlaceEnvironment original = ((WrappedIItemSource) source).container;
// feels like this is extremely likely to cause issues, but this does allow us to recover lost info.
// TODO probably should remove this once all addons migrate over
if (actor != original.actor || chatter != original.chatter) {
AutoPlaceEnvironment newEnv = original;
if (actor != original.actor) newEnv = newEnv.withActor(actor);
if (chatter != original.chatter) newEnv = newEnv.withChatter(chatter);
return newEnv;
}
return original;
}
return new AutoPlaceEnvironment(source, actor, chatter, null, null, null, null, null);
}
AutoPlaceEnvironment(EntityPlayer actor, Consumer<IChatComponent> chatter, IStructureDefinition<?> definition,
String piece, ExtendedFacing facing, int[] baseOffsetABC) {
this.source = null;
this.actor = actor;
this.chatter = chatter;
this.definition = definition;
this.piece = piece;
this.facing = facing;
this.offsetABC = new int[3];
this.baseOffsetABC = baseOffsetABC;
}
AutoPlaceEnvironment(IItemSource source, EntityPlayer actor, Consumer<IChatComponent> chatter,
IStructureDefinition<?> definition, String piece, ExtendedFacing facing, int[] offsetABC,
int[] baseOffsetABC) {
this.source = definition != null && !(source instanceof WrappedIItemSource)
? new WrappedIItemSource(this, source)
: source;
this.actor = actor;
this.chatter = chatter;
this.definition = definition;
this.piece = piece;
this.facing = facing;
this.offsetABC = offsetABC;
this.baseOffsetABC = baseOffsetABC;
}
protected AutoPlaceEnvironment(AutoPlaceEnvironment parent) {
this(
parent.getSource(),
parent.getActor(),
parent.getChatter(),
parent.definition,
parent.piece,
parent.facing,
parent.offsetABC,
parent.baseOffsetABC);
}
void setSource(IItemSource source) {
this.source = definition != null && !(source instanceof WrappedIItemSource)
? new WrappedIItemSource(this, source)
: source;
}
public APILevel getAPILevel() {
return definition == null ? actor instanceof EntityPlayerMP ? APILevel.Legacy : APILevel.LegacyRelaxed
: APILevel.V2;
}
/**
* From where survival autoplace will drain resources.
*/
public IItemSource getSource() {
return source;
}
/**
* The initiator of actions. for very critical errors you can also just send the error messages here, bypassing any
* filter that {@link #getChatter()} might have. You might want to use
* {@link com.gtnewhorizon.structurelib.StructureLibAPI#addThrottledChat(Object, EntityPlayer, IChatComponent, short)}
* to help reduce spam.
*/
public EntityPlayer getActor() {
return actor;
}
/**
* send error messages here. Caller will choose an appropriate way to forward it to player if the other fallbacks
* also fails.
*/
public Consumer<IChatComponent> getChatter() {
return chatter;
}
/**
* Test if given location is contained within the current piece.
*
* @param offsetA offset in A direction <b>relative to current element</b>
* @param offsetB offset in B direction <b>relative to current element</b>
* @param offsetC offset in C direction <b>relative to current element</b>
* @return true if contained, false otherwise.
*/
public boolean isContainedInPiece(int offsetA, int offsetB, int offsetC) {
if (definition == null) return false;
return definition.isContainedInStructure(
piece,
offsetABC[0] + offsetA + baseOffsetABC[0],
offsetABC[1] + offsetB + baseOffsetABC[1],
offsetABC[2] + offsetC + baseOffsetABC[2]);
}
public ExtendedFacing getFacing() {
return facing;
}
/**
* Return a new instance with source modified to given value.
*
* @param source new source
* @return new instance
*/
public AutoPlaceEnvironment withSource(IItemSource source) {
return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC);
}
/**
* Return a new instance with actor modified to given value.
*
* @param actor new actor
* @return new instance
*/
public AutoPlaceEnvironment withActor(EntityPlayer actor) {
return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC);
}
/**
* Return a new instance with chatter modified to given value.
*
* @param chatter new chatter
* @return new instance
*/
public AutoPlaceEnvironment withChatter(Consumer<IChatComponent> chatter) {
return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC);
}
private static class WrappedIItemSource implements IItemSource {
final AutoPlaceEnvironment container;
final IItemSource delegate;
public WrappedIItemSource(AutoPlaceEnvironment container, IItemSource delegate) {
this.container = container;
this.delegate = delegate;
}
@Override
@Nonnull
public Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count) {
return delegate.take(predicate, simulate, count);
}
@Override
public ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
return delegate.takeOne(predicate, simulate);
}
@Override
public boolean takeAll(Predicate<ItemStack> predicate, boolean simulate, int count) {
return delegate.takeAll(predicate, simulate, count);
}
@Override
public boolean takeOne(ItemStack stack, boolean simulate) {
return delegate.takeOne(stack, simulate);
}
@Override
public boolean takeAll(ItemStack stack, boolean simulate) {
return delegate.takeAll(stack, simulate);
}
}
/**
* Defines the various API level an {@link AutoPlaceEnvironment} has implemented.
* <p>
* Enum constants are defined in chronological order.
*/
public enum APILevel {
/**
* Implements {@link #getChatter()}, {@link #getActor()} and {@link #getSource()}. {@link #getActor()} is
* guaranteed to be an {@link net.minecraft.entity.player.EntityPlayerMP}
*/
Legacy,
/**
* Implements {@link #getChatter()}, {@link #getActor()} and {@link #getSource()}. {@link #getActor()} is
* <b>NOT</b> guaranteed to be an {@link net.minecraft.entity.player.EntityPlayerMP}
*/
LegacyRelaxed,
/**
* Implements everything so far we have defined.
*/
V2,
}
}
@@ -0,0 +1,31 @@
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;
}
}
}
@@ -0,0 +1,24 @@
package com.gtnewhorizon.structurelib.structure;
import net.minecraft.entity.player.EntityPlayer;
class DefaultSurvivalBuildEnvironment implements ISurvivalBuildEnvironment {
private final IItemSource source;
private final EntityPlayer actor;
public DefaultSurvivalBuildEnvironment(IItemSource source, EntityPlayer actor) {
this.source = source;
this.actor = actor;
}
@Override
public IItemSource getSource() {
return source;
}
@Override
public EntityPlayer getActor() {
return actor;
}
}
@@ -3,5 +3,6 @@ package com.gtnewhorizon.structurelib.structure;
import net.minecraft.world.World;
public interface IBlockPosConsumer {
void consume(World world, int x, int y, int z);
}
@@ -4,9 +4,10 @@ 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
* 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
@@ -1,29 +1,61 @@
package com.gtnewhorizon.structurelib.structure;
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;
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
* Retrieve some amount of items from this item source matching given filter.
* <p>
* We probably don't 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
*
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
* @param count how many items to extract.
* @return A nonnull map reflecting the result of extraction. Note this map is NBT and metadata aware.
*/
@Nonnull
Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count);
/**
* Take exactly one item that matches the predicate.
*
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
* @return item stack, or null if none matches
* @throws IllegalArgumentException if given predicate is null.
*/
default ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
if (predicate == null) throw new IllegalArgumentException();
Map<ItemStack, Integer> take = take(predicate, simulate, 1);
return take.isEmpty() ? null : take.keySet().iterator().next();
}
/**
* Take exactly count amount of items matching predicate.
*
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
* @param count how many items to extract.
* @return true if at least count amount of items can be/is taken.
* @throws IllegalArgumentException if given predicate is null
*/
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;
@@ -33,24 +65,56 @@ public interface IItemSource {
return true;
}
/**
* Take exactly one item. ItemStack given must have a stack size of 1.
* <p>
* This is meant to be an optimized version of {@link #takeOne(Predicate, boolean)}. Implementations that support
* item storage indexed by ItemStack can use this to speed up calculation.
*
* @param stack what item to extract. The NBT tag of this ItemStack is siginifcant and will be considered during
* matching. must have a stack size of 1.
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
* @return true if found. false otherwise
* @throws IllegalArgumentException if given stack is invalid, or has a stack size other than 1.
*/
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;
return took != null && took.stackSize > 0;
}
/**
* Take some item. Will not take item if it cannot find enough items to take.
* <p>
* This is meant to be an optimized version of {@link #takeOne(Predicate, boolean)}. Implementations that support
* indexing off ItemStack can use this to speed up calculation.
*
* @param stack what item and how many to extract. The NBT tag of this ItemStack is siginifcant and will be
* considered during matching
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
* @return true if enough item can be/has been extracted. false otherwise
* @throws IllegalArgumentException if given stack is invalid
*/
default boolean takeAll(ItemStack stack, boolean simulate) {
if (stack == null || stack.getItem() == null) throw new IllegalArgumentException();
// 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;
take(predicate, simulate, stack.stackSize);
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) {
@@ -71,6 +135,11 @@ public interface IItemSource {
};
}
/**
* 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);
}
@@ -3,5 +3,6 @@ package com.gtnewhorizon.structurelib.structure;
import net.minecraft.block.Block;
public interface ISpecialItemBlock {
int getItemMetaFromBlockMeta(Block block, int blockMeta);
}
@@ -1,37 +1,93 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
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.alignment.enumerable.ExtendedFacing;
import java.util.Arrays;
import java.util.function.Function;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
/**
* 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
* <p>
* Might, but is not required, to throw {@link java.util.NoSuchElementException} if the given structure piece is not found.
* 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
* @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 isContainedInStructure(String name, int offsetA, int offsetB, int offsetC);
/**
* 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,
@@ -49,17 +105,23 @@ public interface IStructureDefinition<T> {
forceCheckAllBlocks);
}
default boolean hints(
T object,
ItemStack trigger,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
/**
* 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,
@@ -77,17 +139,25 @@ public interface IStructureDefinition<T> {
null);
}
default boolean build(
T object,
ItemStack trigger,
String piece,
World world,
ExtendedFacing extendedFacing,
int basePositionX,
int basePositionY,
int basePositionZ,
int basePositionA,
int basePositionB,
/**
* 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,
@@ -105,19 +175,30 @@ public interface IStructureDefinition<T> {
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) {
/**
* 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,
@@ -135,34 +216,93 @@ public interface IStructureDefinition<T> {
}
/**
* @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
* 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);
@Deprecated
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) {
EntityPlayer realActor;
if (actor == null) {
realActor = ISurvivalConstructable.__get_player();
if (realActor == null) throw new IllegalArgumentException();
} else {
realActor = actor;
}
return survivalBuild(
object,
trigger,
piece,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
elementBudget,
ISurvivalBuildEnvironment.create(source, realActor),
check);
}
/**
* 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 env build environment.
* @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 IStructureDefinition#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, ISurvivalBuildEnvironment env, boolean check) {
SurvivalBuildStructureWalker<T> walker = new SurvivalBuildStructureWalker<>(
object,
trigger,
elementBudget,
env,
this,
piece,
extendedFacing,
new int[] { basePositionA, basePositionB, basePositionC },
check);
StructureUtility.iterateV2(
getStructureFor(piece),
world,
@@ -178,158 +318,111 @@ public interface IStructureDefinition<T> {
return walker.getBuilt();
}
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) {
/**
* 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;
}
// change base position to base offset
basePositionA = -basePositionA;
basePositionB = -basePositionB;
basePositionC = -basePositionC;
int[] abc = new int[] {basePositionA, basePositionB, basePositionC};
int[] xyz = new int[3];
if (checkBlocksIfNotNullForceCheckAllIfTrue != null) {
boolean success;
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;
StructureLib.LOGGER.info(
"Multi [{}, {}, {}] step @ {} {}",
basePositionX,
basePositionY,
basePositionZ,
Arrays.toString(xyz),
Arrays.toString(abc));
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;
}
}
success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
(e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z),
"check");
} 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;
StructureLib.LOGGER.info(
"Multi [{}, {}, {}] step @ {} {}",
basePositionX,
basePositionY,
basePositionZ,
Arrays.toString(xyz),
Arrays.toString(abc));
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;
}
}
success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
skipBlockUnloaded((e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z)),
"check force");
}
if (DEBUG_MODE) {
StructureLib.LOGGER.info(
"Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
if (StructureLibAPI.isDebugEnabled() && success) {
StructureLib.LOGGER
.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
}
return success;
} 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;
}
}
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.spawnHint(object, world, x, y, z, trigger);
return true;
}),
"spawnHint");
} 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;
}
}
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.placeBlock(object, world, x, y, z, trigger);
return true;
}),
"placeBlock");
}
}
return true;
}
/**
* Create a new instance of builder.
*
* @param <T> type of context object
*/
static <T> StructureDefinition.Builder<T> builder() {
return StructureDefinition.builder();
}
@@ -1,20 +1,35 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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.nbt.NBTTagCompound;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
/**
* Use StructureUtility to instantiate
* Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It
* represents what a particular element can be and how this particular element should be autoplaced.
*/
public interface IStructureElement<T> {
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);
@@ -23,30 +38,142 @@ public interface IStructureElement<T> {
/**
* 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, net.minecraft.entity.player.EntityPlayer)}
* or its overloads to implement this.
*
* @param trigger trigger item
* @param s drain resources from this place
* @param actor source of action.
* @param chatter
* @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.
* @deprecated caller should call the non deprecated overload. implementor for reusable {@link IStructureElement}
* should still implement this. implementor for private implementations can freely ignore this as long
* as you know the caller will not call this overload.
*/
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
@Deprecated
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 (DEBUG_MODE)
LOGGER.error(
"Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
getClass().getName());
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;
}
/**
* A simplified version of survivalPlaceBlock. Return null if not implemented.
* <p>
* <b>BY OVERRIDING THIS TO NULL, YOU ACKNOWLEDGE THAT THE {@link #check(Object, World, int, int, int)} OF THIS
* CLASS IS SIDE EFFECT FREE AND CAN HAVE ITS CHECK CALLED WITHOUT BREAKING ANYTHING. OTHERWISE OVERRIDE THE
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} TO PROVIDE A
* SIDEEFFECT FREE {@link PlaceResult#SKIP}.</b>
* <p>
* It should be noticed that this filter is only advisory and the actual
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} or
* {@link #check(Object, World, int, int, int)} are free to reject/accept/place blocks not contained in this list,
* e.g. when the element can be placed anywhere in the structure, but no more than 3 overall; when the structure
* element cannot know all blocks it will accept at the time of calling; when this is backed by a legacy
* {@link IStructureElement} that just didn't implement this API.
*
* @param trigger trigger item
* @param env autoplacing environment
* @return null if not implemented, otherwise an instance describing what kind of blocks will be placed by
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
*/
@Nullable
default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return null;
}
/**
* Try place the block by taking resource from given {@link IItemSource}.
* <p>
* You might want to use
* {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)}
* or its overloads to implement this.
* <p>
* The default implementation provided will
* <ol>
* <li>Get {@link BlocksToPlace} via
* {@link #getBlocksToPlace(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}. If not null
* <ol>
* <li>call {@link #check(Object, World, int, int, int)}. If this returns {@code true}, {@link PlaceResult#SKIP}
* will be returned without further action.</li>
* <li>Use the {@link BlocksToPlace} retrieved earlier and passed in {@link IItemSource} to determine an item to
* place</li>
* <li>Hand over control to
* {@link StructureUtility#survivalPlaceBlock(ItemStack, NBTMode, NBTTagCompound, boolean, World, int, int, int, IItemSource, EntityPlayer, Consumer)}</li>
* </ol>
* </li>
* <li>Call legacy
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} if
* {@code env.getActor()} contains an {@link EntityPlayerMP}</li>
* <li>Emit warning under debug mode (or throw exception under panic mode), then return
* {@link PlaceResult#SKIP}</li>
* </ol>
* It should be noticed that the default implementation is likely to cause unexpected issues if the
* {@link #check(Object, World, int, int, int)} is not side effect free, or is not idempotent. E.g. the
* {@link #check(Object, World, int, int, int)} will add current coord to a {@link java.util.List}. As this method
* might be invoked at a location that is already accepted, this will cause the machine to have multiple of this
* coord in its list.
*
* @param trigger trigger item
* @param env autoplacing environment
*/
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
IItemSource source = env.getSource();
EntityPlayer actor = env.getActor();
Consumer<IChatComponent> chatter = env.getChatter();
if (e != null) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
if (e.getStacks() == null) {
ItemStack taken = source.takeOne(e.getPredicate(), true);
return StructureUtility.survivalPlaceBlock(
taken,
NBTMode.EXACT,
taken.stackTagCompound,
true,
world,
x,
y,
z,
source,
actor,
chatter);
}
for (ItemStack stack : e.getStacks()) {
if (!source.takeOne(stack, true)) continue;
return StructureUtility.survivalPlaceBlock(
stack,
NBTMode.EXACT,
stack.stackTagCompound,
true,
world,
x,
y,
z,
source,
actor,
chatter);
}
return PlaceResult.REJECT;
}
if (actor instanceof EntityPlayerMP)
return survivalPlaceBlock(t, world, x, y, z, trigger, source, (EntityPlayerMP) actor, chatter);
if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit");
if (StructureLibAPI.isDebugEnabled()) LOGGER.info(
"Fallback shim code of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
getClass().getName());
return PlaceResult.SKIP;
}
/**
* Forget the messed up class dependency graph for now. this is just so convenient.
*/
@@ -95,17 +222,19 @@ public interface IStructureElement<T> {
enum PlaceResult {
/**
* This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or
* some other unforeseen situations.
* 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.
* 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.
* Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait
* for next round.
*/
STOP,
/**
@@ -114,9 +243,88 @@ public interface IStructureElement<T> {
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;
}
final class BlocksToPlace {
public static final BlocksToPlace errored = createEmpty();
private final Predicate<ItemStack> predicate;
private final Iterable<ItemStack> stacks;
public static BlocksToPlace createEmpty() {
return new BlocksToPlace(s -> false, Collections.emptyList());
}
public static BlocksToPlace create(ItemStack... stacks) {
return create(Arrays.asList(stacks));
}
public static BlocksToPlace create(Iterable<ItemStack> stacks) {
Predicate<ItemStack> predicate = null;
for (ItemStack stack : stacks) {
ItemStackPredicate p = ItemStackPredicate.from(stack, NBTMode.EXACT);
if (predicate == null) predicate = p;
else predicate = predicate.or(p);
}
return new BlocksToPlace(predicate, stacks);
}
public static BlocksToPlace create(Block block, int meta) {
Item itemBlock = Item.getItemFromBlock(block);
if (itemBlock instanceof ISpecialItemBlock) {
meta = ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta);
}
return create(itemBlock, meta);
}
public static BlocksToPlace create(Item item, int meta) {
return new BlocksToPlace(
ItemStackPredicate.from(item).setMeta(meta),
Collections.singletonList(new ItemStack(item, 1, meta)));
}
public static BlocksToPlace create(ItemStack itemStack) {
return new BlocksToPlace(ItemStackPredicate.from(itemStack), Collections.singletonList(itemStack));
}
public static BlocksToPlace create(Predicate<ItemStack> predicate) {
return new BlocksToPlace(predicate, null);
}
BlocksToPlace(Predicate<ItemStack> predicate, Iterable<ItemStack> stacks) {
this.predicate = predicate;
this.stacks = stacks;
}
/**
* Get the blocks to place filter. This is usually slower as it requires walking through inventories one by one.
* <p>
* Suitable for use with {@link IItemSource#takeOne(Predicate, boolean)}
*
* @return a predicate. never null.
*/
@Nonnull
public Predicate<ItemStack> getPredicate() {
return predicate;
}
/**
* Get the ItemStacks that this one is known to accept. Can be null if this info is not available at the time
* this instance is constructed.
* <p>
* Suitable for use with {@link IItemSource#takeOne(ItemStack, boolean)}.
* <p>
* This should be equivalent to the predicate returned from {@link #getPredicate()}
*/
@Nullable
public Iterable<ItemStack> getStacks() {
return stacks;
}
}
}
@@ -3,15 +3,22 @@ package com.gtnewhorizon.structurelib.structure;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
import com.google.common.collect.Iterables;
/**
* Use StructureUtility to instantiate
*/
public interface IStructureElementChain<T> extends IStructureElement<T> {
IStructureElement<T>[] fallbacks();
@Override
@@ -44,17 +51,26 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
return false;
}
@Nullable
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
Predicate<ItemStack> predicate = null;
List<Iterable<ItemStack>> is = new ArrayList<>();
for (IStructureElement<T> fallback : fallbacks()) {
BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
if (e == null) continue;
if (predicate == null) predicate = e.getPredicate();
else predicate = predicate.or(e.getPredicate());
is.add(e.getStacks());
}
if (predicate == null) return null;
return new BlocksToPlace(predicate, Iterables.concat(is));
}
@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()) {
@@ -74,4 +90,28 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
// TODO need reconsider to ensure this is the right course of action
return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT;
}
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
boolean haveSkip = false;
List<IChatComponent> bufferedNoise = new ArrayList<>();
for (IStructureElement<T> fallback : fallbacks()) {
PlaceResult result = fallback
.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add));
switch (result) {
case REJECT:
break;
case SKIP:
haveSkip = true;
break;
default:
return result;
}
}
// dump all that noise back into upstream
bufferedNoise.forEach(env.getChatter());
// TODO need reconsider to ensure this is the right course of action
return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT;
}
}
@@ -1,12 +1,14 @@
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;
@@ -18,16 +20,14 @@ public interface IStructureElementCheckOnly<T> extends IStructureElement<T> {
}
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
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;
}
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return PlaceResult.SKIP;
}
}
@@ -3,4 +3,5 @@ package com.gtnewhorizon.structurelib.structure;
/**
* Use StructureUtility to instantiate
*/
public interface IStructureElementDeferred<T> extends IStructureElement<T> {}
public interface IStructureElementDeferred<T> extends IStructureElement<T> {
}
@@ -1,28 +1,28 @@
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 PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
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 PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return PlaceResult.REJECT;
}
@@ -1,6 +1,7 @@
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;
@@ -10,6 +11,7 @@ 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;
@@ -26,16 +28,14 @@ interface IStructureNavigate<T> extends IStructureElement<T> {
}
@Override
default PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
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;
}
@Override
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return PlaceResult.SKIP;
}
@@ -2,6 +2,44 @@ 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);
interface IStructureWalker<T> {
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) {
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, int a, int b, int c) {
return walker.visit(element, world, x, y, z, a, b, c);
}
@Override
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) {
return walker.visit(element, world, x, y, z, a, b, c);
}
};
}
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, int a, int b, int c) {
return walker.visit(element, world, x, y, z, a, b, c);
}
@Override
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
int c) {
return true;
}
};
}
}
@@ -0,0 +1,41 @@
package com.gtnewhorizon.structurelib.structure;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
/*
* Architecture notes: this should never get any callback from survival build. All structure significant code belongs
* within the IStructureElement. This will make the user easier to maintain the IStructureDefinition. Structure Element
* knows the most about what this structure element should do, not the caller.
*/
public interface ISurvivalBuildEnvironment {
/**
* Get the source of the items. This will be invoked exactly once per structure element build.
* <p>
* If the call chain ever got into the old API, this will be called at least once per
* {@link IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)}
* call.
*/
IItemSource getSource();
/**
* Get the origin of action. This will be invoked at least once per
* {@link IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)}
* call.
*/
EntityPlayer getActor();
/**
* Create a default implementation with three parameters all being constants.
*
* @param source from where to drain resource
* @param actor source of action
*/
static ISurvivalBuildEnvironment create(IItemSource source, EntityPlayer actor) {
return new DefaultSurvivalBuildEnvironment(source, actor);
}
}
@@ -9,5 +9,6 @@ import net.minecraft.block.Block;
*/
@FunctionalInterface
public interface ITierConverter<TIER> {
TIER convert(Block block, int meta);
}
@@ -3,7 +3,7 @@ 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.
@@ -11,5 +11,6 @@ package com.gtnewhorizon.structurelib.structure;
* @param <CTX> the temporary state context
*/
public interface IWithExtendedContext<CTX> {
CTX getCurrentContext();
}
@@ -2,12 +2,16 @@ package com.gtnewhorizon.structurelib.structure;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
class LazyStructureElement<T> implements IStructureElementDeferred<T> {
private Function<T, IStructureElement<T>> to;
private IStructureElement<T> elem;
@@ -38,17 +42,22 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
return get(t).spawnHint(t, world, x, y, z, trigger);
}
@Nullable
@Override
public PlaceResult survivalPlaceBlock(
T t,
World world,
int x,
int y,
int z,
ItemStack trigger,
IItemSource s,
EntityPlayerMP actor,
Consumer<IChatComponent> chatter) {
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return get(t).getBlocksToPlace(t, world, x, y, z, trigger, env);
}
@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);
}
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, env);
}
}
@@ -1,31 +1,33 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.*;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import java.util.*;
import java.util.stream.Collectors;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
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<String, Set<Vec3Impl>> occupiedSpaces;
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) {
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
this.elements = elements;
this.shapes = shapes;
this.structures = structures;
this.occupiedSpaces = occupiedSpaces;
}
public static class Builder<T> {
private static final char A = '\uA000';
private static final char B = '\uB000';
private static final char C = '\uC000';
@@ -33,11 +35,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
private final Map<Vec3Impl, Character> navigates;
private final Map<Character, IStructureElement<T>> elements;
private final Map<String, String> shapes;
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
private Builder() {
navigates = new HashMap<>();
elements = new HashMap<>();
shapes = new HashMap<>();
occupiedSpaces = new HashMap<>();
}
public Map<Character, IStructureElement<T>> getElements() {
@@ -49,21 +53,25 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
}
/**
* 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
* Adds shape. Shape is a two-dimensional string array. Each <b>character</b> inside any of these strings will
* be later mapped to a particular type of {@link IStructureElement} as indicated by
* {@link #addElement(Character, IStructureElement)}.
* <p>
* next char is next block(a)
* next string is next line(b)
* next string[] is next slice(c)
* next char is next block(a) next string is next line(b) next string[] is next slice(c)
* <p>
* There are a few special reserved characters that are pre-mapped to some common structure elements
* <ul>
* <li>+ is anything but air</li>
* <li>- is air checks</li>
* <li>space bar is skip</li>
* <li>~ is also skip (but marks controller position, not optional)</li>
* <li>char {@code A000-FFFF} range is reserved for generated skips</li>
* </ul>
* <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
* @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) {
@@ -80,22 +88,37 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
}
builder.setLength(builder.length() - 1);
}
Set<Vec3Impl> occupiedSpace = new HashSet<>();
// these track the global current location
int aa = 0, bb = 0, cc = 0;
// these track the vec3 towards next meaningful element
int a = 0, b = 0, c = 0;
// I do know these are pretty bad variable naming, but I have no better idea
for (int i = 0; i < builder.length(); i++) {
char ch = builder.charAt(i);
if (ch == ' ' || ch == '~') {
if (ch == ' ') {
builder.setCharAt(i, A);
ch = A;
} else if (ch == '~') {
builder.setCharAt(i, A);
ch = A;
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
}
if (ch == A) {
aa++;
a++;
} else if (ch == B) {
aa = 0;
a = 0;
bb++;
b++;
} else if (ch == C) {
aa = 0;
bb = 0;
a = 0;
b = 0;
c++;
cc++;
} else if (a != 0 || b != 0 || c != 0) {
Vec3Impl vec3 = new Vec3Impl(a, b, c);
Character navigate = navigates.get(vec3);
@@ -105,12 +128,19 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
addElement(navigate, step(vec3));
}
builder.setCharAt(i - 1, navigate);
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
aa++;
a = 0;
b = 0;
c = 0;
} else {
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
aa++;
}
}
occupiedSpaces.put(name, occupiedSpace);
String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", "");
if (built.contains("+")) {
@@ -123,33 +153,42 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
return this;
}
/**
* @deprecated use the unboxed version
*/
@Deprecated
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
elements.putIfAbsent(name, structurePiece);
return this;
}
public Builder<T> addElement(char 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;
}
return new StructureDefinition<>(
new HashMap<>(elements),
new HashMap<>(shapes),
structures,
occupiedSpaces);
}
@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)));
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()));
throw new RuntimeException(
"Missing Structure Element bindings for (chars as integers): "
+ Arrays.toString(missing.toArray()));
}
}
@@ -157,22 +196,21 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
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)));
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()));
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());
return shapes.values().stream().flatMapToInt(CharSequence::chars)
.filter(i -> !elements.containsKey((char) i)).boxed().collect(Collectors.toSet());
}
}
@@ -194,4 +232,11 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
if (elements == null) throw new NoSuchElementException(name);
return elements;
}
@Override
public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) {
Set<Vec3Impl> occupiedSpace = occupiedSpaces.get(name);
if (occupiedSpace == null) throw new NoSuchElementException(name);
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
}
}
@@ -0,0 +1,26 @@
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;
/**
* Internal bridge class for easing implementation
*/
abstract class StructureElement_Bridge<T> implements IStructureElement<T> {
// seal the deprecated method to prevent accidental overrides
@Deprecated
@Override
public final PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
return survivalPlaceBlock(t, world, x, y, z, trigger, AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
}
// clear the default implementation. we don't need that.
public abstract PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env);
}
File diff suppressed because it is too large. Load diff
@@ -1,35 +1,48 @@
package com.gtnewhorizon.structurelib.structure;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
private final T object;
private final ItemStack trigger;
private final IItemSource source;
private final EntityPlayerMP actor;
final T object;
final ItemStack trigger;
private final int elementBudget;
private final ISurvivalBuildEnvironment params;
private final boolean check;
private int built = -1;
public SurvivalBuildStructureWalker(
T object, ItemStack trigger, IItemSource source, EntityPlayerMP actor, int elementBudget, boolean check) {
private final AutoPlaceEnvironment env;
public SurvivalBuildStructureWalker(T object, ItemStack trigger, int elementBudget,
ISurvivalBuildEnvironment params, IStructureDefinition<?> definition, String piece, ExtendedFacing facing,
int[] baseOffsetABC, boolean check) {
this.object = object;
this.trigger = trigger;
this.source = source;
this.actor = actor;
this.elementBudget = elementBudget;
this.params = params;
this.check = check;
env = new AutoPlaceEnvironment(
params.getActor(),
params.getActor()::addChatComponentMessage,
definition,
piece,
facing,
baseOffsetABC);
}
@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);
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
env.offsetABC[0] = a;
env.offsetABC[1] = b;
env.offsetABC[2] = c;
env.setSource(params.getSource());
PlaceResult placeResult = element.survivalPlaceBlock(object, world, x, y, z, trigger, env);
if (placeResult != PlaceResult.SKIP && built == -1) built = 0;
switch (placeResult) {
case SKIP:
@@ -38,8 +51,7 @@ class SurvivalBuildStructureWalker<T> implements IStructureWalker<T> {
if (check) element.check(object, world, x, y, z);
return ++built < elementBudget;
case REJECT:
if (ConstructableUtility.redBrokenOne)
StructureLibAPI.updateHintParticleTint(actor, world, x, y, z, new short[] {255, 128, 128, 0});
StructureLibAPI.markHintParticleError(params.getActor(), world, x, y, z);
return false;
case ACCEPT_STOP:
if (check) element.check(object, world, x, y, z);
@@ -3,6 +3,7 @@ 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)
*
@@ -3,6 +3,7 @@ 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)
*
@@ -0,0 +1,4 @@
/**
* Adder interfaces used in various standard structure elements.
*/
package com.gtnewhorizon.structurelib.structure.adders;
@@ -1,5 +1,7 @@
/**
* 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.
* 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;
@@ -2,10 +2,12 @@ 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;
@@ -25,6 +27,7 @@ public class InventoryIterable<Inv extends IInventory> implements Iterable<ItemS
@Override
public Iterator<ItemStack> iterator() {
return new Iterator<ItemStack>() {
private int ptr = 0;
@Override
@@ -1,31 +1,27 @@
/**
* Copyright (c) 2022, glee8e
* This file is part of StructureLib.
* 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.
* 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.
* 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
* 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.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;
@@ -33,10 +29,15 @@ import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryEnderChest;
import net.minecraft.item.ItemStack;
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
import com.gtnewhorizon.structurelib.util.InventoryUtility.ItemStackExtractor.APIType;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
/**
* 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<>();
/**
@@ -46,6 +47,7 @@ public class InventoryUtility {
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);
@@ -58,8 +60,9 @@ public class InventoryUtility {
inv.getInventory().player.inventoryContainer.detectAndSendChanges();
}
});
inventoryProviders.register(
"7000-ender-inventory", new InventoryProvider<InventoryIterable<InventoryEnderChest>>() {
inventoryProviders
.register("7000-ender-inventory", new InventoryProvider<InventoryIterable<InventoryEnderChest>>() {
@Override
public InventoryIterable<InventoryEnderChest> getInventory(EntityPlayerMP player) {
if (enableEnder.stream().anyMatch(p -> p.test(player)))
@@ -85,8 +88,8 @@ public class InventoryUtility {
stackExtractors.register(key, val);
}
public static <Inv extends IInventory> void registerStackExtractor(
String key, Function<ItemStack, ? extends Inv> extractor) {
public static <Inv extends IInventory> void registerStackExtractor(String key,
Function<ItemStack, ? extends Inv> extractor) {
registerStackExtractor(key, newItemStackProvider(extractor));
}
@@ -94,8 +97,8 @@ public class InventoryUtility {
inventoryProviders.register(key, val);
}
public static <Inv extends IInventory> void registerInventoryProvider(
String key, Function<EntityPlayerMP, ? extends Inv> extractor) {
public static <Inv extends IInventory> void registerInventoryProvider(String key,
Function<EntityPlayerMP, ? extends Inv> extractor) {
registerInventoryProvider(key, newInventoryProvider(extractor));
}
@@ -106,6 +109,7 @@ public class InventoryUtility {
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);
@@ -121,24 +125,26 @@ public class InventoryUtility {
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) {
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);
new InventoryIterable<>(inv),
predicate,
simulate,
count,
store,
filter,
player,
false);
if (found > 0) inv.markDirty();
return found;
}
@@ -146,8 +152,8 @@ public class InventoryUtility {
}
/**
* 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.
* 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
@@ -155,8 +161,8 @@ public class InventoryUtility {
* @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) {
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) {
@@ -167,8 +173,8 @@ public class InventoryUtility {
}
/**
* 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.
* 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
@@ -188,14 +194,9 @@ public class InventoryUtility {
}
// 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) {
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);
@@ -214,29 +215,23 @@ public class InventoryUtility {
* @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) {
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) {
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(); ) {
for (Iterator<ItemStack> iterator = inv.iterator(); iterator.hasNext();) {
ItemStack stack = iterator.next();
// invalid stack
if (stack == null || stack.getItem() == null || stack.stackSize <= 0) continue;
@@ -271,9 +266,10 @@ public class InventoryUtility {
}
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.
* 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.
*
@@ -287,12 +283,14 @@ public class InventoryUtility {
}
public interface InventoryProvider<R extends Iterable<ItemStack>> {
R getInventory(EntityPlayerMP player);
void markDirty(R inv);
}
public interface ItemStackExtractor {
enum APIType {
MAIN,
EXTRACT_ONE_STACK,
@@ -301,13 +299,13 @@ public class InventoryUtility {
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.
* 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 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.
@@ -315,18 +313,12 @@ public class InventoryUtility {
* @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);
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.
* 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.
*
@@ -342,20 +334,15 @@ public class InventoryUtility {
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) {
public int takeFromStack(Predicate<ItemStack> predicate, boolean simulate, int count,
ItemStackCounter store, ItemStack stack, ItemStack filter, EntityPlayerMP player) {
return 0;
}
@@ -368,6 +355,7 @@ public class InventoryUtility {
}
public interface ItemStackCounter {
/**
* Add some amount of stack. Note stackSize is used instead of stack.stackSize.
*/
@@ -375,7 +363,8 @@ public class InventoryUtility {
}
private static class ItemStackCounterImpl implements ItemStackCounter {
private final Map<ItemStack, Integer> store = new ItemStackMap<>();
private final Map<ItemStack, Integer> store = new ItemStackMap<>(true);
@Override
public void add(ItemStack stack, int stackSize) {
@@ -1,9 +1,11 @@
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) {
@@ -17,6 +19,7 @@ public class ItemStackArrayIterable implements Iterable<ItemStack> {
@Override
public Iterator<ItemStack> iterator() {
return new Iterator<ItemStack>() {
private int ptr = 0;
@Override
@@ -1,464 +0,0 @@
/*
* Copyright (c) 2021, glee8e, Code Chicken.
*
* This file is originally part of NotEnoughtItem by Code Chicken.
* It is adapted to implement the standard Map interface by glease
*
* This program 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.1 of the License, or
* (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.gtnewhorizon.structurelib.util;
import static net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE;
import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
/**
* A map class for ItemStack keys with wildcard damage/NBT. Optimised for lookup.
* <p>
* This map does NOT support null values or null keys! null will be silently ignored!
* <p>
* Originally created by CodeChicken for NotEnoughItems. Adapted to {@code Map<ItemStack, T>} interface by glee8e
* @author CodeChiken
* @author glee8e
*/
public final class ItemStackMap<T> extends AbstractMap<ItemStack, T> {
public static final NBTTagCompound WILDCARD_TAG;
static {
WILDCARD_TAG = new NBTTagCompound();
WILDCARD_TAG.setBoolean("*", true);
}
private final HashMap<Item, DetailMap> itemMap = new HashMap<>();
private int size;
static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) {
ItemStack stack = new ItemStack(item, size, damage);
stack.stackTagCompound = tag;
return stack;
}
static int actualDamage(ItemStack stack) {
return Items.diamond.getDamage(stack);
}
static int getKeyType(int damage, NBTTagCompound tag) {
int i = 0;
if (isWildcard(damage)) i = 1;
if (isWildcard(tag)) i |= 2;
return i;
}
static ItemStack wildcard(Item item) {
return newItemStack(item, 1, WILDCARD_VALUE, WILDCARD_TAG);
}
static boolean isWildcard(int damage) {
return damage == WILDCARD_VALUE;
}
static boolean isWildcard(NBTTagCompound tag) {
return tag != null && tag.getBoolean("*");
}
@Override
public T get(Object key) {
if (!(key instanceof ItemStack)) return null;
ItemStack stack = ((ItemStack) key);
if (stack.getItem() == null) return null;
DetailMap map = itemMap.get(stack.getItem());
return map == null ? null : map.get(stack);
}
@Override
public T put(ItemStack key, T value) {
if (key == null || key.getItem() == null || value == null) return null;
return itemMap.computeIfAbsent(key.getItem(), k -> new DetailMap()).put(key, value);
}
@Override
public T remove(Object key) {
if (!(key instanceof ItemStack)) return null;
ItemStack stack = ((ItemStack) key);
if (stack.getItem() == null) return null;
DetailMap map = itemMap.get(stack.getItem());
return map == null ? null : map.remove(stack);
}
@Override
public boolean containsKey(Object key) {
if (!(key instanceof ItemStack)) return false;
ItemStack stack = ((ItemStack) key);
if (stack.getItem() == null) return false;
DetailMap map = itemMap.get(stack.getItem());
return map != null && map.get(stack) != null;
}
@Override
public void clear() {
itemMap.clear();
size = 0;
}
@Override
public Set<Map.Entry<ItemStack, T>> entrySet() {
return new SetView();
}
private static class StackMetaKey {
public final int damage;
public final NBTTagCompound tag;
public StackMetaKey(int damage, NBTTagCompound tag) {
this.damage = damage;
this.tag = tag;
}
public StackMetaKey(ItemStack key) {
this(actualDamage(key), key.stackTagCompound);
}
public int hashCode() {
return Objects.hashCode(damage, tag);
}
public boolean equals(Object o) {
if (!(o instanceof StackMetaKey)) return false;
StackMetaKey t = (StackMetaKey) o;
return damage == t.damage && Objects.equal(tag, t.tag);
}
}
private static class DetailIter<T> implements Iterator<Map.Entry<ItemStack, T>> {
private final Item owner;
private final ItemStackMap<T>.DetailMap backing;
@Nullable
private final Iterator<Map.Entry<Integer, T>> damageIter;
@Nullable
private final Iterator<Map.Entry<NBTTagCompound, T>> tagIter;
@Nullable
private final Iterator<Map.Entry<StackMetaKey, T>> metaIter;
private DetailIterState state = DetailIterState.NOT_STARTED;
private boolean removed = false;
private DetailIter(Map.Entry<Item, ItemStackMap<T>.DetailMap> input) {
this.owner = input.getKey();
this.backing = input.getValue();
damageIter =
backing.damageMap != null ? backing.damageMap.entrySet().iterator() : null;
tagIter = backing.tagMap != null ? backing.tagMap.entrySet().iterator() : null;
metaIter = backing.metaMap != null ? backing.metaMap.entrySet().iterator() : null;
}
private DetailIterState nextState() {
switch (state) {
case NOT_STARTED:
if (backing.hasWildcard) return DetailIterState.WILDCARD;
case WILDCARD:
case DAMAGE:
if (damageIter != null && damageIter.hasNext()) return DetailIterState.DAMAGE;
case TAG:
if (tagIter != null && tagIter.hasNext()) return DetailIterState.TAG;
case META:
if (metaIter != null && metaIter.hasNext()) return DetailIterState.META;
case DONE:
return DetailIterState.DONE;
default:
throw new IllegalStateException("Unexpected value: " + state);
}
}
@Override
public void remove() {
if (removed) throw new IllegalStateException("remove() called twice");
state.remove(this);
removed = true;
}
@Override
public boolean hasNext() {
return nextState() != DetailIterState.DONE;
}
@Override
public Map.Entry<ItemStack, T> next() {
DetailIterState nextState = nextState();
if (nextState == DetailIterState.DONE) throw new NoSuchElementException();
state = nextState;
removed = false;
return nextState.get(this);
}
private enum DetailIterState {
NOT_STARTED {
@Override
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
throw new AssertionError("Should not call get on NOT_STARTED");
}
@Override
<T> void remove(DetailIter<T> iter) {
throw new IllegalStateException("next() never called");
}
},
WILDCARD {
@Override
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
return new Map.Entry<ItemStack, T>() {
private final ItemStack key = wildcard(iter.owner);
@Override
public ItemStack getKey() {
return key;
}
@Override
public T getValue() {
return iter.backing.wildcard;
}
@Override
public T setValue(T value) {
return iter.backing.wildcard = value;
}
};
}
@Override
<T> void remove(DetailIter<T> iter) {
assert iter.backing.hasWildcard;
iter.backing.wildcard = null;
iter.backing.hasWildcard = false;
}
},
DAMAGE {
@Override
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
assert iter.damageIter != null;
Map.Entry<Integer, T> entry = iter.damageIter.next();
return new ItemStackEntry<>(newItemStack(iter.owner, 1, entry.getKey(), WILDCARD_TAG), entry);
}
@Override
<T> void remove(DetailIter<T> iter) {
assert iter.damageIter != null;
iter.damageIter.remove();
}
},
TAG {
@Override
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
assert iter.tagIter != null;
Map.Entry<NBTTagCompound, T> entry = iter.tagIter.next();
return new ItemStackEntry<>(newItemStack(iter.owner, 1, WILDCARD_VALUE, entry.getKey()), entry);
}
@Override
<T> void remove(DetailIter<T> iter) {
assert iter.tagIter != null;
iter.tagIter.remove();
}
},
META {
@Override
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
assert iter.metaIter != null;
Map.Entry<StackMetaKey, T> entry = iter.metaIter.next();
return new ItemStackEntry<>(
newItemStack(iter.owner, 1, entry.getKey().damage, entry.getKey().tag), entry);
}
@Override
<T> void remove(DetailIter<T> iter) {
assert iter.metaIter != null;
iter.metaIter.remove();
}
},
DONE {
@Override
<T> Map.Entry<ItemStack, T> get(DetailIter<T> iter) {
throw new AssertionError("Should not call get on DONE");
}
@Override
<T> void remove(DetailIter<T> iter) {
throw new AssertionError("Should not call remove on DONE");
}
};
abstract <T> Map.Entry<ItemStack, T> get(DetailIter<T> iter);
abstract <T> void remove(DetailIter<T> iter);
private static class ItemStackEntry<T> implements Map.Entry<ItemStack, T> {
private final ItemStack key;
private final Map.Entry<?, T> entry;
public ItemStackEntry(ItemStack key, Map.Entry<?, T> entry) {
this.key = key;
this.entry = entry;
}
@Override
public ItemStack getKey() {
return key;
}
@Override
public T getValue() {
return entry.getValue();
}
@Override
public T setValue(T value) {
return entry.setValue(value);
}
}
}
}
private class DetailMap {
private boolean hasWildcard;
private T wildcard;
private HashMap<Integer, T> damageMap;
private HashMap<NBTTagCompound, T> tagMap;
private HashMap<StackMetaKey, T> metaMap;
private int size;
public T get(ItemStack key) {
if (wildcard != null) return wildcard;
if (damageMap != null) {
final T ret = damageMap.get(actualDamage(key));
if (ret != null) return ret;
}
if (tagMap != null) {
final T ret = tagMap.get(key.stackTagCompound);
if (ret != null) return ret;
}
if (metaMap != null) return metaMap.get(new StackMetaKey(key));
return null;
}
public T put(ItemStack key, T value) {
try {
switch (getKeyType(actualDamage(key), key.stackTagCompound)) {
case 0:
if (metaMap == null) metaMap = new HashMap<>();
return metaMap.put(new StackMetaKey(key), value);
case 1:
if (tagMap == null) tagMap = new HashMap<>();
return tagMap.put(key.stackTagCompound, value);
case 2:
if (damageMap == null) damageMap = new HashMap<>();
return damageMap.put(actualDamage(key), value);
case 3:
T ret = wildcard;
wildcard = value;
hasWildcard = true;
return ret;
}
} finally {
updateSize();
}
return null;
}
public T remove(ItemStack key) {
try {
switch (getKeyType(actualDamage(key), key.stackTagCompound)) {
case 0:
return metaMap != null ? metaMap.remove(new StackMetaKey(key)) : null;
case 1:
return tagMap != null ? tagMap.remove(key.stackTagCompound) : null;
case 2:
return damageMap != null ? damageMap.remove(actualDamage(key)) : null;
case 3:
T ret = wildcard;
wildcard = null;
hasWildcard = false;
return ret;
}
} finally {
updateSize();
}
return null;
}
private void updateSize() {
int newSize = (hasWildcard ? 1 : 0)
+ (metaMap != null ? metaMap.size() : 0)
+ (tagMap != null ? tagMap.size() : 0)
+ (damageMap != null ? damageMap.size() : 0);
if (newSize != size) {
ItemStackMap.this.size += newSize - size;
size = newSize;
}
}
}
private class SetView extends AbstractSet<Map.Entry<ItemStack, T>> {
@Override
public boolean contains(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
return java.util.Objects.equals(get(entry.getKey()), entry.getValue());
}
@Override
public boolean add(Map.Entry<ItemStack, T> itemStackTEntry) {
return itemStackTEntry.getKey() != null
&& itemStackTEntry.getValue() != null
&& put(itemStackTEntry.getKey(), itemStackTEntry.getValue()) == null;
}
@Override
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
return ItemStackMap.this.remove(entry.getKey(), entry.getValue());
}
@Nonnull
@Override
public Iterator<Map.Entry<ItemStack, T>> iterator() {
return Iterators.concat(Iterators.transform(itemMap.entrySet().iterator(), DetailIter::new));
}
@Override
public int size() {
return size;
}
}
}
@@ -3,6 +3,7 @@ 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;
@@ -11,6 +12,7 @@ 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);
}
@@ -20,8 +22,7 @@ public final class ItemStackPredicate implements Predicate<ItemStack> {
}
public static ItemStackPredicate from(ItemStack itemStack, NBTMode mode) {
return new ItemStackPredicate(itemStack.getItem())
.setMeta(Items.feather.getDamage(itemStack))
return new ItemStackPredicate(itemStack.getItem()).setMeta(Items.feather.getDamage(itemStack))
.setTag(mode, itemStack.stackTagCompound);
}
@@ -63,13 +64,16 @@ public final class ItemStackPredicate implements Predicate<ItemStack> {
}
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;
@@ -87,6 +91,7 @@ public final class ItemStackPredicate implements Predicate<ItemStack> {
}
},
EXACT {
@Override
public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) {
if (lhs != null && lhs.hasNoTags()) lhs = null;
@@ -95,6 +100,7 @@ public final class ItemStackPredicate implements Predicate<ItemStack> {
}
},
IGNORE_KNOWN_INSIGNIFICANT_TAGS {
@Override
public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) {
// fast path for empty tags
@@ -105,8 +111,8 @@ public final class ItemStackPredicate implements Predicate<ItemStack> {
return EXACT.test(lhs, rhs);
}
};
private static final String[] KNOWN_INSIGNIFICANT_TAGS = {
"display", // TODO expand/refine this
private static final String[] KNOWN_INSIGNIFICANT_TAGS = { "display", // TODO expand/refine this
};
}
}
@@ -1,12 +1,14 @@
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")
@@ -4,6 +4,7 @@ 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
@@ -1,14 +1,28 @@
package com.gtnewhorizon.structurelib.util;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import net.minecraft.dispenser.IPosition;
import net.minecraftforge.common.util.ForgeDirection;
public class Vec3Impl implements Comparable<Vec3Impl> {
public static final Vec3Impl NULL_VECTOR = new Vec3Impl(0, 0, 0);
private static final Map<Vec3Impl, Vec3Impl> pool = new HashMap<>();
private final int val0;
private final int val1;
private final int val2;
/**
* THIS WILL CAUSE THIS VECTOR TO PERMANENTLY RESIDE IN STATIC MEMORY. DO NOT USE THIS UNLESS YOU KNOW WHAT YOU ARE
* DOING!!!
*/
public static Vec3Impl getFromPool(int in0, int in1, int in2) {
return pool.computeIfAbsent(new Vec3Impl(in0, in1, in2), Function.identity());
}
public Vec3Impl(int in0, int in1, int in2) {
this.val0 = in0;
this.val1 = in1;
@@ -57,8 +71,7 @@ public class Vec3Impl implements Comparable<Vec3Impl> {
}
public Vec3Impl offset(ForgeDirection facing, int n) {
return n == 0
? this
return n == 0 ? this
: new Vec3Impl(val0 + facing.offsetX * n, val1 + facing.offsetY * n, val2 + facing.offsetZ * n);
}
@@ -1,37 +1,20 @@
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
@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
*/
* 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
*/
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
/**
* XSTR - Xorshift ThermiteRandom
* Modified by Bogdan-G
* 03.06.2016
* version 0.0.4
* XSTR - Xorshift ThermiteRandom Modified by Bogdan-G 03.06.2016 version 0.0.4
*/
public class XSTR extends Random {
@@ -40,10 +23,11 @@ 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() {
@Override
public synchronized void setSeed(long seed) {
if (!Thread.currentThread().getStackTrace()[2].getClassName().equals(Random.class.getName())) {
@@ -53,13 +37,11 @@ public class XSTR extends Random {
};
/*
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
* the current time, as if by
* Creates a new pseudo random number generator. The seed is initialized to the current time, as if by
* <code>setSeed(System.currentTimeMillis());</code>.
*/
public XSTR() {
@@ -79,8 +61,8 @@ public class XSTR extends Random {
}
/**
* Creates a new pseudo random number generator, starting with the specified
* seed, using <code>setSeed(seed);</code>.
* Creates a new pseudo random number generator, starting with the specified seed, using
* <code>setSeed(seed);</code>.
*
* @param seed the initial seed
*/
@@ -108,9 +90,8 @@ public class XSTR extends Random {
}
/**
* Sets the seed for this pseudo random number generator. As described
* above, two instances of the same random class, starting with the same
* seed, produce the same results, if the same methods are called.
* Sets the seed for this pseudo random number generator. As described above, two instances of the same random
* class, starting with the same seed, produce the same results, if the same methods are called.
*
* @param seed the new seed
*/
@@ -128,9 +109,8 @@ public class XSTR extends Random {
}
/**
* Implementation of George Marsaglia's elegant Xorshift random generator
* 30% faster and better quality than the built-in java.util.random see also
* see http://www.javamex.com/tutorials/random_numbers/xorshift.shtml
* Implementation of George Marsaglia's elegant Xorshift random generator 30% faster and better quality than the
* built-in java.util.random see also see http://www.javamex.com/tutorials/random_numbers/xorshift.shtml
*
* @param nbits will shift nbits bits
* @return next seed
@@ -170,15 +150,14 @@ public class XSTR extends Random {
}
/**
* Returns a pseudorandom, uniformly distributed {@code int} value between 0
* (inclusive) and the specified value (exclusive), drawn from this random
* number generator's sequence. The general contract of {@code nextInt} is
* that one {@code int} value in the specified range is pseudorandomly
* generated and returned. All {@code bound} possible {@code int} values are
* produced with (approximately) equal probability. The method
* {@code nextInt(int bound)} is implemented by class {@code Random} as if
* by:
* <pre> {@code
* Returns a pseudorandom, uniformly distributed {@code int} value between 0 (inclusive) and the specified value
* (exclusive), drawn from this random number generator's sequence. The general contract of {@code nextInt} is that
* one {@code int} value in the specified range is pseudorandomly generated and returned. All {@code bound} possible
* {@code int} values are produced with (approximately) equal probability. The method {@code nextInt(int bound)} is
* implemented by class {@code Random} as if by:
*
* <pre>
* {@code
* public int nextInt(int bound) {
* if (bound <= 0)
* throw new IllegalArgumentException("bound must be positive");
@@ -192,35 +171,29 @@ public class XSTR extends Random {
* val = bits % bound;
* } while (bits - val + (bound-1) < 0);
* return val;
* }}</pre>
* }}
* </pre>
*
* <p>The hedge "approx
* imately" is used in the foregoing description only because the next
* method is only approximately an unbiased source of independently chosen
* bits. If it were a perfect source of randomly chosen bits, then the
* algorithm shown would choose {@code int} values from the stated range
* with perfect uniformity.
* <p>
* The algorithm is slightly tricky. It rejects values that would result in
* an uneven distribution (due to the fact that 2^31 is not divisible by n).
* The probability of a value being rejected depends on n. The worst case is
* n=2^30+1, for which the probability of a reject is 1/2, and the expected
* number of iterations before the loop terminates is 2.
* The hedge "approx imately" is used in the foregoing description only because the next method is only
* approximately an unbiased source of independently chosen bits. If it were a perfect source of randomly chosen
* bits, then the algorithm shown would choose {@code int} values from the stated range with perfect uniformity.
* <p>
* The algorithm treats the case where n is a power of two specially: it
* returns the correct number of high-order bits from the underlying
* pseudo-random number generator. In the absence of special treatment, the
* correct number of <i>low-order</i> bits would be returned. Linear
* congruential pseudo-random number generators such as the one implemented
* by this class are known to have short periods in the sequence of values
* of their low-order bits. Thus, this special case greatly increases the
* length of the sequence of values returned by successive calls to this
* method if n is a small power of two.
* The algorithm is slightly tricky. It rejects values that would result in an uneven distribution (due to the fact
* that 2^31 is not divisible by n). The probability of a value being rejected depends on n. The worst case is
* n=2^30+1, for which the probability of a reject is 1/2, and the expected number of iterations before the loop
* terminates is 2.
* <p>
* The algorithm treats the case where n is a power of two specially: it returns the correct number of high-order
* bits from the underlying pseudo-random number generator. In the absence of special treatment, the correct number
* of <i>low-order</i> bits would be returned. Linear congruential pseudo-random number generators such as the one
* implemented by this class are known to have short periods in the sequence of values of their low-order bits.
* Thus, this special case greatly increases the length of the sequence of values returned by successive calls to
* this method if n is a small power of two.
*
* @param bound the upper bound (exclusive). Must be positive.
* @return the next pseudorandom, uniformly distributed {@code int} value
* between zero (inclusive) and {@code bound} (exclusive) from this random
* number generator's sequence
* @return the next pseudorandom, uniformly distributed {@code int} value between zero (inclusive) and {@code bound}
* (exclusive) from this random number generator's sequence
* @throws IllegalArgumentException if bound is not positive
* @since 1.2
*/
@@ -230,18 +203,10 @@ public class XSTR extends Random {
// throw new RuntimeException("BadBound");
// }
/*int r = next(31);
int m = bound - 1;
if ((bound & m) == 0) // i.e., bound is a power of 2
{
r = (int) ((bound * (long) r) >> 31);
} else {
for (int u = r;
u - (r = u % bound) + m < 0;
u = next(31))
;
}
return r;*/
/*
* int r = next(31); int m = bound - 1; if ((bound & m) == 0) // i.e., bound is a power of 2 { r = (int) ((bound
* * (long) r) >> 31); } else { for (int u = r; u - (r = u % bound) + m < 0; u = next(31)) ; } return r;
*/
// speedup, new nextInt ~+40%
long last = seed ^ seed << 21;
last ^= last >>> 35;
@@ -269,10 +234,9 @@ 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 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) {
bytes_arr[iba++] = (byte) rndba;
}
}
+255
View File
@@ -0,0 +1,255 @@
<!DOCTYPE html>
<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>
<h2>{@link com.gtnewhorizon.structurelib.StructureEvent}</h2>
<p>
This class is the base class of all events that will be posted while a IStructureDefinition are visited.
These events are disabled by default, and can be enabled by calling {@link com.gtnewhorizon.structurelib.StructureLibAPI#enableInstrument(Object)}.
To receive these events, register an event handler to {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}.
Handlers should filter the events received by {@link com.gtnewhorizon.structurelib.StructureEvent#getInstrumentIdentifier()}.
</p>
<p>
<b>These events are intended for third party tools to inspect structure details.</b> Normal users should not use
these at all.
</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&gt; implements IStructureElement&lt;T&gt; {
//...
{@literal @}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.
<h2>Instrumenting StructureLib</h2>
<p>
StructureLib support third party tools to poke into structure's internals via events. This is done by listening to {@link com.gtnewhorizon.structurelib.StructureEvent}
and its subclasses on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS forge main event bus}.
</p>
<p>
Please refer to {@link com.gtnewhorizon.structurelib.StructureEvent} to see what events are available.
</p>
</BODY>
</HTML>
@@ -26,12 +26,22 @@ item.structurelib.constructableTrigger.desc.1=Shows multiblock construction sche
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.too_fast=Please wait %d ticks before another autoplace...
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
@@ -26,12 +26,22 @@ item.structurelib.constructableTrigger.desc.1=只能在多方块主机上使用
item.structurelib.constructableTrigger.desc.2=结构的示意图。 (译者注:请搭高使用)
item.structurelib.constructableTrigger.desc.3= (潜行右键可以直接完成搭建)
item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显示的机器等级/模式/类型
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待%dtick...
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.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号
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@@ -0,0 +1,29 @@
package com.gtnewhorizon.structurelib.alignment.enumerable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import net.minecraftforge.common.util.ForgeDirection;
import org.junit.jupiter.api.Test;
class ExtendedFacingTest {
@Test
void ensureGetWorldDirectionDoesNotCrashNorReturnNull() {
for (ExtendedFacing ef : ExtendedFacing.VALUES) {
for (ForgeDirection src : ForgeDirection.VALID_DIRECTIONS) {
assertNotNull(ef.getWorldDirection(src));
}
}
}
@Test
void ensureGetWorldDirectionIsCorrect() {
assertEquals(ForgeDirection.DOWN, ExtendedFacing.EAST_NORMAL_NONE.getWorldDirection(ForgeDirection.UP));
assertEquals(ForgeDirection.DOWN, ExtendedFacing.NORTH_NORMAL_NONE.getWorldDirection(ForgeDirection.UP));
assertEquals(ForgeDirection.NORTH, ExtendedFacing.NORTH_NORMAL_NONE.getWorldDirection(ForgeDirection.NORTH));
assertEquals(ForgeDirection.NORTH, ExtendedFacing.NORTH_CLOCKWISE_NONE.getWorldDirection(ForgeDirection.NORTH));
assertEquals(ForgeDirection.DOWN, ExtendedFacing.NORTH_CLOCKWISE_NONE.getWorldDirection(ForgeDirection.EAST));
}
}