diff --git a/annotations/src/main/java/omaloon/annotations/lombok/autoimpl/ImplProcessor.java b/annotations/src/main/java/omaloon/annotations/lombok/autoimpl/ImplProcessor.java index 358fe4d7..86853dfc 100644 --- a/annotations/src/main/java/omaloon/annotations/lombok/autoimpl/ImplProcessor.java +++ b/annotations/src/main/java/omaloon/annotations/lombok/autoimpl/ImplProcessor.java @@ -5,6 +5,7 @@ import bytelogic.lombok.util.*; import com.sun.tools.javac.code.*; import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; import lombok.*; import lombok.core.*; import lombok.javac.*; @@ -112,7 +113,8 @@ public void visitType(JavacNode typeNode, JCTree.JCClassDecl type){ if(!autoImplClasses.contains(canonicalFullname(typeNode))) return; InfoAndPos[] interfaceInfos = StreamEx - .of(type.implementing) + .ofNullable(type.implementing) + .flatMap(StreamEx::of) .mapToEntry(Function.identity()) .mapKeys(Object::toString) .filterKeys(string -> !string.contains("<")) //TODO Generic implement @@ -120,6 +122,7 @@ public void visitType(JavacNode typeNode, JCTree.JCClassDecl type){ .mapKeys(CollectedHierarchyInfo::interfaceInfo) .nonNullKeys() .mapKeys(simpleInterfaceToAutoImpl::get) + .nonNullKeys() .flatMapKeys(StreamEx::of) .distinctKeys() .mapKeyValue(InfoAndPos::new) @@ -135,19 +138,15 @@ public void visitType(JavacNode typeNode, JCTree.JCClassDecl type){ private void implement(InterfaceInfo info, @NonNull JCTree.JCExpression producer, JavacNode typeNode, JCTree.JCClassDecl type){ AutoImplInformation information = info.get(ImplMarker.isAutoImpl); - Map>> childrenMap = StreamEx + Map> childrenMap = StreamEx .of(typeNode.down().iterator()) .mapToEntry(LombokNode::getKind, Function.identity()) .sortedBy(Map.Entry::getKey) .collapseKeys() - .mapValues(StreamEx::of) - .mapValues(it -> it.mapToEntry(JavacNode::getName, Function.identity())) - .mapValues(it -> it.collapseKeys().sortedBy(Map.Entry::getKey).toMap()) - .toMap(); - Map fields = EntryStream - .of(childrenMap.getOrDefault(AST.Kind.FIELD, new HashMap<>())) - .mapValues(it -> it.get(0)) .toMap(); + Map fields = StreamEx + .of(childrenMap.getOrDefault(AST.Kind.FIELD, Collections.emptyList())) + .toMap(JavacNode::getName,Function.identity()); for(Map.Entry entry : information.fields.entrySet()){ JavacNode existed = fields.get(entry.getKey()); AutoImplInformation.FieldInfo fieldInfo = entry.getValue(); @@ -184,7 +183,6 @@ private void implement(InterfaceInfo info, @NonNull JCTree.JCExpression producer Map methods = EntryStream .of(childrenMap.get(AST.Kind.METHOD)) .values() - .flatMap(StreamEx::of) .mapToEntry(it -> Util.methodDesc(it, (JCTree.JCMethodDecl)it.get()), Function.identity()) .toMap(); diff --git a/build.gradle.kts b/build.gradle.kts index 7631ff10..9b3f59a1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,8 @@ import arc.util.OS import arc.util.serialization.Jval import de.undercouch.gradle.tasks.download.Download import ent.EntityAnnoExtension +import mmc.JarMindustryTask +import org.jetbrains.kotlin.gradle.plugin.KaptExtension import java.io.BufferedWriter buildscript { @@ -36,6 +38,7 @@ val asmLib: (String) -> Any = { val arcVersion: String by project val arcLibraryVersion: String by project +val zelauxCoreVersion: String by project val mindustryVersion: String by project val mindustryBEVersion: String by project val entVersion: String by project @@ -57,7 +60,11 @@ fun arc(module: String): String { } fun arcLibrary(module: String): String { - return "com.github.Zelaux.ArcLibrary$module:$arcLibraryVersion" + return "com.github.Zelaux.ArcLibrary:${module.trim(':').replace(':', '-')}:$arcLibraryVersion" +} + +fun zelauxCore(module: String): String { + return "com.github.Zelaux.MindustryModCore:${module.trim(':').replace(':', '-')}:$zelauxCoreVersion" } fun mindustry(module: String): String { @@ -70,8 +77,13 @@ fun entity(module: String): String { extra.set("asmLib", asmLib) project(":") { + apply { + from("tests/setup-tests.gradle") + } apply(plugin = "java") sourceSets["main"].java.setSrcDirs(listOf(layout.projectDirectory.dir("src"))) + sourceSets["test"].java.setSrcDirs(listOf(layout.projectDirectory.dir("tests/test"))) + sourceSets["test"].resources.setSrcDirs(listOf(layout.projectDirectory.dir("tests/resources"))) configurations.configureEach { // Resolve the correct Mindustry dependency, and force Arc version. @@ -117,6 +129,13 @@ project(":") { } project(":") { + //sometimes task checkKotlinGradlePluginConfigurationErrors are missing... +// tasks.register("checkKotlinGradlePluginConfigurationErrors1"){ } + tasks.register("mindustryJar", JarMindustryTask::class) { + dependsOn(tasks.getByPath("jar")) + group = "build" + } + apply(plugin = "com.github.GlennFolker.EntityAnno") configure { modName = project.properties["modName"].toString() @@ -127,6 +146,13 @@ project(":") { genSrcPackage = modGenSrc genPackage = modGen } + configure { + arguments { + arg("ROOT_DIRECTORY", project.rootDir.canonicalPath) + arg("rootPackage", "ol") + arg("classPrefix", "Ol") + } + } //Added debuging diring compilation to debug annotation processors tasks.withType(JavaCompile::class).configureEach { @@ -139,21 +165,39 @@ project(":") { ) } dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") annotationProcessor("org.projectlombok:lombok:1.18.32") annotationProcessor(asmLib("annotations:debug-print")) annotationProcessor(project(":annotations")) // Use the entity generation annotation processor. - compileOnly(entity(":entity")) - add("kapt", entity(":entity")) - - compileOnly("org.jetbrains:annotations:24.0.1") - - compileOnly(mindustry(":core")) - compileOnly(arc(":arc-core")) - implementation(arcLibrary(":graphics-draw3d")) - implementation(arcLibrary(":graphics-dashDraw")) - implementation(arcLibrary(":graphics-extendedDraw")) + var kaptAnno = listOf( + entity(":entity"), + zelauxCore(":annotations:remote") + ) + kaptAnno.forEach { + compileOnly(it) { + this.isTransitive = false; + } + add("kapt", it) + } + arrayOf( + "org.jetbrains:annotations:24.0.1", + mindustry(":core"), + arc(":arc-core"), + ).forEach { + compileOnly(it); + testImplementation(it); + } + arrayOf( + arcLibrary(":graphics:drawText"), + arcLibrary(":graphics-draw3d"), + arcLibrary(":graphics-dashDraw"), + arcLibrary(":graphics-extendedDraw"), + ).forEach { + implementation(it) + testImplementation(it) + } } val jar = tasks.named("jar") { diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..985c8324 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,22 @@ + + +apply plugin: 'groovy' +apply plugin: 'java' + + +repositories{ + mavenCentral() + maven { url 'https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository' } +} +println "root: "+getRootDir() +dependencies{ + compileOnly gradleApi() + compileOnly localGroovy() + compileOnly("org.jetbrains:annotations:24.0.1") + implementation 'commons-io:commons-io:2.6' + + + + compileOnly 'org.projectlombok:lombok:1.18.30' + annotationProcessor 'org.projectlombok:lombok:1.18.30' +} \ No newline at end of file diff --git a/buildSrc/build/libs/buildSrc.jar b/buildSrc/build/libs/buildSrc.jar new file mode 100644 index 00000000..6ada4090 Binary files /dev/null and b/buildSrc/build/libs/buildSrc.jar differ diff --git a/buildSrc/build/tmp/compileJava/compileTransaction/stash-dir/JarMindustryTask.class.uniqueId0 b/buildSrc/build/tmp/compileJava/compileTransaction/stash-dir/JarMindustryTask.class.uniqueId0 new file mode 100644 index 00000000..ac010ade Binary files /dev/null and b/buildSrc/build/tmp/compileJava/compileTransaction/stash-dir/JarMindustryTask.class.uniqueId0 differ diff --git a/buildSrc/build/tmp/compileJava/previous-compilation-data.bin b/buildSrc/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 00000000..dac769c1 Binary files /dev/null and b/buildSrc/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/buildSrc/build/tmp/jar/MANIFEST.MF b/buildSrc/build/tmp/jar/MANIFEST.MF new file mode 100644 index 00000000..59499bce --- /dev/null +++ b/buildSrc/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/buildSrc/gradle/wrapper/gradle-wrapper.jar b/buildSrc/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..94336fca Binary files /dev/null and b/buildSrc/gradle/wrapper/gradle-wrapper.jar differ diff --git a/buildSrc/gradle/wrapper/gradle-wrapper.properties b/buildSrc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..84a0b92f --- /dev/null +++ b/buildSrc/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/buildSrc/gradlew b/buildSrc/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /dev/null +++ b/buildSrc/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/buildSrc/gradlew.bat b/buildSrc/gradlew.bat new file mode 100644 index 00000000..f9553162 --- /dev/null +++ b/buildSrc/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/buildSrc/src/main/java/mmc/JarMindustryTask.java b/buildSrc/src/main/java/mmc/JarMindustryTask.java new file mode 100644 index 00000000..4a9f4133 --- /dev/null +++ b/buildSrc/src/main/java/mmc/JarMindustryTask.java @@ -0,0 +1,78 @@ +package mmc; + +import org.apache.commons.io.*; +import org.gradle.api.*; +import org.gradle.api.tasks.*; +import org.gradle.api.tasks.bundling.*; +import org.jetbrains.annotations.*; +import org.slf4j.*; + +import java.io.*; +import java.nio.charset.*; +import java.util.*; + +public class JarMindustryTask extends DefaultTask{ + static final Logger logger = LoggerFactory.getLogger(JarMindustryTask.class); + + public static String defaultMindustryPath(){ + String mindustryDataDir = System.getenv("MINDUSTRY_DATA_DIR"); + if(mindustryDataDir != null) return mindustryDataDir; + return MockOS.getAppDataDirectoryString("Mindustry"); + } + + + private static String[] parseOutputs(@Nullable File outputFile) throws IOException{ + if(outputFile == null || !outputFile.exists()) return new String[]{defaultMindustryPath()}; + String[] lines = FileUtils.readFileToString(outputFile, Charset.defaultCharset()) + .replace(System.lineSeparator(), "\n") + .replaceAll("((#|//)[^\n]*\n|\n?(#|//)[^\n]*$)", "")//removing comments + .replaceAll("\n\n+", "")//removing blank lines + .split("\n"); + for(int i = 0; i < lines.length; i++){ + if(lines[i].equals("classic")){ + lines[i] = defaultMindustryPath() + File.pathSeparator + "mods"; + } + } + return lines; + } + + @NotNull + private static File resultJar(Project project, AbstractArchiveTask jar){ + if(true) return jar.getArchiveFile().get().getAsFile(); + String archiveFileName = jar.getArchiveFileName().get(); + + return new File(project.getBuildDir(), "libs/" + archiveFileName); + } + + @TaskAction + public void copyResultJar() throws IOException{ + Project project = getProject(); + AbstractArchiveTask jar = (AbstractArchiveTask)project.getTasks().getByName("jar"); + File source = resultJar(project, jar); + String[] strings = parseOutputs(findOutputFile(project)); + for(String path : strings){ + File destination = new File(project.file(path), source.getName()); + if(destination.exists()){ + //noinspection ResultOfMethodCallIgnored + destination.delete(); + } + FileUtils.copyFile(source, destination); + System.out.println("[I] Jar copied to `" + destination.getAbsolutePath() + "`"); + } + } + + @Nullable + private File findOutputFile(@Nullable Project project){ + if(project == null) return null; + File file = project.file("outputDirectories.txt"); + if(file.exists()) return file; + file = project.file("modsDirectories.txt"); + if(file.exists()){ + logger.warn("`modsDirectories.txt` is deprecated, use `outputDirectories.txt`"); + return file; + } + Project parent = project.getParent(); + if(parent == project) return null;//VERY, VERY strange, but I think can happen. + return findOutputFile(parent); + } +} diff --git a/buildSrc/src/main/java/mmc/MockOS.java b/buildSrc/src/main/java/mmc/MockOS.java new file mode 100644 index 00000000..e581ab91 --- /dev/null +++ b/buildSrc/src/main/java/mmc/MockOS.java @@ -0,0 +1,40 @@ +package mmc; + +public class MockOS{ + + /** User's home directory. */ + public static final String userHome = prop("user.home"); + public static boolean isWindows = propNoNull("os.name").contains("Windows"); + public static boolean isLinux = propNoNull("os.name").contains("Linux") || propNoNull("os.name").contains("BSD"); + public static boolean isMac = propNoNull("os.name").contains("Mac"); + + public static String env(String name){ + return System.getenv(name); + } + + public static String propNoNull(String name){ + String s = prop(name); + return s == null ? "" : s; + } + + public static String prop(String name){ + return System.getProperty(name); + } + + public static String getAppDataDirectoryString(String appname){ + if(MockOS.isWindows){ + return env("AppData") + "\\" + appname; + }else if(isLinux){ + if(System.getenv("XDG_DATA_HOME") != null){ + String dir = System.getenv("XDG_DATA_HOME"); + if(!dir.endsWith("/")) dir += "/"; + return dir + appname + "/"; + } + return userHome + "/.local/share/" + appname + "/"; + }else if(isMac){ + return userHome + "/Library/Application Support/" + appname + "/"; + }else{ //else, probably web + return ""; + } + } +} diff --git a/gradle.properties b/gradle.properties index 350f3752..90771531 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,6 +27,7 @@ mindustryBEVersion = arcVersion = v146 # Arc library version, has some things that omaloon needs arcLibraryVersion = v1.0.7 +zelauxCoreVersion = v2.0.3b ##### Android SDK configuration for building Android artifacts. # Android platform SDK version. diff --git a/revisions/DroneUnit/1.json b/revisions/DroneUnit/1.json new file mode 100644 index 00000000..673baa4b --- /dev/null +++ b/revisions/DroneUnit/1.json @@ -0,0 +1 @@ +{version:1,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:ownerID,type:int},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/revisions/DroneUnit/2.json b/revisions/DroneUnit/2.json new file mode 100644 index 00000000..b0ac8276 --- /dev/null +++ b/revisions/DroneUnit/2.json @@ -0,0 +1 @@ +{version:2,fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:abilityIndex,type:int},{name:ammo,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:ownerID,type:int},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/revisions/FlyingUnit/0.json b/revisions/FlyingUnit/0.json new file mode 100644 index 00000000..545bb6c8 --- /dev/null +++ b/revisions/FlyingUnit/0.json @@ -0,0 +1 @@ +{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/revisions/LegsUnit/0.json b/revisions/LegsUnit/0.json new file mode 100644 index 00000000..545bb6c8 --- /dev/null +++ b/revisions/LegsUnit/0.json @@ -0,0 +1 @@ +{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/revisions/UnitEntity/0.json b/revisions/UnitEntity/0.json new file mode 100644 index 00000000..545bb6c8 --- /dev/null +++ b/revisions/UnitEntity/0.json @@ -0,0 +1 @@ +{fields:[{name:abilities,type:"mindustry.entities.abilities.Ability[]"},{name:ammo,type:float},{name:controller,type:mindustry.entities.units.UnitController},{name:elevation,type:float},{name:flag,type:double},{name:health,type:float},{name:isShooting,type:boolean},{name:mineTile,type:mindustry.world.Tile},{name:mounts,type:"mindustry.entities.units.WeaponMount[]"},{name:plans,type:arc.struct.Queue},{name:rotation,type:float},{name:shield,type:float},{name:spawnedByCore,type:boolean},{name:stack,type:mindustry.type.ItemStack},{name:statuses,type:arc.struct.Seq},{name:team,type:mindustry.game.Team},{name:type,type:mindustry.type.UnitType},{name:updateBuilding,type:boolean},{name:vel,type:arc.math.geom.Vec2},{name:x,type:float},{name:y,type:float}]} \ No newline at end of file diff --git a/src/omaloon/OmaloonMod.java b/src/omaloon/OmaloonMod.java index 92698be0..94f157cc 100644 --- a/src/omaloon/OmaloonMod.java +++ b/src/omaloon/OmaloonMod.java @@ -8,15 +8,18 @@ import mindustry.game.*; import mindustry.mod.*; import mindustry.type.*; +import ol.gen.OlCall; import omaloon.content.*; import omaloon.core.*; import omaloon.gen.*; import omaloon.graphics.*; +import omaloon.net.*; import omaloon.ui.*; import omaloon.ui.dialogs.*; import omaloon.ui.fragments.*; import omaloon.utils.*; import omaloon.world.blocks.environment.*; +import omaloon.world.save.OlDelayedItemTransfer; import static arc.Core.app; @@ -39,7 +42,8 @@ public class OmaloonMod extends Mod{ public OmaloonMod(){ super(); - + OlCall.registerPackets(); + new OlDelayedItemTransfer(); Events.on(EventType.ClientLoadEvent.class, e -> { StartSplash.build(Vars.ui.menuGroup); StartSplash.show(); @@ -55,6 +59,14 @@ public OmaloonMod(){ } return false; }); + Core.app.addListener(new ApplicationListener(){ + @Override + public void update(){ + if(Core.input.keyTap(OlBinding.switchDebugDraw)){ + DebugDraw.switchEnabled(); + } + } + }); }); @@ -83,9 +95,20 @@ public OmaloonMod(){ OlShaders.dispose() ); + Log.info("Loaded OmaloonMod constructor."); } + @Override + public void registerServerCommands(CommandHandler handler){ + OlServer.registerServerCommands(handler); + } + + @Override + public void registerClientCommands(CommandHandler handler){ + OlServer.registerClientCommands(handler); + } + @Override public void init(){ super.init(); diff --git a/src/omaloon/ai/DroneAI.java b/src/omaloon/ai/DroneAI.java index fca9f3d7..5ea443f7 100644 --- a/src/omaloon/ai/DroneAI.java +++ b/src/omaloon/ai/DroneAI.java @@ -6,16 +6,32 @@ import mindustry.gen.*; public class DroneAI extends AIController{ + public static final float maxAnchorDst = 5f; + public static final float minAnchorDst = 2f; + public static final float maxAnchorDst2 = maxAnchorDst * maxAnchorDst; + public static final float minAnchorDst2 = minAnchorDst * minAnchorDst; protected Unit owner; - protected Vec2 anchorPos; + protected Vec2 anchorPos = new Vec2(); protected PosTeam posTeam; public DroneAI(Unit owner){ this.owner = owner; - this.anchorPos = new Vec2(); this.posTeam = PosTeam.create(); } + @Override + public void updateVisuals(){ + if(this.unit.isFlying()){ + this.unit.wobble(); + + this.unit.lookAt(prefRotation()); + } + } + + public float prefRotation(){ + return unit.rotation; + } + @Override public void updateUnit(){ if(!owner.isValid()){ @@ -31,21 +47,30 @@ public void updateMovement(){ } public void rally(Vec2 pos){ - anchorPos = pos; + anchorPos.set(pos); } public void rally(){ - Tmp.v2.set(owner.x, owner.y); - Vec2 targetPos = Tmp.v1.set(anchorPos).add(Tmp.v2).rotateAround(Tmp.v2, owner.rotation - 90); - - float distance = unit.dst(targetPos); + Vec2 targetPos = Tmp.v1 + .set(anchorPos) + .rotate(owner.rotation - 90) + .add(owner); - moveTo(targetPos, 2f, 30f); + float distance2 = unit.dst2(targetPos); + float pref = unit.rotation; + moveTo(targetPos, minAnchorDst, 30f); - if(distance > 5f){ - unit.lookAt(targetPos.x, targetPos.y); - }else{ + if(distance2 <= maxAnchorDst2){ + unit.rotation=pref; unit.lookAt(owner.rotation()); + }else{ + if(unit.moving() && unit.type.omniMovement){ + unit.lookAt(unit.vel().angle()); + } } } + + public void updateFromClient(){ +//TODO some sync command, to detect is DroneAI on server + } } diff --git a/src/omaloon/ai/MillipedeAI.java b/src/omaloon/ai/MillipedeAI.java index b85c8d4d..c7179d72 100644 --- a/src/omaloon/ai/MillipedeAI.java +++ b/src/omaloon/ai/MillipedeAI.java @@ -8,6 +8,7 @@ import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.type.*; +import omaloon.gen.*; public class MillipedeAI extends GroundAI{ protected Vec2 commandPosition = new Vec2(); diff --git a/src/omaloon/ai/drone/AttackDroneAI.java b/src/omaloon/ai/drone/AttackDroneAI.java index 00682a37..b415476b 100644 --- a/src/omaloon/ai/drone/AttackDroneAI.java +++ b/src/omaloon/ai/drone/AttackDroneAI.java @@ -1,46 +1,139 @@ package omaloon.ai.drone; +import arc.graphics.g2d.*; +import arc.math.geom.*; +import arc.util.*; +import arclibrary.graphics.*; import mindustry.gen.*; +import mindustry.graphics.*; +import mindustry.ui.*; import omaloon.ai.*; +import omaloon.math.*; +import omaloon.utils.*; +import static mindustry.Vars.tilesize; + +/** + * @author Zelaux + */ public class AttackDroneAI extends DroneAI{ + + public static final float SMOOTH = 30f; + public static final float POINT_RADIUS = 1f; + public AttackDroneAI(Unit owner){ super(owner); } @Override public void updateMovement(){ - if(owner.isShooting()){ - if(unit.hasWeapons()){ - posTeam.set(owner.aimX(), owner.aimY()); - - float distanceToTarget = unit.dst(posTeam); - float distanceToOwner = unit.dst(owner); - - if(distanceToOwner < owner.range()){ - moveTo(posTeam, unit.type().range * 0.75f, 30f); - }else{ - moveTo(owner, owner.range() * 0.95f, 30f); - if(distanceToTarget > unit.type().range){ - unit.lookAt(posTeam); - unit.controlWeapons(true, true); + float clearOwnerRange = owner.type.range; + float realRange = clearOwnerRange + unit.type.range; + if(!unit.hasWeapons() || !isOwnerShooting()){ + rally(); + return; + } + + + posTeam.set(owner.aimX(), owner.aimY()); + + float range = unit.type().range; + float moveRange = range * 0.75f; + float posToOwnerDst2 = posTeam.dst2(owner); + float safeOwnerRange = clearOwnerRange - range; + + + if(DebugDraw.isDraw()){ + DebugDraw.request(Layer.end, () -> { + Draw.color(Pal.negativeStat); + Lines.circle(owner.x, owner.y, clearOwnerRange); + Draw.color(Pal.health); + Lines.circle(owner.x, owner.y, realRange); + + Drawf.target(posTeam.x, posTeam.y, 6f, Pal.accent); + + Draw.color(Pal.removeBack); + Lines.circle(unit.x, unit.y, range); + Lines.circle(unit.x, unit.y, moveRange); + + + Draw.color(Pal.thoriumPink); + Lines.circle(posTeam.x, posTeam.y, range); + Lines.circle(posTeam.x, posTeam.y, moveRange); + Draw.color(Pal.items); + Lines.circle(owner.x, owner.y, safeOwnerRange); + }); + } + if(posToOwnerDst2 < safeOwnerRange * safeOwnerRange){ + moveTo(posTeam, moveRange, SMOOTH); + }else{ +// float unitToTarget2 = unit.dst2(posTeam); + + boolean isNewPosNear = owner.within( + Tmp.v1 + .set(unit) + .sub(posTeam) + .nor() + .scl(moveRange) + .add(posTeam), + clearOwnerRange); + if(/*unitToTarget2 >= moveRange * moveRange &&*/ isNewPosNear){ + moveTo(posTeam, moveRange, SMOOTH); + }else{ + Vec2 output = Tmp.v2; + if(!OlGeometry.intersectionPoint( + owner, clearOwnerRange, posTeam, moveRange, Tmp.v1.set(unit).sub(posTeam), output + )){ + + output + .set(posTeam) + .sub(owner) + .nor() + .scl(clearOwnerRange) + .add(owner); + if(DebugDraw.isDraw()){ + DebugDraw.request(Layer.end, () -> { + DrawText.defaultFont = Fonts.def; + Draw.color(Pal.items, 1f); + DrawText.drawText(owner.x, owner.y + tilesize * 2, "-^-"); + }); } + }else if(DebugDraw.isDraw()){ + float x = output.x; + float y = output.y; + DebugDraw.request(Layer.end, () -> { + DrawText.defaultFont = Fonts.def; + Draw.color(Pal.spore, 1f); + DrawText.drawText(owner.x, owner.y + tilesize, Tmp.v1.set(x, y).toString()); + }); } - - unit.lookAt(posTeam); + if(DebugDraw.isDraw()){ + float x = output.x; + float y = output.y; + DebugDraw.request(Layer.end, () -> { + Draw.color(Pal.spore); + EFill.polyCircle(x, y, POINT_RADIUS); + }); + } + moveTo(output, POINT_RADIUS, SMOOTH); } - }else{ - rally(); + } + unit.lookAt(posTeam); + unit.controlWeapons(true, true); + } + + private boolean isOwnerShooting(){ + return owner.isShooting() || owner.controller() instanceof Player player && player.shooting; } @Override public Teamc target(float x, float y, float range, boolean air, boolean ground){ - return (!owner.isValid() && !owner.isShooting()) ? null : posTeam; + return (!owner.isValid() && !isOwnerShooting()) ? null : posTeam; } @Override public boolean shouldShoot(){ - return owner.isShooting(); + return isOwnerShooting(); } } \ No newline at end of file diff --git a/src/omaloon/ai/drone/UtilityDroneAI.java b/src/omaloon/ai/drone/UtilityDroneAI.java index 0f175bf3..334f8c74 100644 --- a/src/omaloon/ai/drone/UtilityDroneAI.java +++ b/src/omaloon/ai/drone/UtilityDroneAI.java @@ -1,96 +1,379 @@ package omaloon.ai.drone; -import arc.math.*; +import arc.graphics.g2d.*; +import arc.math.geom.*; +import arc.struct.*; import arc.util.*; +import arclibrary.graphics.*; import mindustry.*; +import mindustry.content.*; import mindustry.entities.units.*; -import mindustry.game.*; import mindustry.gen.*; +import mindustry.graphics.*; +import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.*; +import mindustry.world.blocks.ConstructBlock.*; +import mindustry.world.blocks.storage.*; +import mindustry.world.blocks.storage.CoreBlock.*; +import ol.gen.*; import omaloon.ai.*; +import omaloon.gen.*; +import omaloon.math.*; +import omaloon.utils.*; +import org.intellij.lang.annotations.*; +import org.jetbrains.annotations.*; +import static mindustry.Vars.*; + +/** + * @author Zelaux + */ public class UtilityDroneAI extends DroneAI{ + public static final float SMOOTH = 30f; + public static final Vec2 PUBLIC_TMP_TO_OUT = Tmp.v3; + public static final float POINT_CIRCLE_LENGHT = 1f; + protected final Vec2 tmpCalculatedPosition = new Vec2(); public float mineRangeScl = 0.75f; public float buildRangeScl = 0.75f; - - public @Nullable Teams.BlockPlan lastPlan; + public float buildRangeSclInv = 1 - buildRangeScl; + @MagicConstant(valuesFromClass = BuildState.class) + protected int buildPositionState = BuildState.unset; public UtilityDroneAI(Unit owner){ super(owner); } + private static float rangeOrInfinite(float buildRange){ + return state.rules.infiniteResources ? Float.MAX_VALUE : buildRange; + } + + @Override + public void updateFromClient(){ + tryBuildMultiple(false); + } + @Override public void updateMovement(){ - unit.updateBuilding = true; + unit.updateBuilding = false; + unit.mineTile = null; + tryTransportItems(); + + if(tryBuildMultiple(true)) return; + if(tryMine()) return; + rally(); + + } - if(owner.activelyBuilding()){ - unit.plans.clear(); + @Override + public void unit(Unit unit){ + super.unit(unit); + buildPositionState = BuildState.unset; + } - BuildPlan plan = owner.buildPlan(); - if(!unit.plans.contains(plan)) unit.plans.addFirst(plan); - lastPlan = null; + private boolean tryBuildMultiple(boolean isServer){ + Dronec drone = (Dronec)unit; + boolean hasBuild = false; + float buildCounter = drone.buildCounter(); + buildCounter += Time.delta; + float counter = 1 - Time.delta; + if(buildCounter < 1f){ + hasBuild |= tryBuild(drone, false,isServer); + } + for(int i = 0; i < buildCounter; i++){ + drone.buildCounter(counter); + hasBuild |= tryBuild(drone, true,isServer); + buildCounter -= 1f; + } - if(unit.buildPlan() != null){ - BuildPlan req = unit.buildPlan(); + drone.buildCounter(buildCounter); + return hasBuild; + } - if(!req.breaking && timer.get(timerTarget2, 40f)){ - for(Player player : Groups.player){ - if(player.isBuilder() && player.unit().activelyBuilding() && player.unit().buildPlan().samePos(req) && player.unit().buildPlan().breaking){ - unit.plans.removeFirst(); - unit.team.data().plans.remove(p -> p.x == req.x && p.y == req.y); - return; - } + + private void tryTransportItems(){ + if(unit.stack.amount <= 0) return; + + CoreBlock.CoreBuild core = unit.closestCore(); + + if(core != null && !unit.within(core, owner.type.range)){ + core = owner.closestCore(); + if(owner.within(core, mineTransferRange)){ + OlCall.chainTransfer(unit.stack.item, unit.x, unit.y, owner, core); + }else{ + for(int i = 0; i < unit.stack.amount; i++){ + Call.transferItemToUnit(unit.stack.item, unit.x, unit.y, owner); + } + } + }else{ + Call.transferItemTo(unit, unit.stack.item, unit.stack.amount, unit.x, unit.y, core); + } + unit.clearItem(); + } + + private boolean tryBuild(Dronec drone, boolean shouldReallyBuild, boolean isServerInvoke){ + Queue prev = unit.plans; + prev.clear(); + + Queue plans = owner.plans; + if(plans.isEmpty()) return false; + if(!owner.updateBuilding) return false; + + CoreBlock.CoreBuild core = unit.team.core(); + + + int totalSkipped = 0; + if(DebugDraw.isDraw()){ + DrawText.defaultFont = Fonts.def; + DebugDraw.request(Layer.end, () -> { + Draw.color(Pal.heal); + Lines.circle(owner.x, owner.y, owner.type.buildRange); + Draw.color(Pal.berylShot); + Lines.circle(unit.x, unit.y, unit.type.buildRange); + EFill.polyCircle(unit.x, unit.y, Vars.tilesize / 4f); + }); + for(int i = 0; i < plans.size; i++){ + BuildPlan plan = plans.get(i); + int i1 = i; + DebugDraw.request(Layer.end, () -> { + DrawText.drawText(plan, "" + i1); + }); + } + } + final float ownerRange = rangeOrInfinite(owner.type.buildRange); + + //IMPORTANT unit.plans.size must be 0 + for(int i = 0; i < plans.size; i++){ + BuildPlan buildPlan = plans.first(); + if(canBuild(buildPlan, core, ownerRange)) + break; + plans.removeFirst(); + if(DebugDraw.isDraw()) Fx.fireSmoke.at(buildPlan); + plans.addLast(buildPlan); + totalSkipped++; + } + @NotNull + var currentPlan = plans.first(); + + + boolean withinOwner = owner.within(currentPlan, ownerRange); + boolean isConstructing = withinOwner && currentPlan.tile().build instanceof ConstructBuild; + //not enough resources +// if(totalSkipped == plans.size && !isConstructing) +// return false; + + float myRange = unit.type.buildRange; + float moveToRange = myRange * buildRangeScl; + + + if(isServerInvoke){ + if(unit.within(currentPlan, rangeOrInfinite(myRange))){ + unit.lookAt(currentPlan); + }else{ + unit.lookAt(unit.vel().angle()); + } + if(!state.rules.infiniteResources){ + if(isCachedBuilding()){ + if(!canBuild(currentPlan, myRange)){ + buildPositionState = BuildState.unset; } } + if(isNotCachedBuilding()){ + label: + { + if(plans.size <= 1){ + moveTo(currentPlan, moveToRange, SMOOTH); + break label; + } + - boolean valid = !(lastPlan != null && lastPlan.removed) && - ((req.tile() != null && req.tile().build instanceof ConstructBlock.ConstructBuild cons && cons.current == req.block) || - (req.breaking ? Build.validBreak(unit.team(), req.x, req.y) : - Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation))); + for(int i = 1; i < plans.size; i++){ + BuildPlan next = plans.get(i); + if(!canBuild(next, core, ownerRange)) continue; + BuildPlan next2 = i + 1 < plans.size ? plans.get(i + 1) : null; + Vec2 direction = Tmp.v4.set(unit).sub(currentPlan); + buildPositionState = BuildState.buildingPair; + if(canBuild(next2, core, ownerRange)){ + if(OlGeometry.calculateCircle( + Tmp.v1.set(currentPlan), + Tmp.v2.set(next), + Tmp.v3.set(next2), + Tmp.cr1 + )){ + if(DebugDraw.isDraw()){ + float x1 = Tmp.cr1.x; + float y1 = Tmp.cr1.y; + float radius1 = Tmp.cr1.radius; + float x2 = Tmp.v3.set(next2).x; + float y2 = Tmp.v3.y; + DebugDraw.request(Layer.end, () -> { + Draw.color(radius1 < moveToRange ? Pal.ammo : Pal.negativeStat); + Lines.circle(x1, y1, radius1); + Draw.color(Pal.lancerLaser); + Lines.circle(x2, y2, moveToRange); + }); + } + if(Tmp.cr1.radius < moveToRange){ + buildPositionState = BuildState.building3; + tmpCalculatedPosition.set(Tmp.cr1.x, Tmp.cr1.y); + moveTo(tmpCalculatedPosition, POINT_CIRCLE_LENGHT, SMOOTH); + break label; + } + } - if(valid){ - moveTo(req.tile(), unit.type.buildRange * buildRangeScl, 30f); + direction.set(next2).sub(currentPlan); + } + Vec2 out = PUBLIC_TMP_TO_OUT; + + resolveMidPosition(currentPlan, next, moveToRange, out, direction); + moveTo(out, POINT_CIRCLE_LENGHT, SMOOTH); + tmpCalculatedPosition.set(out); + + break label; + } + moveTo(currentPlan, moveToRange, SMOOTH); + } + if(!canBuild(currentPlan, myRange)) + return true; }else{ - unit.plans.removeFirst(); - lastPlan = null; + moveTo(tmpCalculatedPosition, POINT_CIRCLE_LENGHT, SMOOTH); } } }else{ - unit.plans.clear(); - if(owner.mineTile() != null && owner.stack.amount != owner.type.itemCapacity && - ((owner.getMineResult(owner.mineTile) == owner.stack.item && owner.stack.amount > 0) || - (owner.stack.amount == 0))){ - Tmp.v1.set(owner.mineTile.worldx(), owner.mineTile.worldy()); - if(unit.dst(Tmp.v1) <= unit.type.mineRange) unit.mineTile = owner.mineTile; - moveTo(Tmp.v1, unit.type.mineRange * mineRangeScl, 30f); - }else{ - unit.mineTile = null; - rally(); - } + if(!canBuild(currentPlan, myRange)) + return true; + } + if(!shouldReallyBuild) return true; + unit.plans = plans; + unit.updateBuilding = true; + int wasSize = plans.size; + unit.updateBuildLogic(); + sync: + { +// if(!net.server()) break sync; +// if(!(currentPlan.tile().build instanceof ConstructBuild entity)) break sync; +// float bs = 1f / entity.buildCost * unit.type.buildSpeed * unit.buildSpeedMultiplier * state.rules.buildSpeed(unit.team); +// OlCall.utilityDroneSyncBuilding(owner,unit, currentPlan, core, bs); } - if(unit.stack.amount > 0){ - if(!unit.within(unit.closestCore(), owner.type.range) && unit.closestCore() != null){ - for(int i = 0; i < unit.stack.amount; i++){ - Call.transferItemToUnit(unit.stack.item, unit.x, unit.y, owner); - } + if(isServerInvoke){ + boolean isFirst = plans.size != 0 && plans.first() == currentPlan; + boolean isProcessFinished = currentPlan.breaking ? currentPlan.progress == 0f : currentPlan.progress == 1f; + if(isFirst && isProcessFinished){ + plans.removeFirst(); + } + int curSize = plans.size; + //noinspection UnnecessaryLocalVariable + boolean finished = isProcessFinished; + if(!finished){ + unit.lookAt(currentPlan); }else{ - Call.transferItemTo(unit, unit.stack.item, unit.stack.amount, unit.x, unit.y, unit.closestCore()); + if((buildPositionState == BuildState.buildingPair || buildPositionState == BuildState.building2Of3) && wasSize >= 2){ + buildPositionState = BuildState.buildingLastBuildOfPair; + }else if(buildPositionState == BuildState.building3 && wasSize >= 3){ + buildPositionState = BuildState.building2Of3; + }else if(buildPositionState == BuildState.buildingLastBuildOfPair){ + buildPositionState = BuildState.unset; + }else{ + buildPositionState = BuildState.unset; + } + + } + if(!state.rules.infiniteResources && currentPlan.progress <= 1){ + for(int i = 0; i < plans.size; i++){ + BuildPlan nextPlan = plans.get(i); + if(!canBuild(nextPlan, core, ownerRange) || nextPlan == currentPlan) continue; + if(finished){ + if(isNotCachedBuilding()) moveTo(nextPlan, moveToRange, SMOOTH); + unit.lookAt(nextPlan); + break; + } + break; + } + } + for(BuildPlan plan : plans){//TODO remove double looping + if(plan.tile().build instanceof ConstructBlock.ConstructBuild it){ + if(it.progress > 0 && !plan.initialized){ + plan.initialized = true; + } + } } - unit.clearItem(); } + unit.updateBuilding = false; + unit.plans = prev; + return true; } - //TODO: implement ignoring shouldSkip plans + private boolean canBuild(BuildPlan currentPlan, float myRange){ + return unit.within(currentPlan, myRange - Math.min(tilesize * 1.5f, myRange * buildRangeSclInv / 2)); + } + + private boolean isCachedBuilding(){ + return !isNotCachedBuilding(); + } + + private boolean isNotCachedBuilding(){ + return buildPositionState != BuildState.buildingLastBuildOfPair && buildPositionState != BuildState.building2Of3; + } + + private void resolveMidPosition(BuildPlan currentPlan, BuildPlan nextPlan, float moveToRange, Vec2 out, Vec2 direction){ + + boolean calculated = OlGeometry.calculateIntersectionPointOfCircles( + Tmp.v1.set(currentPlan), + Tmp.v2.set(nextPlan), + moveToRange, + out, + direction + ); + if(!calculated){ + out.set(Tmp.v2) + .sub(Tmp.v1) + .nor() + .scl(moveToRange) + .add(Tmp.v1); + } + if(DebugDraw.isDraw()){ + float x1 = Tmp.v1.x, y1 = Tmp.v1.y; + float x2 = Tmp.v2.x, y2 = Tmp.v2.y; + float x3 = out.x, y3 = out.y; + DebugDraw.request(Layer.end, () -> { + Draw.color(Pal.negativeStat); + Lines.circle(x1, y1, moveToRange); + Draw.color(Pal.lancerLaser); + Lines.circle(x2, y2, moveToRange); - /** @return whether this plan should be skipped, in favor of the next one. */ - @SuppressWarnings({"unused"}) - boolean shouldSkip(BuildPlan plan, @Nullable Building core){ - //plans that you have at least *started* are considered - if(Vars.state.rules.infiniteResources || unit.team.rules().infiniteResources || plan.breaking || core == null || plan.isRotation(unit.team) || (unit.isBuilding() && !unit.within(unit.plans.last(), owner.type.buildRange + unit.type.buildRange))) + Draw.color(Pal.place); + EFill.polyCircle(x3, y3, tilesize / 4f); + + }); + } + } + + private boolean canBuild(BuildPlan buildPlan, CoreBuild core, float ownerRange){ + return buildPlan != null && !unit.shouldSkip(buildPlan, core) && owner.within(buildPlan, ownerRange); + } + + protected boolean tryMine(){ + Tile mineTile = owner.mineTile(); + if(mineTile == null) return false; + if(owner.stack.amount == owner.type.itemCapacity) return false; + if((owner.getMineResult(owner.mineTile) != owner.stack.item || owner.stack.amount <= 0) && (owner.stack.amount != 0)) return false; - return (plan.stuck && !core.items.has(plan.block.requirements)) || (Structs.contains(plan.block.requirements, i -> !core.items.has(i.item, Math.min(i.amount, 15)) && Mathf.round(i.amount * Vars.state.rules.buildCostMultiplier) > 0) && !plan.initialized); + + if(!owner.within(mineTile.worldx(), mineTile.worldy(), owner.type.mineRange)) return false; + unit.mineTile = owner.mineTile; + + moveTo(Tmp.v1.set(mineTile.worldx(), mineTile.worldy()), unit.type.mineRange * mineRangeScl, SMOOTH); + unit.lookAt(unit.angleTo(owner.mineTile)); + return true; + } + + static class BuildState{ + public static final int unset = -1; + public static final int buildingLastBuildOfPair = 0; + public static final int buildingPair = 1; + public static final int building3 = 2; + public static final int building2Of3 = 3; } } diff --git a/src/omaloon/content/OlUnitTypes.java b/src/omaloon/content/OlUnitTypes.java index 7708cd97..99ed28e2 100644 --- a/src/omaloon/content/OlUnitTypes.java +++ b/src/omaloon/content/OlUnitTypes.java @@ -102,19 +102,15 @@ public static void load(){ //region core attackDroneAlpha = new DroneUnitType("combat-drone-alpha"){{ - constructor = DroneUnit::create; - itemCapacity = 0; speed = 2.2f; accel = 0.08f; drag = 0.04f; - flying = hidden = true; health = 70; engineOffset = 4f; engineSize = 2; hitSize = 9; - isEnemy = false; weapons.add(new Weapon(){{ y = 0f; @@ -143,16 +139,13 @@ public static void load(){ shadowElevationScl = 0.4f; }}; - actionDroneMono = new GlassmoreUnitType("main-drone-mono"){{ - constructor = DroneUnit::create; - + actionDroneMono = new DroneUnitType("main-drone-mono"){{ mineTier = 3; itemCapacity = 1; speed = 2.2f; accel = 0.08f; drag = 0.04f; - flying = hidden = true; health = 70; engineOffset = 4f; engineSize = 2; @@ -163,7 +156,6 @@ public static void load(){ mineRange = 40; hitSize = 9; - isEnemy = false; shadowElevationScl = 0.4f; }}; @@ -185,9 +177,8 @@ public static void load(){ mineTier = 3; abilities.addAll( - new DroneAbility(){{ + new DroneAbility(attackDroneAlpha){{ name = "omaloon-combat-drone"; - droneUnit = attackDroneAlpha; droneController = AttackDroneAI::new; spawnTime = 180f; spawnX = 5f; @@ -198,9 +189,8 @@ public static void load(){ new Vec2(12f, 0f), }; }}, - new DroneAbility(){{ + new DroneAbility(actionDroneMono){{ name = "omaloon-utility-drone"; - droneUnit = actionDroneMono; droneController = UtilityDroneAI::new; spawnTime = 180f; spawnX = -5f; @@ -736,4 +726,6 @@ public static void load(){ }}; //endregion } + + } diff --git a/src/omaloon/core/OlNet.java b/src/omaloon/core/OlNet.java new file mode 100644 index 00000000..f7c4870c --- /dev/null +++ b/src/omaloon/core/OlNet.java @@ -0,0 +1,18 @@ +package omaloon.core; + +import mindustry.annotations.Annotations; +import mindustry.annotations.Annotations.Loc; +import mindustry.annotations.Annotations.Remote; +import mindustry.gen.Building; +import mindustry.gen.Itemsc; +import mindustry.gen.Unit; +import mindustry.type.Item; +import omaloon.world.save.OlDelayedItemTransfer; + +public class OlNet { + @Remote(called = Loc.both, targets = Loc.server) + public static void chainTransfer(Item item, float x, float y, Unit owner, Building core) { + OlDelayedItemTransfer.makeRequest(item,x,y,owner,core); +// InputHandler.transferItemToUnit(); + } +} diff --git a/src/omaloon/core/OlTimer.java b/src/omaloon/core/OlTimer.java new file mode 100644 index 00000000..3c38dcfb --- /dev/null +++ b/src/omaloon/core/OlTimer.java @@ -0,0 +1,24 @@ +package omaloon.core; + +import arc.*; +import mindustry.game.EventType.*; +import omaloon.entities.abilities.*; + +public class OlTimer{ + public static int prevClock; + public static int clock; + private static int internalClock; + static { + Events.run(Trigger.update,()->{ + prevClock=internalClock; + internalClock++; + clock=internalClock; + }); + } + + private static class TestClass implements IClockUpdatable{ + public void update(){ + System.out.println("Hello world"); + } + } +} diff --git a/src/omaloon/entities/abilities/DroneAbility.java b/src/omaloon/entities/abilities/DroneAbility.java index 1fa89de3..1691f6c7 100644 --- a/src/omaloon/entities/abilities/DroneAbility.java +++ b/src/omaloon/entities/abilities/DroneAbility.java @@ -1,53 +1,72 @@ package omaloon.entities.abilities; import arc.*; +import arc.func.*; import arc.graphics.g2d.*; import arc.math.geom.*; import arc.scene.event.*; import arc.scene.ui.*; import arc.scene.ui.layout.*; +import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.entities.abilities.*; +import mindustry.entities.units.*; import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; import mindustry.world.meta.*; import omaloon.ai.*; +import omaloon.core.*; import omaloon.gen.*; +import omaloon.type.*; -import java.util.*; -import java.util.function.*; - -public class DroneAbility extends Ability{ - private Unit paramUnit; - private DroneAbility paramAbility; - private final Vec2 paramPos = new Vec2(); - +public class DroneAbility extends Ability implements IClockedAbility{ + public static final int DRONE_SEARCH_TIME = 50; + private static final Vec2[] EMPTY_VEC2_ARRAY = new Vec2[0]; + private final Vec2 calculatedSpawnPos = new Vec2(); public String name = "omaloon-drone"; - public UnitType droneUnit; + public DroneUnitType droneUnit; public float spawnTime = 60f; public float spawnX = 0f; public float spawnY = 0f; public Effect spawnEffect = Fx.spawn; public boolean parentizeEffects = false; - public Vec2[] anchorPos = {new Vec2(5 * 8f, -5 * 8f)}; + public Vec2[] anchorPos = EMPTY_VEC2_ARRAY; public float layer = Layer.groundUnit - 0.01f; public float rotation = 0f; public int maxDroneCount = 1; + public Seq drones = new Seq<>(); + public Func droneController = DroneAI::new; protected float timer = 0f; - public ArrayList drones = new ArrayList<>(); - public Function droneController = DroneAI::new; + protected float droneSearchTimer = DRONE_SEARCH_TIME; + + protected DroneAI sampleController; + + public DroneAbility(DroneUnitType droneUnit){ + this.droneUnit = droneUnit; + } + + public DroneAbility(UnitType droneUnit){ + droneUnit(droneUnit); + } + + private DroneAbility(){ + + } + + public void droneUnit(UnitType droneType){ + if(!(droneType instanceof DroneUnitType drone)) throw new IllegalArgumentException("Expected " + DroneUnitType.class + " but found " + droneType.getClass()); + this.droneUnit = drone; + } @Override public void init(UnitType type){ this.data = 0; - } - - public DroneAbility(){ + sampleController = droneController.get(Nulls.unit); } @Override @@ -68,7 +87,7 @@ public void addStats(Table t){ @Override public Ability copy(){ DroneAbility ability = (DroneAbility)super.copy(); - ability.drones = new ArrayList<>(); + ability.drones = new Seq<>(); return ability; } @@ -79,27 +98,39 @@ public String localized(){ @Override public void update(Unit unit){ - paramUnit = unit; - paramAbility = this; - paramPos.set(spawnX, spawnY).rotate(unit.rotation - 90f).add(unit); + calculateSpawnPos(unit); timer += Time.delta * Vars.state.rules.unitBuildSpeed(unit.team()); if(drones.isEmpty()){ - for(Unit u : Groups.unit){ - if(u.team() == unit.team() - && u.type == this.droneUnit - && u instanceof DroneUnit - && ((DroneUnit)u).owner == unit){ - drones.add(u); - u.controller(droneController.apply(unit)); - data++; - updateAnchor(); + if(data > 0){ + droneSearchTimer -= Time.delta; + if(droneSearchTimer <= 0){ + droneSearchTimer = DRONE_SEARCH_TIME; + data = 0; } + }else{ + droneSearchTimer = DRONE_SEARCH_TIME; } + + + //TODO mod groups + //but I dont want to make PL into EntityAnno + //this feature exits more than 1 or 2 years in MindustryModCore + /*for(Unit u : Groups.unit){ + if(u.team() == unit.team() + && u.type == this.droneUnit + && u instanceof DroneUnit + && ((DroneUnit)u).owner == unit){ + registerDrone(u.self(), unit); + } + }*/ + }else{ + droneSearchTimer = DRONE_SEARCH_TIME; + updateAnchor(unit);//TODO better solution } - drones.removeIf(u -> { + drones.removeAll(u -> { if(!u.isValid()){ data--; timer = 0; @@ -110,50 +141,96 @@ public void update(Unit unit){ if(data < maxDroneCount){ if(timer > spawnTime){ - spawnDrone(); + spawnDrone(unit); timer = 0; } } } - protected void spawnDrone(){ - spawnEffect.at(paramPos.x, paramPos.y, 0f, parentizeEffects ? paramUnit : null); - Unit u = droneUnit.create(paramUnit.team()); - u.set(paramPos.x, paramPos.y); - u.rotation = paramUnit.rotation + rotation; + private Vec2 calculateSpawnPos(Unit unit){ + return calculatedSpawnPos.set(spawnX, spawnY).rotate(unit.rotation - 90f).add(unit); + } + + protected void spawnDrone(Unit unit){ + calculateSpawnPos(unit); + spawnEffect.at(calculatedSpawnPos.x, calculatedSpawnPos.y, 0f, parentizeEffects ? unit : null); - if(u instanceof DroneUnit drone) drone.owner(paramUnit); + Unit drone = droneUnit.create(unit.team()); + Dronec dronec = drone.self(); + drone.set(calculatedSpawnPos.x, calculatedSpawnPos.y); + drone.rotation = unit.rotation + rotation; - drones.add(0, u); - data++; - for(int i = 0; i < paramUnit.abilities.length; i++){ - Ability self = paramUnit.abilities[i]; - if(self == this && u instanceof Dronec drone) drone.abilityIndex(i); + dronec.ownerID(unit.id); + + boolean isNotClient = !Vars.net.client(); + + registerDrone(dronec, unit, isNotClient); + for(int i = 0; i < unit.abilities.length; i++){ + Ability self = unit.abilities[i]; + if(self != this) continue; + dronec.abilityIndex(i); + break; } - u.controller(droneController.apply(paramUnit)); - updateAnchor(); - Events.fire(new UnitCreateEvent(u, null, paramUnit)); - if(!Vars.net.client()){ - u.add(); + Events.fire(new UnitCreateEvent(drone, null, unit)); + if(isNotClient){ + drone.add(); } } - public void updateAnchor(){ - for(int i = 0; i < drones.size(); i++){ + public void updateAnchor(Unit unit){ + for(int i = 0; i < drones.size; i++){ Unit u = drones.get(i); - ((DroneAI)u.controller()).rally(anchorPos[i]); + UnitController controller = u.controller(); + DroneAI droneAI; + if(controller instanceof DroneAI it){ + droneAI = it; + }else{ + unit.controller(droneAI = new DroneAI(unit)); + controller.unit(Nulls.unit); + droneAI.unit(u); + } + droneAI.rally(anchorPos[i]); } } @Override public void draw(Unit unit){ - paramUnit = unit; - paramAbility = this; - paramPos.set(spawnX, spawnY).rotate(unit.rotation - 90f).add(unit); + calculateSpawnPos(unit); + + if(!(data < maxDroneCount) || !(timer <= spawnTime)) return; - if(data < maxDroneCount && timer <= spawnTime){ - Draw.draw(layer, () -> Drawf.construct(paramPos.x, paramPos.y, droneUnit.fullIcon, paramUnit.rotation - 90, timer / spawnTime, 1f, timer)); + Draw.draw(layer, () -> Drawf.construct(calculatedSpawnPos.x, calculatedSpawnPos.y, droneUnit.fullIcon, unit.rotation - 90, timer / spawnTime, 1f, timer)); + } + + public boolean registerDrone(Dronec u, Unit owner, boolean addInList){ + Class aClass = droneUnit.getClass(); + if(!aClass.isInstance(u.type())) + return false; + if(addInList){ + int indexToReplace = drones.indexOf(it -> !it.isValid() || it == u); + if(indexToReplace != -1){ + drones.set(indexToReplace, u.self()); + }else{ + if(data == maxDroneCount && (!Vars.net.client() || drones.size == data)) return false; + if(drones.size == data) + data++; + drones.add((Unit)u); + } + } + boolean hasController = u.controller().getClass() == sampleController.getClass(); + DroneAI controller = hasController ? (DroneAI)u.controller() : droneController.get(owner); + u.controller(controller); + updateAnchor(owner); + if(addInList && !hasController && u.whenWasUpdated() == OlTimer.clock){ + if(!u.dead()){ + if(Vars.net.client()){ + controller.updateFromClient(); + }else{ + controller.updateUnit(); + } + } } + return true; } } \ No newline at end of file diff --git a/src/omaloon/entities/abilities/IClockUpdatable.java b/src/omaloon/entities/abilities/IClockUpdatable.java new file mode 100644 index 00000000..5540f85d --- /dev/null +++ b/src/omaloon/entities/abilities/IClockUpdatable.java @@ -0,0 +1,18 @@ +package omaloon.entities.abilities; + +import omaloon.annotations.*; +import omaloon.annotations.AutoImplement.*; +import omaloon.annotations.AutoImplement.Inject.*; +import omaloon.core.*; + +@AutoImplement +public interface IClockUpdatable extends IClocked{ + + @Inject(InjectPosition.Head) + default void update(){ + { + int __clock = OlTimer.clock; + AutoImplement.Util.Param("__clocked_expr__", "__Internal__clockUpdateTime = __clock"); + } + } +} diff --git a/src/omaloon/entities/abilities/IClocked.java b/src/omaloon/entities/abilities/IClocked.java new file mode 100644 index 00000000..cced30df --- /dev/null +++ b/src/omaloon/entities/abilities/IClocked.java @@ -0,0 +1,18 @@ +package omaloon.entities.abilities; + +import ent.anno.Annotations.*; +import mindustry.gen.*; +import omaloon.annotations.*; +import omaloon.annotations.AutoImplement.*; +import omaloon.annotations.AutoImplement.Inject.*; +import omaloon.core.*; +import omaloon.struct.*; +@SuppressWarnings("UnnecessaryModifier") +@AutoImplement +public interface IClocked{ + public int __Internal__clockUpdateTime = OlTimer.clock; + default int whenWasUpdated(){ + return __Internal__clockUpdateTime; + } + +} diff --git a/src/omaloon/entities/abilities/IClockedAbility.java b/src/omaloon/entities/abilities/IClockedAbility.java new file mode 100644 index 00000000..15ad64a2 --- /dev/null +++ b/src/omaloon/entities/abilities/IClockedAbility.java @@ -0,0 +1,20 @@ +package omaloon.entities.abilities; + +import mindustry.gen.*; +import omaloon.annotations.*; +import omaloon.annotations.AutoImplement.*; +import omaloon.annotations.AutoImplement.Inject.*; +import omaloon.core.*; + +@SuppressWarnings({"unused", "UnnecessaryModifier"}) +@AutoImplement +public interface IClockedAbility extends IClocked{ + + @Inject(InjectPosition.Head) + default void update(Unit unit){ + { + int __clock = OlTimer.clock; + AutoImplement.Util.Param("__clocked_expr__", "__Internal__clockUpdateTime = __clock"); + } + } +} diff --git a/src/omaloon/entities/comp/ChainedComp.java b/src/omaloon/entities/comp/ChainedComp.java index 52482ddf..4ff649eb 100644 --- a/src/omaloon/entities/comp/ChainedComp.java +++ b/src/omaloon/entities/comp/ChainedComp.java @@ -11,6 +11,7 @@ import mindustry.game.*; import mindustry.gen.*; import mindustry.type.*; +import omaloon.gen.*; import omaloon.type.*; import omaloon.utils.*; diff --git a/src/omaloon/entities/comp/DroneComp.java b/src/omaloon/entities/comp/DroneComp.java index 37ad8283..5456e8b1 100644 --- a/src/omaloon/entities/comp/DroneComp.java +++ b/src/omaloon/entities/comp/DroneComp.java @@ -2,55 +2,122 @@ import arc.util.io.*; import ent.anno.Annotations.*; +import mindustry.entities.abilities.*; +import mindustry.entities.units.*; import mindustry.game.*; import mindustry.gen.*; import omaloon.entities.abilities.*; @SuppressWarnings("unused") @EntityComponent -abstract class DroneComp implements Unitc, Flyingc{ +abstract class DroneComp implements Unitc, Flyingc, IClockUpdatable{ + public int abilityIndex = -1; @Import Team team; + int ownerID = -1; + private transient Unit owner; + @Import + private transient float buildCounter; + @Import + private transient BuildPlan lastActive; - transient int abilityIndex = -1; + public static boolean validOwner(Unit owner, Unit self){ + return owner != null && owner.isValid() && owner.team() == self.team(); + } - transient Unit owner; - transient int ownerID = -1; + public float buildCounter(){ + return buildCounter; + } - public boolean hasOwner(){ - return owner != null && owner.isValid() && owner.team() == team; + public void buildCounter(float buildCounter){ + this.buildCounter = buildCounter; } + public BuildPlan lastActive(){ + return lastActive; + } + + public void lastActive(BuildPlan lastActive){ + this.lastActive = lastActive; + } + + @Override public void read(Reads read){ - ownerID = read.i(); - abilityIndex = read.i(); - if(ownerID != -1){ - owner = Groups.unit.getByID(ownerID); + int rawOwnerID = read.i(); + int rawAbilityIndex = read.i(); + if(rawAbilityIndex != -1){ + abilityIndex = rawAbilityIndex; + } + if(rawOwnerID != -1){ + ownerID = rawOwnerID; } } + @Override + public void afterSync(){ + owner = null; + tryResolveOwner(false); + } + @Override + public void afterRead(){ + owner = null; + tryResolveOwner(false); + } + + @Override public void update(){ - if(ownerID != -1 && owner == null){ - owner = Groups.unit.getByID(ownerID); + if(ownerID == -1){ + Call.unitDespawn(self()); + return; + } + + if(owner == null){ + tryResolveOwner(true); + return; + } + if(ownerID != owner.id){ + owner = null; + return; + } + if(!validOwner(owner, self())){ ownerID = -1; + } + } - if(hasOwner() && abilityIndex < owner.abilities.length && owner.abilities[abilityIndex] instanceof DroneAbility a){ - a.drones.add(0, self()); - a.data++; - controller(a.droneController.apply(owner)); - }else abilityIndex = -1; + private void tryResolveOwner(boolean shouldReset){ + owner = Groups.unit.getByID(ownerID); + if(!validOwner(owner, self())){ + if(shouldReset) ownerID = -1; + return; } - if(!hasOwner() || abilityIndex == -1){ - Call.unitDespawn(self()); + if(abilityIndex == -1){ + Ability[] abilities = owner.abilities; + for(int i = 0; i < abilities.length; i++){ + if(!(abilities[i] instanceof DroneAbility droneAbility)) continue; + if(!droneAbility.registerDrone(self(), owner, true)) continue; + abilityIndex = i; + return; + } + if(shouldReset) ownerID = -1; + return; + } + if( + abilityIndex >= owner.abilities.length || + !(owner.abilities[abilityIndex] instanceof DroneAbility a) || + !a.registerDrone(self(), owner, true) + ){ + if(shouldReset) ownerID = -1; + return; } + return; } @Override public void write(Writes write){ - write.i(hasOwner() ? owner.id() : -1); - write.i(abilityIndex); + write.i(-1); + write.i(-1); } } \ No newline at end of file diff --git a/src/omaloon/io/OlTypeIO.java b/src/omaloon/io/OlTypeIO.java new file mode 100644 index 00000000..d7336af2 --- /dev/null +++ b/src/omaloon/io/OlTypeIO.java @@ -0,0 +1,27 @@ +package omaloon.io; + +import arc.util.io.Reads; +import arc.util.io.Writes; +import mindustry.annotations.Annotations; +import mindustry.gen.Itemsc; +import mindustry.io.TypeIO; +import mindustry.type.Item; + +@Annotations.TypeIOHandler +@ent.anno.Annotations.TypeIOHandler +public class OlTypeIO extends TypeIO{ + public static void writeItemCons(Writes writes,Itemsc[] itemscs){ + writes.i(itemscs.length); + for (Itemsc itemsc : itemscs) { + TypeIO.writeObject(writes, itemsc); + } + } + public static Itemsc[] readItemConsumers(Reads read){ + int amount = read.i(); + Itemsc[] itemscs = new Itemsc[amount]; + for (int i = 0; i < itemscs.length; i++) { + itemscs[i]=TypeIO.readEntity(read); + } + return itemscs; + } +} diff --git a/src/omaloon/math/OlGeometry.java b/src/omaloon/math/OlGeometry.java new file mode 100644 index 00000000..067e5c63 --- /dev/null +++ b/src/omaloon/math/OlGeometry.java @@ -0,0 +1,147 @@ +package omaloon.math; + +import arc.math.*; +import arc.math.geom.*; + +public class OlGeometry{ + public static final float oneFourth = 1 / 4f; + private static final Vec2 + tmp1 = new Vec2(), + tmp2 = new Vec2(), + tmp3 = new Vec2(); + private static final Vec2 two = new Vec2(2, 2); + private static final float[] tmpFloats6 = new float[6]; + private static final Circle circle1 = new Circle(), circle2 = new Circle(); +// public static boolean intersectionPointCanBeBetween = false; + + /** + * @author Zelaux + * Demo + * Desmos + */ + public static boolean calculateIntersectionPointOfCircles(Vec2 a, Vec2 b, float radius, Vec2 out, Vec2 directionFromA){ + float radius2 = radius * radius; + float dst2 = a.dst2(b); + if(dst2 > 4 * radius2 || dst2 <= Float.MIN_NORMAL) return false; + + out.set(a).add(b).div(two);//midpoint + if(dst2 == 4 * radius2){ + return true; + } + float dy = a.x - b.x; + float dx = -(a.y - b.y); + + + float offsetScale = Mathf.sqrt(radius2 / dst2 - oneFourth); + + float scale = + Math.signum(dx * directionFromA.x + dy * directionFromA.y) * offsetScale; + + out.add(dx * scale, dy * scale); + + + return true; + } + + private static void setMatrix(Vec2 a, Vec2 b, float[] matrix, int i){ + float ax = a.x; + float ay = a.y; + float bx = b.x; + float by = b.y; + + matrix[i] = ax - bx; + matrix[i + 1] = ay - by; +// matrix[i++] = ((b.y + a.y) * (b.y - a.y) - (a.x + b.x) * (a.x - b.x)) / 2; +// matrix[i + 2] = ((b.y * b.y - a.y * a.y) - (a.x * a.x - b.x * b.x)) / 2; + matrix[i + 2] = (by * by + bx * bx - ax * ax - ay * ay) / 2; +// matrix[i + 2] = (b.len2() - a.len2()) / 2; + } + + public static boolean intersectionPoint(Circle a, Circle b, Vec2 directionPoint, Vec2 out){ + return intersectionPoint(a.x, a.y, a.radius, b.x, b.y, b.radius, directionPoint, out); + } + + public static boolean intersectionPoint(Vec2 p1, float r1, Vec2 p2, float r2, Vec2 directionPoint, Vec2 out){ + return intersectionPoint(p1.x, p1.y, r1, p2.x, p2.y, r2, directionPoint, out); + } + + public static boolean intersectionPoint(Position p1, float r1, Position p2, float r2, Vec2 directionPoint, Vec2 out){ + return intersectionPoint(p1.getX(), p1.getY(), r1, p2.getX(), p2.getY(), r2, directionPoint, out); + } + + /** + * @author Zeluax + * Demo + * Desmos + */ + public static boolean intersectionPoint(float x1, float y1, float r1, float x2, float y2, float r2, Vec2 directionPoint, Vec2 out){ + float dx0 = x1 - x2; + float dy0 = y1 - y2; + float dst2 = Mathf.len2(dx0, dy0); + if(dst2 == 0) return false; + float sumRadius = r1 + r2; + if(dst2 > sumRadius * sumRadius) return false; + float dst = Mathf.sqrt(dst2); + + float r1_2 = r1 * r1; + float r2_2 = r2 * r2; + float cos = r1 * (dst2 + r1_2 - r2_2) / (2 * dst * r1); + if(cos > r1 || cos < -r1) return false; + + float sin = Mathf.sqrt(r1_2 - cos * cos); + + float dx = -dx0 / dst; + float dy = -dy0 / dst; + + float dx2 = dy * sin; + float dy2 = -dx * sin; + + /*if(directionPoint.isZero()){ + float cx=x1+dx*cos; + float cy=y1+dy*cos; + + directionPoint + .set(cx,cy) + .add(dx2,dy2); + out + .set(cx,cy) + .sub(dx2,dy2); + + }else*/ + { + float scale =/* + intersectionPointCanBeBetween ? + Math.signum(directionPoint.dot(dx2, dy2)) :*/ + Mathf.sign(directionPoint.dot(dx2, dy2)); + out.set(x1 + dx * cos + dx2 * scale, y1 + dy * cos + dy2 * scale); + } + return true; + } + + /** + * @author Zelaux + */ + public static boolean calculateCircle(Vec2 a, Vec2 b, Vec2 c, Circle circle){ + float[] fls = tmpFloats6; + setMatrix(a, b, fls, 0); + setMatrix(b, c, fls, 3); + //(0 1 | 2) + //(3 4 | 5) + float mainDet = calculateDet(fls, 0, 1); + if(mainDet == 0) return false; + + float x = calculateDet(fls, 1, 2) / mainDet; + float y = -calculateDet(fls, 0, 2) / mainDet; + + circle.x = x; + circle.y = y; + circle.radius = a.dst(x, y); + + + return true; + } + + private static float calculateDet(float[] matrix, int column1, int column2){ + return matrix[column1] * matrix[column2 + 3] - matrix[column2] * matrix[column1 + 3]; + } +} diff --git a/src/omaloon/net/OlClient.java b/src/omaloon/net/OlClient.java new file mode 100644 index 00000000..63ff75e7 --- /dev/null +++ b/src/omaloon/net/OlClient.java @@ -0,0 +1,85 @@ +package omaloon.net; + +import arc.*; +import arc.struct.*; +import arc.util.pooling.Pool.*; +import lombok.*; +import mindustry.annotations.Annotations.*; +import mindustry.entities.units.*; +import mindustry.game.EventType.*; +import mindustry.gen.*; +import mindustry.world.blocks.ConstructBlock.*; +import mindustry.world.blocks.storage.CoreBlock.*; +import omaloon.gen.*; + +import static mindustry.Vars.world; + +public class OlClient{ + private static final Seq requests = new Seq<>(SetPlanRequest.class); + + static{ + Events.run(Trigger.preDraw, OlClient::handlePlanRequest); + } + + private static void planRequest(@NonNull Dronec unit, BuildPlan plan){ + if(requests.size >= requests.items.length || requests.items[requests.size] == null){ + requests.add(new SetPlanRequest(unit, plan)); + return; + } + requests.items[requests.size].set(unit, plan); + requests.size++; + } + + private static void handlePlanRequest(){ + for(int i = 0; i < requests.size; i++){ + SetPlanRequest request = requests.items[i]; + Dronec unit = request.unit; + unit.lastActive(request.plan); + unit.buildAlpha(1f); + request.reset(); + } + requests.size = 0; + } + + @Remote(called = Loc.client, targets = Loc.server) + public static void utilityDroneSyncBuilding(Unit owner, Unit unit, BuildPlan rawPlan, Building core0, float bs){ + if(!(unit instanceof Dronec drone)) return; + Queue ownerPlans = owner.plans; + int expectedTilePos = rawPlan.tile().pos(); + BuildPlan plan = null; + for(int i = 0; i < ownerPlans.size; i++){ + BuildPlan buildPlan = ownerPlans.get(i); + if(buildPlan.tile().pos() != expectedTilePos) continue; + plan = buildPlan; + } + if(plan == null) return; + planRequest(drone, plan); + if(!(world.build(plan.tile().pos()) instanceof ConstructBuild entity)) return; + if(!(core0 instanceof CoreBuild core)) return; + if(plan.breaking){ + entity.deconstruct(unit, core, bs); + }else{ + entity.construct(unit, core, bs, plan.config); + } + } + + + @AllArgsConstructor + @NoArgsConstructor + private static class SetPlanRequest implements Poolable{ + Dronec unit; + BuildPlan plan; + + public SetPlanRequest set(Dronec unit, BuildPlan plan){ + this.unit = unit; + this.plan = plan; + return this; + } + + @Override + public void reset(){ + unit = null; + plan = null; + } + } +} diff --git a/src/omaloon/net/OlServer.java b/src/omaloon/net/OlServer.java new file mode 100644 index 00000000..16d78bdd --- /dev/null +++ b/src/omaloon/net/OlServer.java @@ -0,0 +1,39 @@ +package omaloon.net; + +import arc.util.*; +import arc.util.CommandHandler.*; +import mindustry.entities.abilities.*; +import mindustry.gen.*; +import omaloon.entities.abilities.*; +import omaloon.gen.*; + +public class OlServer{ + public static void registerServerCommands(CommandHandler handler){ + + + } + + public static void registerClientCommands(CommandHandler handler){ + handler.register("debug_id", "", command((args, player) -> { + player.sendMessage(player.unit().id + ""); + for(Ability ability : player.unit().abilities){ + if(ability instanceof DroneAbility droneAbility){ + for(Unit drone : droneAbility.drones){ + player.sendMessage(" - "+drone.id); + } + } + } + player.sendMessage("------------"); + for(Unit unit : Groups.unit){ + if(unit instanceof Dronec drone){ + player.sendMessage("|"+drone.id()); + } + } + player.sendMessage("------------"); + })); + } + + private static CommandRunner command(CommandRunner playerCommandRunner){ + return playerCommandRunner; + } +} diff --git a/src/omaloon/struct/UnsavebleInt.java b/src/omaloon/struct/UnsavebleInt.java new file mode 100644 index 00000000..949f941a --- /dev/null +++ b/src/omaloon/struct/UnsavebleInt.java @@ -0,0 +1,10 @@ +package omaloon.struct; + +import lombok.*; +import lombok.experimental.*; + +@AllArgsConstructor +@FieldDefaults(level = AccessLevel.PUBLIC) +public class UnsavebleInt{ + int value; +} diff --git a/src/omaloon/type/DroneUnitType.java b/src/omaloon/type/DroneUnitType.java index da5a2698..42092b4e 100644 --- a/src/omaloon/type/DroneUnitType.java +++ b/src/omaloon/type/DroneUnitType.java @@ -1,14 +1,44 @@ package omaloon.type; +import mindustry.*; +import mindustry.game.*; +import mindustry.gen.*; +import omaloon.ai.*; import omaloon.gen.*; public class DroneUnitType extends GlassmoreUnitType{ public DroneUnitType(String name){ super(name); hidden = flying = true; - playerControllable = logicControllable = false; + allowedInPayloads = playerControllable = logicControllable = false; isEnemy = false; drawItems = true; constructor = DroneUnit::create; } + + @Override + public void init(){ + super.init(); + if(!(sample instanceof Dronec)){ + throw new IllegalArgumentException(String.format( + "%s is not implementing %s",sample.getClass(),Dronec.class + )); + } + } + + @Override + public Unit create(Team team){ + return super.create(team); + } + + @Override + public void update(Unit unit){ + super.update(unit); + if(!Vars.net.client() || unit.dead)return; + if(!(unit.controller() instanceof DroneAI droneAI)){ + + return; + } + droneAI.updateFromClient(); + } } diff --git a/src/omaloon/type/MillipedeUnitType.java b/src/omaloon/type/MillipedeUnitType.java index 1a52ac29..53d19201 100644 --- a/src/omaloon/type/MillipedeUnitType.java +++ b/src/omaloon/type/MillipedeUnitType.java @@ -13,6 +13,7 @@ import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; +import omaloon.gen.*; import static arc.Core.atlas; diff --git a/src/omaloon/ui/OlBinding.java b/src/omaloon/ui/OlBinding.java index f5675c06..402bc73d 100644 --- a/src/omaloon/ui/OlBinding.java +++ b/src/omaloon/ui/OlBinding.java @@ -9,6 +9,7 @@ public enum OlBinding implements KeyBind{ shaped_env_placer(KeyCode.o, "omaloon-editor"), + switchDebugDraw(KeyCode.f12, "omaloon-debug-draw"), cliff_placer(KeyCode.p, "omaloon-editor"); private final KeybindValue defaultValue; diff --git a/src/omaloon/utils/DebugDraw.java b/src/omaloon/utils/DebugDraw.java new file mode 100644 index 00000000..5f1bb3f7 --- /dev/null +++ b/src/omaloon/utils/DebugDraw.java @@ -0,0 +1,66 @@ +package omaloon.utils; + +import arc.*; +import arc.graphics.g2d.*; +import arc.struct.*; +import mindustry.*; +import mindustry.game.EventType.*; +import mindustry.graphics.*; +import org.intellij.lang.annotations.*; + +public class DebugDraw{ + private static final Seq requests = new Seq<>(); + private static final Seq requests2 = new Seq<>(); + private static final String settingKey = "omaloon-debug-draw"; + private static boolean step1 = false; + private static boolean isDraw = true; + + static{ + register(); + } + + public static void request(Runnable runnable){ + if(step1) requests.add(runnable); + else requests2.add(runnable); + } + + public static void request(@MagicConstant(valuesFromClass = Layer.class) float layer, Runnable runnable){ + request(() -> { + Draw.draw(layer, runnable); + }); + } + + private static void register(){ + Events.run(Trigger.draw, DebugDraw::draw); + Events.run(Trigger.update, DebugDraw::update); + } + + private static void update(){ + isDraw = Core.settings.getBool(settingKey, false); + + } + + private static void draw(){ + step1 = !step1; + Seq current; + if(!step1) current = requests; + else current = requests2; + if(!isDraw()){ + current.clear(); + return; + } + current.removeAll(it -> { + it.run(); + return true; + }); + } + + public static boolean isDraw(){ + return isDraw && !Vars.headless; + } + + public static void switchEnabled(){ + isDraw = !isDraw; + Core.settings.put(settingKey, isDraw); + } +} diff --git a/src/omaloon/world/save/OlDelayedItemTransfer.java b/src/omaloon/world/save/OlDelayedItemTransfer.java new file mode 100644 index 00000000..99c76bd3 --- /dev/null +++ b/src/omaloon/world/save/OlDelayedItemTransfer.java @@ -0,0 +1,143 @@ +package omaloon.world.save; + +import arc.*; +import arc.struct.Seq; +import arc.util.io.Reads; +import arc.util.io.Writes; +import arc.util.pooling.Pool; +import arc.util.pooling.Pools; +import mindustry.Vars; +import mindustry.gen.Building; +import mindustry.gen.Call; +import mindustry.gen.Unit; +import mindustry.input.InputHandler; +import mindustry.io.TypeIO; +import mindustry.type.Item; + +import javax.swing.plaf.basic.BasicComboBoxUI.*; +import java.io.DataInput; +import java.io.DataOutput; + +public class OlDelayedItemTransfer extends OlSaveChunk { + + + private static final Seq requests = new Seq<>(); + private static final Pool requestPool = Pools.get(Request.class, Request::new); + + public OlDelayedItemTransfer() { + super("delayed-item-transfer"); + } + + public static void makeRequest(Item item, float x, float y, Unit owner, Building core) { + if(core.acceptStack(item,1,owner)==0){ + InputHandler.transferItemToUnit(item,x,y,owner); + return; + } + firstState(item, x, y, owner, core); + } + + private static void firstState(Item item, float x, float y, Unit owner, Building core) { + Request request = requestPool.obtain().set(item, x, y, owner, core); + requests.add(request); + InputHandler.createItemTransfer(item, 1, x, y, owner, () -> { + request.state++; + if(owner.hasItem() && owner.item()!=item){ + int amount = core.acceptStack(item, owner.stack.amount, owner); + Call.transferItemTo(owner,owner.item(),amount,owner.x,owner.y,core); + owner.clearItem(); + } + owner.addItem(item); + secondState(request); + }); + } + + private static void secondState(Request request) { + Item item = request.item; + Unit owner = request.owner; + Building core = request.core; + if(core.acceptStack(item,1,owner)==0){ + requests.remove(request); + requestPool.free(request); + return; + } + InputHandler.createItemTransfer(item, 1, owner.x, owner.y, core, () -> { + if (owner.stack().item != item || owner.stack().amount <= 0) return; + int amount = core.acceptStack(item, 1, owner); + owner.stack().amount -=amount; + core.handleStack(item,amount,owner); + requests.remove(request); + requestPool.free(request); + }); + } + + @Override + int version() { + return 0; + } + + @Override + protected void write(Writes write, DataOutput dataOutput) { + write.i(requests.size); + for (Request request : requests) { + write.str(request.item.name); + write.f(request.x); + write.f(request.y); + TypeIO.writeUnit(write, request.owner); + TypeIO.writeBuilding(write, request.core); + write.i(request.state); + } + } + + @Override + protected void read(Reads read, DataInput dataInput, int version) { + int amount = read.i(); + requests.clear(); + requests.ensureCapacity(amount); + for (int i = 0; i < amount; i++) { + Item item = Vars.content.item(read.str()); + float x = read.f(); + float y = read.f(); + Unit owner = TypeIO.readUnit(read); + Building build = TypeIO.readBuilding(read); + int state = read.i(); + Core.app.post(() -> { + if (state == 0) { + makeRequest(item, x, y, owner, build); + } else { + Request set = requestPool.obtain() + .set(item, x, y, owner, build); + set.state=state; + secondState(set + ); + } + }); + } + } + + public static class Request implements Pool.Poolable { + Item item; + float x; + float y; + Unit owner; + Building core; + int state = 0; + + public Request set(Item item, float x, float y, Unit owner, Building core) { + this.item = item; + this.x = x; + this.y = y; + this.owner = owner; + this.core = core; + return this; + } + + public void reset() { + item = null; + owner = null; + core = null; + x = y = 0; + state = 0; + } + } + +} diff --git a/src/omaloon/world/save/OlSaveChunk.java b/src/omaloon/world/save/OlSaveChunk.java new file mode 100644 index 00000000..4c370401 --- /dev/null +++ b/src/omaloon/world/save/OlSaveChunk.java @@ -0,0 +1,37 @@ +package omaloon.world.save; + +import arc.util.io.Reads; +import arc.util.io.Writes; +import mindustry.io.SaveFileReader; +import mindustry.io.SaveVersion; + +import java.io.*; + +public abstract class OlSaveChunk implements SaveFileReader.CustomChunk { + private final Writes writes = new Writes(new DataOutputStream(new ByteArrayOutputStream())); + private final Reads reads = new Reads(new DataInputStream(new ByteArrayInputStream(new byte[0]))); + public OlSaveChunk(String chunkName){ + SaveVersion.addCustomChunk("omaloon-"+chunkName,this); + } + + abstract int version(); + + @Override + public void write(DataOutput dataOutput) throws IOException { + dataOutput.writeInt(version()); + writes.output = dataOutput; + write(writes,dataOutput); + writes.output = null; + } + + protected abstract void write(Writes write, DataOutput dataOutput); + protected abstract void read(Reads read, DataInput dataInput,int version); + + @Override + public void read(DataInput dataInput) throws IOException { + int version = dataInput.readInt(); + reads.input=dataInput; + read(reads,dataInput,version); + reads.input=null; + } +} diff --git a/tests/setup-tests.gradle b/tests/setup-tests.gradle new file mode 100644 index 00000000..ae1a4815 --- /dev/null +++ b/tests/setup-tests.gradle @@ -0,0 +1,12 @@ +test { + useJUnitPlatform() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + testCompileOnly 'org.projectlombok:lombok:1.18.32' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' + + +} \ No newline at end of file diff --git a/tests/test/omaloon/AlwaysTrueTrue.java b/tests/test/omaloon/AlwaysTrueTrue.java new file mode 100644 index 00000000..651bf058 --- /dev/null +++ b/tests/test/omaloon/AlwaysTrueTrue.java @@ -0,0 +1,10 @@ +package omaloon; + +import org.junit.jupiter.api.*; + +public class AlwaysTrueTrue{ + @Test + void alwaysTrue(){ + Assertions.assertTrue(true); + } +} diff --git a/tests/test/omaloon/math/CircleStuffTest.java b/tests/test/omaloon/math/CircleStuffTest.java new file mode 100644 index 00000000..c0c41316 --- /dev/null +++ b/tests/test/omaloon/math/CircleStuffTest.java @@ -0,0 +1,23 @@ +package omaloon.math; + +import arc.math.geom.*; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class CircleStuffTest{ + static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2(); + + static final float FLT_DELTA=0.0001f; + @Test + void intersection(){ + assertTrue(OlGeometry.intersectionPoint( + 1, 1, 1.5f, + 0, 0, 2, + tmp1.set(2, 1).sub(1, 1), + tmp2 + ), "Cannot compute intersection"); + Assertions.assertEquals(1.99632,tmp2.x,FLT_DELTA); + Assertions.assertEquals(-0.12132,tmp2.y,FLT_DELTA); + } +}