diff --git a/.gitignore b/.gitignore index 369b210..bdc08b2 100644 --- a/.gitignore +++ b/.gitignore @@ -154,4 +154,10 @@ gradle-app.setting .DS_Store Thumbs.db android/libs/ -andriodlib/ \ No newline at end of file +andriodlib/ + +## Unused but very interesting shit, such as analytics :3 +## Yes, i did implement analytics, but I'm not risking keeping them in a public version +## + leaking a discord webhook would be a problem +/.archive/ +## Why are you reading a random .gitignore file, anyways? \ No newline at end of file diff --git a/README.md b/README.md index 02c0219..aa2e17b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -# NOTICE: Currently shelved unill V7's release or ealier, when controls feels more mature -not that this mod shows in the mod browser, it doesnt for some reason # New-Controls A Fork of ***Mnemotechnician/New-Controls-Public*** to make an Ai do simple tasks for when your lazy and/or afk! This mod ***is multiplayer-compatible***, meaning you can use all its features (even the unfair ones) in the multiplayer. +~~TLDR: off brand Foo's client~~ # Enabling To enable the controls: press the 🔧 (wrench) button while in-game. You will see a panel with a status bar and a big red "DISABLED" label. Clicking on the label toggles the controls. diff --git a/assets/bundles/bundle.properties b/assets/bundles/bundle.properties index 6d3d963..fa44533 100644 --- a/assets/bundles/bundle.properties +++ b/assets/bundles/bundle.properties @@ -1,4 +1,4 @@ -newcontrols.ai.header = Ai behaviour Panel +newcontrols.ai.header = [purple]Ai behaviour Panel newcontrols.ai.status = Status: newcontrols.ai.enabled-ai = [green]Enabled[] newcontrols.ai.enabled-manual = [lime]MOBILE[] @@ -7,34 +7,43 @@ newcontrols.ai.toggle = Pause / Resume newcontrols.ai.manual = Joystick mode newcontrols.ai.action = Action: -newcontrols.ai.action-AUTO-TYPE = [lime]auto -newcontrols.ai.action-AUTO = [lime]auto[]\n({0}[]) +newcontrols.ai.action-AUTO-TYPE = [lime]auto +newcontrols.ai.action-AUTO = [green]auto\n[white]([lime]{0}[]) newcontrols.ai.action-NONE = [accent]manual control newcontrols.ai.action-IDLE = [scarlet]no suitable actions -newcontrols.ai.action-ATTACK = [green]attacking -newcontrols.ai.action-MINE = [green]mining -newcontrols.ai.action-PATROL = [green]patrolling -newcontrols.ai.action-BUILD = [green]Building -newcontrols.ai.action-REPAIR = [green]Repairing -newcontrols.ai.action-RETREAT=[green]Retreating + +newcontrols.ai.action-ATTACK = Attacking +newcontrols.ai.action-MINE = Mining +newcontrols.ai.action-PATROL = Patrolling +newcontrols.ai.action-BUILD = Building +newcontrols.ai.action-REBUILD= Rebuilding +newcontrols.ai.action-ASSIST= Assisting +newcontrols.ai.action-REPAIR = Repairing +newcontrols.ai.action-RETREAT= Retreating +newcontrols.ai.action-TURRET= Turret newcontrols.ai.settings = Settings newcontrols.ai.actions-select = Select action newcontrols.ai.actions-preferences = Preferences newcontrols.ai.actions-enable = Toggle auto actions newcontrols.ai.joystick = Joystick - -newcontrols.ai.indicator.retreat = - R -newcontrols.ai.indicator.respawn = - r +newcontrols.ai.joystick-autoAim = Automatic Aiming +newcontrols.ai.joystick-switch = switch move and aim sticks newcontrols.ai.prefs.attack-radius = Attack radius newcontrols.ai.prefs.mine-radius = Mine radius -newcontrols.ai.prefs.mine-items = Mine items +newcontrols.ai.prefs.useCorePos = Use core position +newcontrols.ai.prefs.mine-keep-minimum = Minimum amount +newcontrols.ai.prefs.mine-items = Mine config newcontrols.ai.prefs.hp-respawn = Respawn threshold newcontrols.ai.prefs.retreat_instead = Retreat instead +newcontrols.ai.prefs.attack_ignore_buildings = Attack ignores buildings + +newcontrols.ai.prefs.tooltip.useCorePos = Use the core in instead of the unit for AI's Actions +newcontrols.ai.prefs.tooltip.mine-keep-minimum = Amount to mine to before switch to other actions -newcontrols.unit.nolimit = [grey]Unlimited -newcontrols.unit.noautorespawn = [grey]Off +newcontrols.unit.nolimit = [grey]Unlimited[] +newcontrols.unit.off = [grey]Off[] newcontrols.unit.percent = [accent]%[] newcontrols.manual.command = Command diff --git a/assets/bundles/bundle_uk_UA.properties b/assets/bundles/bundle_uk_UA.properties new file mode 100644 index 0000000..af58d04 --- /dev/null +++ b/assets/bundles/bundle_uk_UA.properties @@ -0,0 +1,33 @@ +newcontrols.ai.header = Панель поведінки +newcontrols.ai.status = Статус: +newcontrols.ai.enabled-ai = [green]ENABLED + AI[] +newcontrols.ai.enabled-manual = [green]ENABLED[] +newcontrols.ai.disabled = [red]DISABLED[] +newcontrols.ai.toggle = Зупинити / Продовжити +newcontrols.ai.manual = Режим джойстика +newcontrols.ai.action = Режим: + +newcontrols.ai.action-AUTO-TYPE = [lime]автоматич. вибір +newcontrols.ai.action-AUTO = [lime]автоматич. []({0}[]) +newcontrols.ai.action-NONE = [accent]ручне керування +newcontrols.ai.action-IDLE = [scarlet]Немає підходящих дій +newcontrols.ai.action-ATTACK = [green]атака +newcontrols.ai.action-MINE = [green]видобуток +newcontrols.ai.action-PATROL = [green]патрулювання + +newcontrols.ai.settings = Налаштування +newcontrols.ai.actions-select = Виберіть дію +newcontrols.ai.actions-preferences = Уподобання +newcontrols.ai.actions-enable = Увімк./Вимкн. автоматичні дії + +newcontrols.ai.prefs.attack-radius = Радіус атаки +newcontrols.ai.prefs.mine-radius = Радіус видобутку +newcontrols.ai.prefs.mine-items = Видобувати ресурси + +newcontrols.unit.nolimit = [grey]Необмежений + +newcontrols.manual.command = Командувати +newcontrols.manual.pickup-unit = Підібрати одиницю +newcontrols.manual.pickup-block = Підібрати блок +newcontrols.manual.drop = Скинути вантаж +newcontrols.manual.pay-enter = Керувати блоком diff --git a/assets/bundles/bundle_zh_CN.properties b/assets/bundles/bundle_zh_CN.properties index 4c3b8b6..7f407a7 100644 --- a/assets/bundles/bundle_zh_CN.properties +++ b/assets/bundles/bundle_zh_CN.properties @@ -30,4 +30,4 @@ newcontrols.manual.command = 命令 newcontrols.manual.pickup-unit = 装载单位 newcontrols.manual.pickup-block = 装载方块 newcontrols.manual.drop = 放下荷载 -newcontrols.manual.pay-enter = 进入方块 \ No newline at end of file +newcontrols.manual.pay-enter = 进入方块 diff --git a/build.gradle b/build.gradle index debe980..5a91981 100644 --- a/build.gradle +++ b/build.gradle @@ -4,86 +4,81 @@ version '1.0' group = "newcontrols" -//compile java 16 code, targeting java 8 -targetCompatibility = 8 -sourceCompatibility = JavaVersion.VERSION_17 - sourceSets.main.java.srcDirs = ["src"] repositories{ - mavenCentral() - maven{ url 'https://www.jitpack.io' } + mavenCentral() + maven { + url 'https://www.jitpack.io' + } } ext{ - //the build number that this mod is made for - mindustryVersion = 'v135' //h.. - jabelVersion = "0.7.0" - sdkRoot = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT") + //the build number that this mod is made for + sdkRoot = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT") } -//java 8 backwards compatibility flag allprojects{ - tasks.withType(JavaCompile){ - options.compilerArgs.addAll(['--release', '8']) - } + tasks.withType(JavaCompile){ + options.compilerArgs.addAll(['--release', '8']) + } } dependencies{ - compileOnly "com.github.Anuken.Arc:arc-core:$mindustryVersion" - compileOnly "com.github.Anuken.Mindustry:core:$mindustryVersion" - annotationProcessor "com.github.Anuken:jabel:$jabelVersion" - - implementation files("lib/Autoupdate-lib.jar") + annotationProcessor 'com.github.Anuken:jabel:0.8.0' + compileOnly "com.github.Anuken.Mindustry:core:v140" + compileOnly "com.github.Anuken.Arc:arc-core:dfcb21ce56" + + implementation "com.github.mnemotechnician:autoupdate-lib:-SNAPSHOT" } task jarAndroid{ - dependsOn "jar" + dependsOn "jar" - doLast{ - if(!sdkRoot || !new File(sdkRoot).exists()) throw new GradleException("No valid Android SDK found. Ensure that ANDROID_HOME is set to your Android SDK directory."); + doLast{ + if(!sdkRoot || !new File(sdkRoot).exists()) throw new GradleException("No valid Android SDK found. Ensure that ANDROID_HOME is set to your Android SDK directory.") - def platformRoot = new File("$sdkRoot/platforms/").listFiles().sort().reverse().find{ f -> new File(f, "android.jar").exists()} + def platformRoot = new File("$sdkRoot/platforms/").listFiles().sort().reverse().find{ f -> new File(f, "android.jar").exists()} - if(!platformRoot) throw new GradleException("No android.jar found. Ensure that you have an Android platform installed.") + if(!platformRoot) throw new GradleException("No android.jar found. Ensure that you have an Android platform installed.") - //collect dependencies needed for desugaring - def dependencies = (configurations.compileClasspath.asList() + configurations.runtimeClasspath.asList() + [new File(platformRoot, "android.jar")]).collect{ "--classpath $it.path" }.join(" ") + //collect dependencies needed for desugaring + def dependencies = (configurations.compileClasspath.asList() + configurations.runtimeClasspath.asList() + [new File(platformRoot, "android.jar")]).collect{ "--classpath $it.path" }.join(" ") - //dex and desugar files - this requires d8 in your PATH - "d8 $dependencies --min-api 14 --output ${project.archivesBaseName}Android.jar ${project.archivesBaseName}Desktop.jar" - .execute(null, new File("$buildDir/libs")).waitForProcessOutput(System.out, System.err) - } + //dex and desugar files - this requires d8 in your PATH + "d8 $dependencies --min-api 14 --output ${project.archivesBaseName}Android.jar ${project.archivesBaseName}Desktop.jar" + .execute(null, new File("$buildDir/libs")).waitForProcessOutput(System.out, System.err) + } } jar{ - archiveFileName = "${project.archivesBaseName}Desktop.jar" + archiveFileName = "${project.archivesBaseName}Desktop.jar" - from{ - configurations.runtimeClasspath.collect{ it.isDirectory() ? it : zipTree(it) } - } + from{ + configurations.runtimeClasspath.collect{ it.isDirectory() ? it : zipTree(it) } + } - from(rootDir){ - include "mod.hjson" - //include "icon.png" //Will make new icon - } + from(rootDir){ + include "mod.hjson" + include "icon.png" + } - from("assets/"){ - include "**" - } + from("assets/"){ + include "**" + } } task deploy(type: Jar){ - dependsOn jarAndroid - dependsOn jar - archiveFileName = "Newcontrols In A Jar.jar" - - from{ [zipTree("$buildDir/libs/${project.archivesBaseName}Desktop.jar"), zipTree("$buildDir/libs/${project.archivesBaseName}Android.jar")] } - - doLast{ - delete{ - delete "$buildDir/libs/${project.archivesBaseName}Desktop.jar" - delete "$buildDir/libs/${project.archivesBaseName}Android.jar" - } - } + dependsOn jarAndroid + dependsOn jar + archiveFileName = "Newcontrols In A Jar.jar" + + from{ [zipTree("$buildDir/libs/${project.archivesBaseName}Desktop.jar"), zipTree("$buildDir/libs/${project.archivesBaseName}Android.jar")] } + + doLast{ + delete{ + delete "$buildDir/libs/${project.archivesBaseName}Desktop.jar" + delete "$buildDir/libs/${project.archivesBaseName}Android.jar" + } + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 94336fc..41d9927 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0185a6a..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon May 02 19:45:58 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index cccdd3d..1b6c787 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# 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. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## 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/master/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_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${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="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # 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 @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac 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 +# 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. + +# 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" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + 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 - i=$((i+1)) + # 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 - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,89 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem 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" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/Autoupdate-lib.jar b/lib/Autoupdate-lib.jar deleted file mode 100644 index fd73b70..0000000 Binary files a/lib/Autoupdate-lib.jar and /dev/null differ diff --git a/mod.hjson b/mod.hjson index f48fd16..2e75e03 100644 --- a/mod.hjson +++ b/mod.hjson @@ -1,14 +1,14 @@ -#!VERSION 1.0.0 +#!VERSION 1.1.0 #!BRANCH "master" #!REPO "JiroCab/Lazy-Alt-Controls" displayName: "Lazy Alt controls" name: "ai-controls" author: "JiroCab/Rush, Мнемотехник" -description: "(Wip) Fork of 'Mnemotechnician/New-Controls-Public' that focuses on the Ai part. Ai will attack, mine, rebuild & patrol \n\n tailored for Desktop for maximum laziness and/or afk \n with some legacy mobile joysticks!" -version: "1.0.0" -minGameVersion: "134" +description: "Fork of 'Mnemotechnician/New-Controls-Public' that focuses on the Ai part. Ai will attack, mine, rebuild & patrol \n\n tailored for Desktop for maximum laziness or when afk \n with some legacy mobile joysticks!" +version: "1.1.0" +minGameVersion: "136" hidden: true java: true -main: "newcontrols.NCMod" \ No newline at end of file +main: "newcontrols.NCMod" diff --git a/src/newcontrols/NCMod.java b/src/newcontrols/NCMod.java index f7ff3d3..82b85c8 100644 --- a/src/newcontrols/NCMod.java +++ b/src/newcontrols/NCMod.java @@ -1,23 +1,17 @@ package newcontrols; import arc.Events; -import io.mnemotechnician.autoupdater.Updater; +import com.github.mnemotechnician.autoupdater.Updater; import mindustry.game.EventType; import mindustry.mod.Mod; +import newcontrols.ui.NCStyles; public class NCMod extends Mod { - public NCMod() { - NCVars.init(); - //NCSpying.init(); - Events.on(EventType.ClientLoadEvent.class, a -> { Updater.checkUpdates(this); + NCStyles.init(); + NCVars.init(); }); } - - @Override - public void loadContent(){ - - } } diff --git a/src/newcontrols/NCVars.java b/src/newcontrols/NCVars.java index a0b5391..8f4463f 100644 --- a/src/newcontrols/NCVars.java +++ b/src/newcontrols/NCVars.java @@ -1,19 +1,14 @@ package newcontrols; -import arc.*; -import mindustry.*; -import mindustry.game.*; - -import newcontrols.ui.fragments.*; +import mindustry.Vars; +import newcontrols.input.AiEnabler; +import newcontrols.ui.fragments.AIPanel; public class NCVars { - public static AIPanel aipanel = new AIPanel(); - + public static AiEnabler enabler = new AiEnabler(); + public static void init() { - Events.on(EventType.ClientLoadEvent.class, you -> { - aipanel.build(Vars.ui.hudGroup); - }); + aipanel.build(Vars.ui.hudGroup); } - -} \ No newline at end of file +} diff --git a/src/newcontrols/input/AIInput.java b/src/newcontrols/input/AIInput.java index 65d4a10..93eb4a3 100644 --- a/src/newcontrols/input/AIInput.java +++ b/src/newcontrols/input/AIInput.java @@ -1,11 +1,12 @@ package newcontrols.input; + import arc.Core; -import arc.graphics.Color; +import arc.Graphics; import arc.graphics.g2d.Draw; -import arc.graphics.g2d.Lines; import arc.input.KeyCode; import arc.math.Angles; +import arc.math.Interp; import arc.math.Mathf; import arc.math.geom.Geometry; import arc.math.geom.Position; @@ -19,12 +20,13 @@ import mindustry.Vars; import mindustry.ai.Pathfinder; import mindustry.content.Blocks; -import mindustry.core.World; +import mindustry.content.UnitTypes; import mindustry.entities.Predict; import mindustry.entities.Units; import mindustry.entities.units.BuildPlan; import mindustry.game.Teams; import mindustry.gen.*; +import mindustry.graphics.Drawf; import mindustry.graphics.Pal; import mindustry.input.Binding; import mindustry.input.InputHandler; @@ -32,69 +34,45 @@ import mindustry.type.Item; import mindustry.type.ItemStack; import mindustry.type.UnitType; +import mindustry.type.Weapon; import mindustry.ui.Styles; import mindustry.world.Block; import mindustry.world.Build; import mindustry.world.Tile; import mindustry.world.blocks.ConstructBlock; import mindustry.world.meta.BlockFlag; -import newcontrols.ui.fragments.AIPanel; +import newcontrols.NCVars; import newcontrols.ui.fragments.ActionPanel; +import newcontrols.util.LocalBlockIndexer; import static arc.Core.*; import static mindustry.Vars.*; import static mindustry.input.PlaceMode.*; -/** +/** * Emulates basic player actions && handles thumbstick controls, configurable. - * - * TODO: - * [] REFACTOR THIS SHIT, remove unused imports - * [] split this shit into multiple classes - * [] do something you fucking lazy fox - * Alternative: - * [] get rid of ai stuff at all and focus on mobile ui? - * - * I really hate the way ive done this thing. I tried to refactor & split it, been doing that for several days, but... - * I had to do a hard reset. All the changes were lost, and so was my motivation. - * I couldn't and still can't find a non-dumb way of storing ai pattern-spicific values, such as mining range, mining items, etc. - * - * FOR THE SAKE OWN YOUR SANITY, PLEASE, DO NOT READ THIS CODE - * YOU WILL DIE OF CRINGE - * SERIOUSLY, DONT - * I WARNED YOU - * ... - * - * TODO -JiroCab - * fix hp threshold slider - * add button for shouldRetreat - * retreat ai - * configurable maxRange offset when attacking - * toggle attack buildings maybe - * try to removing hide data base & map etc **/ public class AIInput extends InputHandler { - public enum AIAction {NONE, AUTO, ATTACK, MINE, REPAIR, RETREAT, BUILD, PATROL, IDLE } + public enum AIAction {NONE, AUTO, ATTACK, MINE, REPAIR, RETREAT, BUILD, ASSIST, REBUILD, PATROL, TURRET, IDLE } public Interval updateInterval = new Interval(4); - public boolean paused = false, manualMode = false; - public float lastZoom = -1; //no idea + public boolean paused = false, manualMode = false, ignoreBuildings; + public float lastZoom = -1, scalar = -1;//no idea //Whether these actions are enabled. todo: actually make this comprehensible? - public boolean attack = true, mine = true, build = true, repair = true, retreat = true, patrol = true; + public boolean attack = true, mine = true, build = true, repair = true, retreat = true, patrol = true, assist = true, rebuild = true ; /** Current action selected by the user */ - public AIAction current = AIAction.AUTO; + public AIAction current = AIAction.AUTO, previous = AIAction.AUTO; /** If the current action is auto, this field is used to save the current auto action */ public AIAction auto = AIAction.ATTACK; - public Teamc target = null; - public Tile mineTile = null; + public Teamc target = null, lastTarget = null; + public Tile mineTile = null, patrolTile = null, tappedMineTile = null; public Item mineItem = null; - public boolean mining = false; - public Tile patrolTile = null; - public Tile retreatTile = null; + public boolean mining = false, useCorePos = false; + public float mineMinimumPercent = 1f; //resetting /** Current movement direction, used for manual control. -1 to 1. Reset every frame. */ @@ -102,15 +80,11 @@ public enum AIAction {NONE, AUTO, ATTACK, MINE, REPAIR, RETREAT, BUILD, PATROL, /** Current shoot direction, used for manual control. -1 to 1. Reset every frame. */ public Vec2 shootDir = new Vec2(); /** Whether the unit should shoot, used by manual control. Reset every frame */ - public boolean shoot = false; + public boolean shoot = false, freeCam = false, autoAim = false; //settings - public float attackRadius = 1200f; - public float mineRadius = 2f; - public float respawnThreshold = 5f * 100; - public boolean retreatInstead = false; - public boolean shouldRetreat = true; - public boolean fullyHealed = false; + public float attackRadius = 1200f, mineRadius = 2f ,respawnThreshold = 500f, crosshairScale; + public boolean retreatInstead = false, shouldRetreat = true, fullyHealed = false; /** Items that won't be mined */ public final Seq mineExclude = new Seq(); @@ -119,101 +93,157 @@ public enum AIAction {NONE, AUTO, ATTACK, MINE, REPAIR, RETREAT, BUILD, PATROL, public static Vec2 movement = new Vec2(); - //Building related stuff public @Nullable Teams.BlockPlan lastPlan; - /** Selected build request for movement. */ - public @Nullable BuildPlan sreq; - /** Position where the player started dragging a line. */ - public int selectX = -1, selectY = -1, schemX = -1, schemY = -1; - /** Maximum line length. */ - final static int maxLength = 100; - /** Previously selected tile. */ - public Tile prevSelected; + public boolean shouldShoot = false, panning = false, strictMovement = true; + public static LocalBlockIndexer localIndexer; + /** Whether selecting mode is active. */ public PlaceMode mode; - /** Whether the player is currently in line-place mode. */ - public boolean lineMode, schematicMode; + public boolean schematicMode; + /** Whether no recipe was available when switching to break mode. */ public @Nullable Block lastBlock; - /** Place requests to be removed. */ - public Seq removals = new Seq<>(); - /** Whether or not the player is currently shifting all placed tiles. */ - public boolean selecting; - - int tileX(float cursorX){ - Vec2 vec = Core.input.mouseWorld(cursorX, 0); - if(selectedBlock()){vec.sub(block.offset, block.offset);} - return World.toTile(vec.x); - } - int tileY(float cursorY){ - Vec2 vec = Core.input.mouseWorld(0, cursorY); - if(selectedBlock()){vec.sub(block.offset, block.offset);} - return World.toTile(vec.y); - } + public Graphics.Cursor cursorType = Graphics.Cursor.SystemCursor.arrow; + //Builder Ai + public @Nullable Unit assistFollowing, following; + /** Mouse pan speed. */ public float panScale = 0.005f, panSpeed = 4.5f, panBoostSpeed = 15f; - /** Whether player is currently deleting removal requests. */ - public boolean deleting = false, shouldShoot = false, panning = false; + public InputHandler inputHandler; @Override public boolean tap(float x, float y, int count, KeyCode button){ - //if(state.isMenu()) return false; + if(mobile) { + float worldx = input.mouseWorld(x, y).x, worldy = input.mouseWorld(x, y).y; + Tile cursor = world.tile(Math.round(worldx / 8), Math.round(worldy / 8)); - float worldx = Core.input.mouseWorld(x, y).x, worldy = Core.input.mouseWorld(x, y).y; - Tile cursor = Vars.world.tile(Math.round(worldx / 8), Math.round(worldy / 8)); + if (cursor == null || scene.hasMouse(x, y)) return false; - if (cursor == null || scene.hasMouse(x, y)) return false; + Call.tileTap(player, cursor); + Tile linked = cursor.build == null ? cursor : cursor.build.tile; - Call.tileTap(player, cursor); - Tile linked = cursor.build == null ? cursor : cursor.build.tile; + //control units + if (count == 2) { + Unit unit = player.unit(); - tileTappedH(linked.build); + //control a unit/block detected on first tap of double-tap + if (unitTapped != null) { + Call.unitControl(player, unitTapped); + recentRespawnTimer = 1f; + } else if (buildingTapped != null) { + Call.buildingControlSelect(player, buildingTapped); + recentRespawnTimer = 1f; + } else if (cursor.block() == Blocks.air && unit.within(cursor, unit.type.mineRange)) { + unit.mineTile = mineTile; + } + return false; + } + + if (player.dead()) { + cursorType = Graphics.Cursor.SystemCursor.arrow; + if (!scene.hasMouse()) { + graphics.cursor(cursorType); + } + } - unitTapped = selectedUnit(); - buildingTapped = selectedControlBuild(); + if (!scene.hasMouse()) { + graphics.cursor(cursorType); + } + + cursorType = Graphics.Cursor.SystemCursor.arrow; + tileTappedH(linked.build); - return false; + unitTapped = selectedUnit(); + buildingTapped = selectedControlBuild(); + + return false; + } else { + if(scene.hasMouse() || !commandMode) return false; + + tappedOne = true; + + //click: select a single unit + if(button == KeyCode.mouseLeft){ + if(count >= 2){ + selectTypedUnits(); + }else{ + tapCommandUnit(); + } + } + + return super.tap(x, y, count, button); + } + } + + @Override + public boolean touchDown(float x, float y, int pointer, KeyCode button){ + if(scene.hasMouse() || !commandMode) return false; + + if(button == KeyCode.mouseRight){ + commandTap(x, y); + } + + return super.touchDown(x, y, pointer, button); } - // @Anuke#4986 why the fuck does this method has default visibility - protected void tileTappedH(Building build) { - if (build == null) { - frag.inv.hide(); - frag.config.hideConfig(); - return; + /** @Anuke#4986 why the fuck does this method has default visibility */ + protected boolean tileTappedH(Building build) { + // !!! notice + // fully copy-pasted from the superclass + if(build == null){ + inv.hide(); + config.hideConfig(); + //commandBuild = null; + return false; } boolean consumed = false, showedInventory = false; - if (build.block.configurable && build.interactable(player.team())) { + + //select building for commanding + if(build.block.commandable && commandMode){ + //TODO handled in tap. consumed = true; - if ((!frag.config.isShown() && build.shouldShowConfigure(player)) //if the config fragment is hidden, show - || (frag.config.isShown() && frag.config.getSelectedTile().onConfigureTileTapped(build))) { + }else if(build.block.configurable && build.interactable(player.team())){ //check if tapped block is configurable + consumed = true; + if((!config.isShown() && build.shouldShowConfigure(player)) //if the config fragment is hidden, show + //alternatively, the current selected block can 'agree' to switch config tiles + || (config.isShown() && config.getSelected().onConfigureBuildTapped(build))){ Sounds.click.at(build); - frag.config.showConfig(build); + config.showConfig(build); } - } else if (!frag.config.hasConfigMouse()) { //make sure a configuration fragment isn't on the cursor - if (frag.config.isShown() && frag.config.getSelectedTile().onConfigureTileTapped(build)) { + //otherwise... + }else if(!config.hasConfigMouse()){ //make sure a configuration fragment isn't on the cursor + //then, if it's shown and the current block 'agrees' to hide, hide it. + if(config.isShown() && config.getSelected().onConfigureBuildTapped(build)){ consumed = true; - frag.config.hideConfig(); + config.hideConfig(); } - if (frag.config.isShown()) { + + if(config.isShown()){ consumed = true; } } - if (!consumed && build.interactable(player.team())) { + + //call tapped event + if(!consumed && build.interactable(player.team())){ build.tapped(); } - if (build.interactable(player.team()) && build.block.consumesTap) { + + //consume tap event if necessary + if(build.interactable(player.team()) && build.block.consumesTap){ consumed = true; - } else if (build.interactable(player.team()) && build.block.synthetic() && (!consumed || build.block.allowConfigInventory)) { - if (build.block.hasItems && build.items.total() > 0) { - frag.inv.showFor(build); + }else if(build.interactable(player.team()) && build.block.synthetic() && (!consumed || build.block.allowConfigInventory)){ + if(build.block.hasItems && build.items.total() > 0){ + inv.showFor(build); consumed = true; showedInventory = true; } } - if (!showedInventory) { - frag.inv.hide(); + + if(!showedInventory){ + inv.hide(); } + + return consumed; } @Override @@ -222,24 +252,22 @@ public void buildPlacementUI(Table table){ table.row(); table.left().margin(0f).defaults().size(48f).left(); if(!manualMode){ //desktop ui - table.button(Icon.paste, Styles.clearPartiali, () -> ui.schematics.show()).tooltip("@schematics"); - table.button(Icon.book, Styles.clearPartiali, () -> ui.database.show()).tooltip("@database"); - table.button(Icon.tree, Styles.clearPartiali, () -> ui.research.show()).visible(() -> state.isCampaign()).tooltip("@research"); - table.button(Icon.map, Styles.clearPartiali, () -> ui.planet.show()).visible(() -> state.isCampaign()).tooltip("@planetmap"); + table.button(Icon.paste, Styles.cleari, () -> ui.schematics.show()).tooltip("@schematics"); + table.button(Icon.book, Styles.cleari, () -> ui.database.show()).tooltip("@database"); + table.button(Icon.tree, Styles.cleari, () -> ui.research.show()).visible(() -> state.isCampaign()).tooltip("@research"); + table.button(Icon.map, Styles.cleari, () -> ui.planet.show()).visible(() -> state.isCampaign()).tooltip("@planetmap"); } else { //mobile ui - table.button(Icon.hammer, Styles.clearTogglePartiali, () -> { + table.button(Icon.hammer, Styles.clearTogglei, () -> { mode = mode == breaking ? block == null ? none : placing : breaking; lastBlock = block; }).update(l -> l.setChecked(mode == breaking)).name("breakmode"); //diagonal swap button - table.button(Icon.diagonal, Styles.clearTogglePartiali, () -> { - Core.settings.put("swapdiagonal", !Core.settings.getBool("swapdiagonal")); - }).update(l -> l.setChecked(Core.settings.getBool("swapdiagonal"))); + table.button(Icon.diagonal, Styles.clearTogglei, () -> Core.settings.put("swapdiagonal", !Core.settings.getBool("swapdiagonal"))).update(l -> l.setChecked(Core.settings.getBool("swapdiagonal"))); //rotate button - table.button(Icon.right, Styles.clearTogglePartiali, () -> { + table.button(Icon.right, Styles.clearTogglei, () -> { if(block != null && block.rotate){ rotation = Mathf.mod(rotation + 1, 4); }else{ @@ -256,209 +284,116 @@ public void buildPlacementUI(Table table){ i.getStyle().imageUp = arrow ? Icon.right : Icon.copy; i.setChecked(!arrow && schematicMode); }); - - //confirm button - table.button(Icon.ok, Styles.clearPartiali, () -> { - for(BuildPlan request : selectRequests){ - Tile tile = request.tile(); - - //actually place/break all selected blocks - if(tile != null){ - if(!request.breaking){ - if(validPlace(request.x, request.y, request.block, request.rotation)){ - BuildPlan other = getRequest(request.x, request.y, request.block.size, null); - BuildPlan copy = request.copy(); - - if(other == null){ - player.unit().addBuild(copy); - }else if(!other.breaking && other.x == request.x && other.y == request.y && other.block.size == request.block.size){ - player.unit().plans().remove(other); - player.unit().addBuild(copy); - } - } - - rotation = request.rotation; - }else{ - tryBreakBlock(tile.x, tile.y); - } - } - } - - //move all current requests to removal array so they fade out - removals.addAll(selectRequests.select(r -> !r.breaking)); - selectRequests.clear(); - selecting = false; - }).visible(() -> !selectRequests.isEmpty()).name("confirmplace"); - } + } - - /* Removes the old one for the one from DesktopInput.java - side effect of mobile having the desktop ui when In Ai or Joystick Mode but oh well - - table.image().color(Pal.gray).height(4f).colspan(2).growX(); - table.row(); - table.left().margin(0f).defaults().size(48f). left(); - table.button(b -> b.image(() -> paused ? Icon.pause.getRegion() : Icon.play.getRegion()), Styles.clearPartiali, () -> { - paused = !paused; - }).tooltip("@newcontrols.ai.toggle"); - - table.button(Icon.move, Styles.clearTogglePartiali, () -> { - manualMode = !manualMode; - }).update(l -> l.setChecked(manualMode)).tooltip("@ai.manual-mode");*/ - } - - boolean showHint(){ - return ui.hudfrag.shown && Core.settings.getBool("hints") && selectRequests.isEmpty() && + boolean showHint(){ + return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() && (!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore()); } - @Override - - public void buildUI(Group origin) { - super.buildUI(origin); - if (manualMode){ - origin.fill(t -> { - t.center().bottom(); - ActionPanel.buildLandscape(t, this); - });} - //Add so we can have basic building (?) - else{ - origin.fill(t -> { - t.color.a = 0f; - t.visible(() -> (t.color.a = Mathf.lerpDelta(t.color.a, Mathf.num(showHint()), 0.15f)) > 0.001f); - t.bottom(); - t.table(Styles.black6, b -> { - StringBuilder str = new StringBuilder(); - b.defaults().left(); - b.label(() -> { - if(!showHint()) return str; - str.setLength(0); - if(!isBuilding && !Core.settings.getBool("buildautopause") && !player.unit().isBuilding()){ - str.append(Core.bundle.format("enablebuilding", Core.keybinds.get(Binding.pause_building).key.toString())); - }else if(player.unit().isBuilding()){ - str.append(Core.bundle.format(isBuilding ? "pausebuilding" : "resumebuilding", Core.keybinds.get(Binding.pause_building).key.toString())) - .append("\n").append(Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.toString())) - .append("\n").append(Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.toString())); - } - if(!player.dead() && !player.unit().spawnedByCore()){ - str.append(str.length() != 0 ? "\n" : "").append(Core.bundle.format("respawn", Core.keybinds.get(Binding.respawn).key.toString())); - } - return str; - }).style(Styles.outlineLabel); - }).margin(10f); - }); - origin.fill(t -> { - t.visible(() -> ui.hudfrag.shown && lastSchematic != null && !selectRequests.isEmpty()); - t.bottom(); - t.table(Styles.black6, b -> { - b.defaults().left(); - b.label(() -> Core.bundle.format("schematic.flip", - Core.keybinds.get(Binding.schematic_flip_x).key.toString(), - Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel).visible(() -> Core.settings.getBool("hints")); - b.row(); - b.table(a -> { - a.button("@schematic.add", Icon.save, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null); - }); - }).margin(6f); - }); - } + + @Override + public void buildUI(Group origin) { + super.buildUI(origin); + origin.fill(t -> { + t.center().bottom(); + ActionPanel.buildLandscape(t, this); + }); + + /*building and respawn hints*/ + origin.fill(t -> { + if(!manualMode) { + t.color.a = 0f; + t.visible(() -> (t.color.a = Mathf.lerpDelta(t.color.a, Mathf.num(showHint()), 0.15f)) > 0.001f); + t.bottom(); + t.table(Styles.black6, b -> { + StringBuilder str = new StringBuilder(); + b.defaults().left(); + b.label(() -> { + if (!showHint()) return str; + str.setLength(0); + if (!isBuilding && !Core.settings.getBool("buildautopause") && !player.unit().isBuilding()) { + str.append(Core.bundle.format("enablebuilding", Core.keybinds.get(Binding.pause_building).key.toString())); + } else if (player.unit().isBuilding()) { + str.append(Core.bundle.format(isBuilding ? "pausebuilding" : "resumebuilding", Core.keybinds.get(Binding.pause_building).key.toString())) + .append("\n").append(Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.toString())) + .append("\n").append(Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.toString())); + } + if (!player.dead() && !player.unit().spawnedByCore()) { + str.append(str.length() != 0 ? "\n" : "").append(Core.bundle.format("respawn", Core.keybinds.get(Binding.respawn).key.toString())); + } + return str; + }).style(Styles.outlineLabel); + }).margin(10f); + } + }); } + @Override - public void drawTop(){ - Lines.stroke(1f); - int cursorX = tileX(Core.input.mouseX()); - int cursorY = tileY(Core.input.mouseY()); + public void drawOverSelect(){ + //draw list of plans + for(BuildPlan plan : selectPlans){ + Tile tile = plan.tile(); - //draw break selection - if(mode == breaking){ - drawBreakSelection(selectX, selectY, cursorX, cursorY, !Core.input.keyDown(Binding.schematic_select) ? maxLength : Vars.maxSchematicSize); - } + if(tile == null) continue; - if(Core.input.keyDown(Binding.schematic_select) && !Core.scene.hasKeyboard() && mode != breaking){ - drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize); - } + if((!plan.breaking && validPlace(tile.x, tile.y, plan.block, plan.rotation)) + || (plan.breaking && validBreak(tile.x, tile.y))){ + plan.animScale = Mathf.lerpDelta(plan.animScale, 1f, 0.2f); + }else{ + plan.animScale = Mathf.lerpDelta(plan.animScale, 0.6f, 0.1f); + } - Draw.reset(); - } - @Override - public void drawBottom(){ - int cursorX = tileX(Core.input.mouseX()); - int cursorY = tileY(Core.input.mouseY()); + Tmp.c1.set(Draw.getMixColor()); - //draw request being moved - if(sreq != null){ - boolean valid = validPlace(sreq.x, sreq.y, sreq.block, sreq.rotation, sreq); - if(sreq.block.rotate){ - drawArrow(sreq.block, sreq.x, sreq.y, sreq.rotation, valid); + if(!plan.breaking && plan.block != null){ + Draw.mixcol(); + if(plan.block.rotate) drawArrow(plan.block, tile.x, tile.y, plan.rotation); } - sreq.block.drawPlan(sreq, allRequests(), valid); + Draw.reset(); + drawPlan(plan); + if(!plan.breaking){ + drawOverPlan(plan); + } - drawSelected(sreq.x, sreq.y, sreq.block, getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null ? Pal.remove : Pal.accent); - } + //draw last placed plan + if(!plan.breaking && plan.block != null && plan.block.drawArrow){ + boolean valid = validPlace(tile.x, tile.y, plan.block, rotation); + Draw.mixcol(); + plan.block.drawPlace(tile.x, tile.y, rotation, valid); + + drawOverlapCheck(plan.block, tile.x, tile.y, valid); - //draw hover request - if(!isPlacing()){ - BuildPlan req = getRequest(cursorX, cursorY); - if(req != null){ - drawSelected(req.x, req.y, req.breaking ? req.tile().block() : req.block, Pal.accent); } } - if(player.isBuilder()){ - //draw things that may be placed soon - if(block != null){ - for(int i = 0; i < lineRequests.size; i++){ - BuildPlan req = lineRequests.get(i); - if(i == lineRequests.size - 1 && req.block.rotate){ - drawArrow(block, req.x, req.y, req.rotation); - } - drawRequest(lineRequests.get(i)); - } - lineRequests.each(this::drawOverRequest); - }else if(isPlacing()){ - if(block.rotate && block.drawArrow){ - drawArrow(block, cursorX, cursorY, rotation); - } - Draw.color(); - boolean valid = validPlace(cursorX, cursorY, block, rotation); - drawRequest(cursorX, cursorY, block, rotation); - block.drawPlace(cursorX, cursorY, rotation, valid); - - if(block.saveConfig){ - Draw.mixcol(!valid ? Pal.breakInvalid : Color.white, (!valid ? 0.4f : 0.24f) + Mathf.absin(Time.globalTime, 6f, 0.28f)); - brequest.set(cursorX, cursorY, rotation, block); - brequest.config = block.lastConfig; - block.drawRequestConfig(brequest, allRequests()); - brequest.config = null; - Draw.reset(); - } + //draw targeting crosshair + if(target != null && !state.isEditor() && ( !manualMode || autoAim)){ + if(target != lastTarget){ + crosshairScale = 0f; + lastTarget = target; } + + crosshairScale = Mathf.lerpDelta(crosshairScale, 1f, 0.2f); + + Drawf.target(target.getX(), target.getY(), 7f * Interp.swingIn.apply(crosshairScale), 0.5f, Pal.remove); } Draw.reset(); } - @Override - public boolean isBreaking(){ - return mode == breaking; - } - //REGION CONTROLS @Override public void update() { super.update(); - if (!(player.dead() || state.isPaused())) { - Core.camera.position.lerpDelta(player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f); - } - - if (!ui.chatfrag.shown() && Math.abs(Core.input.axis(Binding.zoom)) > 0) { - renderer.scaleCamera(Core.input.axis(Binding.zoom)); - } + boolean locked = locked(); + boolean panCam = false; + float camSpeed = (!input.keyDown(Binding.boost) ? panSpeed : panBoostSpeed) * Time.delta; - if (!paused) { + if (!paused || !inputHandler.locked() ) { if (manualMode) { manualMovement(player.unit()); auto = AIAction.NONE; @@ -466,32 +401,88 @@ public void update() { aiActions(player.unit()); } } - AIPanel enabledAI = new AIPanel(); //Heal & retreat handles fullyHealed = player.unit().health == player.unit().maxHealth; shouldRetreat = retreatInstead && player.unit().health <= respawnThreshold; + shouldShoot = !scene.hasMouse() && !locked; + + if(Core.input.keyRelease(Binding.select) || player.shooting && !canShoot()){ + player.shooting = false; + } + + //Possessing + if(!scene.hasMouse() && state.rules.possessionAllowed){ + if(Core.input.keyDown(Binding.control) && Core.input.keyTap(Binding.select)){ + Unit on = selectedUnit(); + var build = selectedControlBuild(); + if(on != null){ + Call.unitControl(player, on); + shouldShoot = false; + recentRespawnTimer = 1f; + }else if(build != null){ + Call.buildingControlSelect(player, build); + recentRespawnTimer = 1f; + } + } + } + + if (!freeCam && !scene.hasField() && !scene.hasDialog() && !(player.dead() || state.isPaused())) { + Core.camera.position.lerpDelta(player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f); + } + + if (!ui.chatfrag.shown() && Math.abs(input.axis(Binding.zoom)) > 0) { + renderer.scaleCamera(input.axis(Binding.zoom)); + } + + if(!locked && block == null && !scene.hasField() && + //disable command mode when player unit can boost and command mode binding is the same + !(!player.dead() && player.unit().type.canBoost && keybinds.get(Binding.command_mode).key == keybinds.get(Binding.boost).key)){ + if(settings.getBool("commandmodehold")){ + commandMode = input.keyDown(Binding.command_mode); + }else if(input.keyTap(Binding.command_mode)){ + commandMode = !commandMode; + } + }else{ + commandMode = false; + } + //Shortcut keys if(state.isGame() && !scene.hasDialog() && !(scene.getKeyboardFocus() instanceof TextField)){ - if(Core.input.keyTap(Binding.minimap)) ui.minimapfrag.toggle(); - if(Core.input.keyTap(Binding.planet_map) && state.isCampaign()) ui.planet.toggle(); - if(Core.input.keyTap(Binding.research) && state.isCampaign()) ui.research.toggle(); - //if(Core.input.keyTap(KeyCode.h)) enabledAI.enabled = !enabledAI.enabled; + if(input.keyTap(Binding.minimap)) ui.minimapfrag.toggle(); + if(input.keyTap(Binding.planet_map) && state.isCampaign()) ui.planet.toggle(); + if(input.keyTap(Binding.research) && state.isCampaign()) ui.research.toggle(); + if(Core.input.keyTap(Binding.research) && Core.input.keyDown(Binding.boost)) NCVars.enabler.ToggleAi(); + if(Core.input.keyTap(Binding.planet_map) && Core.input.keyDown(Binding.boost)) manualMode = !manualMode; } - //Camera panning - boolean locked = locked(); - boolean panCam = false; - float camSpeed = (!Core.input.keyDown(Binding.boost) ? panSpeed : panBoostSpeed) * Time.delta; + if(Core.input.keyTap(Binding.pause_building)){ + isBuilding = !isBuilding; + buildWasAutoPaused = false; + + if(isBuilding){ + player.shooting = false; + } + } + + if (input.keyTap(Binding.pan)){ + freeCam = true; + }else if(input.keyTap(Binding.mouse_move)){ + freeCam = false; + } + + if(input.keyDown(Binding.mouse_move) && (!scene.hasField())){ + panning = false; + } if(input.keyDown(Binding.pan) && !scene.hasField() && !scene.hasDialog()){ panCam = true; panning = true; } - if((Math.abs(Core.input.axis(Binding.move_x)) > 0 || Math.abs(Core.input.axis(Binding.move_y)) > 0 || input.keyDown(Binding.mouse_move)) && (!scene.hasField())){ - panning = false; + if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content) || scene.getKeyboardFocus().isDescendantOf(ui.minimapfrag.elem))){ + ui.listfrag.toggle(); } if(!locked){ @@ -500,14 +491,14 @@ public void update() { panCam = true; } - Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(camSpeed)); + Core.camera.position.add(Tmp.v1.setZero().add(input.axis(Binding.move_x), input.axis(Binding.move_y)).nor().scl(camSpeed)); }else if(!player.dead() && !panning){ Core.camera.position.lerpDelta(player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f); } if(panCam){ - Core.camera.position.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * panScale, -1, 1) * camSpeed; - Core.camera.position.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * panScale, -1, 1) * camSpeed; + Core.camera.position.x += Mathf.clamp((input.mouseX() - Core.graphics.getWidth() / 2f) * panScale, -1, 1) * camSpeed; + Core.camera.position.y += Mathf.clamp((input.mouseY() - Core.graphics.getHeight() / 2f) * panScale, -1, 1) * camSpeed; } } @@ -516,53 +507,63 @@ public void update() { if(!player.dead() && !state.isPaused() && !scene.hasField() && !locked){ //updateMovement(player.unit()); - if(Core.input.keyTap(Binding.respawn)){ + if(input.keyTap(Binding.respawn)){ controlledType = null; recentRespawnTimer = 1f; Call.unitClear(player); } } - //Possessing - shouldShoot = !scene.hasMouse() && !locked; - if(!scene.hasMouse() && !locked){ - if(Core.input.keyDown(Binding.control) && Core.input.keyTap(Binding.select)){ - Unit on = selectedUnit(); - var build = selectedControlBuild(); - if(on != null){ - Call.unitControl(player, on); - shouldShoot = false; - recentRespawnTimer = 1f; - }else if(build != null){ - Call.buildingControlSelect(player, build); - recentRespawnTimer = 1f; - } - } - } - if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){ + + if(input.keyTap(Binding.select) && !scene.hasMouse()){ Tile selected = world.tileWorld(input.mouseWorldX(), input.mouseWorldY()); if(selected != null){ Call.tileTap(player, selected); } } + if (input.keyTap(Binding.boost) && input.keyTap(Binding.select)){ + tappedMineTile = world.tileWorld(input.mouseWorldX(), input.mouseWorldY()); + } + + //update payload input, global and manual + if(player.unit() instanceof Payloadc){ + if(Core.input.keyTap(Binding.pickupCargo)){ + tryPickupPayload(); + } + + if(Core.input.keyTap(Binding.dropCargo)){ + tryDropPayload(); + } + } + + //Strict movement, Make the AI be at max range for building, rebuilding ,assisting, patrolling & repairing + if(!strictMovement){ + scalar = 0; + } } protected void manualMovement(Unit unit) { if (!moveDir.isZero()) { unit.movePref(moveDir.scl(unit.speed())); } - if (shootDir.isZero()) { - if (!moveDir.isZero()) { - aimLook(moveDir.add(unit)); + if (autoAim){ + passiveAttack(unit, unit.type.omniMovement, true); + }else { + if (shootDir.isZero()) { + if (!moveDir.isZero()) { + aimLook(moveDir.add(unit)); + } + } else { + aimLook(shootDir.scl(1600f).add(unit)); } - } else { - aimLook(shootDir.scl(1600f).add(unit)); + unit.controlWeapons(false, player.shooting = shoot); + shoot = false; } - unit.controlWeapons(false, player.shooting = shoot); - //reset to prevent stucking and smth + + + //reset to prevent stucking moveDir.set(0, 0); shootDir.set(0, 0); - shoot = false; unit.mineTile = null; } @@ -574,15 +575,15 @@ protected void aiActions(Unit unit) { unit.mineTile = null; boolean canAttack = false; - for (var w : type.weapons) { - if (w.bullet.damage > 0 && w.bullet.collides) { + for (Weapon w : type.weapons) { + if (w.bullet != null && w.bullet.collides && w.bullet.damage > 1f && !w.noAttack) { canAttack = true; break; } } boolean canHealBuilding = false; for (var h : type.weapons) { - if (h.bullet.healPercent > 0 && h.bullet.collidesTeam) { + if (h.bullet.healPercent > 0 ) { canHealBuilding = true; break; } @@ -594,23 +595,49 @@ protected void aiActions(Unit unit) { !mineExclude.contains(i) && indexer.hasOre(i) && unit.canMine(i) && core.acceptStack(i, 1, unit) > 0, i -> core.items.get(i) ); } + boolean shouldAssistBuilding = net.active() && assistFollowing != null && assistFollowing.activelyBuilding() && unit.canBuild(); + boolean shouldRebuildBuildings = !unit.team.data().plans.isEmpty() && unit.canBuild(); + + //Mining is now split to High-Low, To avoid cases where Ai only mines, instead Ai mines to a minimum then switches to other task before mining to full + boolean shouldMineLow = core != null && unit.canMine() && mineItem != null && (indexer.findClosestOre(unit, mineItem) != null || localIndexer.findClosestWallOre(unit, mineItem) != null); + boolean shouldMineHigh = shouldMineLow && player.team().items().get(mineItem) > Math.round( mineMinimumPercent * 0.1 * player.team().core().storageCapacity); + + Teamc checkTarget = null; + if (updateInterval.get(1, 10) || Units.invalidateTarget(target, unit.team, unit.x, unit.y)) { + float x = useCorePos ? unit.closestCore().getX() : unit.x; + float y = useCorePos ? unit.closestCore().getY() : unit.y; + + checkTarget = Units.closestTarget(unit.team, x, y, attackRadius > 0 ? attackRadius : Float.MAX_VALUE, u -> true, b -> ignoreBuildings); + } if (current == AIAction.AUTO && updateInterval.get(20)) { - if (retreat && shouldRetreat && !fullyHealed) { + if (retreat && (shouldRetreat && !fullyHealed )) { auto = AIAction.RETREAT; - } else if (attack && canAttack && (target = Units.closestTarget(unit.team, unit.x, unit.y, attackRadius > 0 ? attackRadius : Float.MAX_VALUE, t -> true)) != null) { - auto = AIAction.ATTACK; + } else if (attack && canAttack && checkTarget != null) { + auto = AIAction.ATTACK; + } else if (mine && shouldMineHigh) { + auto = AIAction.MINE; } else if (repair && canHealBuilding && (target = Units.findDamagedTile(unit.team, unit.x, unit.y)) != null) { auto = AIAction.REPAIR; - } else if (build && unit.canBuild() && lastPlan != null ) { + } else if (build && unit.canBuild() && unit.buildPlan() != null && isBuilding) { auto = AIAction.BUILD; - } else if (mine && unit.canMine() && mineItem != null && indexer.findClosestOre(unit, mineItem) != null) { + } else if (build && shouldAssistBuilding){ + auto = AIAction.ASSIST; + }else if (assist && shouldRebuildBuildings){ + auto = AIAction.REBUILD; + } else if (mine && shouldMineLow) { auto = AIAction.MINE; } else if (patrol && canAttack && ((state.rules.waves && spawner.countSpawns() > 0) || (indexer.getEnemy(unit.team(), BlockFlag.core) != null))) { auto = AIAction.PATROL; + } else if (!unit.moving() || unit.type == UnitTypes.block){ + auto = AIAction.TURRET; } else { auto = AIAction.IDLE; } + if (AiEnabler.extraLogs && current != previous){ + Log.info("Auto Ai has switched to -" + current); + previous = current; + } } AIAction action = current != AIAction.AUTO ? current : auto; @@ -620,73 +647,77 @@ protected void aiActions(Unit unit) { case ATTACK -> attackAI(unit); case MINE -> mineAI(unit); case BUILD -> buildAi(unit); + case REBUILD -> rebuildAi(unit); + case ASSIST -> assistAi(unit); case REPAIR -> repairAi(unit); case PATROL -> patrolAI(unit); + case TURRET -> turretAi(unit); } unit.controlWeapons(false, player.shooting); //Building - if(Core.input.keyTap(Binding.clear_building)){ + if(input.keyTap(Binding.clear_building)){ player.unit().clearBuilding(); } } protected void retreatAI(Unit unit) { //retreat to heal point else to a core - retreatTile = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team(), BlockFlag.repair)); - float offset; - if (retreatTile == null) {retreatTile = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team(), BlockFlag.core));} - offset = 60f - mineRadius; //why not + Building retreatTile = Geometry.findClosest(unit.x, unit.y, indexer.getFlagged(unit.team(), BlockFlag.repair)); + if(retreatTile == null){ retreatTile = Geometry.findClosest(unit.x, unit.y, indexer.getFlagged(unit.team, BlockFlag.core));} - if (retreatTile != null) { - if (unit.type.flying) { - float dst = unit.dst(retreatTile); - movement.set(retreatTile).sub(unit).limit(unit.speed()); - if (dst < offset - 16f) { - movement.scl(-1); - unit.movePref(movement); - } else if (dst > offset) {unit.movePref(movement);} + commonMovement(unit, retreatTile, 60f); - aimLook(retreatTile); - } else { - pathfind(unit); - } + //Attack Units While in transit to the tile + if (updateInterval.get(1, 10) || Units.invalidateTarget(target, unit.team, unit.x, unit.y)) { + target = Units.closestTarget(unit.team, unit.x, unit.y, unit.range() * 5, t -> true); } + + if(target != null && unit.dst(retreatTile) > 60f){passiveAttack(unit); + } else if(retreatTile != null) aimLook(retreatTile); + } protected void attackAI(Unit unit) { + Building core = unit.closestCore(); + float x = useCorePos && core != null ? core.getX() : unit.x; + float y = useCorePos && core != null ? core.getY() : unit.y; + if (updateInterval.get(1, 10) || Units.invalidateTarget(target, unit.team, unit.x, unit.y)) { - target = Units.closestTarget(unit.team, unit.x, unit.y, unit.range() * 5, t -> true); + if(!ignoreBuildings){ + target = Units.closestTarget(unit.team, x, y, unit.range() * 5, t -> true); + } else { + target = Units.closestEnemy(unit.team, x, y, unit.range() * 5, t -> true); + } } UnitType type = unit.type; - - if (target != null && type != null) { - - if (respawnThreshold >= 1 && player.unit().health <= respawnThreshold &&!retreatInstead) - //Respawn when threshold is met to save time by not getting lock on the unit's death, still allow - {unitClear(player);} - else if (respawnThreshold >= 1 && player.unit().health <= respawnThreshold && !fullyHealed) { - retreatAI(unit);} //Allow for retreating when exclusively attacking - else { - + if (target != null && type != null){ + + if (respawnThreshold > 0 && player.unit().health <= (respawnThreshold * 5) % player.unit().maxHealth()) { + if ( auto != AIAction.AUTO && (retreatInstead || !fullyHealed)) { + //Allow for retreating when exclusively attacking + retreatAI(unit); + } else{ + //Respawn when threshold is met to save time by not getting lock on the unit's death, for max uptime + controlledType = null; + recentRespawnTimer = 1f; + Call.unitClear(player); + } + } else { float bulletSpeed = unit.hasWeapons() ? type.weapons.first().bullet.speed : 0; - float approachRadius = 0.95f; - - float approachRadiusClose = 0.50f; - boolean useCloseApproach = false; + float approachRadius = 0.50f; + //Handles if units have close range weapons or bombing weapons for(var r : type.weapons) { - if (r.minShootVelocity <= 0) { - useCloseApproach = true; + if (r.minShootVelocity >= 0 || r.bullet.hittable) { + approachRadius = 0.95f; break; } } + float dist = unit.range() * approachRadius; - if (useCloseApproach) { - dist = unit.range() * approachRadiusClose; - } float angle = target.angleTo(unit); Tmp.v1.set(Angles.trnsx(angle, dist), Angles.trnsy(angle, dist)); movement.set(target).add(Tmp.v1).sub(unit).limit(unit.speed()); @@ -694,17 +725,20 @@ else if (respawnThreshold >= 1 && player.unit().health <= respawnThreshold && !f Vec2 intercept = Predict.intercept(unit, target, bulletSpeed); player.shooting = unit.within(intercept, unit.range() * 1.25f); - aimLook(intercept);} + if(intercept != null) aimLook(intercept); } - + } } - //Yes this just BuilderAi with extra/fewer steps + //Yes, This is just BuilderAi but split into three protected void buildAi(Unit unit){ + if(unit.team.data().plans.isEmpty() || !unit.canBuild()) return; + passiveAttack(unit, unit.type.omniMovement); + + BuildPlan req = unit.buildPlan(); if(unit.buildPlan() != null){ - //approach request if building - BuildPlan req = unit.buildPlan(); + //approach plan if building boolean valid = !(lastPlan != null && lastPlan.removed) && ((req.tile() != null && req.tile().build instanceof ConstructBlock.ConstructBuild cons && cons.current == req.block) || @@ -713,107 +747,179 @@ protected void buildAi(Unit unit){ Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation))); if(valid){ - //move toward the request - aimLook(req); - movement.set(0, 0).trns(req.angleTo(unit), buildingRange - 5).add(req).sub(unit).limit(unit.speed()); - unit.movePref(movement); + unit.updateBuilding = true; + commonMovement(unit, req.tile(), unit.type.buildRange); }else{ - //discard invalid request + //Don't linger on one thing but don't clear it + unit.plans.addLast(unit.plans.first()); unit.plans.removeFirst(); lastPlan = null; } } - //float rebuildTime = (unit.team.rules().ai ? Mathf.lerp(15f, 2f, unit.team.rules().aiTier) : 2f) * 60f; + } + + protected void rebuildAi(Unit unit){ - //find new request - if(!unit.team.data().blocks.isEmpty() //&& timer.get(timerTarget3, rebuildTime) - ){ - Queue blocks = unit.team.data().blocks; - Teams.BlockPlan block = blocks.first(); + if(unit.team.data().plans.isEmpty() || !unit.canBuild()) return; + Queue blocks = unit.team.data().plans; + Teams.BlockPlan block = blocks.first(); + if(unit.buildPlan() == null){ //check if it's already been placed if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block().id == block.block){ blocks.removeFirst(); }else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid lastPlan = block; - //add build request + //add build plan unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config)); //shift build plan to tail so next unit builds something else blocks.addLast(blocks.removeFirst()); + }else if (!state.rules.revealedBlocks.contains(content.block(block.block))) { + /*If the block can't be rebuilt, remove from plan*/ + blocks.removeFirst(); }else{ //shift head of queue to tail, try something else next time blocks.addLast(blocks.removeFirst()); } + + } else if (current != AIAction.AUTO && build){ + //When in auto, let auto handle switching but when exclusively this, call buildAi + buildAi(unit); } } + protected void assistAi(Unit unit){ + + if(!net.active() || !unit.canBuild()) return; + + if(unit.buildPlan() == null){ + + //Builder Ai helper + if(assistFollowing != null && assistFollowing.activelyBuilding()){ + following = assistFollowing; + } + + if(following != null && (!following.isValid() || !following.activelyBuilding())){ + following = null; + } + if (following != null && following.activelyBuilding()){ + unit.plans.addFirst(following.buildPlan()); + } + + } else if (current != AIAction.AUTO && build){ + //When in auto, let auto handle switching but when exclusively this, call buildAi + buildAi(unit); + } + + } + protected void repairAi(Unit unit) { Building target = Units.findDamagedTile(unit.team, unit.x, unit.y); if(target instanceof ConstructBlock.ConstructBuild) target = null; if(target != null){ - movement.set(target).add(Tmp.v1).sub(unit).limit(unit.speed()); - player.shooting = unit.within(target, unit.range() ); - aimLook(target); - unit.movePref(movement);} + if (unit.within(target, unit.range())){ + player.shooting = true; + aimLook(target); + } else {passiveAttack(unit);} + + float dist = unit.range() * 0.9f; + for(var r : unit.type.weapons){ + //Bombing Weapons (quad) + if(r.minShootVelocity >= 1){ + dist = 0; + break; + } + //Less strict for inaccurate weapons for better heal (Poly) and close range (Pulsar, Oxynoe) + if(r.inaccuracy >= 5 || !r.bullet.hittable){ + dist = unit.range() * 0.6f; + break; + } + + } + + commonMovement(unit, target, dist); + } } //Yes, yes and yes. I literally copied the MinerAI. protected void mineAI(Unit unit) { Building core = unit.closestCore(); - if(core == null) return; - if(unit.canMine()) { //to avoid forcing units who can't mine to mine at all - if (mining) { - //Core doesn't need this item - if (mineItem != null && core.acceptStack(mineItem, 1, unit) == 0) { - if (unit.stack.amount > 0) dropItem(player, unit.rotation); - mineItem = null; - return; - } - //Mine - if (unit.stack.amount >= unit.type.itemCapacity || (mineItem != null && !unit.acceptsItem(mineItem))) { - mining = false; - } else { - if (updateInterval.get(3, 30) && mineItem != null) { - mineTile = indexer.findClosestOre(unit, mineItem); - } + //to avoid forcing units who can't mine to mine at all + if(!unit.canMine() || core == null) return; - if (mineTile != null) { - movement.set(0, 0).trns(mineTile.angleTo(unit), mineRadius).add(mineTile).sub(unit).limit(unit.speed()); - unit.movePref(movement); - aimLook(Tmp.v1.set(mineTile).scl(8)); + if (mining) { + //Core doesn't need this item + if (mineItem != null && core.acceptStack(mineItem, 1, unit) == 0) { + if (unit.stack.amount > 0) dropItem(player, unit.rotation); + mineItem = null; + return; + } - if (mineTile.block() == Blocks.air && unit.within(mineTile, unit.type.miningRange)) { - unit.mineTile = mineTile; - } + //Mine TODO:: add don't mine when erekir cores and mineItem.buildable = false + if (unit.stack.amount >= unit.type.itemCapacity || (mineItem != null && !unit.acceptsItem(mineItem) || unit.item() != mineItem)) { + mining = false; + } else { - if (mineTile.block() != Blocks.air) { - mining = false; - } + Position mineLocation = useCorePos ? unit.closestCore() : unit; + float mineLocationX = mineLocation.getX(), mineLocationY = mineLocation.getY(); + + if (updateInterval.get(3, 30) && mineItem != null) { + + if(unit.type.mineFloor){ + mineTile = indexer.findClosestOre(mineLocationX, mineLocationY, mineItem); + } else { + mineTile = localIndexer.findClosestWallOre(mineLocationX, mineLocationY, mineItem); } + } - } else { - //Unload to core - if (unit.stack.amount == 0) { - mining = true; - return; + + if (tappedMineTile != null){ + mineTile = tappedMineTile; } - if (core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0) { - tryDropItems(core, player.x, player.y); + if(mineTile != null){ + aimLook(Tmp.v1.set(mineTile).scl(8)); + commonMovement(unit, mineTile, mineRadius); + if(unit.type.omniMovement){passiveAttack(unit);} + + //Prevents units that can't mine walls/floors to mine ore walls/floors + if ((mineTile.block() == Blocks.air && unit.type.mineFloor|| mineTile.block() != Blocks.air && unit.type.mineWalls) && unit.within(mineTile, unit.type.mineRange)) { + unit.mineTile = mineTile; + }else { + mining = false; + } } + } + } else { + //Unload to core + if (unit.stack.amount == 0 || unit.stack.item != mineItem && unit.stack.amount <= unit.type.itemCapacity) { + mining = true; + return; + } - movement.set(core).sub(unit).limit(unit.speed()); - unit.movePref(movement); - aimLook(core); + if (core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0 ) { + tryDropItems(core, player.x, player.y); } + + commonMovement(unit, core, 60f); + aimLook(core); + if(unit.type.omniMovement){passiveAttack(unit);} } } - + protected void patrolAI(Unit unit) { - patrolTile = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team(), BlockFlag.core)); - + Building candidate = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team(), BlockFlag.core)); + + patrolTile = candidate != null ? candidate.tile() : null; + + if (updateInterval.get(1, 10) || Units.invalidateTarget(target, unit.team, unit.x, unit.y)) { + target = Units.closestTarget(unit.team, unit.x, unit.y, unit.range() * 2, t -> true); + } + + if (target != null) {attackAI(unit); return;} + float offset; if (patrolTile != null) { offset = unit.range() * 0.8f; @@ -821,38 +927,48 @@ protected void patrolAI(Unit unit) { patrolTile = Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns()); offset = state.rules.dropZoneRadius + 96f; } - + if (patrolTile != null) { if (unit.type.flying) { float dst = unit.dst(patrolTile); movement.set(patrolTile).sub(unit).limit(unit.speed()); if (dst < offset - 16f) { - movement.scl(-1); + movement.scl(scalar); unit.movePref(movement); } else if (dst > offset) { unit.movePref(movement); } - + aimLook(patrolTile); } else { - pathfind(unit); + pathfind(unit, Pathfinder.fieldCore); } } } - //REGION CONTROLS - protected void pathfind(Unit unit) { + protected void turretAi(Unit unit){ + unit.aim(Core.input.mouseWorld()); + unit.controlWeapons(true, player.shooting); + + player.boosting = Core.input.keyDown(Binding.boost); + player.mouseX = unit.aimX(); + player.mouseY = unit.aimY(); + player.shooting = Core.input.keyDown(Binding.select); + } + //ENDREGION CONTROLS + + protected void pathfind(Unit unit, int pathType) { int costType = unit.pathType(); Tile tile = unit.tileOn(); if (tile == null) return; - Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, Pathfinder.fieldCore)); - + Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathType)); + if (tile == targetTile || (costType == Pathfinder.costNaval && !targetTile.floor().isLiquid)) return; - + unit.movePref(movement.set(targetTile).sub(unit).limit(unit.speed())); aimLook(targetTile); } - + @Override public boolean zoom(float initialDistance, float distance) { //todo: what the fuck does last zoom do @@ -864,7 +980,7 @@ public boolean zoom(float initialDistance, float distance) { renderer.setScale(distance / initialDistance * lastZoom); return true; } - + /** I have no idea why this method is required. But it just doesn't work on servers if i hardcode these methods. */ public void tryDropItems(Building build, float x, float y) { ItemStack stack = player.unit().stack; @@ -874,32 +990,84 @@ public void tryDropItems(Building build, float x, float y) { Call.dropItem(player.angleTo(x, y)); } } - + /** Multiplayer-compatible aiming */ public void aimLook(Position pos) { player.unit().aimLook(pos); player.mouseX = pos.getX(); player.mouseY = pos.getY(); } - - /** Should be called when the ai is being disabled */ + + /** Should be called when the AI is being disabled */ public void finish() { player.shooting = false; player.unit().mineTile = null; } - - /** Represents the current ai action, formatted according to bundle */ + + /** Represents the current AI action, formatted according to bundle */ @Override public String toString() { final String first = "newcontrols.ai.action-"; - + return manualMode ? bundle.get(first + AIAction.NONE) : - current == AIAction.AUTO ? bundle.format(first + current, bundle.get(first + auto)) : - bundle.get(first + current); + current == AIAction.AUTO ? bundle.format(first + current, bundle.get(first + auto)) : + "[accent]" + bundle.get(first + current); } - @Override - public boolean selectedBlock(){ - return isPlacing() && mode != breaking; + public void passiveAttack(Unit unit, Boolean aimLook, boolean handleShooting){ + boolean canHealBuilding = false; + for (var h : unit.type.weapons) { + if (h.bullet.healPercent > 0 ) { + canHealBuilding = true; + break; + } } + + if (updateInterval.get(1, 10) || Units.invalidateTarget(target, unit.team, unit.x, unit.y) && !ignoreBuildings) { + if(!ignoreBuildings){ + target = Units.closestTarget(unit.team, unit.x, unit.y, unit.range() * 5, t -> true); + } else if(canHealBuilding){ + Building checkDmg = Units.findDamagedTile(unit.team, unit.x, unit.y); + float offset = unit.type.range * 0.9f; + float dst = unit.dst((checkDmg)); + + if(dst > offset ){ target = checkDmg;} + } else { + Unit check = Units.closestEnemy(unit.team, unit.x, unit.y, unit.range() * 5, t -> true); + if (check != null){target = check;} + } + } + if(target == null || !unit.hasWeapons()) return; + + float bulletSpeed = unit.hasWeapons() ? unit.type.weapons.first().bullet.speed : 0; + Vec2 intercept = Predict.intercept(unit, target, bulletSpeed); + + //For cases, you only don't want to shoot and slow the unit if the unit's weapon is not in a turret or other reasons + if (aimLook && intercept != null) {aimLook(intercept);} + if (handleShooting) { shoot = unit.within(intercept, unit.range() * 1.25f); + player.shooting = shoot;} + } + + public void passiveAttack(Unit unit){ + passiveAttack(unit, true, false); + } + public void passiveAttack(Unit unit, boolean aimLook){ + passiveAttack(unit, aimLook, false); + } + + /* Now less copying and pasting the same code*/ + public void commonMovement(Unit unit, Position point, float offset, float dist){ + if(point == null) return; + movement.set(point).sub(unit).limit(unit.speed()); + if (dist < offset - 16f) { + movement.scl(scalar); + unit.movePref(movement); + } else if (dist > offset) { + unit.movePref(movement); + } + } + + public void commonMovement(Unit unit, Position pos, float offset){ + commonMovement(unit, pos, offset, unit.dst(pos)); } -} \ No newline at end of file + +} diff --git a/src/newcontrols/input/AiEnabler.java b/src/newcontrols/input/AiEnabler.java new file mode 100644 index 0000000..5e82b97 --- /dev/null +++ b/src/newcontrols/input/AiEnabler.java @@ -0,0 +1,50 @@ +package newcontrols.input; + +import arc.Core; +import arc.scene.ui.layout.Table; +import arc.util.Log; +import mindustry.Vars; +import mindustry.input.InputHandler; +import newcontrols.ui.fragments.JoyStickFragment; + +public class AiEnabler { + /** This exists so hotkeys and different Ui styles can turn on/off the Ai & other components*/ + InputHandler lastHandler = null; + public static AIInput ai = new AIInput(); + public static JoyStickFragment joyStick = new JoyStickFragment(); + public boolean enabled = false; + private final Table inputTable = (Table) Core.scene.find("inputTable"); + public static boolean extraLogs = true; + + public void ToggleAi(){ + enabled =! enabled; + + /*AI handler*/ + if(!enabled){ + if(lastHandler != null){ + ai.finish(); + Vars.control.setInput(lastHandler); + } else { + Log.info("Can't Set input to previous Input!"); + } + } else{ + lastHandler = Vars.control.input; + Vars.control.setInput(ai); + } + } + + /* Joystick Fragment */ + public void ToggleJoystick(){ + if (ai.manualMode && enabled){ + joyStick.Build(Vars.ui.hudGroup); + } + /* Rebuild the table */ + if(inputTable != null) { + inputTable.clear(); + ai.buildPlacementUI(inputTable); + }else{ + Log.info("InputTable is Null!"); + } + + } +} diff --git a/src/newcontrols/ui/Joystick.java b/src/newcontrols/ui/Joystick.java index 287051c..6ecd77a 100644 --- a/src/newcontrols/ui/Joystick.java +++ b/src/newcontrols/ui/Joystick.java @@ -11,72 +11,70 @@ import arc.scene.event.InputListener; public class Joystick extends Element { - - public Color backColor = new Color(0, 0, 0), stickColor = new Color(0.7f, 0.7f, 0.7f); - - public Cons usedListener; - public Vec2 offset = new Vec2(); - public boolean isDragging = false; - - private static Vec2 tmp = new Vec2(), tmp2 = new Vec2(); - - public Joystick() { - addListener(new InputListener() { - @Override - public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button) { - isDragging = true; - touchDragged(event, x, y, pointer); //set offset - return true; - } - - @Override - public void touchDragged(InputEvent event, float x, float y, int pointer) { - float radius = Math.min(width, height) / 2f; - offset.set(x - width / 2, y - width / 2).limit(radius); - } - - @Override - public void touchUp(InputEvent e, float x, float y, int pointer, KeyCode button) { - isDragging = false; - offset.set(0, 0); - if (usedListener != null) usedListener.get(getMovement()); - } - }); - } - - /** Called when the joystick is used. vec2 has a length length from -1 to 1 */ - public void used(Cons usedListener) { - this.usedListener = usedListener; - } - - @Override - public void act(float delta) { - super.act(delta); - if (isDragging && usedListener != null) usedListener.get(getMovement()); - } - - @Override - public void draw() { - super.draw(); - float radius = Math.min(width, height) / 2f; - - float x = this.x + width / 2; - float y = this.y + height / 2; - - tmp.set(offset).limit(radius * 0.8f); //this way the stick will always be in le circle - - Draw.color(backColor); - Draw.alpha(0.5f); - Fill.circle(x, y, radius); - Fill.circle(x, y, radius / 1.5f); - - Draw.color(stickColor); - Fill.circle(x + tmp.x, y + tmp.y, radius / 5f); - } - - /** Movement vector with a range of [-1; 1] */ - public Vec2 getMovement() { - return tmp.set(offset).div(tmp2.set(width / 2, height / 2)); - } - + public Color backColor = new Color(0, 0, 0), stickColor = new Color(0.7f, 0.7f, 0.7f); + + public Cons usedListener; + public Vec2 offset = new Vec2(); + public boolean isDragging = false; + + private static Vec2 tmp = new Vec2(), tmp2 = new Vec2(); + + public Joystick() { + addListener(new InputListener() { + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button) { + isDragging = true; + touchDragged(event, x, y, pointer); //set offset + return true; + } + + @Override + public void touchDragged(InputEvent event, float x, float y, int pointer) { + float radius = Math.min(width, height) / 2f; + offset.set(x - width / 2, y - width / 2).limit(radius); + } + + @Override + public void touchUp(InputEvent e, float x, float y, int pointer, KeyCode button) { + isDragging = false; + offset.set(0, 0); + if (usedListener != null) usedListener.get(getMovement()); + } + }); + } + + /** Called when the joystick is used. vec2 has a length length from -1 to 1 */ + public void used(Cons usedListener) { + this.usedListener = usedListener; + } + + @Override + public void act(float delta) { + super.act(delta); + if (isDragging && usedListener != null) usedListener.get(getMovement()); + } + + @Override + public void draw() { + super.draw(); + float radius = Math.min(width, height) / 2f; + + float x = this.x + width / 2; + float y = this.y + height / 2; + + tmp.set(offset).limit(radius * 0.8f); //this way the stick will always be in le circle + + Draw.color(backColor); + Draw.alpha(0.5f); + Fill.circle(x, y, radius); + Fill.circle(x, y, radius / 1.5f); + + Draw.color(stickColor); + Fill.circle(x + tmp.x, y + tmp.y, radius / 5f); + } + + /** Movement vector with a range of [-1; 1] */ + public Vec2 getMovement() { + return tmp.set(offset).div(tmp2.set(width / 2, height / 2)); + } } diff --git a/src/newcontrols/ui/NCStyles.java b/src/newcontrols/ui/NCStyles.java new file mode 100644 index 0000000..af31a03 --- /dev/null +++ b/src/newcontrols/ui/NCStyles.java @@ -0,0 +1,40 @@ +package newcontrols.ui; + +import arc.graphics.Color; +import arc.scene.style.Drawable; +import arc.scene.style.TextureRegionDrawable; +import arc.scene.ui.TextButton.TextButtonStyle; +import mindustry.gen.Tex; +import mindustry.ui.Fonts; + +import static mindustry.ui.Styles.*; + +public class NCStyles { + public static TextButtonStyle fullt, clearPartialt; + public static Drawable toggled, hovered; + + public static void init() { + fullt = new TextButtonStyle(){{ + font = Fonts.def; + fontColor = Color.white; + down = flatOver; + up = black; + over = flatOver; + disabled = black; + disabledFontColor = Color.gray; + }}; + + clearPartialt = new TextButtonStyle() {{ + font = Fonts.def; + down = flatDown; + up = black6; + over = flatOver; + disabled = black8; + disabledFontColor = Color.lightGray; + }}; + + var whiteui = (TextureRegionDrawable) Tex.whiteui; + toggled = whiteui.tint(Color.green); + hovered = whiteui.tint(Color.lime); + } +} diff --git a/src/newcontrols/ui/NiceSlider.java b/src/newcontrols/ui/NiceSlider.java index 135dad4..d33f547 100644 --- a/src/newcontrols/ui/NiceSlider.java +++ b/src/newcontrols/ui/NiceSlider.java @@ -1,21 +1,16 @@ package newcontrols.ui; -import arc.*; -import arc.func.*; -import arc.scene.*; -import arc.scene.ui.*; -import arc.scene.ui.layout.*; -import arc.scene.actions.*; -import arc.scene.event.*; -import arc.scene.utils.*; -import mindustry.gen.*; -import mindustry.ui.*; - -import newcontrols.func.*; +import arc.func.Floatc; +import arc.scene.event.Touchable; +import arc.scene.ui.Label; +import arc.scene.ui.Slider; +import arc.scene.ui.layout.Table; +import mindustry.ui.Styles; +import newcontrols.func.Provf; +import newcontrols.func.StringProcessor; /** Slider with extra funny stuff */ public class NiceSlider extends Table { - StringProcessor processor; Provf minProv, maxProv; @@ -70,5 +65,4 @@ public void act(float delta) { slider.setRange(min, max); } } - -} \ No newline at end of file +} diff --git a/src/newcontrols/ui/Spinner.java b/src/newcontrols/ui/Spinner.java index 2c74a33..82d501e 100644 --- a/src/newcontrols/ui/Spinner.java +++ b/src/newcontrols/ui/Spinner.java @@ -1,25 +1,24 @@ package newcontrols.ui; -import arc.*; -import arc.func.*; -import arc.struct.*; -import arc.math.*; -import arc.math.geom.*; -import arc.util.*; -import arc.scene.*; -import arc.scene.ui.*; -import arc.scene.ui.layout.*; -import arc.scene.actions.*; -import arc.scene.event.*; -import arc.scene.utils.*; -import mindustry.gen.*; -import mindustry.ui.*; +import arc.func.Cons; +import arc.graphics.Color; +import arc.math.geom.Vec2; +import arc.scene.Element; +import arc.scene.Scene; +import arc.scene.event.Touchable; +import arc.scene.ui.Image; +import arc.scene.ui.TextButton; +import arc.scene.ui.layout.Collapser; +import arc.scene.ui.layout.Scl; +import arc.scene.ui.layout.Table; +import arc.util.Interval; +import arc.util.Timer; +import mindustry.gen.Icon; +import mindustry.ui.Styles; /** Text button, clicking on which shows/hides a collapser. The collapser is displayed over other elements. */ public class Spinner extends TextButton { - public Collapser col; - public TextButton button; public Image image; /** Whether to remove collapser if any of ancestors are invisible / untouchable */ @@ -31,9 +30,14 @@ public class Spinner extends TextButton { Interval hideInterval = new Interval(); Timer.Task hideTask; - public Spinner(String header, Cons constructor) { - super(header, Styles.clearTogglet); - add(image = new Image(Icon.downOpen)).size(Icon.downOpen.imageSize() * Scl.scl(1f)).padLeft(padW / 2f).left(); + public Spinner(String header, Boolean side, Color color, Cons
constructor) { + super(header, Styles.fullTogglet); + if (!side){ + add(image = new Image(Icon.downOpen)).size(Icon.downOpen.imageSize() * Scl.scl(1f)).padLeft(padW / 2f).left(); + } else { + add(image = new Image(Icon.downOpen)).size(Icon.downOpen.imageSize() * Scl.scl(1f)).padRight(padW / 2f).right(); + } + getCells().reverse(); clicked(() -> { @@ -51,7 +55,7 @@ public Spinner(String header, Cons
constructor) { }); col = new Collapser(base -> base.pane(t -> { - t.left(); + t.left().setBackground(Styles.black3); constructor.get(t); }).growX().scrollX(false), true).setDuration(collapseTime); @@ -59,10 +63,11 @@ public Spinner(String header, Cons
constructor) { if (col.getScene() != null) { col.visible = true; col.color.a = parentAlpha * color.a; + col.color.set(color); col.setSize(width, col.getPrefHeight()); Vec2 point = localToStageCoordinates(tmp.set(0, -col.getPrefHeight())); - if (point.y < getPrefHeight()) point = localToStageCoordinates(tmp.set(0, col.getPrefHeight())); + if (point.y < getPrefHeight()) point = localToStageCoordinates(tmp.set(0, height)); col.setPosition(point.x, point.y); } @@ -80,7 +85,17 @@ public Spinner(String header, Cons
constructor) { } }); } - + + public Spinner(String header, Cons
constructor){ + this(header, false, Color.black, constructor); + } + public Spinner(String header, Boolean side, Cons
constructor){ + this(header, side, Color.black, constructor); + } + public Spinner(String header, Cons
constructor, Color color ){ + this(header, false, color, constructor); + } + public void show(boolean animate) { if (hideTask != null) hideTask.cancel(); @@ -118,5 +133,4 @@ public float getPrefWidth() { public float getPrefHeight() { return super.getPrefWidth() + padH; } - -} \ No newline at end of file +} diff --git a/src/newcontrols/ui/Toggle.java b/src/newcontrols/ui/Toggle.java index a8cd3ec..50871b5 100644 --- a/src/newcontrols/ui/Toggle.java +++ b/src/newcontrols/ui/Toggle.java @@ -1,22 +1,69 @@ package newcontrols.ui; import arc.func.Boolc; +import arc.func.Func; +import arc.graphics.Color; +import arc.graphics.g2d.Draw; +import arc.scene.style.Drawable; import arc.scene.ui.TextButton; import mindustry.ui.Styles; public class Toggle extends TextButton { - /** I don't like this approach too but something's wrong with Button.isChecked as it returns false for no reason. */ public boolean enabled = false; + public Func toggleProv; + public Drawable currentStyle = Styles.black; - public Toggle(String text, boolean enabled, Boolc cons) { - super(text, Styles.clearTogglet); + /** This might be overly complex for no reason but to look nice */ + public Toggle(String text, boolean enabled, Boolc cons, Drawable hovered, Drawable toggled) { + super(text, Styles.fullTogglet); clicked(() -> { setChecked(this.enabled = !this.enabled); cons.get(this.enabled); + currentStyle = toggled; }); - + hovered(()-> currentStyle = hovered); + + this.setBackground(currentStyle); + setBackground(currentStyle); this.enabled = enabled; } -} \ No newline at end of file + public Toggle(String text, boolean enabled, Boolc cons){ + this(text, enabled, cons, Styles.grayPanel, Styles.black); + } + + public Toggle(String text, Func enabled, Boolc cons, Drawable hovered, Drawable toggled) { + this(text, false, cons, hovered, toggled); + toggle(enabled); + this.enabled = enabled.get(this); + + clicked(() -> currentStyle = toggled); + hovered(()-> currentStyle = hovered); + + this.setBackground(currentStyle); + setBackground(currentStyle); + + } + + public Toggle(String text, Func enabled, Boolc cons) { + this(text, false, cons); + toggle(enabled); + this.enabled = enabled.get(this); + } + @Override + public void act(float delta) { + super.act(delta); + + if (toggleProv != null) { + boolean state = toggleProv.get(this); + enabled = state; + setChecked(state); + } + } + + public Toggle toggle(Func func) { + toggleProv = func; + return this; + } +} diff --git a/src/newcontrols/ui/fragments/AIPanel.java b/src/newcontrols/ui/fragments/AIPanel.java index 4d6d305..8f1429a 100644 --- a/src/newcontrols/ui/fragments/AIPanel.java +++ b/src/newcontrols/ui/fragments/AIPanel.java @@ -1,36 +1,35 @@ package newcontrols.ui.fragments; + import arc.func.Prov; +import arc.graphics.Color; import arc.scene.Element; import arc.scene.Group; +import arc.struct.Seq; import mindustry.Vars; +import mindustry.content.Items; import mindustry.content.UnitTypes; import mindustry.gen.Icon; -import mindustry.input.InputHandler; import mindustry.type.Item; import mindustry.ui.Styles; -import mindustry.ui.fragments.Fragment; +import newcontrols.NCVars; import newcontrols.input.AIInput; -import newcontrols.input.AIInput.AIAction; -import newcontrols.ui.Joystick; +import newcontrols.input.AiEnabler; +import newcontrols.ui.NCStyles; import newcontrols.ui.NiceSlider; import newcontrols.ui.Spinner; import newcontrols.ui.Toggle; +import newcontrols.util.LocalBlockIndexer; import static arc.Core.bundle; +import static mindustry.Vars.player; //"why so many whitespaces?" //Because this shit is unreadable without them! -public class AIPanel extends Fragment { - +public class AIPanel { public static float dsize = 65f; - - public boolean shown = false, enabled = false; - - InputHandler lastHandler = null; - AIInput ai = new AIInput(); - - @Override + public boolean shown = false; + public void build(Group parent) { //Main menu (wrench) @@ -38,150 +37,163 @@ public void build(Group parent) { table.center().left(); table.name = "ai-panel"; - table.button(Icon.wrench, Styles.clearTransi, () -> shown = !shown).size(dsize).left().row(); - - table.table(Styles.black3, panel -> { - + + table.button(Icon.wrench, Styles.cleari, () -> shown = !shown).size(dsize).left().row(); + + table.collapser(panel -> { + panel.setBackground(Styles.black3); + panel.add("@newcontrols.ai.header").colspan(2).row(); panel.table(h -> { - + h.add("@newcontrols.ai.status").padRight(1f); - Prov lText = () -> enabled ? "@newcontrols.ai.enabled-ai" : "@newcontrols.ai.disabled"; + Prov lText = () -> NCVars.enabler.enabled ? "@newcontrols.ai.enabled-ai" : "@newcontrols.ai.disabled"; h.label(lText).height(60f).with(l -> { - l.clicked(() -> { - enabled = !enabled; - - if (enabled) { - lastHandler = Vars.control.input; - Vars.control.setInput(ai); - } else if (lastHandler != null) { - ai.finish(); - Vars.control.setInput(lastHandler); - } - }); + l.clicked(() -> NCVars.enabler.ToggleAi()); l.setStyle(Styles.techLabel); }); + h.row(); - }).padLeft(8f).row(); - panel.collapser(control -> { - + }).padLeft(8f).row(); + panel.collapser(control -> { + control.table(h -> { h.add("@newcontrols.ai.action").padRight(5f); - h.label(() -> ai.toString()).marginBottom(5f).row(); + h.label(() -> AiEnabler.ai.toString()).marginBottom(5f).row(); }).row(); - + + /*Ai Settings*/ control.collapser(actions -> { actions.add("@newcontrols.ai.settings").row(); //action selection actions.add((Element) new Spinner("@newcontrols.ai.actions-select", s -> { - + s.defaults().growX().height(40f); - s.button("@newcontrols.ai.action-AUTO-TYPE", Styles.clearPartialt, () -> ai.current = AIAction.AUTO).row(); - s.button("@newcontrols.ai.action-ATTACK", Styles.clearPartialt, () -> ai.current = AIAction.ATTACK).row(); - s.button("@newcontrols.ai.action-MINE", Styles.clearPartialt, () -> ai.current = AIAction.MINE).row(); - s.button("@newcontrols.ai.action-BUILD", Styles.clearPartialt, () -> ai.current = AIAction.BUILD).row(); - s.button("@newcontrols.ai.action-REPAIR", Styles.clearPartialt, () -> ai.current = AIAction.REPAIR).row(); - s.button("@newcontrols.ai.action-RETREAT", Styles.clearPartialt, () -> ai.current = AIAction.RETREAT).row(); - s.button("@newcontrols.ai.action-PATROL", Styles.clearPartialt, () -> ai.current = AIAction.PATROL).row(); + s.button("@newcontrols.ai.action-AUTO-TYPE", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.AUTO).row(); + s.button("@newcontrols.ai.action-ATTACK", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.ATTACK).row(); + s.button("@newcontrols.ai.action-MINE", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.MINE).row(); + s.button("@newcontrols.ai.action-BUILD", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.BUILD).row(); + s.button("@newcontrols.ai.action-REBUILD", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.REBUILD).row(); + s.button("@newcontrols.ai.action-ASSIST", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.ASSIST).row(); + s.button("@newcontrols.ai.action-REPAIR", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.REPAIR).row(); + s.button("@newcontrols.ai.action-RETREAT", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.RETREAT).row(); + s.button("@newcontrols.ai.action-PATROL", NCStyles.fullt, () -> AiEnabler.ai.current = AIInput.AIAction.PATROL).row(); })).growX().row(); - + //auto actions actions.add((Element) new Spinner("@newcontrols.ai.actions-enable", s -> { - s.defaults().growX().height(40f); - s.add(new Toggle("@newcontrols.ai.action-ATTACK", true, enabled -> ai.attack = enabled)).row(); - s.add(new Toggle("@newcontrols.ai.action-MINE", true, enabled -> ai.mine = enabled)).row(); - s.add(new Toggle("@newcontrols.ai.action-BUILD", true, enabled -> ai.build = enabled)).row(); - s.add(new Toggle("@newcontrols.ai.action-REPAIR", true, enabled -> ai.repair = enabled)).row(); - s.add(new Toggle("@newcontrols.ai.action-RETREAT", true, enabled -> ai.retreat = enabled)).row(); - s.add(new Toggle("@newcontrols.ai.action-PATROL", true, enabled -> ai.patrol = enabled)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-ATTACK") , it -> AiEnabler.ai.attack, enabled -> AiEnabler.ai.attack = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-MINE") , it -> AiEnabler.ai.mine, enabled -> AiEnabler.ai.mine = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-BUILD") , it -> AiEnabler.ai.build, enabled -> AiEnabler.ai.build = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-REPAIR") , it -> AiEnabler.ai.repair, enabled -> AiEnabler.ai.repair = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-RETREAT"), it -> AiEnabler.ai.retreat, enabled -> AiEnabler.ai.retreat = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-ASSIST"), it -> AiEnabler.ai.assist, enabled -> AiEnabler.ai.assist = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-REBUILD"), it -> AiEnabler.ai.rebuild, enabled -> AiEnabler.ai.rebuild = enabled, NCStyles.toggled, NCStyles.hovered)).row(); + s.add(new Toggle(bundle.get("newcontrols.ai.action-PATROL"), it -> AiEnabler.ai.patrol, enabled -> AiEnabler.ai.patrol = enabled, NCStyles.toggled, NCStyles.hovered)).row(); - })).growX().row(); + }, Color.lime)).growX().row(); //preferences actions.add((Element) new Spinner("@newcontrols.ai.actions-preferences", s -> { s.defaults().growX().height(35f); + //Mining Config + s.add(new Spinner("@newcontrols.ai.prefs.mine-items",true, items -> { + //Items selection + items.center().top(); + Seq addedItems = new Seq(); // some items are duplicated + + items.table(picks -> { + Seq mineables = new Seq(); + + mineables.add(Item.getAllOres()); + mineables.add(LocalBlockIndexer.getFloorWithItems()); + mineables.add(LocalBlockIndexer.getWallWithItems()); + + mineables.each(i -> { + if (i == null) return; + if (addedItems.contains(i)) return; + addedItems.add(i); + + final Item item = i; //else it cannot be used in lambdas + boolean shouldMine = UnitTypes.gamma.mineItems.contains(i) || i == Items.beryllium || i == Items.graphite; + + if (!shouldMine) AiEnabler.ai.mineExclude.add(i); + picks.add(new Toggle(i.emoji(), shouldMine, enabled -> { + if (enabled) { + AiEnabler.ai.mineExclude.remove(item); + } else { + AiEnabler.ai.mineExclude.add(item); + } + },NCStyles.toggled, NCStyles.hovered)).size(50).get().toggle(it -> !AiEnabler.ai.mineExclude.contains(item)); + if (picks.getChildren().size % 6 == 0) picks.row(); + }); + }); + items.row(); + //Mining minimum amount, for cases to avoid the core running amount of x items first + items.add(new NiceSlider(bundle.get("newcontrols.ai.prefs.mine-keep-minimum"),0 , 10 , 1, max -> AiEnabler.ai.mineMinimumPercent = max) + .max(()-> 10f).process( + max -> max == 0 ? bundle.get("newcontrols.unit.off"): Math.round(max) + "%" + "[lightgray](" + Math.round(AiEnabler.ai.mineMinimumPercent * 0.1 * player.team().core().storageCapacity) + ")" + ).background(Styles.black5)).tooltip(bundle.get("newcontrols.ai.prefs.tooltip.useCorePos ")).row(); + + //mining range + items.add(new NiceSlider("@newcontrols.ai.prefs.mine-radius", 0, 10, 4, radius -> AiEnabler.ai.mineRadius = radius) + .max(() -> Vars.player.unit().type == null ? 100 : Vars.player.unit().type.mineRange) + .process(v -> Math.round(v / 8) + " " + bundle.get("unit.blocks"))).growX().row(); + })).growX().row(); + //Attack range - s.add(new NiceSlider("@newcontrols.ai.prefs.attack-radius", 0, 1200, 16, radius -> ai.attackRadius = radius) + s.add(new NiceSlider("@newcontrols.ai.prefs.attack-radius", 0, 1200, 16, radius -> AiEnabler.ai.attackRadius = radius) .max(() -> Vars.player.unit().type == null ? 1200 : Vars.player.unit().range() * 5) .process(v -> v <= 0 ? bundle.get("newcontrols.unit.nolimit") : Math.round(v / 8) + " " + bundle.get("unit.blocks"))).growX().row(); - //mining range - s.add(new NiceSlider("@newcontrols.ai.prefs.mine-radius", 0, 10, 4, radius -> ai.mineRadius = radius) - .max(() -> Vars.player.unit().type == null ? 100 : Vars.player.unit().type.miningRange) - .process(v -> Math.round(v / 8) + " " + bundle.get("unit.blocks"))).growX().row(); - //Hp threshold respawn - s.add(new NiceSlider("@newcontrols.ai.prefs.hp-respawn", 0, 100, 5, percent -> ai.respawnThreshold = percent) - .max(() -> 100f) - .process(v -> v <= 0 ? bundle.get("newcontrols.unit.noautorespawn") : Math.round(v) + "%" )).growX().row(); + s.add(new NiceSlider("@newcontrols.ai.prefs.hp-respawn", 0, 10, 1, percent -> AiEnabler.ai.respawnThreshold = percent) + .max(() -> 10f) + .process(v -> v <= 0 ? bundle.get("newcontrols.unit.off") : Math.round(v) + "%" )).growX().row(); //Retreat Instead of Respawn Toggle - s.add(new Spinner("@newcontrols.ai.prefs.retreat_instead", check -> ai.shouldRetreat = !ai.shouldRetreat).center()).growX().row(); - //Items selection - s.add(new Spinner("@newcontrols.ai.prefs.mine-items", items -> { - items.center().top(); + s.check("@newcontrols.ai.prefs.retreat_instead", AiEnabler.ai.retreatInstead, r -> AiEnabler.ai.retreatInstead = r).checked(AiEnabler.ai.shouldRetreat).growX(); + s.row(); - Item.getAllOres().each(i -> { - final Item item = i; //else it cannot be used in lambdas - boolean shouldMine = UnitTypes.gamma.mineItems.contains(i); - - if (!shouldMine) ai.mineExclude.add(i); - items.add(new Toggle(i.emoji(), shouldMine, enabled -> { - if (enabled) { - ai.mineExclude.remove(item); - } else { - ai.mineExclude.add(item); - } - })).size(50); - if (items.getChildren().size % 6 == 0) items.row(); - }); - })).growX(); - })).growX().row(); - //Mobile JoyStick toggle - actions.button(Icon.logic, Styles.clearTransi, () -> ai.manualMode = !ai.manualMode).growX().row(); - }, true, () -> true).growX().row(); + //Use core as basis instead of the unit for basing of unit actions + s.check("@newcontrols.ai.prefs.useCorePos", AiEnabler.ai.useCorePos, r -> AiEnabler.ai.useCorePos = r).checked(AiEnabler.ai.useCorePos).growX().tooltip(bundle.get("newcontrols.ai.prefs.tooltip.useCorePos ")); + s.row().background(Styles.black5); - }, true, () -> enabled).growX().row(); - - }).visible(() -> shown).padLeft(8f).row(); + //Attack Ai ignores buildings + s.check(bundle.get("newcontrols.ai.prefs.attack_ignore_buildings"), AiEnabler.ai.ignoreBuildings, r -> AiEnabler.ai.ignoreBuildings = r).checked(a -> AiEnabler.ai.ignoreBuildings).growX(); + s.row(); - }); - //movement joystick, Moved here to avoid wonkiness - parent.fill(table -> { - table.center().left(); - table.collapser(c -> { - Joystick move = new Joystick(); - c.add(move).size(200); - move.used(pos -> ai.moveDir.set(pos)); - }, true, () -> enabled && ai.manualMode); - }); - //aim & shoot joystick - parent.fill(table -> { - - table.center().right(); - - table.collapser(c -> { - ActionPanel.buildPortrait(c, ai); - c.row(); + })).growX().row(); + //actions.button(Icon.logic, Styles.cleari, () -> ai.manualMode = !ai.manualMode).growX().row(); + }, true, () -> !AiEnabler.ai.manualMode).growX().row(); + /*JoyStick settings*/ + control.collapser(stick-> { + + /* Auto aim like in mobile version*/ + stick.check(bundle.get("newcontrols.ai.joystick-autoAim"), AiEnabler.ai.autoAim, r -> AiEnabler.ai.autoAim = r).checked(a -> AiEnabler.ai.autoAim).growX().row(); + /* Switch movement and Aim joy stick*/ + stick.check(bundle.get("newcontrols.ai.joystick-switch"), r -> AiEnabler.joyStick.switchJoySticks = r).checked(a -> AiEnabler.joyStick.switchJoySticks).growX().row(); + + }, true, () -> AiEnabler.ai.manualMode).growX().row(); + + }, true, () -> NCVars.enabler.enabled).growX().row(); + panel.row(); + //Mobile JoyStick toggle + panel.check("@newcontrols.ai.joystick", AiEnabler.ai.manualMode, r ->{ + AiEnabler.ai.manualMode = r; + NCVars.enabler.ToggleJoystick(); + }).checked(a -> AiEnabler.ai.manualMode).growX(); + }, true, () -> shown).padLeft(15f).row(); - Joystick shoot = new Joystick(); - c.add(shoot).size(200); - shoot.used(pos -> { - ai.shootDir.set(pos); - ai.shoot = true; - }); - }, true, () -> enabled && ai.manualMode); }); - } - -} \ No newline at end of file +} diff --git a/src/newcontrols/ui/fragments/ActionPanel.java b/src/newcontrols/ui/fragments/ActionPanel.java index 2706267..52e0d97 100644 --- a/src/newcontrols/ui/fragments/ActionPanel.java +++ b/src/newcontrols/ui/fragments/ActionPanel.java @@ -1,6 +1,5 @@ package newcontrols.ui.fragments; -import arc.Core; import arc.scene.style.TextureRegionDrawable; import arc.scene.ui.layout.Table; import mindustry.entities.Units; @@ -10,111 +9,105 @@ import mindustry.gen.Unit; import mindustry.ui.Styles; import newcontrols.input.AIInput; +import newcontrols.ui.NCStyles; import static arc.Core.atlas; +import static arc.Core.graphics; import static mindustry.Vars.player; import static mindustry.Vars.world; public class ActionPanel { - - //landscape, displayed on the bottom - public static void buildLandscape(Table origin, final AIInput input) { - origin.collapser(table -> { - table.table(dangerous -> { - dangerous.defaults().height(55); - - dangerous.button("@newcontrols.manual.pay-enter", Styles.nodet, ActionPanel::payloadEnter).width(120); - }).row(); - - table.table(generic -> { - generic.defaults().height(55); - - generic.button("@newcontrols.manual.command", Styles.nodet, () -> Call.unitCommand(player)).width(120).disabled(b -> player.dead() || player.unit().type.commandLimit < 1); - - generic.button(makeRegion("newcontrols-arrow-up"), Styles.clearTogglei, () -> player.boosting = !player.boosting).size(55).update(b -> b.setChecked(player.boosting && player.unit().type.canBoost)); - }).row(); - - table.table(payloads -> { - payloads.defaults().size(160, 55); - - payloads.button("@newcontrols.manual.pickup-unit", Styles.nodet, ActionPanel::unitPickup) - .disabled(b -> !(player.unit() instanceof Payloadc)); - - payloads.button("@newcontrols.manual.pickup-block", Styles.nodet, ActionPanel::buildPickup) - .disabled(b -> !(player.unit() instanceof Payloadc)); - - payloads.button("@newcontrols.manual.drop", Styles.nodet, input::tryDropPayload) - .disabled(b -> !(player.unit() instanceof Payloadc pay) || !pay.hasPayload()); - }).row(); - }, () -> !Core.graphics.isPortrait()); - } - - //portrait, displayed above right thumbstick - public static void buildPortrait(Table origin, final AIInput input){ - - origin.collapser(table -> { - table.table(dangerous -> { - dangerous.defaults().size(50); - - dangerous.button(makeRegion("newcontrols-enter-payload"), Styles.nodei, ActionPanel::payloadEnter); - }).row(); - - table.table(generic -> { - generic.defaults().size(50); - - generic.button(makeRegion("newcontrols-command"), Styles.nodei, () -> Call.unitCommand(player)).disabled(b -> player.dead() || player.unit().type.commandLimit < 1); - - generic.button(makeRegion("newcontrols-arrow-up"), Styles.clearTogglei, () -> player.boosting = !player.boosting).disabled(b -> !player.unit().type.canBoost).update(b -> b.setChecked(player.boosting && player.unit().type.canBoost)); - }).row(); - - table.table(payloads -> { - payloads.defaults().size(50); - - payloads.button(makeRegion("newcontrols-pick-unit"), Styles.nodei, ActionPanel::unitPickup) - .disabled(b -> !(player.unit() instanceof Payloadc)); - - payloads.button(makeRegion("newcontrols-pick-building"), Styles.nodei, ActionPanel::buildPickup) - .disabled(b -> !(player.unit() instanceof Payloadc)); - - payloads.button(makeRegion("newcontrols-drop-payload"), Styles.nodei, input::tryDropPayload) - .disabled(b -> !(player.unit() instanceof Payloadc pay) || !pay.hasPayload()); - }); - }, () -> Core.graphics.isPortrait()); - } - - - protected static void unitPickup() { - Unit self = player.unit(); - if (!(self instanceof Payloadc pay)) return; - - Unit target = Units.closest(player.team(), self.x, self.y, 8f, u -> u != self && u.isGrounded() && pay.canPickup(u) && u.within(self, u.hitSize + 8f)); - - if (target != null) Call.requestUnitPayload(player, target); - } - - protected static void buildPickup() { - Unit self = player.unit(); - if (!(self instanceof Payloadc pay)) return; - - Building target = self.tileOn().build; - - if (target != null && pay.canPickup(target)) { - Call.requestBuildPayload(player, target); - } - } - - protected static void payloadEnter() { - Unit self = player.unit(); - - Building build = world.buildWorld(self.x, self.y); - if (build != null && self.team() == build.team && build.canControlSelect(self)) { - Call.unitBuildingControlSelect(self, build); - } - } - - //todo: can i not? - protected static TextureRegionDrawable makeRegion(String name) { - return new TextureRegionDrawable(atlas.find(name)); - } - + //landscape, displayed on the bottom + public static void buildLandscape(Table origin, final AIInput input) { + origin.collapser(table -> { + table.table(dangerous -> { + dangerous.defaults().height(55); + + dangerous.button("@newcontrols.manual.pay-enter", NCStyles.clearPartialt, () -> payloadEnter()).width(120); + + dangerous.button(makeRegion("newcontrols-arrow-up"), Styles.cleari, () -> { + player.boosting = !player.boosting; + }).size(55).update(b -> b.setChecked(player.boosting && player.unit().type.canBoost)); + }).row(); + + table.table(payloads -> { + payloads.defaults().size(160, 55); + + payloads.button("@newcontrols.manual.pickup-unit", NCStyles.clearPartialt, () -> unitPickup()) + .disabled(b -> !(player.unit() instanceof Payloadc)); + + payloads.button("@newcontrols.manual.pickup-block", NCStyles.clearPartialt, () -> buildPickup()) + .disabled(b -> !(player.unit() instanceof Payloadc)); + + payloads.button("@newcontrols.manual.drop", NCStyles.clearPartialt, () -> input.tryDropPayload()) + .disabled(b -> !(player.unit() instanceof Payloadc) || !((Payloadc) player.unit()).hasPayload()); + }).row(); + }, () -> !graphics.isPortrait() && input.manualMode ); + } + + //portrait, displayed above right thumbstick + public static void buildPortrait(Table origin, final AIInput input) { + origin.defaults().pad(2f); + + origin.collapser(table -> { + table.table(dangerous -> { + dangerous.defaults().size(50).pad(2f); + + dangerous.button(makeRegion("newcontrols-enter-payload"), Styles.flati, () -> payloadEnter()); + + dangerous.button(makeRegion("newcontrols-arrow-up"), Styles.clearTogglei, () -> { + player.boosting = !player.boosting; + }).disabled(b -> !player.unit().type.canBoost).update(b -> b.setChecked(player.boosting && player.unit().type.canBoost)); + + }).row(); + + table.table(payloads -> { + payloads.defaults().size(50).pad(2f); + + payloads.button(makeRegion("newcontrols-pick-unit"), Styles.flati, () -> unitPickup()) + .disabled(b -> !(player.unit() instanceof Payloadc)); + + payloads.button(makeRegion("newcontrols-pick-building"), Styles.flati, () -> buildPickup()) + .disabled(b -> !(player.unit() instanceof Payloadc)); + + payloads.button(makeRegion("newcontrols-drop-payload"), Styles.flati, () -> input.tryDropPayload()) + .disabled(b -> !(player.unit() instanceof Payloadc) || !((Payloadc) player.unit()).hasPayload()); + }); + }, () -> graphics.isPortrait()); + } + + protected static void unitPickup() { + // todo migrate to kotlin if i'm ever going to continue this + // because java sucks. imagine not understanding that [self] is a (Unit & Payloadc). + if (!(player.unit() instanceof Payloadc pay)) return; + Unit self = player.unit(); + + Unit target = Units.closest(player.team(), self.x, self.y, 8f, u -> u != self && u.isGrounded() && pay.canPickup(u) && u.within(self, u.hitSize + 8f)); + + if (target != null) Call.requestUnitPayload(player, target); + } + + protected static void buildPickup() { + Unit self = player.unit(); + if (!(self instanceof Payloadc)) return; + + Building target = self.tileOn().build; + + if (target != null && ((Payloadc) self).canPickup(target)) { + Call.requestBuildPayload(player, target); + } + } + + protected static void payloadEnter() { + Unit self = player.unit(); + + Building build = world.buildWorld(self.x, self.y); + if (build != null && self.team() == build.team && build.canControlSelect(self)) { + Call.unitBuildingControlSelect(self, build); + } + } + + protected static TextureRegionDrawable makeRegion(String name) { + return new TextureRegionDrawable(atlas.find(name)); + } } diff --git a/src/newcontrols/ui/fragments/JoyStickFragment.java b/src/newcontrols/ui/fragments/JoyStickFragment.java new file mode 100644 index 0000000..27c3b12 --- /dev/null +++ b/src/newcontrols/ui/fragments/JoyStickFragment.java @@ -0,0 +1,55 @@ +package newcontrols.ui.fragments; + +import arc.scene.Group; +import newcontrols.NCVars; +import newcontrols.input.AiEnabler; +import newcontrols.ui.Joystick; + +/** Moved here to be independent of ActionPanel, Handles joystick rendering*/ +public class JoyStickFragment { + public boolean switchJoySticks = false; + + public void Build(Group parent){ + + //default movement joystick + parent.fill(table -> { + table.center(); + table.right(); + + table.collapser(c -> { + Joystick common = new Joystick(); + c.add(common).size(200).padRight(2f).padLeft(2f); + common.used(pos -> { + if (switchJoySticks) { + AiEnabler.ai.moveDir.set(pos); + }else { + AiEnabler.ai.shootDir.set(pos); + AiEnabler.ai.shoot = true; + } + }); + }, true, () -> NCVars.enabler.enabled && AiEnabler.ai.manualMode && (switchJoySticks || !AiEnabler.ai.autoAim)); + }); + + //default aim & shoot joystick + parent.fill(table -> { + table.center(); + table.left(); + + table.collapser(c -> { + ActionPanel.buildPortrait(c, AiEnabler.ai); + c.row(); + + Joystick common = new Joystick(); + c.add(common).size(200).padRight(2f).padLeft(2f); + common.used(pos -> { + if (!switchJoySticks) { + AiEnabler.ai.moveDir.set(pos); + }else { + AiEnabler.ai.shootDir.set(pos); + AiEnabler.ai.shoot = true; + } + }); + }, true, () -> NCVars.enabler.enabled && AiEnabler.ai.manualMode && (!switchJoySticks || !AiEnabler.ai.autoAim) ); + }); + } +} diff --git a/src/newcontrols/util/JSONBuild.java b/src/newcontrols/util/JSONBuild.java index 43c3e11..645c3a8 100644 --- a/src/newcontrols/util/JSONBuild.java +++ b/src/newcontrols/util/JSONBuild.java @@ -5,7 +5,6 @@ /** Simple string-only json constructor. Performs simple verification that prevents from creating invalid json objects. * Unused now... Had a use during private development. */ public class JSONBuild { - public enum Type { OBJECT, ARRAY }; public StringBuilder b = new StringBuilder(); @@ -136,5 +135,4 @@ protected int stackLast() { if (stack.size < 1) return 0; return stack.peek(); } - -} \ No newline at end of file +} diff --git a/src/newcontrols/util/LocalBlockIndexer.java b/src/newcontrols/util/LocalBlockIndexer.java new file mode 100644 index 0000000..5f3e507 --- /dev/null +++ b/src/newcontrols/util/LocalBlockIndexer.java @@ -0,0 +1,127 @@ +package newcontrols.util; + +import arc.Events; +import arc.math.Mathf; +import arc.math.geom.QuadTree; +import arc.math.geom.Rect; +import arc.struct.IntSeq; +import arc.struct.ObjectIntMap; +import arc.struct.Seq; +import mindustry.content.Blocks; +import mindustry.game.EventType; +import mindustry.game.Team; +import mindustry.gen.Building; +import mindustry.gen.Unit; +import mindustry.type.Item; +import mindustry.world.Tile; +import mindustry.world.blocks.environment.Floor; +import mindustry.world.blocks.environment.StaticWall; +import mindustry.world.meta.BlockFlag; + +import static mindustry.Vars.content; +import static mindustry.Vars.world; + +public class LocalBlockIndexer{ + public int quadWidth, quadHeight; + public IntSeq[][][] ores; + public Seq[][] flagMap = new Seq[Team.all.length][BlockFlag.all.length]; + public ObjectIntMap allOres = new ObjectIntMap<>(); + + public LocalBlockIndexer(){ + Events.on(EventType.WorldLoadEvent.class, event -> { + ores = new IntSeq[content.items().size][][]; + }); + + for(Tile tile : world.tiles){ + process(tile); + + var drop = tile.drop(); + + if(drop != null){ + int qx = (tile.x / 20); + int qy = (tile.y / 20); + + //add position of quadrant to list + if(tile.block() == Blocks.air){ + if(ores[drop.id] == null){ + ores[drop.id] = new IntSeq[quadWidth][quadHeight]; + } + if(ores[drop.id][qx][qy] == null){ + ores[drop.id][qx][qy] = new IntSeq(false, 16); + } + ores[drop.id][qx][qy].add(tile.pos()); + allOres.increment(drop); + } + } + } + } + private void process(Tile tile){ + var team = tile.team(); + //only process entity changes with centered tiles + if(tile.isCenter() && tile.build != null){ + var data = team.data(); + + if(tile.block().flags.size > 0 && tile.isCenter()){ + var map = flagMap[team.id]; + + for(BlockFlag flag : tile.block().flags.array){ + map[flag.ordinal()].add(tile.build); + } + } + + //record in list of buildings + data.buildings.add(tile.build); + data.buildingTypes.get(tile.block(), () -> new Seq<>(false)).add(tile.build); + + //update the unit cap when new tile is registered + data.unitCap += tile.block().unitCapModifier; + + //insert the new tile into the quadtree for targeting + if(data.buildingTree == null){ + data.buildingTree = new QuadTree<>(new Rect(0, 0, world.unitWidth(), world.unitHeight())); + } + data.buildingTree.insert(tile.build); + } + } + + public Tile findClosestWallOre(float xp, float yp, Item item){ + quadWidth = Mathf.ceil(world.width() / (float)20); + quadHeight = Mathf.ceil(world.height() / (float)20); + + if(ores[item.id] != null){ + float minDst = 0f; + Tile closest = null; + for(int qx = 0; qx < quadWidth; qx++){ + for(int qy = 0; qy < quadHeight; qy++){ + var arr = ores[item.id][qx][qy]; + if(arr != null && arr.size > 0){ + Tile tile = world.tile(arr.first()); + if(tile.block().itemDrop == item){ + float dst = Mathf.dst2(xp, yp, tile.worldx(), tile.worldy()); + if(closest == null || dst < minDst){ + closest = tile; + minDst = dst; + } + } + } + } + } + return closest; + } + //temp + return world.tile(0,0);//null; + } + public Tile findClosestWallOre(Unit unit, Item item){ + return findClosestWallOre(unit.x, unit.y, item); + } + + public static Seq getFloorWithItems(){ + //For anything things that aren't in ores like sand + return content.blocks().select(b -> b instanceof Floor).map(b -> b.itemDrop); + } + + public static Seq getWallWithItems(){ + //For anything things that aren't in ores like graphite + return content.blocks().select(b -> b instanceof StaticWall).map(b -> b.itemDrop); + } +}