├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── net
│ └── entityoutliner
│ ├── EntityOutliner.java
│ ├── mixin
│ ├── MixinMinecraftClient.java
│ └── MixinWorldRenderer.java
│ └── ui
│ ├── ColorWidget.java
│ ├── EntityListWidget.java
│ ├── EntitySelector.java
│ └── ModMenuIntegration.java
└── resources
├── assets
└── entityoutliner
│ ├── icon.png
│ ├── lang
│ ├── en_us.json
│ ├── fr_fr.json
│ └── zh_cn.json
│ └── textures
│ └── gui
│ └── colors.png
├── entityoutliner.mixins.json
└── fabric.mod.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # gradle
2 |
3 | .gradle/
4 | build/
5 | out/
6 | classes/
7 |
8 | # eclipse
9 |
10 | *.launch
11 |
12 | # idea
13 |
14 | .idea/
15 | *.iml
16 | *.ipr
17 | *.iws
18 |
19 | # vscode
20 |
21 | .settings/
22 | .vscode/
23 | bin/
24 | .classpath
25 | .project
26 |
27 | # fabric
28 |
29 | run/
30 | remappedSrc/
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 Adam Viola
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Entity Outliner
2 | Entity Outliner is a clientside mod that allows you to select entity types to outline, making them visible through obstructions at any distance.
3 |
4 | ## Why Use It?
5 | This mod will help with:
6 |
7 | Finding passive mobs
8 |
9 | by outlining them.
10 |
11 | 
12 |
13 |
14 |
15 | Finding unlit caves
16 |
17 | by outlining zombies, creepers, skeletons, and spiders.
18 |
19 | 
20 |
21 |
22 |
23 |
24 |
25 | Fighting other players
26 |
27 | by outlining players.
28 |
29 | 
30 |
31 |
32 |
33 |
34 | Finding your death location
35 |
36 | by outlining items and experience orbs.
37 |
38 |
39 | 
40 |
41 |
42 |
43 |
44 |
45 | Wither skeleton skull hunting
46 |
47 | by outlining wither skeletons
48 |
49 | 
50 |
51 |
52 |
53 |
54 | Finding mineshafts
55 |
56 | by outlining cave spiders and minecarts with chests.
57 |
58 |
59 | 
60 |
61 |
62 |
63 | And many more!
64 |
65 | ## Features
66 | **Entity Selector**
67 |
68 | 
69 |
70 | This screen allows outlining of any entity in the game. There's a search bar for narrowing down entities and buttons to organize the results by entity category, deselect all entities, and toggle on/off the outlines. Entities added by other mods **do** appear in the results.
71 |
72 | For the technically inclined, the search works using a precomputed hashtable that maps a string prefix to a corresponding list of results. The lists of results are computed for all prefixes that correspond at least one entity type.
73 |
74 | **Controls for toggling the outlines and opening the selector**
75 |
76 | 
77 |
78 | Custom keybinds are provided to open the entity selector and toggle the outline. The outline can also be toggled via a button inside the entity selector.
79 |
80 |
81 | ## Installation
82 | 1. Install [Fabric](https://fabricmc.net/use/)
83 | 2. Drop the [Fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api) jar into the mods folder
84 | 3. Drop the Entity Outliner jar into the mods folder
85 |
86 | ## Compatibility
87 | Works with MobZ. Let me know if you find any compatibility issues.
88 |
89 | ## License
90 | MIT. Feel free to use this mod in any modpack.
91 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'fabric-loom' version '0.11-SNAPSHOT'
3 | id 'maven-publish'
4 | }
5 |
6 | sourceCompatibility = JavaVersion.VERSION_17
7 | targetCompatibility = JavaVersion.VERSION_17
8 |
9 | archivesBaseName = project.archives_base_name
10 | version = project.mod_version
11 | group = project.maven_group
12 |
13 | repositories {
14 | maven {
15 | url "https://maven.terraformersmc.com/releases/"
16 | }
17 | }
18 |
19 | dependencies {
20 | minecraft "com.mojang:minecraft:${project.minecraft_version}"
21 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
22 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
23 |
24 | //Fabric api
25 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
26 |
27 | // ModMenu
28 | modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}"
29 |
30 | compileOnly 'com.google.code.findbugs:jsr305:+'
31 | }
32 |
33 | processResources {
34 | inputs.property "version", project.version
35 |
36 | filesMatching("fabric.mod.json") {
37 | expand "version": project.version
38 | }
39 |
40 | // from(sourceSets.main.resources.srcDirs) {
41 | // exclude "fabric.mod.json"
42 | // }
43 | }
44 |
45 | // ensure that the encoding is set to UTF-8, no matter what the system default is
46 | // this fixes some edge cases with special characters not displaying correctly
47 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
48 | tasks.withType(JavaCompile) {
49 | options.encoding = "UTF-8"
50 | }
51 |
52 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
53 | // if it is present.
54 | // If you remove this task, sources will not be generated.
55 | task sourcesJar(type: Jar, dependsOn: classes) {
56 | classifier = "sources"
57 | from sourceSets.main.allSource
58 | }
59 |
60 | jar {
61 | from "LICENSE"
62 | }
63 |
64 | // configure the maven publication
65 | publishing {
66 | publications {
67 | mavenJava(MavenPublication) {
68 | // add all the jars that should be included when publishing to maven
69 | artifact(remapJar) {
70 | builtBy remapJar
71 | }
72 | artifact(sourcesJar) {
73 | builtBy remapSourcesJar
74 | }
75 | }
76 | }
77 |
78 | // select the repositories you want to publish to
79 | repositories {
80 | // uncomment to publish to the local maven
81 | // mavenLocal()
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Done to increase the memory available to gradle.
2 | org.gradle.jvmargs=-Xmx1G
3 |
4 | # Fabric Properties
5 | # check these on https://fabricmc.net/use
6 | minecraft_version=1.19.3
7 | yarn_mappings=1.19.3+build.5
8 | loader_version=0.14.13
9 |
10 |
11 | # Mod Properties
12 | mod_version = 1.2.6
13 | maven_group = net.entityoutliner
14 | archives_base_name = entity-outliner
15 |
16 |
17 |
18 | # Dependencies
19 | # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
20 | fabric_version=0.73.0+1.19.3
21 | modmenu_version=5.0.2
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamviola/EntityOutliner/2716cd8434f482745032d82045d826013bf8d055/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | jcenter()
4 | maven {
5 | name = 'Fabric'
6 | url = 'https://maven.fabricmc.net/'
7 | }
8 | gradlePluginPortal()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/EntityOutliner.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner;
2 |
3 | import java.io.IOException;
4 | import java.lang.reflect.Type;
5 | import java.nio.file.Files;
6 | import java.nio.file.Path;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.stream.Collectors;
10 |
11 |
12 | import com.google.gson.JsonObject;
13 | import com.google.gson.JsonSyntaxException;
14 | import com.google.gson.Gson;
15 | import com.google.gson.reflect.TypeToken;
16 |
17 | import org.lwjgl.glfw.GLFW;
18 |
19 | import net.entityoutliner.ui.EntitySelector;
20 | import net.entityoutliner.ui.ColorWidget.Color;
21 | import net.fabricmc.api.ClientModInitializer;
22 | import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
23 | import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
24 | import net.fabricmc.loader.api.FabricLoader;
25 | import net.minecraft.client.MinecraftClient;
26 | import net.minecraft.client.option.KeyBinding;
27 | import net.minecraft.client.util.InputUtil;
28 | import net.minecraft.entity.EntityType;
29 | import net.minecraft.registry.Registries;
30 |
31 | public class EntityOutliner implements ClientModInitializer {
32 | private static final Gson GSON = new Gson();
33 | public static boolean outliningEntities;
34 |
35 | private static final KeyBinding CONFIG_BIND = new KeyBinding(
36 | "key.entity-outliner.selector",
37 | InputUtil.Type.KEYSYM,
38 | GLFW.GLFW_KEY_SEMICOLON,
39 | "title.entity-outliner.title"
40 | );
41 |
42 | private static final KeyBinding OUTLINE_BIND = new KeyBinding(
43 | "key.entity-outliner.outline",
44 | InputUtil.Type.KEYSYM,
45 | GLFW.GLFW_KEY_O,
46 | "title.entity-outliner.title"
47 | );
48 |
49 | @Override
50 | public void onInitializeClient() {
51 | KeyBindingHelper.registerKeyBinding(CONFIG_BIND);
52 | KeyBindingHelper.registerKeyBinding(OUTLINE_BIND);
53 |
54 | loadConfig();
55 |
56 | ClientTickEvents.END_CLIENT_TICK.register(this::onEndTick);
57 | }
58 |
59 | private static Path getConfigPath() {
60 | return FabricLoader.getInstance().getConfigDir().resolve("entityoutliner.json");
61 | }
62 |
63 | public static void saveConfig() {
64 | JsonObject config = new JsonObject();
65 |
66 | List> outlinedEntityNames = EntitySelector.outlinedEntityTypes.entrySet().stream()
67 | .map(entry -> List.of(EntityType.getId(entry.getKey()).toString(), entry.getValue().name()))
68 | .collect(Collectors.toList());
69 |
70 | config.add("outlinedEntities", GSON.toJsonTree(outlinedEntityNames));
71 |
72 | try {
73 | Files.write(getConfigPath(), GSON.toJson(config).getBytes());
74 | }
75 | catch (IOException ex) {
76 | logException(ex, "Failed to save EntityOutliner config");
77 | }
78 | }
79 |
80 | private void loadConfig() {
81 | try {
82 | JsonObject config = GSON.fromJson(new String(Files.readAllBytes(getConfigPath())), JsonObject.class);
83 | if (config.has("outlinedEntities")) {
84 | Type setType = new TypeToken>>(){}.getType();
85 | List> outlinedEntityNames = GSON.fromJson(config.get("outlinedEntities"), setType);
86 |
87 | Map, Color> outlinedEntityTypes = outlinedEntityNames.stream()
88 | .collect(Collectors.toMap(list -> EntityType.get(list.get(0)).get(), list -> Color.valueOf(list.get(1))));;
89 |
90 | for (EntityType> entityType : Registries.ENTITY_TYPE)
91 | if (outlinedEntityTypes.containsKey(entityType))
92 | EntitySelector.outlinedEntityTypes.put(entityType, outlinedEntityTypes.get(entityType));
93 | }
94 | }
95 | catch (IOException | JsonSyntaxException ex) {
96 | logException(ex, "Failed to load EntityOutliner config");
97 | }
98 | }
99 |
100 | private void onEndTick(MinecraftClient client) {
101 | while (OUTLINE_BIND.wasPressed()) {
102 | outliningEntities = !outliningEntities;
103 | }
104 |
105 | if (CONFIG_BIND.isPressed()) {
106 | client.setScreen(new EntitySelector(null));
107 | }
108 | }
109 |
110 | public static void logException(Exception ex, String message) {
111 | System.err.printf("[EntityOutliner] %s (%s: %s)", message, ex.getClass().getSimpleName(), ex.getLocalizedMessage());
112 | }
113 | }
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/mixin/MixinMinecraftClient.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner.mixin;
2 |
3 | import net.entityoutliner.EntityOutliner;
4 | import net.entityoutliner.ui.EntitySelector;
5 | import net.minecraft.client.MinecraftClient;
6 | import net.minecraft.entity.Entity;
7 | import org.spongepowered.asm.mixin.Mixin;
8 | import org.spongepowered.asm.mixin.injection.At;
9 | import org.spongepowered.asm.mixin.injection.Inject;
10 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
11 |
12 | @Mixin(MinecraftClient.class)
13 | public abstract class MixinMinecraftClient {
14 |
15 | @Inject(method = "hasOutline", at = @At("HEAD"), cancellable = true)
16 | private void outlineEntities(Entity entity, CallbackInfoReturnable ci) {
17 | if (EntityOutliner.outliningEntities && EntitySelector.outlinedEntityTypes != null) {
18 | if (EntitySelector.outlinedEntityTypes.containsKey(entity.getType())) {
19 | ci.setReturnValue(true);
20 | }
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/mixin/MixinWorldRenderer.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner.mixin;
2 |
3 | import org.spongepowered.asm.mixin.Mixin;
4 | import org.spongepowered.asm.mixin.injection.At;
5 | import org.spongepowered.asm.mixin.injection.Inject;
6 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
7 |
8 | import net.entityoutliner.EntityOutliner;
9 | import net.entityoutliner.ui.EntitySelector;
10 | import net.entityoutliner.ui.ColorWidget.Color;
11 | import net.minecraft.client.render.OutlineVertexConsumerProvider;
12 | import net.minecraft.client.render.VertexConsumerProvider;
13 | import net.minecraft.client.render.WorldRenderer;
14 | import net.minecraft.client.util.math.MatrixStack;
15 | import net.minecraft.entity.Entity;
16 | import net.minecraft.entity.EntityType;
17 | import net.minecraft.entity.player.PlayerEntity;
18 | import net.minecraft.scoreboard.AbstractTeam;
19 |
20 | @Mixin(WorldRenderer.class)
21 | public abstract class MixinWorldRenderer {
22 |
23 | @Inject(method = "renderEntity", at = @At("HEAD"))
24 | private void renderEntity(Entity entity, double cameraX, double cameraY, double cameraZ, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, CallbackInfo ci) {
25 | if (EntityOutliner.outliningEntities
26 | && vertexConsumers instanceof OutlineVertexConsumerProvider
27 | && EntitySelector.outlinedEntityTypes.containsKey(entity.getType())) {
28 |
29 | Color color = EntitySelector.outlinedEntityTypes.get(entity.getType());
30 | OutlineVertexConsumerProvider outlineVertexConsumers = (OutlineVertexConsumerProvider) vertexConsumers;
31 | outlineVertexConsumers.setColor(color.red, color.green, color.blue, 255);
32 |
33 | if (entity.getType() == EntityType.PLAYER) {
34 | PlayerEntity player = (PlayerEntity) entity;
35 | AbstractTeam team = player.getScoreboardTeam();
36 | if (team != null && team.getColor().getColorValue() != null) {
37 | int hexColor = team.getColor().getColorValue();
38 | int blue = hexColor % 256;
39 | int green = (hexColor / 256) % 256;
40 | int red = (hexColor / 65536) % 256;
41 | outlineVertexConsumers.setColor(red, green, blue, 255);
42 | }
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/ui/ColorWidget.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner.ui;
2 |
3 | import java.util.Map;
4 |
5 | import com.mojang.blaze3d.platform.GlStateManager;
6 | import com.mojang.blaze3d.systems.RenderSystem;
7 |
8 | import net.fabricmc.api.EnvType;
9 | import net.fabricmc.api.Environment;
10 | import net.minecraft.client.MinecraftClient;
11 | import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
12 | import net.minecraft.client.gui.widget.PressableWidget;
13 | import net.minecraft.client.util.math.MatrixStack;
14 | import net.minecraft.entity.EntityType;
15 | import net.minecraft.entity.SpawnGroup;
16 | import net.minecraft.text.Text;
17 | import net.minecraft.util.Identifier;
18 |
19 | @Environment(EnvType.CLIENT)
20 | public class ColorWidget extends PressableWidget {
21 | private static final Identifier TEXTURE = new Identifier("entityoutliner:textures/gui/colors.png");
22 | private Color color;
23 | private EntityType> entityType;
24 |
25 | private ColorWidget(int x, int y, int width, int height, Text message, EntityType> entityType) {
26 | super(x, y, width, height, message);
27 | this.entityType = entityType;
28 |
29 | if (EntitySelector.outlinedEntityTypes.containsKey(this.entityType))
30 | onShow();
31 | }
32 |
33 | public ColorWidget(int x, int y, int width, int height, EntityType> entityType) {
34 | this(x, y, width, height, Text.translatable("options.chat.color"), entityType);
35 | }
36 |
37 | public void onShow() {
38 | this.color = EntitySelector.outlinedEntityTypes.get(this.entityType);
39 | }
40 |
41 | public void onPress() {
42 | this.color = this.color.next();
43 | EntitySelector.outlinedEntityTypes.put(this.entityType, this.color);
44 | }
45 |
46 | public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) {
47 | MinecraftClient minecraftClient = MinecraftClient.getInstance();
48 | RenderSystem.setShaderTexture(0, TEXTURE);
49 | RenderSystem.enableDepthTest();
50 | RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
51 | RenderSystem.enableBlend();
52 | RenderSystem.defaultBlendFunc();
53 | RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
54 | drawTexture(matrices, this.getX(), this.getY(), this.isFocused() ? 20.0F : 0.0F, this.color.ordinal() * 20, 20, 20, 40, 180);
55 | this.renderBackground(matrices, minecraftClient, mouseX, mouseY);
56 | }
57 |
58 | public enum Color {
59 | WHITE(255, 255, 255),
60 | BLACK(0, 0, 0),
61 | RED(255, 0, 0),
62 | ORANGE(255, 127, 0),
63 | YELLOW(255, 255, 0),
64 | GREEN(0, 255, 0),
65 | BLUE(0, 0, 255),
66 | PURPLE(127, 0, 127),
67 | PINK(255, 155, 182);
68 |
69 | public int red;
70 | public int green;
71 | public int blue;
72 |
73 | private static final Map spawnGroupColors = Map.of(
74 | SpawnGroup.AMBIENT, Color.PURPLE,
75 | SpawnGroup.AXOLOTLS, Color.PINK,
76 | SpawnGroup.CREATURE, Color.YELLOW,
77 | SpawnGroup.MISC, Color.WHITE,
78 | SpawnGroup.MONSTER, Color.RED,
79 | SpawnGroup.UNDERGROUND_WATER_CREATURE, Color.ORANGE,
80 | SpawnGroup.WATER_AMBIENT, Color.GREEN,
81 | SpawnGroup.WATER_CREATURE, Color.BLUE
82 | );
83 |
84 | private static Color[] colors = Color.values();
85 |
86 | private Color(int red, int green, int blue) {
87 | this.red = red;
88 | this.green = green;
89 | this.blue = blue;
90 | }
91 |
92 | public static Color of(SpawnGroup group) {
93 | return spawnGroupColors.get(group);
94 | }
95 |
96 | public Color next() {
97 | return get((this.ordinal() + 1) % colors.length);
98 | }
99 |
100 | public Color get(int index) {
101 | return colors[index];
102 | }
103 | }
104 |
105 | @Override
106 | protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/ui/EntityListWidget.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner.ui;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.apache.commons.lang3.StringUtils;
7 |
8 | import net.entityoutliner.ui.ColorWidget.Color;
9 | import net.fabricmc.api.EnvType;
10 | import net.fabricmc.api.Environment;
11 | import net.minecraft.client.MinecraftClient;
12 | import net.minecraft.client.font.TextRenderer;
13 | import net.minecraft.client.gui.DrawableHelper;
14 | import net.minecraft.client.gui.Element;
15 | import net.minecraft.client.gui.Selectable;
16 | import net.minecraft.client.gui.widget.CheckboxWidget;
17 | import net.minecraft.client.gui.widget.ElementListWidget;
18 | import net.minecraft.client.gui.widget.PressableWidget;
19 | import net.minecraft.client.util.math.MatrixStack;
20 | import net.minecraft.entity.EntityType;
21 | import net.minecraft.entity.SpawnGroup;
22 | import net.minecraft.util.Language;
23 |
24 | @Environment(EnvType.CLIENT)
25 | public class EntityListWidget extends ElementListWidget {
26 |
27 | public EntityListWidget(MinecraftClient client, int width, int height, int top, int bottom, int itemHeight) {
28 | super(client, width, height, top, bottom, itemHeight);
29 | this.centerListVertically = false;
30 | }
31 |
32 | public void addListEntry(EntityListWidget.Entry entry) {
33 | super.addEntry(entry);
34 | }
35 |
36 | public void clearListEntries() {
37 | super.clearEntries();
38 | }
39 |
40 | public int getRowWidth() {
41 | return 400;
42 | }
43 |
44 | protected int getScrollbarPositionX() {
45 | return super.getScrollbarPositionX() + 32;
46 | }
47 |
48 | @Environment(EnvType.CLIENT)
49 | public static abstract class Entry extends ElementListWidget.Entry { }
50 |
51 | @Environment(EnvType.CLIENT)
52 | public static class EntityEntry extends EntityListWidget.Entry {
53 |
54 | private final CheckboxWidget checkbox;
55 | private final ColorWidget color;
56 | private final EntityType> entityType;
57 | private final List children = new ArrayList<>();
58 |
59 | private EntityEntry(CheckboxWidget checkbox, ColorWidget color, EntityType> entityType) {
60 | this.checkbox = checkbox;
61 | this.entityType = entityType;
62 | this.color = color;
63 |
64 | this.children.add(checkbox);
65 | if (EntitySelector.outlinedEntityTypes.containsKey(entityType))
66 | this.children.add(color);
67 | }
68 |
69 | public static EntityListWidget.EntityEntry create(EntityType> entityType, int width) {
70 | return new EntityListWidget.EntityEntry(
71 | new CheckboxWidget(width / 2 - 155, 0, 310, 20, entityType.getName(), EntitySelector.outlinedEntityTypes.containsKey(entityType)),
72 | new ColorWidget(width / 2 + 130, 0, 310, 20, entityType),
73 | entityType
74 | );
75 | }
76 |
77 | public void render(MatrixStack matrices, int i, int j, int k, int l, int m, int n, int o, boolean bl, float f) {
78 | this.checkbox.setY(j);
79 | this.checkbox.render(matrices, n, o, f);
80 |
81 | if (this.children.contains(this.color)) {
82 | this.color.setY(j);
83 | this.color.render(matrices, n, o, f);
84 | }
85 | }
86 |
87 | public boolean mouseClicked(double mouseX, double mouseY, int button) {
88 | if (EntitySelector.outlinedEntityTypes.containsKey(entityType)) {
89 | if (this.color.isMouseOver(mouseX, mouseY)) {
90 | this.color.onPress();
91 | } else {
92 | EntitySelector.outlinedEntityTypes.remove(entityType);
93 |
94 | this.checkbox.onPress();
95 | this.children.remove(this.color);
96 | }
97 | } else {
98 | EntitySelector.outlinedEntityTypes.put(entityType, Color.of(entityType.getSpawnGroup()));
99 |
100 | this.color.onShow();
101 | this.checkbox.onPress();
102 | this.children.add(this.color);
103 | }
104 |
105 | return true;
106 | }
107 |
108 | public List extends Element> children() {
109 | return this.children;
110 | }
111 |
112 | public EntityType> getEntityType() {
113 | return this.entityType;
114 | }
115 |
116 | public CheckboxWidget getCheckbox() {
117 | return this.checkbox;
118 | }
119 |
120 | public List extends Selectable> selectableChildren() {
121 | return this.children;
122 | }
123 | }
124 |
125 | @Environment(EnvType.CLIENT)
126 | public static class HeaderEntry extends EntityListWidget.Entry {
127 |
128 | private final TextRenderer font;
129 | private final String title;
130 | private final int width;
131 | private final int height;
132 |
133 | private HeaderEntry(SpawnGroup category, TextRenderer font, int width, int height) {
134 | this.font = font;
135 | this.width = width;
136 | this.height = height;
137 |
138 | if (category != null) {
139 | String title = "";
140 | for (String term : category.getName().split("\\p{Punct}|\\p{Space}")) {
141 | title += StringUtils.capitalize(term) + " ";
142 | }
143 | this.title = title.trim();
144 | } else {
145 | this.title = Language.getInstance().get("gui.entity-outliner.no_results");
146 | }
147 |
148 | }
149 |
150 | public static EntityListWidget.HeaderEntry create(SpawnGroup category, TextRenderer font, int width, int height) {
151 | return new EntityListWidget.HeaderEntry(category, font, width, height);
152 | }
153 |
154 | public void render(MatrixStack matrices, int i, int j, int k, int l, int m, int n, int o, boolean bl, float f) {
155 | DrawableHelper.drawCenteredText(matrices, this.font, this.title, this.width / 2, j + (this.height / 2) - (this.font.fontHeight / 2), 16777215);
156 | }
157 |
158 | public List extends Element> children() {
159 | return new ArrayList<>();
160 | }
161 |
162 | public String toString() {
163 | return this.title;
164 | }
165 |
166 | @Override
167 | public List extends Selectable> selectableChildren() {
168 | return new ArrayList<>();
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/ui/EntitySelector.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner.ui;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Comparator;
5 | import java.util.HashMap;
6 | import java.util.List;
7 |
8 | import net.entityoutliner.EntityOutliner;
9 | import net.entityoutliner.ui.ColorWidget.Color;
10 | import net.minecraft.client.gui.screen.Screen;
11 | import net.minecraft.client.gui.widget.ButtonWidget;
12 |
13 | import net.minecraft.client.gui.widget.TextFieldWidget;
14 | import net.minecraft.client.util.math.MatrixStack;
15 | import net.minecraft.entity.SpawnGroup;
16 | import net.minecraft.entity.EntityType;
17 | import net.minecraft.text.Text;
18 | // import net.minecraft.util.registry.Registry;
19 | import net.minecraft.registry.Registries;
20 |
21 | public class EntitySelector extends Screen {
22 | protected final Screen parent;
23 |
24 | private TextFieldWidget searchField;
25 | private EntityListWidget list;
26 | public static boolean groupByCategory = true;
27 | private static String searchText = "";
28 | public static HashMap>> searcher; // Prefix -> arr of results
29 | public static HashMap, Color> outlinedEntityTypes = new HashMap<>();
30 |
31 | public EntitySelector(Screen parent) {
32 | super(Text.translatable("title.entity-outliner.selector"));
33 | this.parent = parent;
34 | }
35 |
36 | public void onClose() {
37 | this.client.setScreen(this.parent);
38 | }
39 |
40 | protected void init() {
41 | if (searcher == null) {
42 | initializePrefixTree();
43 | }
44 |
45 | this.list = new EntityListWidget(this.client, this.width, this.height, 32, this.height - 32, 25);
46 | this.addSelectableChild(list);
47 |
48 | // Create search field
49 | this.searchField = new TextFieldWidget(this.textRenderer, this.width / 2 - 100, 6, 200, 20, Text.of(searchText));
50 | this.searchField.setText(searchText);
51 | this.searchField.setChangedListener(this::onSearchFieldUpdate);
52 | this.addSelectableChild(searchField);
53 |
54 | // Create buttons
55 | int buttonWidth = 80;
56 | int buttonHeight = 20;
57 | int buttonInterval = (this.width - 4 * buttonWidth) / 5;
58 | int buttonOffset = buttonInterval;
59 | int buttonY = this.height - 16 - (buttonHeight / 2);
60 |
61 | // Add sort type button
62 | // this.addDrawableChild(new ButtonWidget(buttonOffset, buttonY, buttonWidth, buttonHeight, Text.translatable(groupByCategory ? "button.entity-outliner.categories" : "button.entity-outliner.no-categories"), (button) -> {
63 | // groupByCategory = !groupByCategory;
64 | // this.onSearchFieldUpdate(this.searchField.getText());
65 | // button.setMessage(Text.translatable(groupByCategory ? "button.entity-outliner.categories" : "button.entity-outliner.no-categories"));
66 | // }));
67 |
68 | this.addDrawableChild(
69 | ButtonWidget.builder(
70 | Text.translatable(groupByCategory ? "button.entity-outliner.categories" : "button.entity-outliner.no-categories"),
71 | (button) -> {
72 | groupByCategory = !groupByCategory;
73 | this.onSearchFieldUpdate(this.searchField.getText());
74 | button.setMessage(Text.translatable(groupByCategory ? "button.entity-outliner.categories" : "button.entity-outliner.no-categories"));
75 | }
76 | ).size(buttonWidth, buttonHeight).position(buttonOffset, buttonY).build()
77 | );
78 |
79 | // Add Deselect All button
80 | // this.addDrawableChild(new ButtonWidget(buttonOffset + (buttonWidth + buttonInterval), buttonY, buttonWidth, buttonHeight, Text.translatable("button.entity-outliner.deselect"), (button) -> {
81 | // outlinedEntityTypes.clear();
82 | // this.onSearchFieldUpdate(this.searchField.getText());
83 | // }));
84 |
85 | this.addDrawableChild(
86 | ButtonWidget.builder(
87 | Text.translatable("button.entity-outliner.deselect"),
88 | (button) -> {
89 | outlinedEntityTypes.clear();
90 | this.onSearchFieldUpdate(this.searchField.getText());
91 | }
92 | ).size(buttonWidth, buttonHeight).position(buttonOffset + (buttonWidth + buttonInterval), buttonY).build()
93 | );
94 |
95 | // Add toggle outlining button
96 | // this.addDrawableChild(new ButtonWidget(buttonOffset + (buttonWidth + buttonInterval) * 2, buttonY, buttonWidth, buttonHeight, Text.translatable(EntityOutliner.outliningEntities ? "button.entity-outliner.on" : "button.entity-outliner.off"), (button) -> {
97 | // EntityOutliner.outliningEntities = !EntityOutliner.outliningEntities;
98 | // button.setMessage(Text.translatable(EntityOutliner.outliningEntities ? "button.entity-outliner.on" : "button.entity-outliner.off"));
99 | // }));
100 |
101 | this.addDrawableChild(
102 | ButtonWidget.builder(
103 | Text.translatable(EntityOutliner.outliningEntities ? "button.entity-outliner.on" : "button.entity-outliner.off"),
104 | (button) -> {
105 | EntityOutliner.outliningEntities = !EntityOutliner.outliningEntities;
106 | button.setMessage(Text.translatable(EntityOutliner.outliningEntities ? "button.entity-outliner.on" : "button.entity-outliner.off"));
107 | }
108 | ).size(buttonWidth, buttonHeight).position(buttonOffset + (buttonWidth + buttonInterval) * 2, buttonY).build()
109 | );
110 |
111 | // Add Done button
112 | // this.addDrawableChild(new ButtonWidget(buttonOffset + (buttonWidth + buttonInterval) * 3, buttonY, buttonWidth, buttonHeight, Text.translatable("button.entity-outliner.done"), (button) -> {
113 | // this.client.setScreen(null);
114 | // }));
115 |
116 | this.addDrawableChild(
117 | ButtonWidget.builder(
118 | Text.translatable("button.entity-outliner.done"),
119 | (button) -> { this.client.setScreen(null); }
120 | ).size(buttonWidth, buttonHeight).position(buttonOffset + (buttonWidth + buttonInterval) * 3, buttonY).build()
121 | );
122 |
123 | this.setInitialFocus(this.searchField);
124 | this.onSearchFieldUpdate(this.searchField.getText());
125 | }
126 |
127 | // Initializes the prefix tree used for searching in the entity selector screen
128 | private void initializePrefixTree() {
129 | EntitySelector.searcher = new HashMap<>();
130 |
131 | // Initialize no-text results
132 | List> allResults = new ArrayList>();
133 | EntitySelector.searcher.put("", allResults);
134 |
135 | // Get sorted list of entity types
136 | List> entityTypes = new ArrayList<>();
137 | for (EntityType> entityType : Registries.ENTITY_TYPE) {
138 | entityTypes.add(entityType);
139 | }
140 | entityTypes.sort(new Comparator>() {
141 | @Override
142 | public int compare(EntityType> o1, EntityType> o2) {
143 | return o1.getName().getString().compareTo(o2.getName().getString());
144 | }
145 | });
146 |
147 | // Add each entity type to everywhere it belongs in the prefix "tree"
148 | for (EntityType> entityType : entityTypes) {
149 |
150 | String name = entityType.getName().getString().toLowerCase();
151 | allResults.add(entityType);
152 |
153 | List prefixes = new ArrayList<>();
154 | prefixes.add("");
155 |
156 | // By looping over the name's length, we add to every possible prefix
157 | for (int i = 0; i < name.length(); i++) {
158 | char character = name.charAt(i);
159 |
160 | // Loop over every prefix
161 | for (int p = 0; p < prefixes.size(); p++) {
162 | String prefix = prefixes.get(p) + character;
163 | prefixes.set(p, prefix);
164 |
165 | // Get results for current prefix
166 | List> results;
167 | if (EntitySelector.searcher.containsKey(prefix)) {
168 | results = EntitySelector.searcher.get(prefix);
169 | } else {
170 | results = new ArrayList>();
171 | EntitySelector.searcher.put(prefix, results);
172 | }
173 |
174 | results.add(entityType);
175 | }
176 |
177 | // Add another prefix to allow searching by second/third/... word
178 | if (Character.isWhitespace(character)) {
179 | prefixes.add("");
180 | }
181 | }
182 | }
183 | }
184 |
185 | // Callback provided to TextFieldWidget triggered when its text updates
186 | private void onSearchFieldUpdate(String text) {
187 | searchText = text;
188 | text = text.toLowerCase().trim();
189 |
190 | this.list.clearListEntries();
191 |
192 | if (searcher.containsKey(text)) {
193 | List> results = searcher.get(text);
194 |
195 | // Splits results into categories and separates them with headers
196 | if (groupByCategory) {
197 | HashMap>> resultsByCategory = new HashMap<>();
198 |
199 | for (EntityType> entityType : results) {
200 | SpawnGroup category = entityType.getSpawnGroup();
201 | if (!resultsByCategory.containsKey(category)) {
202 | resultsByCategory.put(category, new ArrayList<>());
203 | }
204 |
205 | resultsByCategory.get(category).add(entityType);
206 | }
207 |
208 | for (SpawnGroup category : SpawnGroup.values()) {
209 | if (resultsByCategory.containsKey(category)) {
210 | this.list.addListEntry(EntityListWidget.HeaderEntry.create(category, this.client.textRenderer, this.width, 25));
211 |
212 | for (EntityType> entityType : resultsByCategory.get(category)) {
213 | this.list.addListEntry(EntityListWidget.EntityEntry.create(entityType, this.width));
214 | }
215 |
216 | }
217 | }
218 |
219 | } else {
220 | for (EntityType> entityType : results) {
221 | this.list.addListEntry(EntityListWidget.EntityEntry.create(entityType, this.width));
222 | }
223 | }
224 | } else { // If there are no results, let the user know
225 | this.list.addListEntry(EntityListWidget.HeaderEntry.create(null, this.client.textRenderer, this.width, 25));
226 | }
227 |
228 | // This prevents an overscroll when the user is already scrolled down and the results list is shortened
229 | this.list.setScrollAmount(this.list.getScrollAmount());
230 | }
231 |
232 | // Called when config screen is escaped
233 | public void removed() {
234 | EntityOutliner.saveConfig();
235 | }
236 |
237 | public void tick() {
238 | this.searchField.tick();
239 | }
240 |
241 | public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
242 | // Render dirt background
243 | this.renderBackground(matrices);
244 |
245 | // Render scrolling list
246 | this.list.render(matrices, mouseX, mouseY, delta);
247 |
248 | // Render our search bar
249 | this.setFocused(this.searchField);
250 | this.searchField.setTextFieldFocused(true);
251 | this.searchField.render(matrices, mouseX, mouseY, delta);
252 |
253 | // Render buttons
254 | super.render(matrices, mouseX, mouseY, delta);
255 | }
256 |
257 | // Sends mouseDragged event to the scrolling list
258 | public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
259 | return this.list.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/main/java/net/entityoutliner/ui/ModMenuIntegration.java:
--------------------------------------------------------------------------------
1 | package net.entityoutliner.ui;
2 |
3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory;
4 | import com.terraformersmc.modmenu.api.ModMenuApi;
5 |
6 | public class ModMenuIntegration implements ModMenuApi {
7 | @Override
8 | public ConfigScreenFactory> getModConfigScreenFactory() {
9 | return EntitySelector::new;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/main/resources/assets/entityoutliner/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamviola/EntityOutliner/2716cd8434f482745032d82045d826013bf8d055/src/main/resources/assets/entityoutliner/icon.png
--------------------------------------------------------------------------------
/src/main/resources/assets/entityoutliner/lang/en_us.json:
--------------------------------------------------------------------------------
1 | {
2 | "key.entity-outliner.selector": "Open Entity Selector",
3 | "key.entity-outliner.outline": "Toggle Entity Outline",
4 | "title.entity-outliner.selector": "Entity Selector",
5 | "title.entity-outliner.title": "Entity Outliner",
6 | "button.entity-outliner.deselect": "Deselect All",
7 | "button.entity-outliner.done": "Done",
8 | "button.entity-outliner.categories": "Categories",
9 | "button.entity-outliner.no-categories": "No Categories",
10 | "button.entity-outliner.on": "Toggled On",
11 | "button.entity-outliner.off": "Toggled Off",
12 | "gui.entity-outliner.no_results": "No results"
13 | }
--------------------------------------------------------------------------------
/src/main/resources/assets/entityoutliner/lang/fr_fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "key.entity-outliner.selector": "Ouvrir le sélecteur d'entités",
3 | "key.entity-outliner.outline": "Activer le contour d'entités",
4 | "title.entity-outliner.selector": "Sélecteur d'entités",
5 | "title.entity-outliner.title": "Sélecteur d'entités (Entity Outliner)",
6 | "button.entity-outliner.deselect": "Tout déselectionner",
7 | "button.entity-outliner.done": "Terminer",
8 | "button.entity-outliner.categories": "Catégories",
9 | "button.entity-outliner.no-categories": "Aucune catégories",
10 | "button.entity-outliner.on": "Activer",
11 | "button.entity-outliner.off": "Désactiver",
12 | "gui.entity-outliner.no_results": "Aucun résultat"
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/assets/entityoutliner/lang/zh_cn.json:
--------------------------------------------------------------------------------
1 | {
2 | "key.entity-outliner.selector": "打开实体选择器",
3 | "key.entity-outliner.outline": "实体轮廓显示开关",
4 | "title.entity-outliner.selector": "实体选择器",
5 | "title.entity-outliner.title": "实体轮廓显示(Entity Outliner)",
6 | "button.entity-outliner.deselect": "取消选择",
7 | "button.entity-outliner.done": "完成",
8 | "button.entity-outliner.categories": "有分类",
9 | "button.entity-outliner.no-categories": "无分类",
10 | "button.entity-outliner.on": "已打开",
11 | "button.entity-outliner.off": "已关闭",
12 | "gui.entity-outliner.no_results": "无结果"
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/resources/assets/entityoutliner/textures/gui/colors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamviola/EntityOutliner/2716cd8434f482745032d82045d826013bf8d055/src/main/resources/assets/entityoutliner/textures/gui/colors.png
--------------------------------------------------------------------------------
/src/main/resources/entityoutliner.mixins.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "minVersion": "0.8",
4 | "package": "net.entityoutliner.mixin",
5 | "compatibilityLevel": "JAVA_17",
6 | "mixins": [
7 | ],
8 | "client": [
9 | "MixinMinecraftClient",
10 | "MixinWorldRenderer"
11 | ],
12 | "injectors": {
13 | "defaultRequire": 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/resources/fabric.mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 1,
3 | "id": "entityoutliner",
4 | "version": "${version}",
5 |
6 | "name": "Entity Outliner",
7 | "description": "Allows you to select entity types to outline, making them visible through all obstructions within render distance.",
8 | "authors": [
9 | "adamviola"
10 | ],
11 | "contact": {
12 | "homepage": "https://github.com/adamviola/EntityOutliner",
13 | "sources": "https://github.com/adamviola/EntityOutliner"
14 | },
15 |
16 | "license": "MIT",
17 | "icon": "assets/entityoutliner/icon.png",
18 |
19 | "environment": "client",
20 | "entrypoints": {
21 | "client": [
22 | "net.entityoutliner.EntityOutliner"
23 | ],
24 | "modmenu": [
25 | "net.entityoutliner.ui.ModMenuIntegration"
26 | ]
27 | },
28 | "mixins": [
29 | {
30 | "config": "entityoutliner.mixins.json",
31 | "environment": "client"
32 | }
33 | ],
34 |
35 | "depends": {
36 | "fabricloader": ">=0.7.4",
37 | "fabric": "*",
38 | "fabric-lifecycle-events-v1": "*",
39 | "fabric-key-binding-api-v1": "*",
40 | "minecraft": ">=1.19"
41 | },
42 | "suggests": {
43 | "flamingo": "*"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------