Merge branch 'master' of https://github.com/Anuken/Mindustry into v107
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,8 +7,6 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Note**: Do not report any new bugs directly relating to the v6 campaign. They will not be fixed or considered at this time.
|
|
||||||
|
|
||||||
**Platform**: *Android/iOS/Mac/Windows/Linux*
|
**Platform**: *Android/iOS/Mac/Windows/Linux*
|
||||||
|
|
||||||
**Build**: *The build number under the title in the main menu. Required. "LATEST" IS NOT A VERSION, I NEED THE EXACT BUILD NUMBER OF YOUR GAME.*
|
**Build**: *The build number under the title in the main menu. Required. "LATEST" IS NOT A VERSION, I NEED THE EXACT BUILD NUMBER OF YOUR GAME.*
|
||||||
|
|||||||
20
README.md
@@ -9,34 +9,34 @@ _[Trello Board](https://trello.com/b/aE2tcUwF/mindustry-40-plans)_
|
|||||||
_[Wiki](https://mindustrygame.github.io/wiki)_
|
_[Wiki](https://mindustrygame.github.io/wiki)_
|
||||||
_[Javadoc](https://mindustrygame.github.io/docs/)_
|
_[Javadoc](https://mindustrygame.github.io/docs/)_
|
||||||
|
|
||||||
### Contributing
|
## Contributing
|
||||||
|
|
||||||
See [CONTRIBUTING](CONTRIBUTING.md).
|
See [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
### Building
|
## Building
|
||||||
|
|
||||||
Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases).
|
Bleeding-edge builds are generated automatically for every commit. You can see them [here](https://github.com/Anuken/MindustryBuilds/releases).
|
||||||
|
|
||||||
If you'd rather compile on your own, follow these instructions.
|
If you'd rather compile on your own, follow these instructions.
|
||||||
First, make sure you have [JDK 14](https://adoptopenjdk.net/) installed. Open a terminal in the root directory, `cd` to the Mindustry folder and run the following commands:
|
First, make sure you have [JDK 14](https://adoptopenjdk.net/) installed. Open a terminal in the root directory, `cd` to the Mindustry folder and run the following commands:
|
||||||
|
|
||||||
#### Windows
|
### Windows
|
||||||
|
|
||||||
_Running:_ `gradlew desktop:run`
|
_Running:_ `gradlew desktop:run`
|
||||||
_Building:_ `gradlew desktop:dist`
|
_Building:_ `gradlew desktop:dist`
|
||||||
_Sprite Packing:_ `gradlew tools:pack`
|
_Sprite Packing:_ `gradlew tools:pack`
|
||||||
|
|
||||||
#### Linux/Mac OS
|
### Linux/Mac OS
|
||||||
|
|
||||||
_Running:_ `./gradlew desktop:run`
|
_Running:_ `./gradlew desktop:run`
|
||||||
_Building:_ `./gradlew desktop:dist`
|
_Building:_ `./gradlew desktop:dist`
|
||||||
_Sprite Packing:_ `./gradlew tools:pack`
|
_Sprite Packing:_ `./gradlew tools:pack`
|
||||||
|
|
||||||
#### Server
|
### Server
|
||||||
|
|
||||||
Server builds are bundled with each released build (in Releases). If you'd rather compile on your own, replace 'desktop' with 'server', e.g. `gradlew server:dist`.
|
Server builds are bundled with each released build (in Releases). If you'd rather compile on your own, replace 'desktop' with 'server', e.g. `gradlew server:dist`.
|
||||||
|
|
||||||
#### Android
|
### Android
|
||||||
|
|
||||||
1. Install the Android SDK [here.](https://developer.android.com/studio#downloads) Make sure you're downloading the "Command line tools only", as Android Studio is not required.
|
1. Install the Android SDK [here.](https://developer.android.com/studio#downloads) Make sure you're downloading the "Command line tools only", as Android Studio is not required.
|
||||||
2. Set the `ANDROID_HOME` environment variable to point to your unzipped Android SDK directory.
|
2. Set the `ANDROID_HOME` environment variable to point to your unzipped Android SDK directory.
|
||||||
@@ -44,7 +44,9 @@ Server builds are bundled with each released build (in Releases). If you'd rathe
|
|||||||
|
|
||||||
To debug the application on a connected phone, run `gradlew android:installDebug android:run`.
|
To debug the application on a connected phone, run `gradlew android:installDebug android:run`.
|
||||||
|
|
||||||
##### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
|
#### Permission Denied
|
||||||
|
|
||||||
If the terminal returns `Permission denied` or `Command not found` on Mac/Linux, run `chmod +x ./gradlew` before running `./gradlew`. *This is a one-time procedure.*
|
If the terminal returns `Permission denied` or `Command not found` on Mac/Linux, run `chmod +x ./gradlew` before running `./gradlew`. *This is a one-time procedure.*
|
||||||
|
|
||||||
@@ -53,11 +55,11 @@ If the terminal returns `Permission denied` or `Command not found` on Mac/Linux,
|
|||||||
Gradle may take up to several minutes to download files. Be patient. <br>
|
Gradle may take up to several minutes to download files. Be patient. <br>
|
||||||
After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
|
After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
|
||||||
|
|
||||||
### Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
Post feature requests and feedback [here](https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose).
|
Post feature requests and feedback [here](https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose).
|
||||||
|
|
||||||
### Downloads
|
## Downloads
|
||||||
|
|
||||||
[<img src="https://static.itch.io/images/badge.svg"
|
[<img src="https://static.itch.io/images/badge.svg"
|
||||||
alt="Get it on Itch.io"
|
alt="Get it on Itch.io"
|
||||||
|
|||||||
@@ -262,6 +262,8 @@ project(":ios"){
|
|||||||
project(":core"){
|
project(":core"){
|
||||||
apply plugin: "java-library"
|
apply plugin: "java-library"
|
||||||
|
|
||||||
|
compileJava.options.fork = true
|
||||||
|
|
||||||
task preGen{
|
task preGen{
|
||||||
outputs.upToDateWhen{ false }
|
outputs.upToDateWhen{ false }
|
||||||
generateLocales()
|
generateLocales()
|
||||||
@@ -303,7 +305,7 @@ project(":core"){
|
|||||||
|
|
||||||
compileOnly project(":annotations")
|
compileOnly project(":annotations")
|
||||||
annotationProcessor project(":annotations")
|
annotationProcessor project(":annotations")
|
||||||
annotationProcessor 'com.github.Anuken:jabel:40eec868af'
|
annotationProcessor 'com.github.Anuken:jabel:34e4c172e65b3928cd9eabe1993654ea79c409cd'
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 274 B |
@@ -100,8 +100,7 @@ committingchanges = Comitting Changes
|
|||||||
done = Done
|
done = Done
|
||||||
feature.unsupported = Your device does not support this feature.
|
feature.unsupported = Your device does not support this feature.
|
||||||
|
|
||||||
mods.alphainfo = Keep in mind that mods are in alpha, and[scarlet] may be very buggy[].\nReport any issues you find to the Mindustry GitHub or Discord.
|
mods.alphainfo = Keep in mind that mods are in alpha, and[scarlet] may be very buggy[].\nReport any issues you find to the Mindustry GitHub.
|
||||||
mods.alpha = [accent](Alpha)
|
|
||||||
mods = Mods
|
mods = Mods
|
||||||
mods.none = [lightgray]No mods found!
|
mods.none = [lightgray]No mods found!
|
||||||
mods.guide = Modding Guide
|
mods.guide = Modding Guide
|
||||||
@@ -285,6 +284,7 @@ selectschematic = [accent][[{0}][] to select+copy
|
|||||||
pausebuilding = [accent][[{0}][] to pause building
|
pausebuilding = [accent][[{0}][] to pause building
|
||||||
resumebuilding = [scarlet][[{0}][] to resume building
|
resumebuilding = [scarlet][[{0}][] to resume building
|
||||||
wave = [accent]Wave {0}
|
wave = [accent]Wave {0}
|
||||||
|
wave.cap = [accent]Wave {0}/{1}
|
||||||
wave.waiting = [lightgray]Wave in {0}
|
wave.waiting = [lightgray]Wave in {0}
|
||||||
wave.waveInProgress = [lightgray]Wave in progress
|
wave.waveInProgress = [lightgray]Wave in progress
|
||||||
waiting = [lightgray]Waiting...
|
waiting = [lightgray]Waiting...
|
||||||
@@ -478,7 +478,7 @@ requirement.research = Research {0}
|
|||||||
requirement.capture = Capture {0}
|
requirement.capture = Capture {0}
|
||||||
bestwave = [lightgray]Best Wave: {0}
|
bestwave = [lightgray]Best Wave: {0}
|
||||||
launch.text = Launch
|
launch.text = Launch
|
||||||
campaign.multiplayer = While playing multiplayer in campaign, you can only research using items from [accent]your[] sectors, [scarlet]not[] the host's sector that you are on right now.\n\nTo get items to [accent]your[] sectors in multiplayer, use a [accent]launch pad[].
|
research.multiplayer = Only the host can research items.
|
||||||
uncover = Uncover
|
uncover = Uncover
|
||||||
configure = Configure Loadout
|
configure = Configure Loadout
|
||||||
#TODO
|
#TODO
|
||||||
@@ -521,6 +521,7 @@ sectors.resume = Resume
|
|||||||
sectors.launch = Launch
|
sectors.launch = Launch
|
||||||
sectors.select = Select
|
sectors.select = Select
|
||||||
sectors.nonelaunch = [lightgray]none (sun)
|
sectors.nonelaunch = [lightgray]none (sun)
|
||||||
|
sectors.rename = Rename Sector
|
||||||
|
|
||||||
planet.serpulo.name = Serpulo
|
planet.serpulo.name = Serpulo
|
||||||
#TODO better name
|
#TODO better name
|
||||||
@@ -695,7 +696,6 @@ setting.linear.name = Linear Filtering
|
|||||||
setting.hints.name = Hints
|
setting.hints.name = Hints
|
||||||
setting.flow.name = Display Resource Flow Rate
|
setting.flow.name = Display Resource Flow Rate
|
||||||
setting.buildautopause.name = Auto-Pause Building
|
setting.buildautopause.name = Auto-Pause Building
|
||||||
setting.mapcenter.name = Auto Center Map To Player
|
|
||||||
setting.animatedwater.name = Animated Fluids
|
setting.animatedwater.name = Animated Fluids
|
||||||
setting.animatedshields.name = Animated Shields
|
setting.animatedshields.name = Animated Shields
|
||||||
setting.antialias.name = Antialias[lightgray] (requires restart)[]
|
setting.antialias.name = Antialias[lightgray] (requires restart)[]
|
||||||
@@ -729,7 +729,6 @@ setting.fullscreen.name = Fullscreen
|
|||||||
setting.borderlesswindow.name = Borderless Window[lightgray] (restart may be required)
|
setting.borderlesswindow.name = Borderless Window[lightgray] (restart may be required)
|
||||||
setting.fps.name = Show FPS & Ping
|
setting.fps.name = Show FPS & Ping
|
||||||
setting.smoothcamera.name = Smooth Camera
|
setting.smoothcamera.name = Smooth Camera
|
||||||
setting.blockselectkeys.name = Show Block Select Keys
|
|
||||||
setting.vsync.name = VSync
|
setting.vsync.name = VSync
|
||||||
setting.pixelate.name = Pixelate
|
setting.pixelate.name = Pixelate
|
||||||
setting.minimap.name = Show Minimap
|
setting.minimap.name = Show Minimap
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ credits.text = Creado por [royal]Anuken[] - [sky]anukendev@gmail.com[]
|
|||||||
credits = Créditos
|
credits = Créditos
|
||||||
contributors = Traductores y Contribuidores
|
contributors = Traductores y Contribuidores
|
||||||
discord = ¡Únete al Discord de Mindustry!
|
discord = ¡Únete al Discord de Mindustry!
|
||||||
link.discord.description = La sala oficial del Discord de Mindustry
|
link.discord.description = El servidor official de Discord de Mindustry
|
||||||
link.reddit.description = El subreddit de Mindustry
|
link.reddit.description = El subreddit de Mindustry
|
||||||
link.github.description = Código fuente del juego
|
link.github.description = Código fuente del juego
|
||||||
link.changelog.description = Lista de actualizaciones
|
link.changelog.description = Lista de actualizaciones
|
||||||
link.dev-builds.description = Versiones de desarrollo inestables
|
link.dev-builds.description = Versiones en desarrollo inestables
|
||||||
link.trello.description = Tablero de Trello oficial para las características planificadas
|
link.trello.description = Tablero de Trello oficial para las características planificadas
|
||||||
link.itch.io.description = itch.io es la página donde podes descargar las versiones para PC y web
|
link.itch.io.description = itch.io es la página donde podes descargar las versiones para PC y web
|
||||||
link.google-play.description = Ficha en la Google Play Store
|
link.google-play.description = Página de Mindustry en Google Play Store
|
||||||
link.f-droid.description = Página de F-Droid del juego
|
link.f-droid.description = Página de F-Droid del juego
|
||||||
link.wiki.description = Wiki oficial de Mindustry
|
link.wiki.description = Wiki oficial de Mindustry
|
||||||
link.suggestions.description = Sugerir nuevas funciones
|
link.suggestions.description = Sugerir nuevas funciones
|
||||||
@@ -18,7 +18,7 @@ screenshot = Captura de pantalla guardada en {0}
|
|||||||
screenshot.invalid = Mapa demasiado grande, no hay suficiente memoria para la captura de pantalla.
|
screenshot.invalid = Mapa demasiado grande, no hay suficiente memoria para la captura de pantalla.
|
||||||
gameover = Tu núcleo ha sido destruido.
|
gameover = Tu núcleo ha sido destruido.
|
||||||
gameover.pvp = ¡El equipo[accent] {0}[] ha ganado!
|
gameover.pvp = ¡El equipo[accent] {0}[] ha ganado!
|
||||||
highscore = [accent]¡Nueva mejor puntuación!
|
highscore = [accent]¡Nuevo récord de puntuación!
|
||||||
copied = Copiado.
|
copied = Copiado.
|
||||||
indev.popup = [accent]v6[] is currently in [accent]alpha[].\n[lightgray]This means:[]\n[scarlet]- The campaign is completely unfinished[]\n- Content is missing\n - Most [scarlet]Unit AI[] does not work properly\n- Many units are unfinished\n- Everything you see is subject to change or removal.\n\nReport bugs or crashes on [accent]Github[].
|
indev.popup = [accent]v6[] is currently in [accent]alpha[].\n[lightgray]This means:[]\n[scarlet]- The campaign is completely unfinished[]\n- Content is missing\n - Most [scarlet]Unit AI[] does not work properly\n- Many units are unfinished\n- Everything you see is subject to change or removal.\n\nReport bugs or crashes on [accent]Github[].
|
||||||
indev.notready = This part of the game isn't ready yet
|
indev.notready = This part of the game isn't ready yet
|
||||||
@@ -38,12 +38,12 @@ be.ignore = Ignorar
|
|||||||
be.noupdates = No se encontraron actualizaciones.
|
be.noupdates = No se encontraron actualizaciones.
|
||||||
be.check = Revisando actualizaciones
|
be.check = Revisando actualizaciones
|
||||||
|
|
||||||
schematic = Esquemático
|
schematic = Esquema
|
||||||
schematic.add = Guardar esquemático...
|
schematic.add = Guardar esquema...
|
||||||
schematics = Esquemáticos
|
schematics = Esquemas
|
||||||
schematic.replace = Un esquemático con ese nombre ya existe. ¿Deseas remplazarlo?
|
schematic.replace = Un esquema con ese nombre ya existe. ¿Deseas remplazarlo?
|
||||||
schematic.exists = Un esquemático con ese nombre ya existe.
|
schematic.exists = Un esquema con ese nombre ya existe.
|
||||||
schematic.import = Importar esquemático...
|
schematic.import = Importar esquema...
|
||||||
schematic.exportfile = Exportar archivo
|
schematic.exportfile = Exportar archivo
|
||||||
schematic.importfile = Importar archivo
|
schematic.importfile = Importar archivo
|
||||||
schematic.browseworkshop = Buscar en el Steam Workshop
|
schematic.browseworkshop = Buscar en el Steam Workshop
|
||||||
|
|||||||
@@ -95,3 +95,4 @@ ThePlayerA
|
|||||||
YellOw139
|
YellOw139
|
||||||
PetrGasparik
|
PetrGasparik
|
||||||
LeoDog896
|
LeoDog896
|
||||||
|
Summet
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 827 B |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 372 KiB |
|
Before Width: | Height: | Size: 429 KiB After Width: | Height: | Size: 429 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 187 KiB |
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 426 KiB |
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
@@ -36,6 +36,8 @@ public class Vars implements Loadable{
|
|||||||
public static boolean loadLocales = true;
|
public static boolean loadLocales = true;
|
||||||
/** Whether the logger is loaded. */
|
/** Whether the logger is loaded. */
|
||||||
public static boolean loadedLogger = false, loadedFileLogger = false;
|
public static boolean loadedLogger = false, loadedFileLogger = false;
|
||||||
|
/** Whether to show the cliff button in the editor*/
|
||||||
|
public static boolean addCliffButton = false;
|
||||||
/** Maximum extra padding around deployment schematics. */
|
/** Maximum extra padding around deployment schematics. */
|
||||||
public static final int maxLoadoutSchematicPad = 5;
|
public static final int maxLoadoutSchematicPad = 5;
|
||||||
/** Maximum schematic size.*/
|
/** Maximum schematic size.*/
|
||||||
@@ -86,8 +88,10 @@ public class Vars implements Loadable{
|
|||||||
public static final float logicItemTransferRange = 45f;
|
public static final float logicItemTransferRange = 45f;
|
||||||
/** duration of time between turns in ticks */
|
/** duration of time between turns in ticks */
|
||||||
public static final float turnDuration = 2 * Time.toMinutes;
|
public static final float turnDuration = 2 * Time.toMinutes;
|
||||||
/** turns needed to destroy a sector completely */
|
/** chance of an invasion per turn, 1 = 100% */
|
||||||
public static final float sectorDestructionTurns = 2f;
|
public static final float baseInvasionChance = 1f / 25f;
|
||||||
|
/** how many turns have to pass before invasions start */
|
||||||
|
public static final int invasionGracePeriod = 20;
|
||||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||||
public static final float minArmorDamage = 0.1f;
|
public static final float minArmorDamage = 0.1f;
|
||||||
/** launch animation duration */
|
/** launch animation duration */
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import arc.util.*;
|
|||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.ai.BaseRegistry.*;
|
import mindustry.ai.BaseRegistry.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.game.Schematic.*;
|
import mindustry.game.Schematic.*;
|
||||||
import mindustry.game.Teams.*;
|
import mindustry.game.Teams.*;
|
||||||
@@ -23,7 +24,7 @@ public class BaseAI{
|
|||||||
private static final Vec2 axis = new Vec2(), rotator = new Vec2();
|
private static final Vec2 axis = new Vec2(), rotator = new Vec2();
|
||||||
private static final float correctPercent = 0.5f;
|
private static final float correctPercent = 0.5f;
|
||||||
private static final float step = 5;
|
private static final float step = 5;
|
||||||
private static final int attempts = 5;
|
private static final int attempts = 4;
|
||||||
private static final float emptyChance = 0.01f;
|
private static final float emptyChance = 0.01f;
|
||||||
private static final int timerStep = 0, timerSpawn = 1;
|
private static final int timerStep = 0, timerSpawn = 1;
|
||||||
|
|
||||||
@@ -40,11 +41,11 @@ public class BaseAI{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update(){
|
public void update(){
|
||||||
if(timer.get(timerSpawn, 60) && data.hasCore()){
|
if(data.team.rules().aiCoreSpawn && timer.get(timerSpawn, 60 * 2.5f) && data.hasCore()){
|
||||||
CoreBlock block = (CoreBlock)data.core().block;
|
CoreBlock block = (CoreBlock)data.core().block;
|
||||||
|
|
||||||
//create AI core unit
|
//create AI core unit
|
||||||
if(!state.isEditor() && !Groups.unit.contains(u -> u.team() == data.team && u.type() == block.unitType)){
|
if(!state.isEditor() && !Groups.unit.contains(u -> u.team() == data.team && u.type == block.unitType)){
|
||||||
Unit unit = block.unitType.create(data.team);
|
Unit unit = block.unitType.create(data.team);
|
||||||
unit.set(data.core());
|
unit.set(data.core());
|
||||||
unit.add();
|
unit.add();
|
||||||
@@ -68,7 +69,7 @@ public class BaseAI{
|
|||||||
if(pos == null) return;
|
if(pos == null) return;
|
||||||
|
|
||||||
Tmp.v1.rnd(Mathf.random(range));
|
Tmp.v1.rnd(Mathf.random(range));
|
||||||
int wx = (int)(world.toTile(pos.getX()) + Tmp.v1.x), wy = (int)(world.toTile(pos.getY()) + Tmp.v1.y);
|
int wx = (int)(World.toTile(pos.getX()) + Tmp.v1.x), wy = (int)(World.toTile(pos.getY()) + Tmp.v1.y);
|
||||||
Tile tile = world.tiles.getc(wx, wy);
|
Tile tile = world.tiles.getc(wx, wy);
|
||||||
|
|
||||||
Seq<BasePart> parts = null;
|
Seq<BasePart> parts = null;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import arc.struct.EnumSet;
|
|||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.game.Teams.*;
|
import mindustry.game.Teams.*;
|
||||||
@@ -179,8 +180,8 @@ public class BlockIndexer{
|
|||||||
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
|
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
|
||||||
intSet.clear();
|
intSet.clear();
|
||||||
|
|
||||||
int tx = world.toTile(wx);
|
int tx = World.toTile(wx);
|
||||||
int ty = world.toTile(wy);
|
int ty = World.toTile(wy);
|
||||||
|
|
||||||
int tileRange = (int)(range / tilesize + 1);
|
int tileRange = (int)(range / tilesize + 1);
|
||||||
boolean any = false;
|
boolean any = false;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import arc.util.async.*;
|
import arc.util.async.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
@@ -85,9 +86,6 @@ public class Pathfinder implements Runnable{
|
|||||||
tiles[tile.x][tile.y] = packTile(tile);
|
tiles[tile.x][tile.y] = packTile(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
//special preset which may help speed things up; this is optional
|
|
||||||
preloadPath(getField(state.rules.waveTeam, costGround, fieldCore));
|
|
||||||
|
|
||||||
start();
|
start();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,7 +103,7 @@ public class Pathfinder implements Runnable{
|
|||||||
boolean nearLiquid = false, nearSolid = false, nearGround = false;
|
boolean nearLiquid = false, nearSolid = false, nearGround = false;
|
||||||
|
|
||||||
for(int i = 0; i < 4; i++){
|
for(int i = 0; i < 4; i++){
|
||||||
Tile other = tile.getNearby(i);
|
Tile other = tile.nearby(i);
|
||||||
if(other != null){
|
if(other != null){
|
||||||
if(other.floor().isLiquid) nearLiquid = true;
|
if(other.floor().isLiquid) nearLiquid = true;
|
||||||
if(other.solid()) nearSolid = true;
|
if(other.solid()) nearSolid = true;
|
||||||
@@ -114,7 +112,7 @@ public class Pathfinder implements Runnable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return PathTile.get(
|
return PathTile.get(
|
||||||
tile.build == null ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
tile.build == null || !tile.solid() ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
||||||
tile.getTeamID(),
|
tile.getTeamID(),
|
||||||
tile.solid(),
|
tile.solid(),
|
||||||
tile.floor().isLiquid,
|
tile.floor().isLiquid,
|
||||||
@@ -444,7 +442,7 @@ public class Pathfinder implements Runnable{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getPositions(IntSeq out){
|
public void getPositions(IntSeq out){
|
||||||
out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
|
out.add(Point2.pack(World.toTile(position.getX()), World.toTile(position.getY())));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -453,7 +451,7 @@ public class Pathfinder implements Runnable{
|
|||||||
* Data for a flow field to some set of destinations.
|
* Data for a flow field to some set of destinations.
|
||||||
* Concrete subclasses must specify a way to fetch costs and destinations.
|
* Concrete subclasses must specify a way to fetch costs and destinations.
|
||||||
* */
|
* */
|
||||||
static abstract class Flowfield{
|
public static abstract class Flowfield{
|
||||||
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
||||||
protected int refreshRate;
|
protected int refreshRate;
|
||||||
/** Team this path is for. Set before using. */
|
/** Team this path is for. Set before using. */
|
||||||
@@ -462,7 +460,7 @@ public class Pathfinder implements Runnable{
|
|||||||
protected PathCost cost = costTypes.get(costGround);
|
protected PathCost cost = costTypes.get(costGround);
|
||||||
|
|
||||||
/** costs of getting to a specific tile */
|
/** costs of getting to a specific tile */
|
||||||
int[][] weights;
|
public int[][] weights;
|
||||||
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
||||||
int[][] searches;
|
int[][] searches;
|
||||||
/** search frontier, these are Pos objects */
|
/** search frontier, these are Pos objects */
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
@@ -23,11 +24,21 @@ public class WaveSpawner{
|
|||||||
private Seq<Tile> spawns = new Seq<>();
|
private Seq<Tile> spawns = new Seq<>();
|
||||||
private boolean spawning = false;
|
private boolean spawning = false;
|
||||||
private boolean any = false;
|
private boolean any = false;
|
||||||
|
private Tile firstSpawn = null;
|
||||||
|
|
||||||
public WaveSpawner(){
|
public WaveSpawner(){
|
||||||
Events.on(WorldLoadEvent.class, e -> reset());
|
Events.on(WorldLoadEvent.class, e -> reset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Tile getFirstSpawn(){
|
||||||
|
firstSpawn = null;
|
||||||
|
eachGroundSpawn((cx, cy) -> {
|
||||||
|
firstSpawn = world.tile(cx, cy);
|
||||||
|
});
|
||||||
|
return firstSpawn;
|
||||||
|
}
|
||||||
|
|
||||||
public int countSpawns(){
|
public int countSpawns(){
|
||||||
return spawns.size;
|
return spawns.size;
|
||||||
}
|
}
|
||||||
@@ -38,7 +49,7 @@ public class WaveSpawner{
|
|||||||
|
|
||||||
/** @return true if the player is near a ground spawn point. */
|
/** @return true if the player is near a ground spawn point. */
|
||||||
public boolean playerNear(){
|
public boolean playerNear(){
|
||||||
return !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
|
return state.hasSpawns() && !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void spawnEnemies(){
|
public void spawnEnemies(){
|
||||||
@@ -47,7 +58,7 @@ public class WaveSpawner{
|
|||||||
for(SpawnGroup group : state.rules.spawns){
|
for(SpawnGroup group : state.rules.spawns){
|
||||||
if(group.type == null) continue;
|
if(group.type == null) continue;
|
||||||
|
|
||||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
int spawned = group.getSpawned(state.wave - 1);
|
||||||
|
|
||||||
if(group.type.flying){
|
if(group.type.flying){
|
||||||
float spread = margin / 1.5f;
|
float spread = margin / 1.5f;
|
||||||
@@ -89,9 +100,15 @@ public class WaveSpawner{
|
|||||||
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true));
|
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void eachGroundSpawn(Intc2 cons){
|
||||||
|
eachGroundSpawn((x, y, shock) -> cons.get(World.toTile(x), World.toTile(y)));
|
||||||
|
}
|
||||||
|
|
||||||
private void eachGroundSpawn(SpawnConsumer cons){
|
private void eachGroundSpawn(SpawnConsumer cons){
|
||||||
for(Tile spawn : spawns){
|
if(state.hasSpawns()){
|
||||||
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
for(Tile spawn : spawns){
|
||||||
|
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
|
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
|
||||||
@@ -104,7 +121,7 @@ public class WaveSpawner{
|
|||||||
|
|
||||||
//keep moving forward until the max step amount is reached
|
//keep moving forward until the max step amount is reached
|
||||||
while(steps++ < maxSteps){
|
while(steps++ < maxSteps){
|
||||||
int tx = world.toTile(core.x + Tmp.v1.x), ty = world.toTile(core.y + Tmp.v1.y);
|
int tx = World.toTile(core.x + Tmp.v1.x), ty = World.toTile(core.y + Tmp.v1.y);
|
||||||
any = false;
|
any = false;
|
||||||
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
|
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
|
||||||
if(world.solid(x, y)){
|
if(world.solid(x, y)){
|
||||||
@@ -161,7 +178,7 @@ public class WaveSpawner{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void spawnEffect(Unit unit){
|
private void spawnEffect(Unit unit){
|
||||||
Call.spawnEffect(unit.x, unit.y, unit.type());
|
Call.spawnEffect(unit.x, unit.y, unit.type);
|
||||||
Time.run(30f, unit::add);
|
Time.run(30f, unit::add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class BuilderAI extends AIController{
|
|||||||
float dist = Math.min(cons.dst(unit) - buildingRange, 0);
|
float dist = Math.min(cons.dst(unit) - buildingRange, 0);
|
||||||
|
|
||||||
//make sure you can reach the request in time
|
//make sure you can reach the request in time
|
||||||
if(dist / unit.type().speed < cons.buildCost * 0.9f){
|
if(dist / unit.type.speed < cons.buildCost * 0.9f){
|
||||||
following = b;
|
following = b;
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ public class BuilderAI extends AIController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AIController fallback(){
|
public AIController fallback(){
|
||||||
return unit.type().flying ? new FlyingAI() : new GroundAI();
|
return unit.type.flying ? new FlyingAI() : new GroundAI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class FlyingAI extends AIController{
|
|||||||
@Override
|
@Override
|
||||||
public void updateMovement(){
|
public void updateMovement(){
|
||||||
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
|
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
|
||||||
if(unit.type().weapons.first().rotate){
|
if(unit.type.weapons.first().rotate){
|
||||||
moveTo(target, unit.range() * 0.8f);
|
moveTo(target, unit.range() * 0.8f);
|
||||||
unit.lookAt(target);
|
unit.lookAt(target);
|
||||||
}else{
|
}else{
|
||||||
@@ -57,7 +57,7 @@ public class FlyingAI extends AIController{
|
|||||||
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
|
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
|
||||||
}
|
}
|
||||||
|
|
||||||
vec.setLength(unit.type().speed);
|
vec.setLength(unit.type.speed);
|
||||||
|
|
||||||
unit.moveAt(vec);
|
unit.moveAt(vec);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import mindustry.ai.formations.*;
|
|||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
|
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||||
|
|
||||||
public class FormationAI extends AIController implements FormationMember{
|
public class FormationAI extends AIController implements FormationMember{
|
||||||
public Unit leader;
|
public Unit leader;
|
||||||
@@ -26,14 +27,14 @@ public class FormationAI extends AIController implements FormationMember{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUnit(){
|
public void updateUnit(){
|
||||||
UnitType type = unit.type();
|
UnitType type = unit.type;
|
||||||
|
|
||||||
if(leader.dead){
|
if(leader.dead){
|
||||||
unit.resetController();
|
unit.resetController();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unit.type().canBoost && unit.canPassOn()){
|
if(unit.type.canBoost && unit.canPassOn()){
|
||||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ public class FormationAI extends AIController implements FormationMember{
|
|||||||
|
|
||||||
unit.aim(leader.aimX(), leader.aimY());
|
unit.aim(leader.aimX(), leader.aimY());
|
||||||
|
|
||||||
if(unit.type().rotateShooting){
|
if(unit.type.rotateShooting){
|
||||||
unit.lookAt(leader.aimX(), leader.aimY());
|
unit.lookAt(leader.aimX(), leader.aimY());
|
||||||
}else if(unit.moving()){
|
}else if(unit.moving()){
|
||||||
unit.lookAt(unit.vel.angle());
|
unit.lookAt(unit.vel.angle());
|
||||||
@@ -57,6 +58,30 @@ public class FormationAI extends AIController implements FormationMember{
|
|||||||
}else{
|
}else{
|
||||||
unit.moveAt(realtarget.sub(unit).limit(type.speed));
|
unit.moveAt(realtarget.sub(unit).limit(type.speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(unit instanceof Minerc mine && leader instanceof Minerc com){
|
||||||
|
if(mine.validMine(com.mineTile())){
|
||||||
|
mine.mineTile(com.mineTile());
|
||||||
|
|
||||||
|
CoreBuild core = unit.team.core();
|
||||||
|
|
||||||
|
if(core != null && com.mineTile().drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(com.mineTile().drop())){
|
||||||
|
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||||
|
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||||
|
|
||||||
|
unit.clearItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
mine.mineTile(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(unit instanceof Builderc build && leader instanceof Builderc com && com.activelyBuilding()){
|
||||||
|
build.clearBuilding();
|
||||||
|
build.addBuild(com.buildPlan());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ public class GroundAI extends AIController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unit.type().canBoost && !unit.onSolid()){
|
if(unit.type.canBoost && !unit.onSolid()){
|
||||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type().rotateShooting){
|
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting){
|
||||||
if(unit.type().hasWeapons()){
|
if(unit.type.hasWeapons()){
|
||||||
unit.lookAt(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
|
||||||
}
|
}
|
||||||
}else if(unit.moving()){
|
}else if(unit.moving()){
|
||||||
unit.lookAt(unit.vel().angle());
|
unit.lookAt(unit.vel().angle());
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public class LogicAI extends AIController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unit.type().canBoost && !unit.type().flying){
|
if(unit.type.canBoost && !unit.type.flying){
|
||||||
unit.elevation = Mathf.approachDelta(unit.elevation, Mathf.num(boost || unit.onSolid()), 0.08f);
|
unit.elevation = Mathf.approachDelta(unit.elevation, Mathf.num(boost || unit.onSolid()), 0.08f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ public class LogicAI extends AIController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldShoot(){
|
protected boolean shouldShoot(){
|
||||||
return shoot && !(unit.type().canBoost && boost);
|
return shoot && !(unit.type.canBoost && boost);
|
||||||
}
|
}
|
||||||
|
|
||||||
//always aim for the main target
|
//always aim for the main target
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ public class MinerAI extends AIController{
|
|||||||
|
|
||||||
if(!(unit instanceof Minerc miner) || core == null) return;
|
if(!(unit instanceof Minerc miner) || core == null) return;
|
||||||
|
|
||||||
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type().range)){
|
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type.range)){
|
||||||
miner.mineTile(null);
|
miner.mineTile(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mining){
|
if(mining){
|
||||||
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
if(timer.get(timerTarget2, 60 * 4) || targetItem == null){
|
||||||
|
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
//core full of the target item, do nothing
|
//core full of the target item, do nothing
|
||||||
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
||||||
@@ -34,7 +36,7 @@ public class MinerAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//if inventory is full, drop it off.
|
//if inventory is full, drop it off.
|
||||||
if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
|
if(unit.stack.amount >= unit.type.itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
|
||||||
mining = false;
|
mining = false;
|
||||||
}else{
|
}else{
|
||||||
if(retarget() && targetItem != null){
|
if(retarget() && targetItem != null){
|
||||||
@@ -42,9 +44,9 @@ public class MinerAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(ore != null){
|
if(ore != null){
|
||||||
moveTo(ore, unit.type().range / 2f);
|
moveTo(ore, unit.type.range / 2f);
|
||||||
|
|
||||||
if(unit.within(ore, unit.type().range)){
|
if(unit.within(ore, unit.type.range)){
|
||||||
miner.mineTile(ore);
|
miner.mineTile(ore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ public class MinerAI extends AIController{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unit.within(core, unit.type().range)){
|
if(unit.within(core, unit.type.range)){
|
||||||
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||||
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||||
}
|
}
|
||||||
@@ -70,7 +72,7 @@ public class MinerAI extends AIController{
|
|||||||
mining = true;
|
mining = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
circle(core, unit.type().range / 1.8f);
|
circle(core, unit.type.range / 1.8f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class RepairAI extends AIController{
|
|||||||
if(target instanceof Building){
|
if(target instanceof Building){
|
||||||
boolean shoot = false;
|
boolean shoot = false;
|
||||||
|
|
||||||
if(target.within(unit, unit.type().range)){
|
if(target.within(unit, unit.type.range)){
|
||||||
unit.aim(target);
|
unit.aim(target);
|
||||||
shoot = true;
|
shoot = true;
|
||||||
}
|
}
|
||||||
@@ -23,8 +23,8 @@ public class RepairAI extends AIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(target != null){
|
if(target != null){
|
||||||
if(!target.within(unit, unit.type().range * 0.65f)){
|
if(!target.within(unit, unit.type.range * 0.65f) && target instanceof Building){
|
||||||
moveTo(target, unit.type().range * 0.65f);
|
moveTo(target, unit.type.range * 0.65f);
|
||||||
}
|
}
|
||||||
|
|
||||||
unit.lookAt(target);
|
unit.lookAt(target);
|
||||||
@@ -33,12 +33,14 @@ public class RepairAI extends AIController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateTargeting(){
|
protected void updateTargeting(){
|
||||||
target = Units.findDamagedTile(unit.team, unit.x, unit.y);
|
Building target = Units.findDamagedTile(unit.team, unit.x, unit.y);
|
||||||
|
|
||||||
if(target instanceof ConstructBuild) target = null;
|
if(target instanceof ConstructBuild) target = null;
|
||||||
|
|
||||||
if(target == null){
|
if(target == null){
|
||||||
super.updateTargeting();
|
super.updateTargeting();
|
||||||
|
}else{
|
||||||
|
this.target = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class SuicideAI extends GroundAI{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(retarget()){
|
if(retarget()){
|
||||||
target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
|
target = target(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
|
||||||
}
|
}
|
||||||
|
|
||||||
Building core = unit.closestEnemyCore();
|
Building core = unit.closestEnemyCore();
|
||||||
@@ -30,11 +30,11 @@ public class SuicideAI extends GroundAI{
|
|||||||
|
|
||||||
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){
|
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){
|
||||||
rotate = true;
|
rotate = true;
|
||||||
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
|
shoot = unit.within(target, unit.type.weapons.first().bullet.range() +
|
||||||
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
||||||
|
|
||||||
if(unit.type().hasWeapons()){
|
if(unit.type.hasWeapons()){
|
||||||
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
unit.aimLook(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
//do not move toward walls or transport blocks
|
//do not move toward walls or transport blocks
|
||||||
@@ -65,7 +65,7 @@ public class SuicideAI extends GroundAI{
|
|||||||
if(!blocked){
|
if(!blocked){
|
||||||
moveToTarget = true;
|
moveToTarget = true;
|
||||||
//move towards target directly
|
//move towards target directly
|
||||||
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
|
unit.moveAt(vec.set(target).sub(unit).limit(unit.type.speed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class PhysicsProcess implements AsyncProcess{
|
|||||||
PhysicRef ref = entity.physref();
|
PhysicRef ref = entity.physref();
|
||||||
|
|
||||||
ref.body.layer =
|
ref.body.layer =
|
||||||
entity.type().allowLegStep ? layerLegs :
|
entity.type.allowLegStep ? layerLegs :
|
||||||
entity.isGrounded() ? layerGround : layerFlying;
|
entity.isGrounded() ? layerGround : layerFlying;
|
||||||
ref.x = entity.x();
|
ref.x = entity.x();
|
||||||
ref.y = entity.y();
|
ref.y = entity.y();
|
||||||
|
|||||||
@@ -919,8 +919,8 @@ public class Blocks implements ContentList{
|
|||||||
shockMine = new ShockMine("shock-mine"){{
|
shockMine = new ShockMine("shock-mine"){{
|
||||||
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
|
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
|
||||||
hasShadow = false;
|
hasShadow = false;
|
||||||
health = 40;
|
health = 50;
|
||||||
damage = 23;
|
damage = 25;
|
||||||
tileDamage = 7f;
|
tileDamage = 7f;
|
||||||
length = 10;
|
length = 10;
|
||||||
tendrils = 4;
|
tendrils = 4;
|
||||||
@@ -1353,7 +1353,7 @@ public class Blocks implements ContentList{
|
|||||||
size = 5;
|
size = 5;
|
||||||
|
|
||||||
unitCapModifier = 20;
|
unitCapModifier = 20;
|
||||||
researchCostMultiplier = 0.06f;
|
researchCostMultiplier = 0.05f;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
vault = new StorageBlock("vault"){{
|
vault = new StorageBlock("vault"){{
|
||||||
@@ -1713,7 +1713,7 @@ public class Blocks implements ContentList{
|
|||||||
despawnEffect = Fx.instBomb;
|
despawnEffect = Fx.instBomb;
|
||||||
trailSpacing = 20f;
|
trailSpacing = 20f;
|
||||||
damage = 1350;
|
damage = 1350;
|
||||||
tileDamageMultiplier = 0.5f;
|
tileDamageMultiplier = 0.3f;
|
||||||
speed = brange;
|
speed = brange;
|
||||||
hitShake = 6f;
|
hitShake = 6f;
|
||||||
ammoMultiplier = 1f;
|
ammoMultiplier = 1f;
|
||||||
@@ -1912,7 +1912,7 @@ public class Blocks implements ContentList{
|
|||||||
new UnitType[]{UnitTypes.antumbra, UnitTypes.eclipse},
|
new UnitType[]{UnitTypes.antumbra, UnitTypes.eclipse},
|
||||||
new UnitType[]{UnitTypes.arkyid, UnitTypes.toxopid},
|
new UnitType[]{UnitTypes.arkyid, UnitTypes.toxopid},
|
||||||
new UnitType[]{UnitTypes.scepter, UnitTypes.reign},
|
new UnitType[]{UnitTypes.scepter, UnitTypes.reign},
|
||||||
new UnitType[] {UnitTypes.sei, UnitTypes.omura},
|
new UnitType[]{UnitTypes.sei, UnitTypes.omura},
|
||||||
new UnitType[]{UnitTypes.quad, UnitTypes.oct},
|
new UnitType[]{UnitTypes.quad, UnitTypes.oct},
|
||||||
new UnitType[]{UnitTypes.vela, UnitTypes.corvus}
|
new UnitType[]{UnitTypes.vela, UnitTypes.corvus}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ public class Bullets implements ContentList{
|
|||||||
speed = 4f;
|
speed = 4f;
|
||||||
knockback = 1.3f;
|
knockback = 1.3f;
|
||||||
puddleSize = 8f;
|
puddleSize = 8f;
|
||||||
damage = 6f;
|
damage = 5f;
|
||||||
drag = 0.001f;
|
drag = 0.001f;
|
||||||
ammoMultiplier = 2f;
|
ammoMultiplier = 2f;
|
||||||
statusDuration = 60f * 4f;
|
statusDuration = 60f * 4f;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class Fx{
|
|||||||
|
|
||||||
mixcol(Pal.accent, 1f);
|
mixcol(Pal.accent, 1f);
|
||||||
alpha(e.fout());
|
alpha(e.fout());
|
||||||
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type().icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
|
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type.icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
|
||||||
alpha(1f);
|
alpha(1f);
|
||||||
Lines.stroke(e.fslope() * 1f);
|
Lines.stroke(e.fslope() * 1f);
|
||||||
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
|
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
|
||||||
@@ -66,7 +66,7 @@ public class Fx{
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
unitDespawn = new Effect(100f, e -> {
|
unitDespawn = new Effect(100f, e -> {
|
||||||
if(!(e.data instanceof Unit) || e.<Unit>data().type() == null) return;
|
if(!(e.data instanceof Unit) || e.<Unit>data().type == null) return;
|
||||||
|
|
||||||
Unit select = e.data();
|
Unit select = e.data();
|
||||||
float scl = e.fout(Interp.pow2Out);
|
float scl = e.fout(Interp.pow2Out);
|
||||||
@@ -74,7 +74,7 @@ public class Fx{
|
|||||||
Draw.scl *= scl;
|
Draw.scl *= scl;
|
||||||
|
|
||||||
mixcol(Pal.accent, 1f);
|
mixcol(Pal.accent, 1f);
|
||||||
rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
|
rect(select.type.icon(Cicon.full), select.x, select.y, select.rotation - 90f);
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
Draw.scl = p;
|
Draw.scl = p;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class SectorPresets implements ContentList{
|
|||||||
groundZero = new SectorPreset("groundZero", serpulo, 15){{
|
groundZero = new SectorPreset("groundZero", serpulo, 15){{
|
||||||
alwaysUnlocked = true;
|
alwaysUnlocked = true;
|
||||||
captureWave = 10;
|
captureWave = 10;
|
||||||
difficulty = 0;
|
difficulty = 1;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
|
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
|
||||||
@@ -26,23 +26,23 @@ public class SectorPresets implements ContentList{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
|
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
|
||||||
captureWave = 40;
|
captureWave = 20;
|
||||||
difficulty = 1;
|
difficulty = 2;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
craters = new SectorPreset("craters", serpulo, 18){{
|
craters = new SectorPreset("craters", serpulo, 18){{
|
||||||
captureWave = 40;
|
captureWave = 20;
|
||||||
difficulty = 2;
|
difficulty = 2;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
ruinousShores = new SectorPreset("ruinousShores", serpulo, 19){{
|
ruinousShores = new SectorPreset("ruinousShores", serpulo, 19){{
|
||||||
captureWave = 40;
|
captureWave = 30;
|
||||||
difficulty = 3;
|
difficulty = 3;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
stainedMountains = new SectorPreset("stainedMountains", serpulo, 20){{
|
stainedMountains = new SectorPreset("stainedMountains", serpulo, 20){{
|
||||||
captureWave = 30;
|
captureWave = 30;
|
||||||
difficulty = 2;
|
difficulty = 3;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
|
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
|
||||||
@@ -54,7 +54,7 @@ public class SectorPresets implements ContentList{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
tarFields = new SectorPreset("tarFields", serpulo, 23){{
|
tarFields = new SectorPreset("tarFields", serpulo, 23){{
|
||||||
captureWave = 40;
|
captureWave = 50;
|
||||||
difficulty = 5;
|
difficulty = 5;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
|||||||
@@ -872,7 +872,6 @@ public class UnitTypes implements ContentList{
|
|||||||
drag = 0.01f;
|
drag = 0.01f;
|
||||||
flying = true;
|
flying = true;
|
||||||
health = 75;
|
health = 75;
|
||||||
faceTarget = false;
|
|
||||||
engineOffset = 5.5f;
|
engineOffset = 5.5f;
|
||||||
range = 140f;
|
range = 140f;
|
||||||
|
|
||||||
@@ -1449,13 +1448,13 @@ public class UnitTypes implements ContentList{
|
|||||||
trailMult = 0.8f;
|
trailMult = 0.8f;
|
||||||
hitEffect = Fx.massiveExplosion;
|
hitEffect = Fx.massiveExplosion;
|
||||||
knockback = 1.5f;
|
knockback = 1.5f;
|
||||||
lifetime = 140f;
|
lifetime = 100f;
|
||||||
height = 15.5f;
|
height = 15.5f;
|
||||||
width = 15f;
|
width = 15f;
|
||||||
collidesTiles = false;
|
collidesTiles = false;
|
||||||
ammoMultiplier = 4f;
|
ammoMultiplier = 4f;
|
||||||
splashDamageRadius = 60f;
|
splashDamageRadius = 60f;
|
||||||
splashDamage = 85f;
|
splashDamage = 80f;
|
||||||
backColor = Pal.missileYellowBack;
|
backColor = Pal.missileYellowBack;
|
||||||
frontColor = Pal.missileYellow;
|
frontColor = Pal.missileYellow;
|
||||||
trailEffect = Fx.artilleryTrail;
|
trailEffect = Fx.artilleryTrail;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import arc.math.*;
|
|||||||
import arc.scene.ui.*;
|
import arc.scene.ui.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
|
||||||
import mindustry.audio.*;
|
import mindustry.audio.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.core.GameState.*;
|
import mindustry.core.GameState.*;
|
||||||
@@ -25,7 +24,6 @@ import mindustry.maps.Map;
|
|||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.ui.dialogs.*;
|
import mindustry.ui.dialogs.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.text.*;
|
import java.text.*;
|
||||||
@@ -160,9 +158,7 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
|
|
||||||
//delete the save, it is gone.
|
//delete the save, it is gone.
|
||||||
if(saves.getCurrent() != null && !state.rules.tutorial){
|
if(saves.getCurrent() != null && !state.rules.tutorial){
|
||||||
Sector sector = state.getSector();
|
saves.getCurrent().save();
|
||||||
sector.save = null;
|
|
||||||
saves.getCurrent().delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -252,19 +248,6 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO move
|
|
||||||
public void handleLaunch(CoreBuild tile){
|
|
||||||
LaunchCorec ent = LaunchCore.create();
|
|
||||||
ent.set(tile);
|
|
||||||
ent.block(Blocks.coreShard);
|
|
||||||
ent.lifetime(Vars.launchDuration);
|
|
||||||
ent.add();
|
|
||||||
|
|
||||||
//remove schematic requirements from core
|
|
||||||
tile.items.remove(universe.getLastLoadout().requirements());
|
|
||||||
tile.items.remove(universe.getLaunchResources());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playSector(Sector sector){
|
public void playSector(Sector sector){
|
||||||
playSector(sector, sector);
|
playSector(sector, sector);
|
||||||
}
|
}
|
||||||
@@ -281,21 +264,29 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
slot.load();
|
slot.load();
|
||||||
slot.setAutosave(true);
|
slot.setAutosave(true);
|
||||||
state.rules.sector = sector;
|
state.rules.sector = sector;
|
||||||
|
state.secinfo = state.rules.sector.info;
|
||||||
|
|
||||||
//if there is no base, simulate a new game and place the right loadout at the spawn position
|
//if there is no base, simulate a new game and place the right loadout at the spawn position
|
||||||
//TODO this is broken?
|
|
||||||
if(state.rules.defaultTeam.cores().isEmpty()){
|
if(state.rules.defaultTeam.cores().isEmpty()){
|
||||||
|
|
||||||
//kill all friendly units, since they should be dead anwyay
|
//no spawn set -> delete the sector save
|
||||||
for(Unit unit : Groups.unit){
|
if(sector.info.spawnPosition == 0){
|
||||||
if(unit.team() == state.rules.defaultTeam){
|
//delete old save
|
||||||
unit.remove();
|
sector.save = null;
|
||||||
}
|
slot.delete();
|
||||||
|
//play again
|
||||||
|
playSector(origin, sector);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tile spawn = world.tile(sector.getSpawnPosition());
|
//reset wave so things are more fair
|
||||||
//TODO PLACE CORRECT LOADOUT
|
state.wave = 1;
|
||||||
Schematics.placeLoadout(universe.getLastLoadout(), spawn.x, spawn.y);
|
|
||||||
|
//kill all units, since they should be dead anwyay
|
||||||
|
Groups.unit.clear();
|
||||||
|
|
||||||
|
Tile spawn = world.tile(sector.info.spawnPosition);
|
||||||
|
Schematics.placeLaunchLoadout(spawn.x, spawn.y);
|
||||||
|
|
||||||
//set up camera/player locations
|
//set up camera/player locations
|
||||||
player.set(spawn.x * tilesize, spawn.y * tilesize);
|
player.set(spawn.x * tilesize, spawn.y * tilesize);
|
||||||
@@ -317,7 +308,6 @@ public class Control implements ApplicationListener, Loadable{
|
|||||||
}else{
|
}else{
|
||||||
net.reset();
|
net.reset();
|
||||||
logic.reset();
|
logic.reset();
|
||||||
sector.setSecondsPassed(0);
|
|
||||||
world.loadSector(sector);
|
world.loadSector(sector);
|
||||||
state.rules.sector = sector;
|
state.rules.sector = sector;
|
||||||
//assign origin when launching
|
//assign origin when launching
|
||||||
|
|||||||
@@ -41,10 +41,17 @@ public class GameState{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void set(State astate){
|
public void set(State astate){
|
||||||
|
//cannot pause when in multiplayer
|
||||||
|
if(astate == State.paused && net.active()) return;
|
||||||
|
|
||||||
Events.fire(new StateChangeEvent(state, astate));
|
Events.fire(new StateChangeEvent(state, astate));
|
||||||
state = astate;
|
state = astate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasSpawns(){
|
||||||
|
return rules.waves && !(isCampaign() && rules.attackMode);
|
||||||
|
}
|
||||||
|
|
||||||
/** Note that being in a campaign does not necessarily mean having a sector. */
|
/** Note that being in a campaign does not necessarily mean having a sector. */
|
||||||
public boolean isCampaign(){
|
public boolean isCampaign(){
|
||||||
return rules.sector != null;
|
return rules.sector != null;
|
||||||
@@ -68,7 +75,7 @@ public class GameState{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPlaying(){
|
public boolean isPlaying(){
|
||||||
return state == State.playing;
|
return (state == State.playing) || (state == State.paused && !isPaused());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return whether the current state is *not* the menu. */
|
/** @return whether the current state is *not* the menu. */
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import arc.math.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.core.GameState.*;
|
import mindustry.core.GameState.*;
|
||||||
|
import mindustry.ctype.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.game.Teams.*;
|
import mindustry.game.Teams.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
|
import mindustry.maps.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.type.Weather.*;
|
import mindustry.type.Weather.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.*;
|
|
||||||
import mindustry.world.blocks.ConstructBlock.*;
|
|
||||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -39,32 +38,7 @@ public class Logic implements ApplicationListener{
|
|||||||
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
|
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
|
||||||
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
|
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
|
||||||
|
|
||||||
if(block instanceof ConstructBlock){
|
tile.build.addPlan(true);
|
||||||
|
|
||||||
ConstructBuild entity = tile.bc();
|
|
||||||
|
|
||||||
//update block to reflect the fact that something was being constructed
|
|
||||||
if(entity.cblock != null && entity.cblock.synthetic()){
|
|
||||||
block = entity.cblock;
|
|
||||||
}else{
|
|
||||||
//otherwise this was a deconstruction that was interrupted, don't want to rebuild that
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TeamData data = state.teams.get(tile.team());
|
|
||||||
|
|
||||||
//remove existing blocks that have been placed here.
|
|
||||||
//painful O(n) iteration + copy
|
|
||||||
for(int i = 0; i < data.blocks.size; i++){
|
|
||||||
BlockPlan b = data.blocks.get(i);
|
|
||||||
if(b.x == tile.x && b.y == tile.y){
|
|
||||||
data.blocks.removeIndex(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)tile.build.rotation, block.id, tile.build.config()));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.on(BlockBuildEndEvent.class, event -> {
|
Events.on(BlockBuildEndEvent.class, event -> {
|
||||||
@@ -84,51 +58,61 @@ public class Logic implements ApplicationListener{
|
|||||||
Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack));
|
Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack));
|
||||||
|
|
||||||
//when loading a 'damaged' sector, propagate the damage
|
//when loading a 'damaged' sector, propagate the damage
|
||||||
Events.on(WorldLoadEvent.class, e -> {
|
Events.on(SaveLoadEvent.class, e -> {
|
||||||
if(state.isCampaign()){
|
if(state.isCampaign()){
|
||||||
long seconds = state.rules.sector.getSecondsPassed();
|
state.secinfo.write();
|
||||||
CoreBuild core = state.rules.defaultTeam.core();
|
|
||||||
//THE WAVES NEVER END
|
|
||||||
state.rules.waves = true;
|
|
||||||
|
|
||||||
//apply fractional damage based on how many turns have passed for this sector
|
//how much wave time has passed
|
||||||
//float turnsPassed = seconds / (turnDuration / 60f);
|
int wavesPassed = state.secinfo.wavesPassed;
|
||||||
|
|
||||||
//TODO sector damage disabled for now
|
//wave has passed, remove all enemies, they are assumed to be dead
|
||||||
//if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
|
if(wavesPassed > 0){
|
||||||
// SectorDamage.apply(turnsPassed / sectorDestructionTurns);
|
Groups.unit.each(u -> {
|
||||||
//}
|
if(u.team == state.rules.waveTeam){
|
||||||
|
u.remove();
|
||||||
//add resources based on turns passed
|
}
|
||||||
if(state.rules.sector.save != null && core != null){
|
});
|
||||||
//update correct storage capacity
|
|
||||||
state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity;
|
|
||||||
|
|
||||||
//add new items received
|
|
||||||
state.rules.sector.calculateReceivedItems().each((item, amount) -> core.items.add(item, amount));
|
|
||||||
|
|
||||||
//clear received items
|
|
||||||
state.rules.sector.setExtraItems(new ItemSeq());
|
|
||||||
|
|
||||||
//validation
|
|
||||||
for(Item item : content.items()){
|
|
||||||
//ensure positive items
|
|
||||||
if(core.items.get(item) < 0) core.items.set(item, 0);
|
|
||||||
//cap the items
|
|
||||||
if(core.items.get(item) > core.storageCapacity) core.items.set(item, core.storageCapacity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.rules.sector.setSecondsPassed(0);
|
//simulate passing of waves
|
||||||
}
|
if(wavesPassed > 0){
|
||||||
|
//simulate wave counter moving forward
|
||||||
|
state.wave += wavesPassed;
|
||||||
|
state.wavetime = state.rules.waveSpacing;
|
||||||
|
|
||||||
|
SectorDamage.applyCalculatedDamage();
|
||||||
|
}
|
||||||
|
|
||||||
|
//reset values
|
||||||
|
state.secinfo.damage = 0f;
|
||||||
|
state.secinfo.wavesPassed = 0;
|
||||||
|
state.secinfo.hasCore = true;
|
||||||
|
state.secinfo.secondsPassed = 0;
|
||||||
|
|
||||||
|
state.rules.sector.saveInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Events.on(WorldLoadEvent.class, e -> {
|
||||||
//enable infinite ammo for wave team by default
|
//enable infinite ammo for wave team by default
|
||||||
state.rules.waveTeam.rules().infiniteAmmo = true;
|
state.rules.waveTeam.rules().infiniteAmmo = true;
|
||||||
|
if(state.isCampaign()){
|
||||||
|
//enable building AI
|
||||||
|
state.rules.waveTeam.rules().ai = true;
|
||||||
|
state.rules.waveTeam.rules().infiniteResources = true;
|
||||||
|
}
|
||||||
|
|
||||||
//save settings
|
//save settings
|
||||||
Core.settings.manualSave();
|
Core.settings.manualSave();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//sync research
|
||||||
|
Events.on(ResearchEvent.class, e -> {
|
||||||
|
if(net.server()){
|
||||||
|
Call.researched(e.content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds starting items, resets wave time, and sets state to playing. */
|
/** Adds starting items, resets wave time, and sets state to playing. */
|
||||||
@@ -168,11 +152,6 @@ public class Logic implements ApplicationListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void skipWave(){
|
public void skipWave(){
|
||||||
if(state.isCampaign()){
|
|
||||||
//warp time spent forward because the wave was just skipped.
|
|
||||||
state.secinfo.internalTimeSpent += state.wavetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.wavetime = 0;
|
state.wavetime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,12 +178,13 @@ public class Logic implements ApplicationListener{
|
|||||||
state.rules.waves = false;
|
state.rules.waves = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO capturing is disabled
|
|
||||||
/*
|
|
||||||
//if there's a "win" wave and no enemies are present, win automatically
|
//if there's a "win" wave and no enemies are present, win automatically
|
||||||
if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){
|
if(state.rules.waves && (state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()) ||
|
||||||
|
(state.rules.attackMode && state.rules.waveTeam.cores().isEmpty())){
|
||||||
//the sector has been conquered - waves get disabled
|
//the sector has been conquered - waves get disabled
|
||||||
state.rules.waves = false;
|
state.rules.waves = false;
|
||||||
|
//disable attack mode
|
||||||
|
state.rules.attackMode = false;
|
||||||
|
|
||||||
//fire capture event
|
//fire capture event
|
||||||
Events.fire(new SectorCaptureEvent(state.rules.sector));
|
Events.fire(new SectorCaptureEvent(state.rules.sector));
|
||||||
@@ -213,7 +193,7 @@ public class Logic implements ApplicationListener{
|
|||||||
if(!headless){
|
if(!headless){
|
||||||
control.saves.saveSector(state.rules.sector);
|
control.saves.saveSector(state.rules.sector);
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}else{
|
}else{
|
||||||
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
||||||
state.gameOver = true;
|
state.gameOver = true;
|
||||||
@@ -266,6 +246,15 @@ public class Logic implements ApplicationListener{
|
|||||||
netClient.setQuiet();
|
netClient.setQuiet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//called when the remote server researches something
|
||||||
|
@Remote
|
||||||
|
public static void researched(Content content){
|
||||||
|
if(!(content instanceof UnlockableContent u)) return;
|
||||||
|
|
||||||
|
state.rules.researched.add(u.name);
|
||||||
|
ui.hudfrag.showUnlock(u);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose(){
|
public void dispose(){
|
||||||
//save the settings before quitting
|
//save the settings before quitting
|
||||||
@@ -283,7 +272,7 @@ public class Logic implements ApplicationListener{
|
|||||||
|
|
||||||
if(state.isGame()){
|
if(state.isGame()){
|
||||||
if(!net.client()){
|
if(!net.client()){
|
||||||
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted);
|
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type.isCounted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!state.isPaused()){
|
if(!state.isPaused()){
|
||||||
|
|||||||
@@ -575,7 +575,7 @@ public class NetServer implements ApplicationListener{
|
|||||||
shooting = false;
|
shooting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
|
if(!player.dead() && (player.unit().type.flying || !player.unit().type.canBoost)){
|
||||||
boosting = false;
|
boosting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,7 +629,7 @@ public class NetServer implements ApplicationListener{
|
|||||||
Unit unit = player.unit();
|
Unit unit = player.unit();
|
||||||
|
|
||||||
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
|
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
|
||||||
float maxSpeed = ((player.unit().type().canBoost && player.unit().isFlying()) ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
|
float maxSpeed = ((player.unit().type.canBoost && player.unit().isFlying()) ? player.unit().type.boostMultiplier : 1f) * player.unit().type.speed;
|
||||||
if(unit.isGrounded()){
|
if(unit.isGrounded()){
|
||||||
maxSpeed *= unit.floorSpeedMultiplier();
|
maxSpeed *= unit.floorSpeedMultiplier();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,17 @@ public class World{
|
|||||||
return build(Math.round(x / tilesize), Math.round(y / tilesize));
|
return build(Math.round(x / tilesize), Math.round(y / tilesize));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int toTile(float coord){
|
/** Convert from world to logic tile coordinates. Whole numbers are at centers of tiles. */
|
||||||
|
public static float conv(float coord){
|
||||||
|
return coord / tilesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert from tile to world coordinates. */
|
||||||
|
public static float unconv(float coord){
|
||||||
|
return coord * tilesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int toTile(float coord){
|
||||||
return Math.round(coord / tilesize);
|
return Math.round(coord / tilesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +263,7 @@ public class World{
|
|||||||
setSectorRules(sector);
|
setSectorRules(sector);
|
||||||
|
|
||||||
if(state.rules.defaultTeam.core() != null){
|
if(state.rules.defaultTeam.core() != null){
|
||||||
sector.setSpawnPosition(state.rules.defaultTeam.core().pos());
|
sector.info.spawnPosition = state.rules.defaultTeam.core().pos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,8 +277,6 @@ public class World{
|
|||||||
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
|
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
|
||||||
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
||||||
|
|
||||||
float waterFloors = 0, totalFloors = 0;
|
|
||||||
|
|
||||||
for(Tile tile : world.tiles){
|
for(Tile tile : world.tiles){
|
||||||
if(world.getDarkness(tile.x, tile.y) >= 3){
|
if(world.getDarkness(tile.x, tile.y) >= 3){
|
||||||
continue;
|
continue;
|
||||||
@@ -280,10 +288,6 @@ public class World{
|
|||||||
if(liquid != null) content.add(liquid);
|
if(liquid != null) content.add(liquid);
|
||||||
|
|
||||||
if(!tile.block().isStatic()){
|
if(!tile.block().isStatic()){
|
||||||
totalFloors ++;
|
|
||||||
if(liquid == Liquids.water){
|
|
||||||
waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
|
|
||||||
}
|
|
||||||
floorc.increment(tile.floor());
|
floorc.increment(tile.floor());
|
||||||
if(tile.overlay() != Blocks.air){
|
if(tile.overlay() != Blocks.air){
|
||||||
floorc.increment(tile.overlay());
|
floorc.increment(tile.overlay());
|
||||||
@@ -326,9 +330,9 @@ public class World{
|
|||||||
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.secinfo.resources = content.asArray();
|
sector.info.resources = content.asArray();
|
||||||
state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||||
|
sector.saveInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context filterContext(Map map){
|
public Context filterContext(Map map){
|
||||||
|
|||||||
@@ -95,16 +95,17 @@ public abstract class UnlockableContent extends MappableContent{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean unlocked(){
|
public boolean unlocked(){
|
||||||
|
if(net.client()) return state.rules.researched.contains(name);
|
||||||
return unlocked || alwaysUnlocked;
|
return unlocked || alwaysUnlocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||||
public final boolean unlockedNow(){
|
public boolean unlockedNow(){
|
||||||
return unlocked || alwaysUnlocked || !state.isCampaign();
|
return unlocked() || !state.isCampaign();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean locked(){
|
public boolean locked(){
|
||||||
return !unlocked();
|
return !unlocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ public class EditorTile extends Tile{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
|
protected void changeBuild(Team team, Prov<Building> entityprov, int rotation){
|
||||||
if(skip()){
|
if(skip()){
|
||||||
super.changeEntity(team, entityprov, rotation);
|
super.changeBuild(team, entityprov, rotation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import arc.files.*;
|
|||||||
import arc.func.*;
|
import arc.func.*;
|
||||||
import arc.graphics.*;
|
import arc.graphics.*;
|
||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
|
import arc.math.geom.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.editor.DrawOperation.*;
|
import mindustry.editor.DrawOperation.*;
|
||||||
@@ -180,6 +181,52 @@ public class MapEditor{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addCliffs(){
|
||||||
|
for(Tile tile : world.tiles){
|
||||||
|
if(!tile.block().isStatic() || tile.block() == Blocks.cliff) continue;
|
||||||
|
|
||||||
|
int rotation = 0;
|
||||||
|
for(int i = 0; i < 8; i++){
|
||||||
|
Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
|
||||||
|
if(other != null && !other.block().isStatic()){
|
||||||
|
rotation |= (1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rotation != 0){
|
||||||
|
tile.setBlock(Blocks.cliff);
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.data = (byte)rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Tile tile : world.tiles){
|
||||||
|
if(tile.block() != Blocks.cliff && tile.block().isStatic()){
|
||||||
|
tile.setBlock(Blocks.air);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFloorCliffs(){
|
||||||
|
for(Tile tile : world.tiles){
|
||||||
|
if(!tile.floor().hasSurface() || tile.block() == Blocks.cliff) continue;
|
||||||
|
|
||||||
|
int rotation = 0;
|
||||||
|
for(int i = 0; i < 8; i++){
|
||||||
|
Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
|
||||||
|
if(other != null && !other.floor().hasSurface()){
|
||||||
|
rotation |= (1 << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rotation != 0){
|
||||||
|
tile.setBlock(Blocks.cliff);
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.data = (byte)rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void drawCircle(int x, int y, Cons<Tile> drawer){
|
public void drawCircle(int x, int y, Cons<Tile> drawer){
|
||||||
for(int rx = -brushSize; rx <= brushSize; rx++){
|
for(int rx = -brushSize; rx <= brushSize; rx++){
|
||||||
for(int ry = -brushSize; ry <= brushSize; ry++){
|
for(int ry = -brushSize; ry <= brushSize; ry++){
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void build(){
|
public void build(){
|
||||||
float size = 60f;
|
float size = 58f;
|
||||||
|
|
||||||
clearChildren();
|
clearChildren();
|
||||||
table(cont -> {
|
table(cont -> {
|
||||||
@@ -559,10 +559,19 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
|||||||
|
|
||||||
mid.row();
|
mid.row();
|
||||||
|
|
||||||
mid.table(t -> {
|
if(!mobile){
|
||||||
t.button("@editor.center", Icon.move, Styles.cleart, () -> view.center()).growX().margin(9f);
|
mid.table(t -> {
|
||||||
}).growX().top();
|
t.button("@editor.center", Icon.move, Styles.cleart, view::center).growX().margin(9f);
|
||||||
|
}).growX().top();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addCliffButton){
|
||||||
|
mid.row();
|
||||||
|
|
||||||
|
mid.table(t -> {
|
||||||
|
t.button("Cliffs", Icon.terrain, Styles.cleart, editor::addCliffs).growX().margin(9f);
|
||||||
|
}).growX().top();
|
||||||
|
}
|
||||||
}).margin(0).left().growY();
|
}).margin(0).left().growY();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class MapGenerateDialog extends BaseDialog{
|
|||||||
CachedTile ctile = new CachedTile(){
|
CachedTile ctile = new CachedTile(){
|
||||||
//nothing.
|
//nothing.
|
||||||
@Override
|
@Override
|
||||||
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
|
protected void changeBuild(Team team, Prov<Building> entityprov, int rotation){
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class WaveGraph extends Table{
|
|||||||
int sum = 0;
|
int sum = 0;
|
||||||
|
|
||||||
for(SpawnGroup spawn : groups){
|
for(SpawnGroup spawn : groups){
|
||||||
int spawned = spawn.getUnitsSpawned(i);
|
int spawned = spawn.getSpawned(i);
|
||||||
values[index][spawn.type.id] += spawned;
|
values[index][spawn.type.id] += spawned;
|
||||||
if(spawned > 0){
|
if(spawned > 0){
|
||||||
used.add(spawn.type);
|
used.add(spawn.type);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
@@ -82,7 +83,7 @@ public class Damage{
|
|||||||
|
|
||||||
furthest = null;
|
furthest = null;
|
||||||
|
|
||||||
boolean found = world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
|
boolean found = world.raycast(b.tileX(), b.tileY(), World.toTile(b.x + Tmp.v1.x), World.toTile(b.y + Tmp.v1.y),
|
||||||
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
|
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
|
||||||
|
|
||||||
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
|
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import arc.math.*;
|
|||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.bullet.*;
|
import mindustry.entities.bullet.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
@@ -48,7 +49,7 @@ public class Lightning{
|
|||||||
bhit = false;
|
bhit = false;
|
||||||
Vec2 from = lines.get(lines.size - 2);
|
Vec2 from = lines.get(lines.size - 2);
|
||||||
Vec2 to = lines.get(lines.size - 1);
|
Vec2 to = lines.get(lines.size - 1);
|
||||||
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
|
world.raycastEach(World.toTile(from.getX()), World.toTile(from.getY()), World.toTile(to.getX()), World.toTile(to.getY()), (wx, wy) -> {
|
||||||
|
|
||||||
Tile tile = world.tile(wx, wy);
|
Tile tile = world.tile(wx, wy);
|
||||||
if(tile != null && tile.block().insulated){
|
if(tile != null && tile.block().insulated){
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class Units{
|
|||||||
|
|
||||||
nearby(x, y, width, height, unit -> {
|
nearby(x, y, width, height, unit -> {
|
||||||
if(boolResult) return;
|
if(boolResult) return;
|
||||||
if((unit.isGrounded() && !unit.type().hovering) == ground){
|
if((unit.isGrounded() && !unit.type.hovering) == ground){
|
||||||
unit.hitbox(hitrect);
|
unit.hitbox(hitrect);
|
||||||
|
|
||||||
if(hitrect.overlaps(x, y, width, height)){
|
if(hitrect.overlaps(x, y, width, height)){
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class ForceFieldAbility extends Ability{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkRadius(Unit unit){
|
public void checkRadius(Unit unit){
|
||||||
//timer2 is used to store radius scale as an effect
|
//timer2 is used to store radius scale as an effect
|
||||||
realRad = radiusScale * radius;
|
realRad = radiusScale * radius;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ public abstract class BulletType extends Content{
|
|||||||
public float incendChance = 1f;
|
public float incendChance = 1f;
|
||||||
public float homingPower = 0f;
|
public float homingPower = 0f;
|
||||||
public float homingRange = 50f;
|
public float homingRange = 50f;
|
||||||
|
/** Use a negative value to disable homing delay. */
|
||||||
|
public float homingDelay = -1f;
|
||||||
|
|
||||||
public Color lightningColor = Pal.surge;
|
public Color lightningColor = Pal.surge;
|
||||||
public int lightning;
|
public int lightning;
|
||||||
@@ -137,6 +139,15 @@ public abstract class BulletType extends Content{
|
|||||||
this(1f, 1f);
|
this(1f, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return estimated damage per shot. this can be very inaccurate. */
|
||||||
|
public float estimateDPS(){
|
||||||
|
float sum = damage + splashDamage*0.75f;
|
||||||
|
if(fragBullet != null && fragBullet != this){
|
||||||
|
sum += fragBullet.estimateDPS() * fragBullets / 2f;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns maximum distance the bullet this bullet type has can travel. */
|
/** Returns maximum distance the bullet this bullet type has can travel. */
|
||||||
public float range(){
|
public float range(){
|
||||||
return Math.max(speed * lifetime * (1f - drag), range);
|
return Math.max(speed * lifetime * (1f - drag), range);
|
||||||
@@ -251,7 +262,7 @@ public abstract class BulletType extends Content{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void update(Bullet b){
|
public void update(Bullet b){
|
||||||
if(homingPower > 0.0001f){
|
if(homingPower > 0.0001f && b.time >= homingDelay){
|
||||||
Teamc target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> (e.isGrounded() && collidesGround) || (e.isFlying() && collidesAir), t -> collidesGround);
|
Teamc target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> (e.isGrounded() && collidesGround) || (e.isFlying() && collidesAir), t -> collidesGround);
|
||||||
if(target != null){
|
if(target != null){
|
||||||
b.vel.setAngle(Mathf.slerpDelta(b.rotation(), b.angleTo(target), homingPower));
|
b.vel.setAngle(Mathf.slerpDelta(b.rotation(), b.angleTo(target), homingPower));
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ public class ContinuousLaserBulletType extends BulletType{
|
|||||||
this(0);
|
this(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float estimateDPS(){
|
||||||
|
//assume firing duration is about 100 by default, may not be accurate there's no way of knowing in this method
|
||||||
|
return damage * 100f / 5f;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float range(){
|
public float range(){
|
||||||
return length;
|
return length;
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public class LaserBulletType extends BulletType{
|
|||||||
this(1f);
|
this(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float estimateDPS(){
|
||||||
|
return super.estimateDPS() * 2f;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(){
|
public void init(){
|
||||||
super.init();
|
super.init();
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public class LiquidBulletType extends BulletType{
|
|||||||
if(liquid != null){
|
if(liquid != null){
|
||||||
this.liquid = liquid;
|
this.liquid = liquid;
|
||||||
this.status = liquid.effect;
|
this.status = liquid.effect;
|
||||||
|
lightColor = liquid.lightColor;
|
||||||
|
lightOpacity = liquid.lightColor.a;
|
||||||
}
|
}
|
||||||
|
|
||||||
ammoMultiplier = 1f;
|
ammoMultiplier = 1f;
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ import arc.util.io.*;
|
|||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.audio.*;
|
import mindustry.audio.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
|
import mindustry.game.Teams.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
import mindustry.logic.*;
|
import mindustry.logic.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.ui.*;
|
import mindustry.ui.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
import mindustry.world.blocks.ConstructBlock.*;
|
||||||
import mindustry.world.blocks.environment.*;
|
import mindustry.world.blocks.environment.*;
|
||||||
import mindustry.world.blocks.payloads.*;
|
import mindustry.world.blocks.payloads.*;
|
||||||
import mindustry.world.blocks.power.*;
|
import mindustry.world.blocks.power.*;
|
||||||
@@ -191,6 +194,36 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
//endregion
|
//endregion
|
||||||
//region utility methods
|
//region utility methods
|
||||||
|
|
||||||
|
public void addPlan(boolean checkPrevious){
|
||||||
|
if(!block.rebuildable) return;
|
||||||
|
|
||||||
|
if(self() instanceof ConstructBuild entity){
|
||||||
|
//update block to reflect the fact that something was being constructed
|
||||||
|
if(entity.cblock != null && entity.cblock.synthetic()){
|
||||||
|
block = entity.cblock;
|
||||||
|
}else{
|
||||||
|
//otherwise this was a deconstruction that was interrupted, don't want to rebuild that
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TeamData data = state.teams.get(team);
|
||||||
|
|
||||||
|
if(checkPrevious){
|
||||||
|
//remove existing blocks that have been placed here.
|
||||||
|
//painful O(n) iteration + copy
|
||||||
|
for(int i = 0; i < data.blocks.size; i++){
|
||||||
|
BlockPlan b = data.blocks.get(i);
|
||||||
|
if(b.x == tile.x && b.y == tile.y){
|
||||||
|
data.blocks.removeIndex(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)rotation, block.id, config()));
|
||||||
|
}
|
||||||
|
|
||||||
/** Configure with the current, local player. */
|
/** Configure with the current, local player. */
|
||||||
public void configure(Object value){
|
public void configure(Object value){
|
||||||
//save last used config
|
//save last used config
|
||||||
@@ -431,7 +464,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
*/
|
*/
|
||||||
public boolean movePayload(Payload todump){
|
public boolean movePayload(Payload todump){
|
||||||
int trns = block.size/2 + 1;
|
int trns = block.size/2 + 1;
|
||||||
Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
|
Tile next = tile.nearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
|
||||||
|
|
||||||
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
|
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
|
||||||
next.build.handlePayload(self(), todump);
|
next.build.handlePayload(self(), todump);
|
||||||
@@ -514,7 +547,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public float moveLiquidForward(boolean leaks, Liquid liquid){
|
public float moveLiquidForward(boolean leaks, Liquid liquid){
|
||||||
Tile next = tile.getNearby(rotation);
|
Tile next = tile.nearby(rotation);
|
||||||
|
|
||||||
if(next == null) return 0;
|
if(next == null) return 0;
|
||||||
|
|
||||||
@@ -766,9 +799,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void drawCracks(){
|
public void drawCracks(){
|
||||||
if(!damaged() || block.size > Block.maxCrackSize) return;
|
if(!damaged() || block.size > BlockRenderer.maxCrackSize) return;
|
||||||
int id = pos();
|
int id = pos();
|
||||||
TextureRegion region = Block.cracks[block.size - 1][Mathf.clamp((int)((1f - healthf()) * Block.crackRegions), 0, Block.crackRegions-1)];
|
TextureRegion region = renderer.blocks.cracks[block.size - 1][Mathf.clamp((int)((1f - healthf()) * BlockRenderer.crackRegions), 0, BlockRenderer.crackRegions-1)];
|
||||||
Draw.colorl(0.2f, 0.1f + (1f - healthf())* 0.6f);
|
Draw.colorl(0.2f, 0.1f + (1f - healthf())* 0.6f);
|
||||||
Draw.rect(region, x, y, (id%4)*90);
|
Draw.rect(region, x, y, (id%4)*90);
|
||||||
Draw.color();
|
Draw.color();
|
||||||
@@ -1234,8 +1267,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
@Override
|
@Override
|
||||||
public double sense(LAccess sensor){
|
public double sense(LAccess sensor){
|
||||||
return switch(sensor){
|
return switch(sensor){
|
||||||
case x -> x;
|
case x -> World.conv(x);
|
||||||
case y -> y;
|
case y -> World.conv(y);
|
||||||
case team -> team.id;
|
case team -> team.id;
|
||||||
case health -> health;
|
case health -> health;
|
||||||
case maxHealth -> maxHealth;
|
case maxHealth -> maxHealth;
|
||||||
@@ -1263,7 +1296,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
|||||||
case type -> block;
|
case type -> block;
|
||||||
case firstItem -> items == null ? null : items.first();
|
case firstItem -> items == null ? null : items.first();
|
||||||
case config -> block.configurations.containsKey(Item.class) || block.configurations.containsKey(Liquid.class) ? config() : null;
|
case config -> block.configurations.containsKey(Item.class) || block.configurations.containsKey(Liquid.class) ? config() : null;
|
||||||
case payloadType -> getPayload() instanceof UnitPayload p1 ? p1.unit.type() : getPayload() instanceof BuildPayload p2 ? p2.block() : null;
|
case payloadType -> getPayload() instanceof UnitPayload p1 ? p1.unit.type : getPayload() instanceof BuildPayload p2 ? p2.block() : null;
|
||||||
default -> noSensed;
|
default -> noSensed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import arc.math.geom.*;
|
|||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.bullet.*;
|
import mindustry.entities.bullet.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
import mindustry.game.Teams.*;
|
import mindustry.game.Teams.*;
|
||||||
@@ -110,7 +111,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
|||||||
type.update(self());
|
type.update(self());
|
||||||
|
|
||||||
if(type.collidesTiles && type.collides && type.collidesGround){
|
if(type.collidesTiles && type.collides && type.collidesGround){
|
||||||
world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
|
world.raycastEach(World.toTile(lastX()), World.toTile(lastY()), tileX(), tileY(), (x, y) -> {
|
||||||
|
|
||||||
Building tile = world.build(x, y);
|
Building tile = world.build(x, y);
|
||||||
if(tile == null || !isAdded()) return false;
|
if(tile == null || !isAdded()) return false;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ abstract class CommanderComp implements Entityc, Posc{
|
|||||||
units.clear();
|
units.clear();
|
||||||
|
|
||||||
Units.nearby(team, x, y, 150f, u -> {
|
Units.nearby(team, x, y, 150f, u -> {
|
||||||
if(u.isAI() && include.get(u) && u != self() && u.type().flying == type.flying && u.hitSize <= hitSize * 1.1f){
|
if(u.isAI() && include.get(u) && u != self() && u.type.flying == type.flying && u.hitSize <= hitSize * 1.1f){
|
||||||
units.add(u);
|
units.add(u);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -82,7 +82,7 @@ abstract class CommanderComp implements Entityc, Posc{
|
|||||||
FormationAI ai;
|
FormationAI ai;
|
||||||
unit.controller(ai = new FormationAI(self(), formation));
|
unit.controller(ai = new FormationAI(self(), formation));
|
||||||
spacing = Math.max(spacing, ai.formationSize());
|
spacing = Math.max(spacing, ai.formationSize());
|
||||||
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
|
minFormationSpeed = Math.min(minFormationSpeed, unit.type.speed);
|
||||||
}
|
}
|
||||||
this.formation = formation;
|
this.formation = formation;
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ abstract class CommanderComp implements Entityc, Posc{
|
|||||||
//reset controlled units
|
//reset controlled units
|
||||||
for(Unit unit : controlling){
|
for(Unit unit : controlling){
|
||||||
if(unit.controller().isBeingControlled(self())){
|
if(unit.controller().isBeingControlled(self())){
|
||||||
unit.controller(unit.type().createController());
|
unit.controller(unit.type.createController());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import static mindustry.Vars.*;
|
|||||||
@EntityDef(value = {Firec.class}, pooled = true)
|
@EntityDef(value = {Firec.class}, pooled = true)
|
||||||
@Component(base = true)
|
@Component(base = true)
|
||||||
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
|
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
|
||||||
private static final float spreadChance = 0.05f, fireballChance = 0.07f;
|
private static final float spreadChance = 0.04f, fireballChance = 0.06f;
|
||||||
|
|
||||||
@Import float time, lifetime, x, y;
|
@Import float time, lifetime, x, y;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import arc.util.*;
|
|||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
@@ -120,7 +121,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
|||||||
/** @return whether the tile has been successfully placed. */
|
/** @return whether the tile has been successfully placed. */
|
||||||
boolean dropBlock(BuildPayload payload){
|
boolean dropBlock(BuildPayload payload){
|
||||||
Building tile = payload.build;
|
Building tile = payload.build;
|
||||||
int tx = Vars.world.toTile(x - tile.block.offset), ty = Vars.world.toTile(y - tile.block.offset);
|
int tx = World.toTile(x - tile.block.offset), ty = World.toTile(y - tile.block.offset);
|
||||||
Tile on = Vars.world.tile(tx, ty);
|
Tile on = Vars.world.tile(tx, ty);
|
||||||
if(on != null && Build.validPlace(tile.block, tile.team, tx, ty, tile.rotation, false)){
|
if(on != null && Build.validPlace(tile.block, tile.team, tx, ty, tile.rotation, false)){
|
||||||
int rot = (int)((rotation + 45f) / 90f) % 4;
|
int rot = (int)((rotation + 45f) / 90f) % 4;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
admin = typing = false;
|
admin = typing = false;
|
||||||
textFadeTime = 0f;
|
textFadeTime = 0f;
|
||||||
if(!dead()){
|
if(!dead()){
|
||||||
unit.controller(unit.type().createController());
|
unit.controller(unit.type.createController());
|
||||||
unit = Nulls.unit;
|
unit = Nulls.unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
|
|
||||||
@Replace
|
@Replace
|
||||||
public float clipSize(){
|
public float clipSize(){
|
||||||
return unit.isNull() ? 20 : unit.type().hitSize * 2f;
|
return unit.isNull() ? 20 : unit.type.hitSize * 2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -123,7 +123,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
deathTimer = 0;
|
deathTimer = 0;
|
||||||
|
|
||||||
//update some basic state to sync things
|
//update some basic state to sync things
|
||||||
if(unit.type().canBoost){
|
if(unit.type.canBoost){
|
||||||
Tile tile = unit.tileOn();
|
Tile tile = unit.tileOn();
|
||||||
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f);
|
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f);
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
|||||||
|
|
||||||
if(this.unit != Nulls.unit){
|
if(this.unit != Nulls.unit){
|
||||||
//un-control the old unit
|
//un-control the old unit
|
||||||
this.unit.controller(this.unit.type().createController());
|
this.unit.controller(this.unit.type.createController());
|
||||||
}
|
}
|
||||||
this.unit = unit;
|
this.unit = unit;
|
||||||
if(unit != Nulls.unit){
|
if(unit != Nulls.unit){
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package mindustry.entities.comp;
|
|||||||
|
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.environment.*;
|
import mindustry.world.blocks.environment.*;
|
||||||
|
|
||||||
@@ -32,11 +32,11 @@ abstract class PosComp implements Position{
|
|||||||
}
|
}
|
||||||
|
|
||||||
int tileX(){
|
int tileX(){
|
||||||
return Vars.world.toTile(x);
|
return World.toTile(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
int tileY(){
|
int tileY(){
|
||||||
return Vars.world.toTile(y);
|
return World.toTile(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns air if this unit is on a non-air top block. */
|
/** Returns air if this unit is on a non-air top block. */
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
|
|||||||
unit.apply(liquid.effect, 60 * 2);
|
unit.apply(liquid.effect, 60 * 2);
|
||||||
|
|
||||||
if(unit.vel.len() > 0.1){
|
if(unit.vel.len() > 0.1){
|
||||||
Fx.ripple.at(unit.x, unit.y, unit.type().rippleScale, liquid.color);
|
Fx.ripple.at(unit.x, unit.y, unit.type.rippleScale, liquid.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import arc.util.*;
|
|||||||
import mindustry.ai.*;
|
import mindustry.ai.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.entities.abilities.*;
|
import mindustry.entities.abilities.*;
|
||||||
@@ -36,7 +37,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
@Import int id;
|
@Import int id;
|
||||||
|
|
||||||
private UnitController controller;
|
private UnitController controller;
|
||||||
private UnitType type;
|
UnitType type;
|
||||||
boolean spawnedByCore;
|
boolean spawnedByCore;
|
||||||
double flag;
|
double flag;
|
||||||
|
|
||||||
@@ -88,12 +89,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
case rotation -> rotation;
|
case rotation -> rotation;
|
||||||
case health -> health;
|
case health -> health;
|
||||||
case maxHealth -> maxHealth;
|
case maxHealth -> maxHealth;
|
||||||
case x -> x;
|
case ammo -> state.rules.unitAmmo ? type.ammoCapacity : ammo;
|
||||||
case y -> y;
|
case ammoCapacity -> type.ammoCapacity;
|
||||||
|
case x -> World.conv(x);
|
||||||
|
case y -> World.conv(y);
|
||||||
case team -> team.id;
|
case team -> team.id;
|
||||||
case shooting -> isShooting() ? 1 : 0;
|
case shooting -> isShooting() ? 1 : 0;
|
||||||
case shootX -> aimX();
|
case shootX -> World.conv(aimX());
|
||||||
case shootY -> aimY();
|
case shootY -> World.conv(aimY());
|
||||||
case flag -> flag;
|
case flag -> flag;
|
||||||
case payloadCount -> self() instanceof Payloadc pay ? pay.payloads().size : 0;
|
case payloadCount -> self() instanceof Payloadc pay ? pay.payloads().size : 0;
|
||||||
default -> 0;
|
default -> 0;
|
||||||
@@ -108,7 +111,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
case firstItem -> stack().amount == 0 ? null : item();
|
case firstItem -> stack().amount == 0 ? null : item();
|
||||||
case payloadType -> self() instanceof Payloadc pay ?
|
case payloadType -> self() instanceof Payloadc pay ?
|
||||||
(pay.payloads().isEmpty() ? null :
|
(pay.payloads().isEmpty() ? null :
|
||||||
pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type() :
|
pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type :
|
||||||
pay.payloads().peek() instanceof BuildPayload p2 ? p2.block() : null) : null;
|
pay.payloads().peek() instanceof BuildPayload p2 ? p2.block() : null) : null;
|
||||||
default -> noSensed;
|
default -> noSensed;
|
||||||
};
|
};
|
||||||
@@ -161,22 +164,12 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void set(UnitType def, UnitController controller){
|
public void set(UnitType def, UnitController controller){
|
||||||
type(type);
|
if(this.type != def){
|
||||||
|
setType(def);
|
||||||
|
}
|
||||||
controller(controller);
|
controller(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void type(UnitType type){
|
|
||||||
if(this.type == type) return;
|
|
||||||
|
|
||||||
setStats(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UnitType type(){
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return pathfinder path type for calculating costs */
|
/** @return pathfinder path type for calculating costs */
|
||||||
public int pathType(){
|
public int pathType(){
|
||||||
return Pathfinder.costGround;
|
return Pathfinder.costGround;
|
||||||
@@ -206,7 +199,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
return Units.getCap(team);
|
return Units.getCap(team);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStats(UnitType type){
|
public void setType(UnitType type){
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.maxHealth = type.health;
|
this.maxHealth = type.health;
|
||||||
this.drag = type.drag;
|
this.drag = type.drag;
|
||||||
@@ -224,7 +217,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
@Override
|
@Override
|
||||||
public void afterSync(){
|
public void afterSync(){
|
||||||
//set up type info after reading
|
//set up type info after reading
|
||||||
setStats(this.type);
|
setType(this.type);
|
||||||
controller.unit(self());
|
controller.unit(self());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +277,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
|||||||
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f);
|
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f);
|
||||||
|
|
||||||
//apply knockback based on spawns
|
//apply knockback based on spawns
|
||||||
if(team != state.rules.waveTeam){
|
if(team != state.rules.waveTeam && state.hasSpawns()){
|
||||||
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
|
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
|
||||||
for(Tile spawn : spawner.getSpawns()){
|
for(Tile spawn : spawner.getSpawns()){
|
||||||
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
|
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
|
|||||||
Weapon weapon = mount.weapon;
|
Weapon weapon = mount.weapon;
|
||||||
|
|
||||||
float baseX = this.x, baseY = this.y;
|
float baseX = this.x, baseY = this.y;
|
||||||
boolean delay = weapon.firstShotDelay > 0f;
|
boolean delay = weapon.firstShotDelay + weapon.shotDelay > 0f;
|
||||||
|
|
||||||
(delay ? weapon.chargeSound : weapon.shootSound).at(x, y, Mathf.random(0.8f, 1.0f));
|
(delay ? weapon.chargeSound : weapon.shootSound).at(x, y, Mathf.random(0.8f, 1.0f));
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class AIController implements UnitController{
|
|||||||
|
|
||||||
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
|
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
|
||||||
|
|
||||||
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
|
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type.speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateWeapons(){
|
protected void updateWeapons(){
|
||||||
@@ -105,7 +105,7 @@ public class AIController implements UnitController{
|
|||||||
boolean ret = retarget();
|
boolean ret = retarget();
|
||||||
|
|
||||||
if(ret){
|
if(ret){
|
||||||
target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
|
target = findTarget(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(invalid(target)){
|
if(invalid(target)){
|
||||||
@@ -119,7 +119,7 @@ public class AIController implements UnitController{
|
|||||||
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
|
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
|
||||||
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
|
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
|
||||||
|
|
||||||
if(unit.type().singleTarget){
|
if(unit.type.singleTarget){
|
||||||
targets[i] = target;
|
targets[i] = target;
|
||||||
}else{
|
}else{
|
||||||
if(ret){
|
if(ret){
|
||||||
@@ -176,7 +176,7 @@ public class AIController implements UnitController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void circle(Position target, float circleLength){
|
protected void circle(Position target, float circleLength){
|
||||||
circle(target, circleLength, unit.type().speed);
|
circle(target, circleLength, unit.type.speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void circle(Position target, float circleLength, float speed){
|
protected void circle(Position target, float circleLength, float speed){
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public class DefaultWaves{
|
|||||||
new SpawnGroup(dagger){{
|
new SpawnGroup(dagger){{
|
||||||
end = 10;
|
end = 10;
|
||||||
unitScaling = 2f;
|
unitScaling = 2f;
|
||||||
|
max = 30;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(crawler){{
|
new SpawnGroup(crawler){{
|
||||||
@@ -45,6 +46,7 @@ public class DefaultWaves{
|
|||||||
begin = 13;
|
begin = 13;
|
||||||
spacing = 3;
|
spacing = 3;
|
||||||
unitScaling = 0.5f;
|
unitScaling = 0.5f;
|
||||||
|
max = 25;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(mace){{
|
new SpawnGroup(mace){{
|
||||||
@@ -61,7 +63,7 @@ public class DefaultWaves{
|
|||||||
unitAmount = 4;
|
unitAmount = 4;
|
||||||
spacing = 2;
|
spacing = 2;
|
||||||
shieldScaling = 10f;
|
shieldScaling = 10f;
|
||||||
max = 20;
|
max = 14;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(mace){{
|
new SpawnGroup(mace){{
|
||||||
@@ -81,7 +83,7 @@ public class DefaultWaves{
|
|||||||
effect = StatusEffects.overdrive;
|
effect = StatusEffects.overdrive;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(mace){{
|
new SpawnGroup(pulsar){{
|
||||||
begin = 120;
|
begin = 120;
|
||||||
spacing = 2;
|
spacing = 2;
|
||||||
unitScaling = 3;
|
unitScaling = 3;
|
||||||
@@ -94,6 +96,7 @@ public class DefaultWaves{
|
|||||||
unitScaling = 1;
|
unitScaling = 1;
|
||||||
spacing = 2;
|
spacing = 2;
|
||||||
shieldScaling = 20f;
|
shieldScaling = 20f;
|
||||||
|
max = 20;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(quasar){{
|
new SpawnGroup(quasar){{
|
||||||
@@ -111,6 +114,7 @@ public class DefaultWaves{
|
|||||||
unitAmount = 1;
|
unitAmount = 1;
|
||||||
unitScaling = 3;
|
unitScaling = 3;
|
||||||
effect = StatusEffects.shielded;
|
effect = StatusEffects.shielded;
|
||||||
|
max = 25;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(fortress){{
|
new SpawnGroup(fortress){{
|
||||||
@@ -122,7 +126,7 @@ public class DefaultWaves{
|
|||||||
shieldScaling = 30;
|
shieldScaling = 30;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(dagger){{
|
new SpawnGroup(nova){{
|
||||||
begin = 35;
|
begin = 35;
|
||||||
spacing = 3;
|
spacing = 3;
|
||||||
unitAmount = 4;
|
unitAmount = 4;
|
||||||
@@ -138,6 +142,7 @@ public class DefaultWaves{
|
|||||||
effect = StatusEffects.overdrive;
|
effect = StatusEffects.overdrive;
|
||||||
items = new ItemStack(Items.pyratite, 100);
|
items = new ItemStack(Items.pyratite, 100);
|
||||||
end = 130;
|
end = 130;
|
||||||
|
max = 30;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(horizon){{
|
new SpawnGroup(horizon){{
|
||||||
@@ -156,6 +161,7 @@ public class DefaultWaves{
|
|||||||
shields = 100f;
|
shields = 100f;
|
||||||
shieldScaling = 10f;
|
shieldScaling = 10f;
|
||||||
effect = StatusEffects.overdrive;
|
effect = StatusEffects.overdrive;
|
||||||
|
max = 20;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(zenith){{
|
new SpawnGroup(zenith){{
|
||||||
@@ -233,7 +239,7 @@ public class DefaultWaves{
|
|||||||
shieldScaling = 20f;
|
shieldScaling = 20f;
|
||||||
}},
|
}},
|
||||||
|
|
||||||
new SpawnGroup(atrax){{
|
new SpawnGroup(toxopid){{
|
||||||
begin = 210;
|
begin = 210;
|
||||||
unitAmount = 1;
|
unitAmount = 1;
|
||||||
unitScaling = 1;
|
unitScaling = 1;
|
||||||
@@ -258,7 +264,7 @@ public class DefaultWaves{
|
|||||||
{nova, pulsar, quasar, vela, corvus},
|
{nova, pulsar, quasar, vela, corvus},
|
||||||
{crawler, atrax, spiroct, arkyid, toxopid},
|
{crawler, atrax, spiroct, arkyid, toxopid},
|
||||||
//{risso, minke, bryde, sei, omura}, //questionable choices
|
//{risso, minke, bryde, sei, omura}, //questionable choices
|
||||||
//{mono, poly, mega, quad, oct}, //do not attack
|
{poly, poly, mega, quad, quad},
|
||||||
{flare, horizon, zenith, antumbra, eclipse}
|
{flare, horizon, zenith, antumbra, eclipse}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,7 +296,7 @@ public class DefaultWaves{
|
|||||||
begin = f;
|
begin = f;
|
||||||
end = f + next >= cap ? never : f + next;
|
end = f + next >= cap ? never : f + next;
|
||||||
max = 14;
|
max = 14;
|
||||||
unitScaling = rand.random(1f, 2f);
|
unitScaling = rand.random(1f, 3f);
|
||||||
shields = shieldAmount;
|
shields = shieldAmount;
|
||||||
shieldScaling = shieldsPerWave;
|
shieldScaling = shieldsPerWave;
|
||||||
spacing = space;
|
spacing = space;
|
||||||
@@ -329,7 +335,7 @@ public class DefaultWaves{
|
|||||||
|
|
||||||
while(step <= cap){
|
while(step <= cap){
|
||||||
createProgression.get(step);
|
createProgression.get(step);
|
||||||
step += (int)(rand.random(12, 25) * Mathf.lerp(1f, 0.4f, difficulty));
|
step += (int)(rand.random(13, 25) * Mathf.lerp(1f, 0.5f, difficulty));
|
||||||
}
|
}
|
||||||
|
|
||||||
int bossWave = (int)(rand.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty));
|
int bossWave = (int)(rand.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty));
|
||||||
|
|||||||
@@ -35,7 +35,13 @@ public class EventType{
|
|||||||
preDraw,
|
preDraw,
|
||||||
postDraw,
|
postDraw,
|
||||||
uiDrawBegin,
|
uiDrawBegin,
|
||||||
uiDrawEnd
|
uiDrawEnd,
|
||||||
|
//before/after bloom used, skybox or planets drawn
|
||||||
|
universeDrawBegin,
|
||||||
|
//skybox drawn and bloom is enabled - use Vars.renderer.planets
|
||||||
|
universeDraw,
|
||||||
|
//planets drawn and bloom disabled
|
||||||
|
universeDrawEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class WinEvent{}
|
public static class WinEvent{}
|
||||||
@@ -73,6 +79,15 @@ public class EventType{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Called when a sector is destroyed by waves when you're not there. */
|
||||||
|
public static class SectorInvasionEvent{
|
||||||
|
public final Sector sector;
|
||||||
|
|
||||||
|
public SectorInvasionEvent(Sector sector){
|
||||||
|
this.sector = sector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class LaunchItemEvent{
|
public static class LaunchItemEvent{
|
||||||
public final ItemStack stack;
|
public final ItemStack stack;
|
||||||
|
|
||||||
@@ -214,8 +229,8 @@ public class EventType{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when block building begins by placing down the BuildBlock.
|
* Called when block building begins by placing down the ConstructBlock.
|
||||||
* The tile's block will nearly always be a BuildBlock.
|
* The tile's block will nearly always be a ConstructBlock.
|
||||||
*/
|
*/
|
||||||
public static class BlockBuildBeginEvent{
|
public static class BlockBuildBeginEvent{
|
||||||
public final Tile tile;
|
public final Tile tile;
|
||||||
@@ -247,7 +262,7 @@ public class EventType{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a player or drone begins building something.
|
* Called when a player or drone begins building something.
|
||||||
* This does not necessarily happen when a new BuildBlock is created.
|
* This does not necessarily happen when a new ConstructBlock is created.
|
||||||
*/
|
*/
|
||||||
public static class BuildSelectEvent{
|
public static class BuildSelectEvent{
|
||||||
public final Tile tile;
|
public final Tile tile;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public enum Gamemode{
|
|||||||
rules.waveTimer = true;
|
rules.waveTimer = true;
|
||||||
|
|
||||||
rules.waveSpacing /= 2f;
|
rules.waveSpacing /= 2f;
|
||||||
rules.teams.get(rules.waveTeam).ai = true;
|
|
||||||
rules.teams.get(rules.waveTeam).infiniteResources = true;
|
rules.teams.get(rules.waveTeam).infiniteResources = true;
|
||||||
}, map -> map.teams.contains(state.rules.waveTeam.id)),
|
}, map -> map.teams.contains(state.rules.waveTeam.id)),
|
||||||
pvp(rules -> {
|
pvp(rules -> {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public class Objectives{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO fix
|
|
||||||
public static class SectorComplete extends SectorObjective{
|
public static class SectorComplete extends SectorObjective{
|
||||||
|
|
||||||
public SectorComplete(SectorPreset zone){
|
public SectorComplete(SectorPreset zone){
|
||||||
@@ -39,12 +38,12 @@ public class Objectives{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean complete(){
|
public boolean complete(){
|
||||||
return preset.sector.save != null && preset.sector.save.meta.wave >= preset.sector.save.meta.rules.winWave;
|
return preset.sector.save != null && preset.sector.save.meta.wave >= preset.captureWave;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String display(){
|
public String display(){
|
||||||
return Core.bundle.format("requirement.wave", preset.sector.save == null ? "<unknown>" : preset.sector.save.meta.rules.winWave, preset.localizedName);
|
return Core.bundle.format("requirement.capture", preset.localizedName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ public class Rules{
|
|||||||
public Seq<WeatherEntry> weather = new Seq<>(1);
|
public Seq<WeatherEntry> weather = new Seq<>(1);
|
||||||
/** Blocks that cannot be placed. */
|
/** Blocks that cannot be placed. */
|
||||||
public ObjectSet<Block> bannedBlocks = new ObjectSet<>();
|
public ObjectSet<Block> bannedBlocks = new ObjectSet<>();
|
||||||
|
/** Unlocked content names. Only used in multiplayer when the campaign is enabled. */
|
||||||
|
public ObjectSet<String> researched = new ObjectSet<>();
|
||||||
/** Whether ambient lighting is enabled. */
|
/** Whether ambient lighting is enabled. */
|
||||||
public boolean lighting = false;
|
public boolean lighting = false;
|
||||||
/** Whether enemy lighting is visible.
|
/** Whether enemy lighting is visible.
|
||||||
@@ -104,6 +106,8 @@ public class Rules{
|
|||||||
public boolean ai;
|
public boolean ai;
|
||||||
/** TODO Tier of blocks/designs that the AI uses for building. [0, 1]*/
|
/** TODO Tier of blocks/designs that the AI uses for building. [0, 1]*/
|
||||||
public float aiTier = 0f;
|
public float aiTier = 0f;
|
||||||
|
/** Whether, when AI is enabled, ships should be spawned from the core. */
|
||||||
|
public boolean aiCoreSpawn = true;
|
||||||
/** If true, blocks don't require power or resources. */
|
/** If true, blocks don't require power or resources. */
|
||||||
public boolean cheat;
|
public boolean cheat;
|
||||||
/** If true, resources are not consumed when building. */
|
/** If true, resources are not consumed when building. */
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import arc.util.pooling.*;
|
|||||||
import arc.util.serialization.*;
|
import arc.util.serialization.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
@@ -608,8 +609,8 @@ public class Schematics implements Loadable{
|
|||||||
wx = wy;
|
wx = wy;
|
||||||
wy = -x;
|
wy = -x;
|
||||||
}
|
}
|
||||||
req.x = (short)(world.toTile(wx - req.block.offset) + ox);
|
req.x = (short)(World.toTile(wx - req.block.offset) + ox);
|
||||||
req.y = (short)(world.toTile(wy - req.block.offset) + oy);
|
req.y = (short)(World.toTile(wy - req.block.offset) + oy);
|
||||||
req.rotation = (byte)Mathf.mod(req.rotation + direction, 4);
|
req.rotation = (byte)Mathf.mod(req.rotation + direction, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
|
import mindustry.maps.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||||
@@ -25,9 +26,9 @@ public class SectorInfo{
|
|||||||
/** Export statistics. */
|
/** Export statistics. */
|
||||||
public ObjectMap<Item, ExportStat> export = new ObjectMap<>();
|
public ObjectMap<Item, ExportStat> export = new ObjectMap<>();
|
||||||
/** Items stored in all cores. */
|
/** Items stored in all cores. */
|
||||||
public ItemSeq coreItems = new ItemSeq();
|
public ItemSeq items = new ItemSeq();
|
||||||
/** The best available core type. */
|
/** The best available core type. */
|
||||||
public Block bestCoreType = Blocks.air;
|
public Block bestCoreType = Blocks.coreShard;
|
||||||
/** Max storage capacity. */
|
/** Max storage capacity. */
|
||||||
public int storageCapacity = 0;
|
public int storageCapacity = 0;
|
||||||
/** Whether a core is available here. */
|
/** Whether a core is available here. */
|
||||||
@@ -38,8 +39,27 @@ public class SectorInfo{
|
|||||||
public @Nullable Sector destination;
|
public @Nullable Sector destination;
|
||||||
/** Resources known to occur at this sector. */
|
/** Resources known to occur at this sector. */
|
||||||
public Seq<UnlockableContent> resources = new Seq<>();
|
public Seq<UnlockableContent> resources = new Seq<>();
|
||||||
/** Time spent at this sector. Do not use unless you know what you're doing. */
|
/** Whether waves are enabled here. */
|
||||||
public transient float internalTimeSpent;
|
public boolean waves = true;
|
||||||
|
/** Whether attack mode is enabled here. */
|
||||||
|
public boolean attack = false;
|
||||||
|
/** Wave # from state */
|
||||||
|
public int wave = 1, winWave = -1;
|
||||||
|
/** Time between waves. */
|
||||||
|
public float waveSpacing = 60 * 60 * 2;
|
||||||
|
/** Damage dealt to sector. */
|
||||||
|
public float damage;
|
||||||
|
/** How many waves have passed while the player was away. */
|
||||||
|
public int wavesPassed;
|
||||||
|
/** Packed core spawn position. */
|
||||||
|
public int spawnPosition;
|
||||||
|
/** How long the player has been playing elsewhere. */
|
||||||
|
public float secondsPassed;
|
||||||
|
/** Display name. */
|
||||||
|
public @Nullable String name;
|
||||||
|
|
||||||
|
/** Special variables for simulation. */
|
||||||
|
public float sumHealth, sumRps, sumDps, waveHealthBase, waveHealthSlope, waveDpsBase, waveDpsSlope;
|
||||||
|
|
||||||
/** Counter refresh state. */
|
/** Counter refresh state. */
|
||||||
private transient Interval time = new Interval();
|
private transient Interval time = new Interval();
|
||||||
@@ -79,26 +99,68 @@ public class SectorInfo{
|
|||||||
return export.get(item, ExportStat::new).mean;
|
return export.get(item, ExportStat::new).mean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Write contents of meta into main storage. */
|
||||||
|
public void write(){
|
||||||
|
//enable attack mode when there's a core.
|
||||||
|
if(state.rules.waveTeam.core() != null){
|
||||||
|
attack = true;
|
||||||
|
winWave = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if there are infinite waves and no win wave, add a win wave.
|
||||||
|
if(waves && winWave <= 0 && !attack){
|
||||||
|
winWave = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.wave = wave;
|
||||||
|
state.rules.waves = waves;
|
||||||
|
state.rules.waveSpacing = waveSpacing;
|
||||||
|
state.rules.winWave = winWave;
|
||||||
|
state.rules.attackMode = attack;
|
||||||
|
|
||||||
|
CoreBuild entity = state.rules.defaultTeam.core();
|
||||||
|
if(entity != null){
|
||||||
|
entity.items.clear();
|
||||||
|
entity.items.add(items);
|
||||||
|
//ensure capacity.
|
||||||
|
entity.items.each((i, a) -> entity.items.set(i, Math.min(a, entity.storageCapacity)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Prepare data for writing to a save. */
|
/** Prepare data for writing to a save. */
|
||||||
public void prepare(){
|
public void prepare(){
|
||||||
//update core items
|
//update core items
|
||||||
coreItems.clear();
|
items.clear();
|
||||||
|
|
||||||
CoreBuild entity = state.rules.defaultTeam.core();
|
CoreBuild entity = state.rules.defaultTeam.core();
|
||||||
|
|
||||||
if(entity != null){
|
if(entity != null){
|
||||||
ItemModule items = entity.items;
|
ItemModule items = entity.items;
|
||||||
for(int i = 0; i < items.length(); i++){
|
for(int i = 0; i < items.length(); i++){
|
||||||
coreItems.set(content.item(i), items.get(i));
|
this.items.set(content.item(i), items.get(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawnPosition = entity.pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
waveSpacing = state.rules.waveSpacing;
|
||||||
|
wave = state.wave;
|
||||||
|
winWave = state.rules.winWave;
|
||||||
|
waves = state.rules.waves;
|
||||||
|
attack = state.rules.attackMode;
|
||||||
hasCore = entity != null;
|
hasCore = entity != null;
|
||||||
bestCoreType = !hasCore ? Blocks.air : state.rules.defaultTeam.cores().max(e -> e.block.size).block;
|
bestCoreType = !hasCore ? Blocks.air : state.rules.defaultTeam.cores().max(e -> e.block.size).block;
|
||||||
storageCapacity = entity != null ? entity.storageCapacity : 0;
|
storageCapacity = entity != null ? entity.storageCapacity : 0;
|
||||||
|
secondsPassed = 0;
|
||||||
|
wavesPassed = 0;
|
||||||
|
damage = 0;
|
||||||
|
|
||||||
//update sector's internal time spent counter
|
if(state.rules.sector != null){
|
||||||
state.rules.sector.setTimeSpent(internalTimeSpent);
|
state.rules.sector.info = this;
|
||||||
|
state.rules.sector.saveInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
SectorDamage.writeParameters(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update averages of various stats, updates some special sector logic.
|
/** Update averages of various stats, updates some special sector logic.
|
||||||
@@ -107,14 +169,6 @@ public class SectorInfo{
|
|||||||
//updating in multiplayer as a client doesn't make sense
|
//updating in multiplayer as a client doesn't make sense
|
||||||
if(net.client()) return;
|
if(net.client()) return;
|
||||||
|
|
||||||
internalTimeSpent += Time.delta;
|
|
||||||
|
|
||||||
//autorun turns
|
|
||||||
if(internalTimeSpent >= turnDuration){
|
|
||||||
internalTimeSpent = 0;
|
|
||||||
universe.runTurn();
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreBuild ent = state.rules.defaultTeam.core();
|
CoreBuild ent = state.rules.defaultTeam.core();
|
||||||
|
|
||||||
//refresh throughput
|
//refresh throughput
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import mindustry.gen.*;
|
|||||||
import mindustry.io.legacy.*;
|
import mindustry.io.legacy.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,8 +54,8 @@ public class SpawnGroup implements Serializable{
|
|||||||
//serialization use only
|
//serialization use only
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the amount of units spawned on a specific wave. */
|
/** @return amount of units spawned on a specific wave. */
|
||||||
public int getUnitsSpawned(int wave){
|
public int getSpawned(int wave){
|
||||||
if(spacing == 0) spacing = 1;
|
if(spacing == 0) spacing = 1;
|
||||||
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
|
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
|
||||||
return 0;
|
return 0;
|
||||||
@@ -61,6 +63,11 @@ public class SpawnGroup implements Serializable{
|
|||||||
return Math.min(unitAmount + (int)(((wave - begin) / spacing) / unitScaling), max);
|
return Math.min(unitAmount + (int)(((wave - begin) / spacing) / unitScaling), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return amount of shields each unit has at a specific wave. */
|
||||||
|
public float getShield(int wave){
|
||||||
|
return Math.max(shields + shieldScaling*(wave - begin), 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a unit, and assigns correct values based on this group's data.
|
* Creates a unit, and assigns correct values based on this group's data.
|
||||||
* This method does not add() the unit.
|
* This method does not add() the unit.
|
||||||
@@ -76,7 +83,7 @@ public class SpawnGroup implements Serializable{
|
|||||||
unit.addItem(items.item, items.amount);
|
unit.addItem(items.item, items.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
unit.shield(Math.max(shields + shieldScaling*(wave - begin), 0));
|
unit.shield = getShield(wave);
|
||||||
|
|
||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
@@ -133,4 +140,20 @@ public class SpawnGroup implements Serializable{
|
|||||||
", items=" + items +
|
", items=" + items +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o){
|
||||||
|
if(this == o) return true;
|
||||||
|
if(o == null || getClass() != o.getClass()) return false;
|
||||||
|
SpawnGroup group = (SpawnGroup)o;
|
||||||
|
return end == group.end && begin == group.begin && spacing == group.spacing && max == group.max
|
||||||
|
&& Float.compare(group.unitScaling, unitScaling) == 0 && Float.compare(group.shields, shields) == 0
|
||||||
|
&& Float.compare(group.shieldScaling, shieldScaling) == 0 && unitAmount == group.unitAmount &&
|
||||||
|
type == group.type && effect == group.effect && Structs.eq(items, group.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode(){
|
||||||
|
return Arrays.hashCode(new Object[]{type, end, begin, spacing, max, unitScaling, shields, shieldScaling, unitAmount, effect, items});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class Stats{
|
|||||||
|
|
||||||
//weigh used fractions
|
//weigh used fractions
|
||||||
float frac = 0f;
|
float frac = 0f;
|
||||||
Seq<Item> obtainable = zone.save == null ? new Seq<>() : zone.save.meta.secinfo.resources.select(i -> i instanceof Item).as();
|
Seq<Item> obtainable = zone.save == null ? new Seq<>() : zone.info.resources.select(i -> i instanceof Item).as();
|
||||||
for(Item item : obtainable){
|
for(Item item : obtainable){
|
||||||
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
|
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ public class Team implements Comparable<Team>{
|
|||||||
Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
|
Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
|
||||||
crux = new Team(2, "crux", Color.valueOf("f25555"),
|
crux = new Team(2, "crux", Color.valueOf("f25555"),
|
||||||
Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
|
Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
|
||||||
green = new Team(3, "green", Color.valueOf("4dd98b")),
|
green = new Team(3, "green", Color.valueOf("54d67d")),
|
||||||
purple = new Team(4, "purple", Color.valueOf("9a4bdf")),
|
purple = new Team(4, "purple", Color.valueOf("995bb0")),
|
||||||
blue = new Team(5, "blue", Color.royal.cpy());
|
blue = new Team(5, "blue", Color.valueOf("5a4deb"));
|
||||||
|
|
||||||
static{
|
static{
|
||||||
Mathf.rand.setSeed(8);
|
Mathf.rand.setSeed(8);
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class Teams{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void count(Unit unit){
|
private void count(Unit unit){
|
||||||
unit.team.data().updateCount(unit.type(), 1);
|
unit.team.data().updateCount(unit.type, 1);
|
||||||
|
|
||||||
if(unit instanceof Payloadc){
|
if(unit instanceof Payloadc){
|
||||||
((Payloadc)unit).payloads().each(p -> {
|
((Payloadc)unit).payloads().each(p -> {
|
||||||
@@ -178,15 +178,15 @@ public class Teams{
|
|||||||
data.units.add(unit);
|
data.units.add(unit);
|
||||||
data.presentFlag = true;
|
data.presentFlag = true;
|
||||||
|
|
||||||
if(data.unitsByType == null || data.unitsByType.length <= unit.type().id){
|
if(data.unitsByType == null || data.unitsByType.length <= unit.type.id){
|
||||||
data.unitsByType = new Seq[content.units().size];
|
data.unitsByType = new Seq[content.units().size];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(data.unitsByType[unit.type().id] == null){
|
if(data.unitsByType[unit.type.id] == null){
|
||||||
data.unitsByType[unit.type().id] = new Seq<>();
|
data.unitsByType[unit.type.id] = new Seq<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.unitsByType[unit.type().id].add(unit);
|
data.unitsByType[unit.type.id].add(unit);
|
||||||
|
|
||||||
count(unit);
|
count(unit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
|
import mindustry.maps.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.world.blocks.storage.*;
|
import mindustry.world.blocks.storage.*;
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ public class Universe{
|
|||||||
private int netSeconds;
|
private int netSeconds;
|
||||||
private float secondCounter;
|
private float secondCounter;
|
||||||
private int turn;
|
private int turn;
|
||||||
|
private float turnCounter;
|
||||||
|
|
||||||
private Schematic lastLoadout;
|
private Schematic lastLoadout;
|
||||||
private ItemSeq lastLaunchResources = new ItemSeq();
|
private ItemSeq lastLaunchResources = new ItemSeq();
|
||||||
@@ -53,17 +55,19 @@ public class Universe{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return sectors attacked on the current planet, minus the ones that are being played on right now. */
|
|
||||||
public Seq<Sector> getAttacked(Planet planet){
|
|
||||||
return planet.sectors.select(s -> s.hasWaves() && s.hasBase() && !s.isBeingPlayed() && s.getSecondsPassed() > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Update planet rotations, global time and relevant state. */
|
/** Update planet rotations, global time and relevant state. */
|
||||||
public void update(){
|
public void update(){
|
||||||
|
|
||||||
//only update time when not in multiplayer
|
//only update time when not in multiplayer
|
||||||
if(!net.client()){
|
if(!net.client()){
|
||||||
secondCounter += Time.delta / 60f;
|
secondCounter += Time.delta / 60f;
|
||||||
|
turnCounter += Time.delta;
|
||||||
|
|
||||||
|
//auto-run turns
|
||||||
|
if(turnCounter >= turnDuration){
|
||||||
|
turnCounter = 0;
|
||||||
|
runTurn();
|
||||||
|
}
|
||||||
|
|
||||||
if(secondCounter >= 1){
|
if(secondCounter >= 1){
|
||||||
seconds += (int)secondCounter;
|
seconds += (int)secondCounter;
|
||||||
@@ -132,42 +136,81 @@ public class Universe{
|
|||||||
//update relevant sectors
|
//update relevant sectors
|
||||||
for(Planet planet : content.planets()){
|
for(Planet planet : content.planets()){
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasSave()){
|
if(sector.hasSave() && sector.hasBase()){
|
||||||
int spent = (int)(sector.getTimeSpent() / 60);
|
|
||||||
int actuallyPassed = Math.max(newSecondsPassed - spent, 0);
|
|
||||||
|
|
||||||
//increment seconds passed for this sector by the time that just passed with this turn
|
//increment seconds passed for this sector by the time that just passed with this turn
|
||||||
if(!sector.isBeingPlayed()){
|
if(!sector.isBeingPlayed()){
|
||||||
sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed);
|
//increment time
|
||||||
|
sector.info.secondsPassed += turnDuration/60f;
|
||||||
|
|
||||||
|
int wavesPassed = (int)(sector.info.secondsPassed*60f / sector.info.waveSpacing);
|
||||||
|
boolean attacked = sector.info.waves;
|
||||||
|
|
||||||
|
if(attacked){
|
||||||
|
sector.info.wavesPassed = wavesPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
float damage = attacked ? SectorDamage.getDamage(sector.info) : 0f;
|
||||||
|
|
||||||
|
//damage never goes down until the player visits the sector, so use max
|
||||||
|
sector.info.damage = Math.max(sector.info.damage, damage);
|
||||||
|
|
||||||
//TODO sector damage disabled for now
|
|
||||||
//check if the sector has been attacked too many times...
|
//check if the sector has been attacked too many times...
|
||||||
/*if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
|
if(attacked && damage >= 0.999f){
|
||||||
//fire event for losing the sector
|
//fire event for losing the sector
|
||||||
Events.fire(new SectorLoseEvent(sector));
|
Events.fire(new SectorLoseEvent(sector));
|
||||||
|
|
||||||
//if so, just delete the save for now. it's lost.
|
//sector is dead.
|
||||||
//TODO don't delete it later maybe
|
sector.info.items.clear();
|
||||||
sector.save.delete();
|
sector.info.damage = 1f;
|
||||||
//clear recieved
|
sector.info.hasCore = false;
|
||||||
sector.setExtraItems(new ItemSeq());
|
sector.info.production.clear();
|
||||||
sector.save = null;
|
}else if(attacked && wavesPassed > 0 && sector.info.winWave > 1 && sector.info.wave + wavesPassed >= sector.info.winWave && !sector.hasEnemyBase()){
|
||||||
}*/
|
//autocapture the sector
|
||||||
|
sector.info.waves = false;
|
||||||
|
|
||||||
|
//fire the event
|
||||||
|
Events.fire(new SectorCaptureEvent(sector));
|
||||||
|
}
|
||||||
|
|
||||||
|
float scl = sector.getProductionScale();
|
||||||
|
|
||||||
|
//export to another sector
|
||||||
|
if(sector.info.destination != null){
|
||||||
|
Sector to = sector.info.destination;
|
||||||
|
if(to.hasBase()){
|
||||||
|
ItemSeq items = new ItemSeq();
|
||||||
|
//calculated exported items to this sector
|
||||||
|
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * scl)));
|
||||||
|
to.addItems(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//add production, making sure that it's capped
|
||||||
|
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * seconds * scl), sector.info.storageCapacity - sector.info.items.get(item))));
|
||||||
|
|
||||||
|
sector.saveInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
//export to another sector
|
//queue random invasions
|
||||||
if(sector.save != null && sector.save.meta != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.destination != null){
|
if(!sector.isAttacked() && turn > invasionGracePeriod){
|
||||||
Sector to = sector.save.meta.secinfo.destination;
|
//invasion chance depends on # of nearby bases
|
||||||
if(to.save != null){
|
if(Mathf.chance(baseInvasionChance * sector.near().count(Sector::hasEnemyBase))){
|
||||||
ItemSeq items = new ItemSeq();
|
int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : 0) + Mathf.random(2, 5) * 5;
|
||||||
//calculated exported items to this sector
|
|
||||||
sector.save.meta.secinfo.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed)));
|
//assign invasion-related things
|
||||||
to.addItems(items);
|
if(sector.isBeingPlayed()){
|
||||||
|
state.rules.winWave = waveMax;
|
||||||
|
state.rules.waves = true;
|
||||||
|
}else{
|
||||||
|
sector.info.winWave = waveMax;
|
||||||
|
sector.info.waves = true;
|
||||||
|
sector.saveInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
Events.fire(new SectorInvasionEvent(sector));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//reset time spent to 0
|
|
||||||
sector.setTimeSpent(0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +227,7 @@ public class Universe{
|
|||||||
for(Planet planet : content.planets()){
|
for(Planet planet : content.planets()){
|
||||||
for(Sector sector : planet.sectors){
|
for(Sector sector : planet.sectors){
|
||||||
if(sector.hasSave()){
|
if(sector.hasSave()){
|
||||||
count.add(sector.calculateItems());
|
count.add(sector.items());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ import static arc.Core.*;
|
|||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
|
|
||||||
public class BlockRenderer implements Disposable{
|
public class BlockRenderer implements Disposable{
|
||||||
|
public static final int crackRegions = 8, maxCrackSize = 9;
|
||||||
|
|
||||||
private static final int initialRequests = 32 * 32;
|
private static final int initialRequests = 32 * 32;
|
||||||
private static final int expandr = 9;
|
private static final int expandr = 9;
|
||||||
private static final Color shadowColor = new Color(0, 0, 0, 0.71f);
|
private static final Color shadowColor = new Color(0, 0, 0, 0.71f);
|
||||||
|
|
||||||
public final FloorRenderer floor = new FloorRenderer();
|
public final FloorRenderer floor = new FloorRenderer();
|
||||||
|
public TextureRegion[][] cracks;
|
||||||
|
|
||||||
private Seq<Tile> tileview = new Seq<>(false, initialRequests, Tile.class);
|
private Seq<Tile> tileview = new Seq<>(false, initialRequests, Tile.class);
|
||||||
private Seq<Tile> lightview = new Seq<>(false, initialRequests, Tile.class);
|
private Seq<Tile> lightview = new Seq<>(false, initialRequests, Tile.class);
|
||||||
@@ -35,11 +38,20 @@ public class BlockRenderer implements Disposable{
|
|||||||
private FrameBuffer dark = new FrameBuffer();
|
private FrameBuffer dark = new FrameBuffer();
|
||||||
private Seq<Building> outArray2 = new Seq<>();
|
private Seq<Building> outArray2 = new Seq<>();
|
||||||
private Seq<Tile> shadowEvents = new Seq<>();
|
private Seq<Tile> shadowEvents = new Seq<>();
|
||||||
private IntSet processedEntities = new IntSet(), processedLinks = new IntSet();
|
private IntSet procEntities = new IntSet(), procLinks = new IntSet(), procLights = new IntSet();
|
||||||
private boolean displayStatus = false;
|
private boolean displayStatus = false;
|
||||||
|
|
||||||
public BlockRenderer(){
|
public BlockRenderer(){
|
||||||
|
|
||||||
|
Events.on(ClientLoadEvent.class, e -> {
|
||||||
|
cracks = new TextureRegion[maxCrackSize][crackRegions];
|
||||||
|
for(int size = 1; size <= maxCrackSize; size++){
|
||||||
|
for(int i = 0; i < crackRegions; i++){
|
||||||
|
cracks[size - 1][i] = Core.atlas.find("cracks-" + size + "-" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Events.on(WorldLoadEvent.class, event -> {
|
Events.on(WorldLoadEvent.class, event -> {
|
||||||
shadowEvents.clear();
|
shadowEvents.clear();
|
||||||
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
||||||
@@ -179,8 +191,9 @@ public class BlockRenderer implements Disposable{
|
|||||||
|
|
||||||
tileview.clear();
|
tileview.clear();
|
||||||
lightview.clear();
|
lightview.clear();
|
||||||
processedEntities.clear();
|
procEntities.clear();
|
||||||
processedLinks.clear();
|
procLinks.clear();
|
||||||
|
procLights.clear();
|
||||||
|
|
||||||
int minx = Math.max(avgx - rangex - expandr, 0);
|
int minx = Math.max(avgx - rangex - expandr, 0);
|
||||||
int miny = Math.max(avgy - rangey - expandr, 0);
|
int miny = Math.max(avgy - rangey - expandr, 0);
|
||||||
@@ -197,25 +210,25 @@ public class BlockRenderer implements Disposable{
|
|||||||
tile = tile.build.tile;
|
tile = tile.build.tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(block != Blocks.air && block.cacheLayer == CacheLayer.normal && (tile.build == null || !processedEntities.contains(tile.build.id))){
|
if(block != Blocks.air && block.cacheLayer == CacheLayer.normal && (tile.build == null || !procEntities.contains(tile.build.id))){
|
||||||
if(block.expanded || !expanded){
|
if(block.expanded || !expanded){
|
||||||
if(tile.build == null || processedLinks.add(tile.build.id)){
|
if(tile.build == null || procLinks.add(tile.build.id)){
|
||||||
tileview.add(tile);
|
tileview.add(tile);
|
||||||
if(tile.build != null){
|
if(tile.build != null){
|
||||||
processedEntities.add(tile.build.id);
|
procEntities.add(tile.build.id);
|
||||||
processedLinks.add(tile.build.id);
|
procLinks.add(tile.build.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//lights are drawn even in the expanded range
|
//lights are drawn even in the expanded range
|
||||||
if(tile.build != null || tile.block().emitLight){
|
if(((tile.build != null && procLights.add(tile.build.pos())) || tile.block().emitLight)){
|
||||||
lightview.add(tile);
|
lightview.add(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
|
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
|
||||||
for(Building other : tile.build.getPowerConnections(outArray2)){
|
for(Building other : tile.build.getPowerConnections(outArray2)){
|
||||||
if(other.block instanceof PowerNode && processedLinks.add(other.id)){ //TODO need a generic way to render connections!
|
if(other.block instanceof PowerNode && procLinks.add(other.id)){ //TODO need a generic way to render connections!
|
||||||
tileview.add(other.tile);
|
tileview.add(other.tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +236,7 @@ public class BlockRenderer implements Disposable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//special case for floors
|
//special case for floors
|
||||||
if(block == Blocks.air && tile.floor().emitLight){
|
if((block == Blocks.air && tile.floor().emitLight) && procLights.add(tile.pos())){
|
||||||
lightview.add(tile);
|
lightview.add(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ public class LightRenderer{
|
|||||||
|
|
||||||
Draw.vert(ledge.texture, vertices, 0, vertices.length);
|
Draw.vert(ledge.texture, vertices, 0, vertices.length);
|
||||||
|
|
||||||
|
|
||||||
Vec2 v3 = Tmp.v2.trnsExact(rot, stroke);
|
Vec2 v3 = Tmp.v2.trnsExact(rot, stroke);
|
||||||
|
|
||||||
u = ledge.u;
|
u = ledge.u;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class MinimapRenderer implements Disposable{
|
|||||||
|
|
||||||
Draw.mixcol(unit.team().color, 1f);
|
Draw.mixcol(unit.team().color, 1f);
|
||||||
float scale = Scl.scl(1f) / 2f * scaling * 32f;
|
float scale = Scl.scl(1f) / 2f * scaling * 32f;
|
||||||
Draw.rect(unit.type().icon(Cicon.full), x + rx, y + ry, scale, scale, unit.rotation() - 90);
|
Draw.rect(unit.type.icon(Cicon.full), x + rx, y + ry, scale, scale, unit.rotation() - 90);
|
||||||
Draw.reset();
|
Draw.reset();
|
||||||
|
|
||||||
//only disable player names in multiplayer
|
//only disable player names in multiplayer
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class OverlayRenderer{
|
|||||||
//special selection for block "units"
|
//special selection for block "units"
|
||||||
Fill.square(select.x, select.y, ((BlockUnitc)select).tile().block.size * tilesize/2f);
|
Fill.square(select.x, select.y, ((BlockUnitc)select).tile().block.size * tilesize/2f);
|
||||||
}else{
|
}else{
|
||||||
Draw.rect(select.type().icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
|
Draw.rect(select.type.icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lines.stroke(unitFade);
|
Lines.stroke(unitFade);
|
||||||
@@ -121,10 +121,12 @@ public class OverlayRenderer{
|
|||||||
Lines.stroke(2f);
|
Lines.stroke(2f);
|
||||||
Draw.color(Color.gray, Color.lightGray, Mathf.absin(Time.time(), 8f, 1f));
|
Draw.color(Color.gray, Color.lightGray, Mathf.absin(Time.time(), 8f, 1f));
|
||||||
|
|
||||||
for(Tile tile : spawner.getSpawns()){
|
if(state.hasSpawns()){
|
||||||
if(tile.within(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
|
for(Tile tile : spawner.getSpawns()){
|
||||||
Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
|
if(tile.within(player.x, player.y, state.rules.dropZoneRadius + spawnerMargin)){
|
||||||
Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);
|
Draw.alpha(Mathf.clamp(1f - (player.dst(tile) - state.rules.dropZoneRadius) / spawnerMargin));
|
||||||
|
Lines.dashCircle(tile.worldx(), tile.worldy(), state.rules.dropZoneRadius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,16 @@ public class PlanetGrid{
|
|||||||
{5, 3, 10, 1, 4}, {2, 5, 4, 0, 11}, {3, 7, 6, 1, 8}, {7, 2, 9, 0, 6}
|
{5, 3, 10, 1, 4}, {2, 5, 4, 0, 11}, {3, 7, 6, 1, 8}, {7, 2, 9, 0, 6}
|
||||||
};
|
};
|
||||||
|
|
||||||
public final int size;
|
public int size;
|
||||||
public final Ptile[] tiles;
|
public Ptile[] tiles;
|
||||||
public final Corner[] corners;
|
public Corner[] corners;
|
||||||
public final Edge[] edges;
|
public Edge[] edges;
|
||||||
|
|
||||||
PlanetGrid(int size){
|
//this is protected so if you want to make strange grids you should know what you're doing.
|
||||||
|
protected PlanetGrid(int size){
|
||||||
this.size = size;
|
this.size = size;
|
||||||
|
|
||||||
tiles = new Ptile[Buildingount(size)];
|
tiles = new Ptile[tileCount(size)];
|
||||||
for(int i = 0; i < tiles.length; i++){
|
for(int i = 0; i < tiles.length; i++){
|
||||||
tiles[i] = new Ptile(i, i < 12 ? 5 : 6);
|
tiles[i] = new Ptile(i, i < 12 ? 5 : 6);
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,7 @@ public class PlanetGrid{
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PlanetGrid initialGrid(){
|
public static PlanetGrid initialGrid(){
|
||||||
PlanetGrid grid = new PlanetGrid(0);
|
PlanetGrid grid = new PlanetGrid(0);
|
||||||
|
|
||||||
for(Ptile t : grid.tiles){
|
for(Ptile t : grid.tiles){
|
||||||
@@ -111,7 +112,7 @@ public class PlanetGrid{
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PlanetGrid subdividedGrid(PlanetGrid prev){
|
public static PlanetGrid subdividedGrid(PlanetGrid prev){
|
||||||
PlanetGrid grid = new PlanetGrid(prev.size + 1);
|
PlanetGrid grid = new PlanetGrid(prev.size + 1);
|
||||||
|
|
||||||
int prevTiles = prev.tiles.length;
|
int prevTiles = prev.tiles.length;
|
||||||
@@ -207,7 +208,7 @@ public class PlanetGrid{
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Buildingount(int size){
|
static int tileCount(int size){
|
||||||
return 10 * Mathf.pow(3, size) + 2;
|
return 10 * Mathf.pow(3, size) + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,12 +221,12 @@ public class PlanetGrid{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Ptile{
|
public static class Ptile{
|
||||||
public final int id;
|
public int id;
|
||||||
public final int edgeCount;
|
public int edgeCount;
|
||||||
|
|
||||||
public final Ptile[] tiles;
|
public Ptile[] tiles;
|
||||||
public final Corner[] corners;
|
public Corner[] corners;
|
||||||
public final Edge[] edges;
|
public Edge[] edges;
|
||||||
|
|
||||||
public Vec3 v = new Vec3();
|
public Vec3 v = new Vec3();
|
||||||
|
|
||||||
@@ -240,11 +241,11 @@ public class PlanetGrid{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Corner{
|
public static class Corner{
|
||||||
public final int id;
|
public int id;
|
||||||
public final Ptile[] tiles = new Ptile[3];
|
public Ptile[] tiles = new Ptile[3];
|
||||||
public final Corner[] corners = new Corner[3];
|
public Corner[] corners = new Corner[3];
|
||||||
public final Edge[] edges = new Edge[3];
|
public Edge[] edges = new Edge[3];
|
||||||
public final Vec3 v = new Vec3();
|
public Vec3 v = new Vec3();
|
||||||
|
|
||||||
public Corner(int id){
|
public Corner(int id){
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -252,9 +253,9 @@ public class PlanetGrid{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Edge{
|
public static class Edge{
|
||||||
public final int id;
|
public int id;
|
||||||
public final Ptile[] tiles = new Ptile[2];
|
public Ptile[] tiles = new Ptile[2];
|
||||||
public final Corner[] corners = new Corner[2];
|
public Corner[] corners = new Corner[2];
|
||||||
|
|
||||||
public Edge(int id){
|
public Edge(int id){
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import arc.math.geom.*;
|
|||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
import mindustry.graphics.g3d.PlanetGrid.*;
|
import mindustry.graphics.g3d.PlanetGrid.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
@@ -38,19 +39,19 @@ public class PlanetRenderer implements Disposable{
|
|||||||
public float zoom = 1f;
|
public float zoom = 1f;
|
||||||
|
|
||||||
private final Mesh[] outlines = new Mesh[10];
|
private final Mesh[] outlines = new Mesh[10];
|
||||||
private final PlaneBatch3D projector = new PlaneBatch3D();
|
public final PlaneBatch3D projector = new PlaneBatch3D();
|
||||||
private final Mat3D mat = new Mat3D();
|
public final Mat3D mat = new Mat3D();
|
||||||
private final FrameBuffer buffer = new FrameBuffer(2, 2, true);
|
public final FrameBuffer buffer = new FrameBuffer(2, 2, true);
|
||||||
private PlanetInterfaceRenderer irenderer;
|
public PlanetInterfaceRenderer irenderer;
|
||||||
|
|
||||||
private final Bloom bloom = new Bloom(Core.graphics.getWidth()/4, Core.graphics.getHeight()/4, true, false){{
|
public final Bloom bloom = new Bloom(Core.graphics.getWidth()/4, Core.graphics.getHeight()/4, true, false){{
|
||||||
setThreshold(0.8f);
|
setThreshold(0.8f);
|
||||||
blurPasses = 6;
|
blurPasses = 6;
|
||||||
}};
|
}};
|
||||||
private final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, false, 1.5f);
|
public final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, false, 1.5f);
|
||||||
|
|
||||||
//seed: 8kmfuix03fw
|
//seed: 8kmfuix03fw
|
||||||
private final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/"));
|
public final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/"));
|
||||||
|
|
||||||
public PlanetRenderer(){
|
public PlanetRenderer(){
|
||||||
camPos.set(0, 0f, camLength);
|
camPos.set(0, 0f, camLength);
|
||||||
@@ -82,14 +83,20 @@ public class PlanetRenderer implements Disposable{
|
|||||||
projector.proj(cam.combined);
|
projector.proj(cam.combined);
|
||||||
batch.proj(cam.combined);
|
batch.proj(cam.combined);
|
||||||
|
|
||||||
|
Events.fire(Trigger.universeDrawBegin);
|
||||||
|
|
||||||
beginBloom();
|
beginBloom();
|
||||||
|
|
||||||
skybox.render(cam.combined);
|
skybox.render(cam.combined);
|
||||||
|
|
||||||
|
Events.fire(Trigger.universeDraw);
|
||||||
|
|
||||||
renderPlanet(solarSystem);
|
renderPlanet(solarSystem);
|
||||||
|
|
||||||
endBloom();
|
endBloom();
|
||||||
|
|
||||||
|
Events.fire(Trigger.universeDrawEnd);
|
||||||
|
|
||||||
Gl.enable(Gl.blend);
|
Gl.enable(Gl.blend);
|
||||||
|
|
||||||
irenderer.renderProjections();
|
irenderer.renderProjections();
|
||||||
@@ -100,18 +107,21 @@ public class PlanetRenderer implements Disposable{
|
|||||||
cam.update();
|
cam.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginBloom(){
|
public void beginBloom(){
|
||||||
bloom.resize(Core.graphics.getWidth() / 4, Core.graphics.getHeight() / 4);
|
bloom.resize(Core.graphics.getWidth() / 4, Core.graphics.getHeight() / 4);
|
||||||
bloom.capture();
|
bloom.capture();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endBloom(){
|
public void endBloom(){
|
||||||
bloom.render();
|
bloom.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderPlanet(Planet planet){
|
|
||||||
|
public void renderPlanet(Planet planet){
|
||||||
|
if(!planet.visible()) return;
|
||||||
|
|
||||||
//render planet at offsetted position in the world
|
//render planet at offsetted position in the world
|
||||||
planet.mesh.render(cam.combined, planet.getTransform(mat));
|
planet.draw(cam.combined, planet.getTransform(mat));
|
||||||
|
|
||||||
renderOrbit(planet);
|
renderOrbit(planet);
|
||||||
|
|
||||||
@@ -137,8 +147,8 @@ public class PlanetRenderer implements Disposable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderOrbit(Planet planet){
|
public void renderOrbit(Planet planet){
|
||||||
if(planet.parent == null) return;
|
if(planet.parent == null || !planet.visible()) return;
|
||||||
|
|
||||||
Vec3 center = planet.parent.position;
|
Vec3 center = planet.parent.position;
|
||||||
float radius = planet.orbitRadius;
|
float radius = planet.orbitRadius;
|
||||||
@@ -147,7 +157,7 @@ public class PlanetRenderer implements Disposable{
|
|||||||
batch.flush(Gl.lineLoop);
|
batch.flush(Gl.lineLoop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderSectors(Planet planet){
|
public void renderSectors(Planet planet){
|
||||||
//apply transformed position
|
//apply transformed position
|
||||||
batch.proj().mul(planet.getTransform(mat));
|
batch.proj().mul(planet.getTransform(mat));
|
||||||
|
|
||||||
@@ -268,7 +278,7 @@ public class PlanetRenderer implements Disposable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mesh outline(int size){
|
public Mesh outline(int size){
|
||||||
if(outlines[size] == null){
|
if(outlines[size] == null){
|
||||||
outlines[size] = MeshBuilder.buildHex(new HexMesher(){
|
outlines[size] = MeshBuilder.buildHex(new HexMesher(){
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import arc.scene.ui.*;
|
|||||||
import arc.scene.ui.layout.*;
|
import arc.scene.ui.layout.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
@@ -351,10 +352,6 @@ public class DesktopInput extends InputHandler{
|
|||||||
table.button(Icon.map, Styles.clearPartiali, () -> {
|
table.button(Icon.map, Styles.clearPartiali, () -> {
|
||||||
ui.planet.show();
|
ui.planet.show();
|
||||||
}).visible(() -> state.isCampaign()).tooltip("@planetmap");
|
}).visible(() -> state.isCampaign()).tooltip("@planetmap");
|
||||||
|
|
||||||
table.button(Icon.up, Styles.clearPartiali, () -> {
|
|
||||||
ui.planet.showLaunch(state.getSector(), player.team().core());
|
|
||||||
}).visible(() -> state.isCampaign()).tooltip("@launchcore").disabled(b -> player.team().core() == null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pollInput(){
|
void pollInput(){
|
||||||
@@ -363,7 +360,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
||||||
int cursorX = tileX(Core.input.mouseX());
|
int cursorX = tileX(Core.input.mouseX());
|
||||||
int cursorY = tileY(Core.input.mouseY());
|
int cursorY = tileY(Core.input.mouseY());
|
||||||
int rawCursorX = world.toTile(Core.input.mouseWorld().x), rawCursorY = world.toTile(Core.input.mouseWorld().y);
|
int rawCursorX = World.toTile(Core.input.mouseWorld().x), rawCursorY = World.toTile(Core.input.mouseWorld().y);
|
||||||
|
|
||||||
// automatically pause building if the current build queue is empty
|
// automatically pause building if the current build queue is empty
|
||||||
if(Core.settings.getBool("buildautopause") && isBuilding && !player.builder().isBuilding()){
|
if(Core.settings.getBool("buildautopause") && isBuilding && !player.builder().isBuilding()){
|
||||||
@@ -599,11 +596,11 @@ public class DesktopInput extends InputHandler{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void updateMovement(Unit unit){
|
protected void updateMovement(Unit unit){
|
||||||
boolean omni = unit.type().omniMovement;
|
boolean omni = unit.type.omniMovement;
|
||||||
boolean ground = unit.isGrounded();
|
boolean ground = unit.isGrounded();
|
||||||
|
|
||||||
float strafePenalty = ground ? 1f : Mathf.lerp(1f, unit.type().strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
|
float strafePenalty = ground ? 1f : Mathf.lerp(1f, unit.type.strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
|
||||||
float baseSpeed = unit.type().speed;
|
float baseSpeed = unit.type.speed;
|
||||||
|
|
||||||
//limit speed to minimum formation speed to preserve formation
|
//limit speed to minimum formation speed to preserve formation
|
||||||
if(unit.isCommanding()){
|
if(unit.isCommanding()){
|
||||||
@@ -611,7 +608,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
baseSpeed = unit.minFormationSpeed * 0.95f;
|
baseSpeed = unit.minFormationSpeed * 0.95f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float speed = baseSpeed * Mathf.lerp(1f, unit.isCommanding() ? 1f : unit.type().canBoost ? unit.type().boostMultiplier : 1f, unit.elevation) * strafePenalty;
|
float speed = baseSpeed * Mathf.lerp(1f, unit.isCommanding() ? 1f : unit.type.canBoost ? unit.type.boostMultiplier : 1f, unit.elevation) * strafePenalty;
|
||||||
float xa = Core.input.axis(Binding.move_x);
|
float xa = Core.input.axis(Binding.move_x);
|
||||||
float ya = Core.input.axis(Binding.move_y);
|
float ya = Core.input.axis(Binding.move_y);
|
||||||
boolean boosted = (unit instanceof Mechc && unit.isFlying());
|
boolean boosted = (unit instanceof Mechc && unit.isFlying());
|
||||||
@@ -622,7 +619,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
}
|
}
|
||||||
|
|
||||||
float mouseAngle = Angles.mouseAngle(unit.x, unit.y);
|
float mouseAngle = Angles.mouseAngle(unit.x, unit.y);
|
||||||
boolean aimCursor = omni && player.shooting && unit.type().hasWeapons() && unit.type().faceTarget && !boosted && unit.type().rotateShooting;
|
boolean aimCursor = omni && player.shooting && unit.type.hasWeapons() && unit.type.faceTarget && !boosted && unit.type.rotateShooting;
|
||||||
|
|
||||||
if(aimCursor){
|
if(aimCursor){
|
||||||
unit.lookAt(mouseAngle);
|
unit.lookAt(mouseAngle);
|
||||||
@@ -637,11 +634,11 @@ public class DesktopInput extends InputHandler{
|
|||||||
}else{
|
}else{
|
||||||
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
|
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
|
||||||
if(!movement.isZero() && ground){
|
if(!movement.isZero() && ground){
|
||||||
unit.vel.rotateTo(movement.angle(), unit.type().rotateSpeed);
|
unit.vel.rotateTo(movement.angle(), unit.type.rotateSpeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unit.aim(unit.type().faceTarget ? Core.input.mouseWorld() : Tmp.v1.trns(unit.rotation, Core.input.mouseWorld().dst(unit)).add(unit.x, unit.y));
|
unit.aim(unit.type.faceTarget ? Core.input.mouseWorld() : Tmp.v1.trns(unit.rotation, Core.input.mouseWorld().dst(unit)).add(unit.x, unit.y));
|
||||||
unit.controlWeapons(true, player.shooting && !boosted);
|
unit.controlWeapons(true, player.shooting && !boosted);
|
||||||
|
|
||||||
player.boosting = Core.input.keyDown(Binding.boost) && !movement.isZero();
|
player.boosting = Core.input.keyDown(Binding.boost) && !movement.isZero();
|
||||||
@@ -660,7 +657,7 @@ public class DesktopInput extends InputHandler{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//update commander inut
|
//update commander unit
|
||||||
if(Core.input.keyTap(Binding.command)){
|
if(Core.input.keyTap(Binding.command)){
|
||||||
Call.unitCommand(player);
|
Call.unitCommand(player);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import arc.util.*;
|
|||||||
import mindustry.ai.formations.patterns.*;
|
import mindustry.ai.formations.patterns.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
@@ -158,7 +159,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
Payloadc pay = (Payloadc)unit;
|
Payloadc pay = (Payloadc)unit;
|
||||||
|
|
||||||
if(target.isAI() && target.isGrounded() && pay.canPickup(target)
|
if(target.isAI() && target.isGrounded() && pay.canPickup(target)
|
||||||
&& target.within(unit, unit.type().hitSize * 2f + target.type().hitSize * 2f)){
|
&& target.within(unit, unit.type.hitSize * 2f + target.type.hitSize * 2f)){
|
||||||
Call.pickedUnitPayload(unit, target);
|
Call.pickedUnitPayload(unit, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,6 +241,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
pay.set(x, y);
|
pay.set(x, y);
|
||||||
pay.dropLastPayload();
|
pay.dropLastPayload();
|
||||||
pay.set(prevx, prevy);
|
pay.set(prevx, prevy);
|
||||||
|
pay.controlling().each(u -> {
|
||||||
|
if(u instanceof Payloadc){
|
||||||
|
Call.payloadDropped(u, u.x, u.y);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +371,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
|
|
||||||
if(commander.isCommanding()){
|
if(commander.isCommanding()){
|
||||||
commander.clearCommand();
|
commander.clearCommand();
|
||||||
}else if(player.unit().type().commandLimit > 0){
|
}else if(player.unit().type.commandLimit > 0){
|
||||||
|
|
||||||
//TODO try out some other formations
|
//TODO try out some other formations
|
||||||
commander.commandNearby(new CircleFormation());
|
commander.commandNearby(new CircleFormation());
|
||||||
@@ -398,17 +404,17 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && player.unit().ammo <= 0){
|
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && player.unit().ammo <= 0){
|
||||||
player.unit().type().weapons.first().noAmmoSound.at(player.unit());
|
player.unit().type.weapons.first().noAmmoSound.at(player.unit());
|
||||||
}
|
}
|
||||||
|
|
||||||
wasShooting = player.shooting;
|
wasShooting = player.shooting;
|
||||||
|
|
||||||
if(!player.dead()){
|
if(!player.dead()){
|
||||||
controlledType = player.unit().type();
|
controlledType = player.unit().type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(controlledType != null && player.dead()){
|
if(controlledType != null && player.dead()){
|
||||||
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.dead);
|
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type == controlledType && !u.dead);
|
||||||
|
|
||||||
if(unit != null){
|
if(unit != null){
|
||||||
Call.unitControl(player, unit);
|
Call.unitControl(player, unit);
|
||||||
@@ -418,9 +424,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
|
|
||||||
public void checkUnit(){
|
public void checkUnit(){
|
||||||
if(controlledType != null){
|
if(controlledType != null){
|
||||||
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.dead);
|
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type == controlledType && !u.dead);
|
||||||
if(unit == null && controlledType == UnitTypes.block){
|
if(unit == null && controlledType == UnitTypes.block){
|
||||||
unit = world.buildWorld(player.x, player.y) instanceof ControlBlock ? ((ControlBlock)world.buildWorld(player.x, player.y)).unit() : null;
|
unit = world.buildWorld(player.x, player.y) instanceof ControlBlock cont && cont.canControl() ? cont.unit() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unit != null){
|
if(unit != null){
|
||||||
@@ -437,7 +443,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
Unit unit = player.unit();
|
Unit unit = player.unit();
|
||||||
if(!(unit instanceof Payloadc pay)) return;
|
if(!(unit instanceof Payloadc pay)) return;
|
||||||
|
|
||||||
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitSize * 2.5f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
|
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type.hitSize * 2.5f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
|
||||||
if(target != null){
|
if(target != null){
|
||||||
Call.requestUnitPayload(player, target);
|
Call.requestUnitPayload(player, target);
|
||||||
}else{
|
}else{
|
||||||
@@ -568,8 +574,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
wx = wy;
|
wx = wy;
|
||||||
wy = -x;
|
wy = -x;
|
||||||
}
|
}
|
||||||
req.x = world.toTile(wx - req.block.offset) + ox;
|
req.x = World.toTile(wx - req.block.offset) + ox;
|
||||||
req.y = world.toTile(wy - req.block.offset) + oy;
|
req.y = World.toTile(wy - req.block.offset) + oy;
|
||||||
req.rotation = Mathf.mod(req.rotation + direction, 4);
|
req.rotation = Mathf.mod(req.rotation + direction, 4);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -934,11 +940,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
int rawTileX(){
|
int rawTileX(){
|
||||||
return world.toTile(Core.input.mouseWorld().x);
|
return World.toTile(Core.input.mouseWorld().x);
|
||||||
}
|
}
|
||||||
|
|
||||||
int rawTileY(){
|
int rawTileY(){
|
||||||
return world.toTile(Core.input.mouseWorld().y);
|
return World.toTile(Core.input.mouseWorld().y);
|
||||||
}
|
}
|
||||||
|
|
||||||
int tileX(float cursorX){
|
int tileX(float cursorX){
|
||||||
@@ -946,7 +952,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
if(selectedBlock()){
|
if(selectedBlock()){
|
||||||
vec.sub(block.offset, block.offset);
|
vec.sub(block.offset, block.offset);
|
||||||
}
|
}
|
||||||
return world.toTile(vec.x);
|
return World.toTile(vec.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
int tileY(float cursorY){
|
int tileY(float cursorY){
|
||||||
@@ -954,7 +960,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
if(selectedBlock()){
|
if(selectedBlock()){
|
||||||
vec.sub(block.offset, block.offset);
|
vec.sub(block.offset, block.offset);
|
||||||
}
|
}
|
||||||
return world.toTile(vec.y);
|
return World.toTile(vec.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean selectedBlock(){
|
public boolean selectedBlock(){
|
||||||
@@ -984,8 +990,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
|
||||||
if(tile instanceof ControlBlock && tile.team == player.team()){
|
if(tile instanceof ControlBlock cont && cont.canControl() && tile.team == player.team()){
|
||||||
return ((ControlBlock)tile).unit();
|
return cont.unit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import arc.struct.*;
|
|||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.entities.units.*;
|
import mindustry.entities.units.*;
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
@@ -23,6 +24,7 @@ import mindustry.graphics.*;
|
|||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.ui.*;
|
import mindustry.ui.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
import mindustry.world.blocks.*;
|
||||||
|
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
import static mindustry.input.PlaceMode.*;
|
import static mindustry.input.PlaceMode.*;
|
||||||
@@ -85,7 +87,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
|||||||
if(tile != null && player.team().isEnemy(tile.team)){
|
if(tile != null && player.team().isEnemy(tile.team)){
|
||||||
player.miner().mineTile(null);
|
player.miner().mineTile(null);
|
||||||
target = tile;
|
target = tile;
|
||||||
}else if(tile != null && player.unit().type().canHeal && tile.team == player.team() && tile.damaged()){
|
}else if(tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged()){
|
||||||
player.miner().mineTile(null);
|
player.miner().mineTile(null);
|
||||||
target = tile;
|
target = tile;
|
||||||
}
|
}
|
||||||
@@ -405,14 +407,14 @@ public class MobileInput extends InputHandler implements GestureListener{
|
|||||||
protected int schemOriginX(){
|
protected int schemOriginX(){
|
||||||
Tmp.v1.setZero();
|
Tmp.v1.setZero();
|
||||||
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
|
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
|
||||||
return world.toTile(Tmp.v1.scl(1f / selectRequests.size).x);
|
return World.toTile(Tmp.v1.scl(1f / selectRequests.size).x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int schemOriginY(){
|
protected int schemOriginY(){
|
||||||
Tmp.v1.setZero();
|
Tmp.v1.setZero();
|
||||||
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
|
selectRequests.each(r -> Tmp.v1.add(r.drawx(), r.drawy()));
|
||||||
return world.toTile(Tmp.v1.scl(1f / selectRequests.size).y);
|
return World.toTile(Tmp.v1.scl(1f / selectRequests.size).y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -834,10 +836,10 @@ public class MobileInput extends InputHandler implements GestureListener{
|
|||||||
protected void updateMovement(Unit unit){
|
protected void updateMovement(Unit unit){
|
||||||
Rect rect = Tmp.r3;
|
Rect rect = Tmp.r3;
|
||||||
|
|
||||||
UnitType type = unit.type();
|
UnitType type = unit.type;
|
||||||
if(type == null) return;
|
if(type == null) return;
|
||||||
|
|
||||||
boolean omni = unit.type().omniMovement;
|
boolean omni = unit.type.omniMovement;
|
||||||
boolean legs = unit.isGrounded();
|
boolean legs = unit.isGrounded();
|
||||||
boolean allowHealing = type.canHeal;
|
boolean allowHealing = type.canHeal;
|
||||||
boolean validHealTarget = allowHealing && target instanceof Building && ((Building)target).isValid() && target.team() == unit.team &&
|
boolean validHealTarget = allowHealing && target instanceof Building && ((Building)target).isValid() && target.team() == unit.team &&
|
||||||
@@ -855,7 +857,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
|||||||
float attractDst = 15f;
|
float attractDst = 15f;
|
||||||
float strafePenalty = legs ? 1f : Mathf.lerp(1f, type.strafePenalty, Angles.angleDist(unit.vel.angle(), unit.rotation) / 180f);
|
float strafePenalty = legs ? 1f : Mathf.lerp(1f, type.strafePenalty, Angles.angleDist(unit.vel.angle(), unit.rotation) / 180f);
|
||||||
|
|
||||||
float baseSpeed = unit.type().speed;
|
float baseSpeed = unit.type.speed;
|
||||||
|
|
||||||
//limit speed to minimum formation speed to preserve formation
|
//limit speed to minimum formation speed to preserve formation
|
||||||
if(unit.isCommanding()){
|
if(unit.isCommanding()){
|
||||||
@@ -935,7 +937,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
|||||||
unit.aim(player.mouseX = Core.input.mouseWorldX(), player.mouseY = Core.input.mouseWorldY());
|
unit.aim(player.mouseX = Core.input.mouseWorldX(), player.mouseY = Core.input.mouseWorldY());
|
||||||
}else if(target == null){
|
}else if(target == null){
|
||||||
player.shooting = false;
|
player.shooting = false;
|
||||||
if(Core.settings.getBool("autotarget")){
|
if(Core.settings.getBool("autotarget") && !(player.unit() instanceof BlockUnitUnit u && u.tile() instanceof ControlBlock c && !c.shouldAutoTarget())){
|
||||||
target = Units.closestTarget(unit.team, unit.x, unit.y, range, u -> u.team != Team.derelict, u -> u.team != Team.derelict);
|
target = Units.closestTarget(unit.team, unit.x, unit.y, range, u -> u.team != Team.derelict, u -> u.team != Team.derelict);
|
||||||
|
|
||||||
if(allowHealing && target == null){
|
if(allowHealing && target == null){
|
||||||
|
|||||||
@@ -165,6 +165,18 @@ public class JsonIO{
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
json.setSerializer(UnitType.class, new Serializer<>(){
|
||||||
|
@Override
|
||||||
|
public void write(Json json, UnitType object, Class knownType){
|
||||||
|
json.writeValue(object.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UnitType read(Json json, JsonValue jsonData, Class type){
|
||||||
|
return Vars.content.getByName(ContentType.unit, jsonData.asString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
json.setSerializer(ItemStack.class, new Serializer<>(){
|
json.setSerializer(ItemStack.class, new Serializer<>(){
|
||||||
@Override
|
@Override
|
||||||
public void write(Json json, ItemStack object, Class knownType){
|
public void write(Json json, ItemStack object, Class knownType){
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ public class SaveMeta{
|
|||||||
public Map map;
|
public Map map;
|
||||||
public int wave;
|
public int wave;
|
||||||
public Rules rules;
|
public Rules rules;
|
||||||
public SectorInfo secinfo;
|
|
||||||
public StringMap tags;
|
public StringMap tags;
|
||||||
public String[] mods;
|
public String[] mods;
|
||||||
public boolean hasProduction;
|
|
||||||
|
|
||||||
public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, SectorInfo secinfo, StringMap tags){
|
public SaveMeta(int version, long timestamp, long timePlayed, int build, String map, int wave, Rules rules, StringMap tags){
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.build = build;
|
this.build = build;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
@@ -29,8 +27,5 @@ public class SaveMeta{
|
|||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
this.tags = tags;
|
this.tags = tags;
|
||||||
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
|
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
|
||||||
this.secinfo = secinfo;
|
|
||||||
|
|
||||||
secinfo.production.each((e, amount) -> hasProduction |= amount.mean > 0.001f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
map.get("mapname"),
|
map.get("mapname"),
|
||||||
map.getInt("wave"),
|
map.getInt("wave"),
|
||||||
JsonIO.read(Rules.class, map.get("rules", "{}")),
|
JsonIO.read(Rules.class, map.get("rules", "{}")),
|
||||||
JsonIO.read(SectorInfo.class, map.get("secinfo", "{}")),
|
|
||||||
map
|
map
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -74,6 +73,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
//prepare campaign data for writing
|
//prepare campaign data for writing
|
||||||
if(state.isCampaign()){
|
if(state.isCampaign()){
|
||||||
state.secinfo.prepare();
|
state.secinfo.prepare();
|
||||||
|
state.rules.sector.saveInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
//flush tech node progress
|
//flush tech node progress
|
||||||
@@ -89,7 +89,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
"wave", state.wave,
|
"wave", state.wave,
|
||||||
"wavetime", state.wavetime,
|
"wavetime", state.wavetime,
|
||||||
"stats", JsonIO.write(state.stats),
|
"stats", JsonIO.write(state.stats),
|
||||||
"secinfo", state.isCampaign() ? JsonIO.write(state.secinfo) : "{}",
|
|
||||||
"rules", JsonIO.write(state.rules),
|
"rules", JsonIO.write(state.rules),
|
||||||
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
"mods", JsonIO.write(mods.getModStrings().toArray(String.class)),
|
||||||
"width", world.width(),
|
"width", world.width(),
|
||||||
@@ -107,14 +106,13 @@ public abstract class SaveVersion extends SaveFileReader{
|
|||||||
state.wave = map.getInt("wave");
|
state.wave = map.getInt("wave");
|
||||||
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
state.wavetime = map.getFloat("wavetime", state.rules.waveSpacing);
|
||||||
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
state.stats = JsonIO.read(Stats.class, map.get("stats", "{}"));
|
||||||
state.secinfo = JsonIO.read(SectorInfo.class, map.get("secinfo", "{}"));
|
|
||||||
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
state.rules = JsonIO.read(Rules.class, map.get("rules", "{}"));
|
||||||
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
if(state.rules.spawns.isEmpty()) state.rules.spawns = defaultWaves.get();
|
||||||
lastReadBuild = map.getInt("build", -1);
|
lastReadBuild = map.getInt("build", -1);
|
||||||
|
|
||||||
//load time spent on sector into state
|
//load in sector info
|
||||||
if(state.rules.sector != null){
|
if(state.rules.sector != null){
|
||||||
state.secinfo.internalTimeSpent = state.rules.sector.getStoredTimeSpent();
|
state.secinfo = state.rules.sector.info;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!headless){
|
if(!headless){
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ public class TypeIO{
|
|||||||
return unit == null ? Nulls.unit : unit;
|
return unit == null ? Nulls.unit : unit;
|
||||||
}else if(type == 1){ //block
|
}else if(type == 1){ //block
|
||||||
Building tile = world.build(id);
|
Building tile = world.build(id);
|
||||||
return tile instanceof ControlBlock ? ((ControlBlock)tile).unit() : Nulls.unit;
|
return tile instanceof ControlBlock cont ? cont.unit() : Nulls.unit;
|
||||||
}
|
}
|
||||||
return Nulls.unit;
|
return Nulls.unit;
|
||||||
}
|
}
|
||||||
@@ -450,6 +450,16 @@ public class TypeIO{
|
|||||||
return color.set(read.i());
|
return color.set(read.i());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeContent(Writes write, Content cont){
|
||||||
|
write.b(cont.getContentType().ordinal());
|
||||||
|
write.s(cont.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Content readContent(Reads read){
|
||||||
|
byte id = read.b();
|
||||||
|
return content.getByID(ContentType.all[id], read.s());
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeLiquid(Writes write, Liquid liquid){
|
public static void writeLiquid(Writes write, Liquid liquid){
|
||||||
write.s(liquid == null ? -1 : liquid.id);
|
write.s(liquid == null ? -1 : liquid.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public enum LAccess{
|
|||||||
powerNetCapacity,
|
powerNetCapacity,
|
||||||
powerNetIn,
|
powerNetIn,
|
||||||
powerNetOut,
|
powerNetOut,
|
||||||
|
ammo,
|
||||||
|
ammoCapacity,
|
||||||
health,
|
health,
|
||||||
maxHealth,
|
maxHealth,
|
||||||
heat,
|
heat,
|
||||||
|
|||||||
@@ -189,16 +189,28 @@ public class LAssembler{
|
|||||||
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
|
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//remove spaces for non-strings
|
||||||
|
symbol = symbol.replace(' ', '_');
|
||||||
|
|
||||||
try{
|
try{
|
||||||
double value = Double.parseDouble(symbol);
|
double value = parseDouble(symbol);
|
||||||
|
if(Double.isNaN(value) || Double.isInfinite(value)) value = 0;
|
||||||
|
|
||||||
//this creates a hidden const variable with the specified value
|
//this creates a hidden const variable with the specified value
|
||||||
String key = "___" + value;
|
return putConst("___" + value, value).id;
|
||||||
return putConst(key, value).id;
|
|
||||||
}catch(NumberFormatException e){
|
}catch(NumberFormatException e){
|
||||||
return putVar(symbol).id;
|
return putVar(symbol).id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double parseDouble(String symbol) throws NumberFormatException{
|
||||||
|
//parse hex/binary syntax
|
||||||
|
if(symbol.startsWith("0b")) return Long.parseLong(symbol.substring(2), 2);
|
||||||
|
if(symbol.startsWith("0x")) return Long.parseLong(symbol.substring(2), 16);
|
||||||
|
|
||||||
|
return Double.parseDouble(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
/** Adds a constant value by name. */
|
/** Adds a constant value by name. */
|
||||||
public BVar putConst(String name, Object value){
|
public BVar putConst(String name, Object value){
|
||||||
BVar var = putVar(name);
|
BVar var = putVar(name);
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ public class LCanvas extends Table{
|
|||||||
statements.finishLayout();
|
statements.finishLayout();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).growX();
|
}).growX().height(38);
|
||||||
|
|
||||||
row();
|
row();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import arc.util.noise.*;
|
|||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.ai.types.*;
|
import mindustry.ai.types.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
|
import mindustry.core.*;
|
||||||
import mindustry.ctype.*;
|
import mindustry.ctype.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
import mindustry.game.*;
|
import mindustry.game.*;
|
||||||
@@ -110,12 +111,12 @@ public class LExecutor{
|
|||||||
|
|
||||||
public double num(int index){
|
public double num(int index){
|
||||||
Var v = vars[index];
|
Var v = vars[index];
|
||||||
return v.isobj ? v.objval != null ? 1 : 0 : v.numval;
|
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : v.numval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float numf(int index){
|
public float numf(int index){
|
||||||
Var v = vars[index];
|
Var v = vars[index];
|
||||||
return v.isobj ? v.objval != null ? 1 : 0 : (float)v.numval;
|
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : (float)v.numval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int numi(int index){
|
public int numi(int index){
|
||||||
@@ -129,7 +130,7 @@ public class LExecutor{
|
|||||||
public void setnum(int index, double value){
|
public void setnum(int index, double value){
|
||||||
Var v = vars[index];
|
Var v = vars[index];
|
||||||
if(v.constant) return;
|
if(v.constant) return;
|
||||||
v.numval = value;
|
v.numval = Double.isNaN(value) || Double.isInfinite(value) ? 0 : value;
|
||||||
v.objval = null;
|
v.objval = null;
|
||||||
v.isobj = false;
|
v.isobj = false;
|
||||||
}
|
}
|
||||||
@@ -263,8 +264,8 @@ public class LExecutor{
|
|||||||
if(res != null && (!build || res.build != null)){
|
if(res != null && (!build || res.build != null)){
|
||||||
cache.found = true;
|
cache.found = true;
|
||||||
//set result if found
|
//set result if found
|
||||||
exec.setnum(outX, cache.x = build ? res.build.x : res.worldx());
|
exec.setnum(outX, cache.x = World.conv(build ? res.build.x : res.worldx()));
|
||||||
exec.setnum(outY, cache.y = build ? res.build.y : res.worldy());
|
exec.setnum(outY, cache.y = World.conv(build ? res.build.y : res.worldy()));
|
||||||
exec.setnum(outFound, 1);
|
exec.setnum(outFound, 1);
|
||||||
}else{
|
}else{
|
||||||
cache.found = false;
|
cache.found = false;
|
||||||
@@ -332,14 +333,15 @@ public class LExecutor{
|
|||||||
//only control standard AI units
|
//only control standard AI units
|
||||||
if(unitObj instanceof Unit unit && ai != null){
|
if(unitObj instanceof Unit unit && ai != null){
|
||||||
ai.controlTimer = LogicAI.logicControlTimeout;
|
ai.controlTimer = LogicAI.logicControlTimeout;
|
||||||
|
float x1 = World.unconv(exec.numf(p1)), y1 = World.unconv(exec.numf(p2)), d1 = World.unconv(exec.numf(p3));
|
||||||
|
|
||||||
switch(type){
|
switch(type){
|
||||||
case move, stop, approach -> {
|
case move, stop, approach -> {
|
||||||
ai.control = type;
|
ai.control = type;
|
||||||
ai.moveX = exec.numf(p1);
|
ai.moveX = x1;
|
||||||
ai.moveY = exec.numf(p2);
|
ai.moveY = y1;
|
||||||
if(type == LUnitControl.approach){
|
if(type == LUnitControl.approach){
|
||||||
ai.moveRad = exec.numf(p3);
|
ai.moveRad = d1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//stop mining/building
|
//stop mining/building
|
||||||
@@ -353,13 +355,13 @@ public class LExecutor{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case within -> {
|
case within -> {
|
||||||
exec.setnum(p4, unit.within(exec.numf(p1), exec.numf(p2), exec.numf(p3)) ? 1 : 0);
|
exec.setnum(p4, unit.within(x1, y1, d1) ? 1 : 0);
|
||||||
}
|
}
|
||||||
case pathfind -> {
|
case pathfind -> {
|
||||||
ai.control = type;
|
ai.control = type;
|
||||||
}
|
}
|
||||||
case target -> {
|
case target -> {
|
||||||
ai.posTarget.set(exec.numf(p1), exec.numf(p2));
|
ai.posTarget.set(x1, y1);
|
||||||
ai.aimControl = type;
|
ai.aimControl = type;
|
||||||
ai.mainTarget = null;
|
ai.mainTarget = null;
|
||||||
ai.shoot = exec.bool(p3);
|
ai.shoot = exec.bool(p3);
|
||||||
@@ -376,7 +378,7 @@ public class LExecutor{
|
|||||||
unit.flag = exec.num(p1);
|
unit.flag = exec.num(p1);
|
||||||
}
|
}
|
||||||
case mine -> {
|
case mine -> {
|
||||||
Tile tile = world.tileWorld(exec.numf(p1), exec.numf(p2));
|
Tile tile = world.tileWorld(x1, y1);
|
||||||
if(unit instanceof Minerc miner){
|
if(unit instanceof Minerc miner){
|
||||||
miner.mineTile(miner.validMine(tile) ? tile : null);
|
miner.mineTile(miner.validMine(tile) ? tile : null);
|
||||||
}
|
}
|
||||||
@@ -395,7 +397,7 @@ public class LExecutor{
|
|||||||
if(unit instanceof Payloadc pay){
|
if(unit instanceof Payloadc pay){
|
||||||
//units
|
//units
|
||||||
if(exec.bool(p1)){
|
if(exec.bool(p1)){
|
||||||
Unit result = Units.closest(unit.team, unit.x, unit.y, unit.type().hitSize * 2f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
|
Unit result = Units.closest(unit.team, unit.x, unit.y, unit.type.hitSize * 2f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
|
||||||
|
|
||||||
if(result != null){
|
if(result != null){
|
||||||
Call.pickedUnitPayload(unit, result);
|
Call.pickedUnitPayload(unit, result);
|
||||||
@@ -420,7 +422,7 @@ public class LExecutor{
|
|||||||
}
|
}
|
||||||
case build -> {
|
case build -> {
|
||||||
if(unit instanceof Builderc builder && exec.obj(p3) instanceof Block block){
|
if(unit instanceof Builderc builder && exec.obj(p3) instanceof Block block){
|
||||||
int x = world.toTile(exec.numf(p1)), y = world.toTile(exec.numf(p2));
|
int x = World.toTile(x1), y = World.toTile(y1);
|
||||||
int rot = exec.numi(p4);
|
int rot = exec.numi(p4);
|
||||||
|
|
||||||
//reset state of last request when necessary
|
//reset state of last request when necessary
|
||||||
@@ -441,13 +443,12 @@ public class LExecutor{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case getBlock -> {
|
case getBlock -> {
|
||||||
float x = exec.numf(p1), y = exec.numf(p2);
|
|
||||||
float range = Math.max(unit.range(), buildingRange);
|
float range = Math.max(unit.range(), buildingRange);
|
||||||
if(!unit.within(x, y, range)){
|
if(!unit.within(x1, y1, range)){
|
||||||
exec.setobj(p3, null);
|
exec.setobj(p3, null);
|
||||||
exec.setnum(p4, 0);
|
exec.setnum(p4, 0);
|
||||||
}else{
|
}else{
|
||||||
Tile tile = world.tileWorld(x, y);
|
Tile tile = world.tileWorld(x1, y1);
|
||||||
//any environmental solid block is returned as StoneWall, aka "@solid"
|
//any environmental solid block is returned as StoneWall, aka "@solid"
|
||||||
Block block = tile == null ? null : !tile.synthetic() ? (tile.solid() ? Blocks.stoneWall : Blocks.air) : tile.block();
|
Block block = tile == null ? null : !tile.synthetic() ? (tile.solid() ? Blocks.stoneWall : Blocks.air) : tile.block();
|
||||||
exec.setobj(p3, block);
|
exec.setobj(p3, block);
|
||||||
@@ -737,7 +738,7 @@ public class LExecutor{
|
|||||||
v.objval = f.objval;
|
v.objval = f.objval;
|
||||||
v.isobj = true;
|
v.isobj = true;
|
||||||
}else{
|
}else{
|
||||||
v.numval = f.numval;
|
v.numval = Double.isNaN(f.numval) || Double.isInfinite(f.numval) ? 0 : f.numval;
|
||||||
v.isobj = false;
|
v.isobj = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,19 @@ package mindustry.maps;
|
|||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
|
import arc.util.*;
|
||||||
import mindustry.ai.*;
|
import mindustry.ai.*;
|
||||||
import mindustry.content.*;
|
import mindustry.content.*;
|
||||||
import mindustry.entities.*;
|
import mindustry.entities.*;
|
||||||
|
import mindustry.entities.abilities.*;
|
||||||
|
import mindustry.game.*;
|
||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
|
import mindustry.type.*;
|
||||||
import mindustry.world.*;
|
import mindustry.world.*;
|
||||||
|
import mindustry.world.blocks.defense.*;
|
||||||
|
import mindustry.world.blocks.defense.turrets.*;
|
||||||
|
import mindustry.world.blocks.defense.turrets.Turret.*;
|
||||||
import mindustry.world.blocks.storage.*;
|
import mindustry.world.blocks.storage.*;
|
||||||
|
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
@@ -15,6 +23,293 @@ import static mindustry.Vars.*;
|
|||||||
public class SectorDamage{
|
public class SectorDamage{
|
||||||
//direct damage is for testing only
|
//direct damage is for testing only
|
||||||
private static final boolean direct = false, rubble = true;
|
private static final boolean direct = false, rubble = true;
|
||||||
|
private static final int maxWavesSimulated = 50;
|
||||||
|
|
||||||
|
/** @return calculated capture progress of the enemy */
|
||||||
|
public static float getDamage(SectorInfo info){
|
||||||
|
float health = info.sumHealth;
|
||||||
|
int wavesPassed = info.wavesPassed;
|
||||||
|
int wave = info.wave;
|
||||||
|
float waveSpace = info.waveSpacing;
|
||||||
|
|
||||||
|
//this approach is O(n), it simulates every wave passing.
|
||||||
|
//other approaches can assume all the waves come as one, but that's not as fair.
|
||||||
|
if(wavesPassed > 0){
|
||||||
|
int waveBegin = wave;
|
||||||
|
int waveEnd = wave + wavesPassed;
|
||||||
|
|
||||||
|
//do not simulate every single wave if there's too many
|
||||||
|
if(wavesPassed > maxWavesSimulated){
|
||||||
|
waveBegin = waveEnd - maxWavesSimulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = waveBegin; i <= waveEnd; i++){
|
||||||
|
|
||||||
|
float efficiency = health / info.sumHealth;
|
||||||
|
float dps = info.sumDps * efficiency;
|
||||||
|
float rps = info.sumRps * efficiency;
|
||||||
|
|
||||||
|
float enemyDps = info.waveDpsBase + info.waveDpsSlope * (i);
|
||||||
|
float enemyHealth = info.waveHealthBase + info.waveHealthSlope * (i);
|
||||||
|
|
||||||
|
//happens due to certain regressions
|
||||||
|
if(enemyHealth < 0 || enemyDps < 0) continue;
|
||||||
|
|
||||||
|
//calculate time to destroy both sides
|
||||||
|
float timeDestroyEnemy = dps <= 0.0001f ? Float.POSITIVE_INFINITY : enemyHealth / dps; //if dps == 0, this is infinity
|
||||||
|
float timeDestroyBase = health / (enemyDps - rps); //if regen > enemyDps this is negative
|
||||||
|
|
||||||
|
//sector is lost, enemy took too long.
|
||||||
|
if(timeDestroyEnemy > timeDestroyBase){
|
||||||
|
health = 0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//otherwise, the enemy shoots for timeDestroyEnemy seconds, so calculate damage taken
|
||||||
|
float damageTaken = timeDestroyEnemy * (enemyDps - rps);
|
||||||
|
|
||||||
|
//damage the base.
|
||||||
|
health -= damageTaken;
|
||||||
|
|
||||||
|
//regen health after wave.
|
||||||
|
health = Math.min(health + rps / 60f * waveSpace, info.sumHealth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1f - Mathf.clamp(health / info.sumHealth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies wave damage based on sector parameters. */
|
||||||
|
public static void applyCalculatedDamage(){
|
||||||
|
//calculate base damage fraction
|
||||||
|
float damage = getDamage(state.secinfo);
|
||||||
|
|
||||||
|
//scaled damage has a power component to make it seem a little more realistic (as systems fail, enemy capturing gets easier and easier)
|
||||||
|
float scaled = Mathf.pow(damage, 1.5f);
|
||||||
|
|
||||||
|
//apply damage to units
|
||||||
|
float unitDamage = damage * state.secinfo.sumHealth;
|
||||||
|
Tile spawn = spawner.getFirstSpawn();
|
||||||
|
|
||||||
|
//damage only units near the spawn point
|
||||||
|
if(spawn != null){
|
||||||
|
Seq<Unit> allies = new Seq<>();
|
||||||
|
for(Unit ally : Groups.unit){
|
||||||
|
if(ally.team == state.rules.defaultTeam && ally.within(spawn, state.rules.dropZoneRadius * 2.5f)){
|
||||||
|
allies.add(ally);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allies.sort(u -> u.dst2(spawn));
|
||||||
|
|
||||||
|
//damage units one by one, not uniformly
|
||||||
|
for(var u : allies){
|
||||||
|
if(u.health < unitDamage){
|
||||||
|
u.remove();
|
||||||
|
unitDamage -= u.health;
|
||||||
|
}else{
|
||||||
|
u.health -= unitDamage;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(state.secinfo.wavesPassed > 0){
|
||||||
|
//simply remove each block in the spawner range if a wave passed
|
||||||
|
for(Tile spawner : spawner.getSpawns()){
|
||||||
|
spawner.circle((int)(state.rules.dropZoneRadius / tilesize), tile -> {
|
||||||
|
if(tile.team() == state.rules.defaultTeam){
|
||||||
|
if(rubble && tile.floor().hasSurface() && Mathf.chance(0.4)){
|
||||||
|
Effect.rubble(tile.build.x, tile.build.y, tile.block().size);
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//finally apply scaled damage
|
||||||
|
apply(scaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculates damage simulation parameters before a game is saved. */
|
||||||
|
public static void writeParameters(SectorInfo info){
|
||||||
|
Building core = state.rules.defaultTeam.core();
|
||||||
|
Seq<Tile> spawns = new Seq<>();
|
||||||
|
spawner.eachGroundSpawn((x, y) -> spawns.add(world.tile(x, y)));
|
||||||
|
|
||||||
|
if(spawns.isEmpty() && state.rules.waveTeam.core() != null){
|
||||||
|
spawns.add(state.rules.waveTeam.core().tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(core == null || spawns.isEmpty()) return;
|
||||||
|
|
||||||
|
Tile start = spawns.first();
|
||||||
|
|
||||||
|
Time.mark();
|
||||||
|
var field = pathfinder.getField(state.rules.waveTeam, Pathfinder.costGround, Pathfinder.fieldCore);
|
||||||
|
Seq<Tile> path = new Seq<>();
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
if(field != null && field.weights != null){
|
||||||
|
int[][] weights = field.weights;
|
||||||
|
int count = 0;
|
||||||
|
Tile current = start;
|
||||||
|
while(count < world.width() * world.height()){
|
||||||
|
int minCost = Integer.MAX_VALUE;
|
||||||
|
int cx = current.x, cy = current.y;
|
||||||
|
for(Point2 p : Geometry.d4){
|
||||||
|
int nx = cx + p.x, ny = cy + p.y;
|
||||||
|
|
||||||
|
Tile other = world.tile(nx, ny);
|
||||||
|
if(other != null && weights[nx][ny] < minCost && weights[nx][ny] != -1){
|
||||||
|
minCost = weights[nx][ny];
|
||||||
|
current = other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.add(current);
|
||||||
|
|
||||||
|
if(current.build == core){
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
count ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found){
|
||||||
|
path = Astar.pathfind(start, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
|
||||||
|
}
|
||||||
|
|
||||||
|
//create sparse tile array for fast range query
|
||||||
|
int sparseSkip = 6;
|
||||||
|
//TODO if this is slow, use a quadtree
|
||||||
|
Seq<Tile> sparse = new Seq<>(path.size / sparseSkip + 1);
|
||||||
|
|
||||||
|
for(int i = 0; i < path.size; i++){
|
||||||
|
if(i % sparseSkip == 0){
|
||||||
|
sparse.add(path.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//regen is in health per second
|
||||||
|
//dps is per second
|
||||||
|
float sumHealth = 0f, sumRps = 0f, sumDps = 0f;
|
||||||
|
float totalPathBuild = 0;
|
||||||
|
|
||||||
|
//first, calculate the total health of blocks in the path
|
||||||
|
|
||||||
|
for(Tile t : path){
|
||||||
|
int radius = 2;
|
||||||
|
|
||||||
|
//radius is square.
|
||||||
|
for(int dx = -radius; dx <= radius; dx++){
|
||||||
|
for(int dy = -radius; dy <= radius; dy++){
|
||||||
|
int wx = dx + t.x, wy = dy + t.y;
|
||||||
|
if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height()){
|
||||||
|
Tile tile = world.rawTile(wx, wy);
|
||||||
|
|
||||||
|
if(tile.build != null && tile.team() == state.rules.defaultTeam){
|
||||||
|
//health is divided by block size, because multiblocks are counted multiple times.
|
||||||
|
sumHealth += tile.build.health / tile.block().size;
|
||||||
|
totalPathBuild += 1f / tile.block().size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float avgHealth = totalPathBuild <= 1 ? sumHealth : sumHealth / totalPathBuild;
|
||||||
|
|
||||||
|
//block dps + regen + extra health/shields
|
||||||
|
for(Building build : Groups.build){
|
||||||
|
float e = build.efficiency();
|
||||||
|
if(e > 0.08f){
|
||||||
|
if(build.team == state.rules.defaultTeam && build instanceof Ranged ranged && sparse.contains(t -> t.within(build, ranged.range()))){
|
||||||
|
if(build.block instanceof Turret t && build instanceof TurretBuild b && b.hasAmmo()){
|
||||||
|
sumDps += t.shots / t.reloadTime * 60f * b.peekAmmo().estimateDPS() * e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(build.block instanceof MendProjector m){
|
||||||
|
sumRps += m.healPercent / m.reload * avgHealth * 60f / 100f * e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(build.block instanceof ForceProjector f){
|
||||||
|
sumHealth += f.breakage * e;
|
||||||
|
sumRps += 1f * e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float curEnemyHealth = 0f, curEnemyDps = 0f;
|
||||||
|
|
||||||
|
//unit regen + health + dps
|
||||||
|
for(Unit unit : Groups.unit){
|
||||||
|
//skip player
|
||||||
|
if(unit.isPlayer()) continue;
|
||||||
|
|
||||||
|
if(unit.team == state.rules.defaultTeam){
|
||||||
|
//scale health based on armor - yes, this is inaccurate, but better than nothing
|
||||||
|
float healthMult = 1f + Mathf.clamp(unit.armor / 20f);
|
||||||
|
|
||||||
|
sumHealth += unit.health*healthMult + unit.shield;
|
||||||
|
sumDps += unit.type.dpsEstimate;
|
||||||
|
if(unit.abilities.find(a -> a instanceof HealFieldAbility) instanceof HealFieldAbility h){
|
||||||
|
sumRps += h.amount / h.reload * 60f;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
curEnemyDps += unit.type.dpsEstimate;
|
||||||
|
curEnemyHealth += unit.health;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculate DPS and health for the next few waves and store in list
|
||||||
|
var reg = new LinearRegression();
|
||||||
|
Seq<Vec2> waveDps = new Seq<>(), waveHealth = new Seq<>();
|
||||||
|
|
||||||
|
for(int wave = state.wave, i = 0; i < 3; wave += (1 + i++)){
|
||||||
|
float sumWaveDps = 0f, sumWaveHealth = 0f;
|
||||||
|
|
||||||
|
//first wave has to take into account current dps
|
||||||
|
if(wave == state.wave){
|
||||||
|
sumWaveDps += curEnemyDps;
|
||||||
|
sumWaveHealth += curEnemyHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(SpawnGroup group : state.rules.spawns){
|
||||||
|
float healthMult = 1f + Mathf.clamp(group.type.armor / 20f);
|
||||||
|
StatusEffect effect = (group.effect == null ? StatusEffects.none : group.effect);
|
||||||
|
int spawned = group.getSpawned(wave);
|
||||||
|
if(spawned <= 0) continue;
|
||||||
|
sumWaveHealth += spawned * (group.getShield(wave) + group.type.health * effect.healthMultiplier * healthMult);
|
||||||
|
sumWaveDps += spawned * group.type.dpsEstimate * effect.damageMultiplier;
|
||||||
|
}
|
||||||
|
waveDps.add(new Vec2(wave, sumWaveDps));
|
||||||
|
waveHealth.add(new Vec2(wave, sumWaveHealth));
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculate linear regression of the wave data and store it
|
||||||
|
reg.calculate(waveHealth);
|
||||||
|
info.waveHealthBase = reg.intercept;
|
||||||
|
info.waveHealthSlope = reg.slope;
|
||||||
|
|
||||||
|
reg.calculate(waveDps);
|
||||||
|
info.waveDpsBase = reg.intercept;
|
||||||
|
info.waveDpsSlope = reg.slope;
|
||||||
|
|
||||||
|
//enemy units like to aim for a lot of non-essential things, so increase resulting health slightly
|
||||||
|
info.sumHealth = sumHealth * 1.2f;
|
||||||
|
//players tend to have longer range units/turrets, so assume DPS is higher
|
||||||
|
info.sumDps = sumDps * 1.2f;
|
||||||
|
info.sumRps = sumRps;
|
||||||
|
|
||||||
|
//finally, find an equation to put it all together and produce a 0-1 number
|
||||||
|
//due to the way most defenses are structured, this number will likely need a ^4 power or so
|
||||||
|
}
|
||||||
|
|
||||||
public static void apply(float fraction){
|
public static void apply(float fraction){
|
||||||
Tiles tiles = world.tiles;
|
Tiles tiles = world.tiles;
|
||||||
@@ -35,23 +330,71 @@ public class SectorDamage{
|
|||||||
if(core != null && !frontier.isEmpty()){
|
if(core != null && !frontier.isEmpty()){
|
||||||
for(Tile spawner : frontier){
|
for(Tile spawner : frontier){
|
||||||
//find path from spawn to core
|
//find path from spawn to core
|
||||||
//TODO this is broken
|
|
||||||
Seq<Tile> path = Astar.pathfind(spawner, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
|
Seq<Tile> path = Astar.pathfind(spawner, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
|
||||||
int amount = (int)(path.size * fraction);
|
Seq<Building> removal = new Seq<>();
|
||||||
for(int i = 0; i < amount; i++){
|
|
||||||
Tile t = path.get(i);
|
|
||||||
Geometry.circle(t.x, t.y, tiles.width, tiles.height, 5, (cx, cy) -> {
|
|
||||||
Tile other = tiles.getn(cx, cy);
|
|
||||||
//just remove all the buildings in the way - as long as they're not cores!
|
|
||||||
if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){
|
|
||||||
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
|
|
||||||
Effect.rubble(other.build.x, other.build.y, other.block().size);
|
|
||||||
}
|
|
||||||
|
|
||||||
other.remove();
|
int radius = 3;
|
||||||
|
|
||||||
|
//only penetrate a certain % by health, not by distance
|
||||||
|
float totalHealth = damage >= 1f ? 1f : path.sumf(t -> {
|
||||||
|
float s = 0;
|
||||||
|
for(int dx = -radius; dx <= radius; dx++){
|
||||||
|
for(int dy = -radius; dy <= radius; dy++){
|
||||||
|
int wx = dx + t.x, wy = dy + t.y;
|
||||||
|
if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height() && Mathf.within(dx, dy, radius)){
|
||||||
|
Tile other = world.rawTile(wx, wy);
|
||||||
|
s += other.team() == state.rules.defaultTeam ? other.build.health / other.block().size : 0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
|
float targetHealth = totalHealth * fraction;
|
||||||
|
float healthCount = 0;
|
||||||
|
|
||||||
|
out:
|
||||||
|
for(int i = 0; i < path.size && (healthCount < targetHealth || damage >= 1f); i++){
|
||||||
|
Tile t = path.get(i);
|
||||||
|
|
||||||
|
for(int dx = -radius; dx <= radius; dx++){
|
||||||
|
for(int dy = -radius; dy <= radius; dy++){
|
||||||
|
int wx = dx + t.x, wy = dy + t.y;
|
||||||
|
if(wx >= 0 && wy >= 0 && wx < world.width() && wy < world.height() && Mathf.within(dx, dy, radius)){
|
||||||
|
Tile other = world.rawTile(wx, wy);
|
||||||
|
|
||||||
|
//just remove all the buildings in the way - as long as they're not cores
|
||||||
|
if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){
|
||||||
|
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
|
||||||
|
Effect.rubble(other.build.x, other.build.y, other.block().size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//since the whole block is removed, count the whole health
|
||||||
|
healthCount += other.build.health;
|
||||||
|
|
||||||
|
removal.add(other.build);
|
||||||
|
|
||||||
|
if(healthCount >= targetHealth && damage < 0.999f){
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(Building r : removal){
|
||||||
|
if(r.tile.build == r){
|
||||||
|
r.addPlan(false);
|
||||||
|
r.tile.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//kill every core if damage is maximum
|
||||||
|
if(fraction >= 1){
|
||||||
|
for(Building c : state.rules.defaultTeam.cores().copy()){
|
||||||
|
c.tile.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +433,7 @@ public class SectorDamage{
|
|||||||
Effect.rubble(other.build.x, other.build.y, other.block().size);
|
Effect.rubble(other.build.x, other.build.y, other.block().size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
other.build.addPlan(false);
|
||||||
other.remove();
|
other.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,12 +412,12 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
|
|||||||
if(sector.hasEnemyBase()){
|
if(sector.hasEnemyBase()){
|
||||||
basegen.generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector, difficulty);
|
basegen.generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector, difficulty);
|
||||||
|
|
||||||
state.rules.attackMode = true;
|
state.rules.attackMode = sector.info.attack = true;
|
||||||
}else{
|
}else{
|
||||||
state.rules.winWave = 15 * (int)Math.max(difficulty * 10, 1);
|
state.rules.winWave = sector.info.winWave = 10 + 5 * (int)Math.max(difficulty * 10, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.rules.waves = true;
|
state.rules.waves = sector.info.waves = true;
|
||||||
|
|
||||||
//TODO better waves
|
//TODO better waves
|
||||||
state.rules.spawns = DefaultWaves.generate(difficulty);
|
state.rules.spawns = DefaultWaves.generate(difficulty);
|
||||||
|
|||||||
@@ -260,8 +260,8 @@ public class ContentParser{
|
|||||||
//TODO test this!
|
//TODO test this!
|
||||||
read(() -> {
|
read(() -> {
|
||||||
//add reconstructor type
|
//add reconstructor type
|
||||||
if(value.hasChild("requirements")){
|
if(value.has("requirements")){
|
||||||
JsonValue rec = value.remove("requirements");
|
JsonValue rec = value.remove("requirements");
|
||||||
|
|
||||||
//intermediate class for parsing
|
//intermediate class for parsing
|
||||||
class UnitReq{
|
class UnitReq{
|
||||||
@@ -286,6 +286,17 @@ public class ContentParser{
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//read extra default waves
|
||||||
|
if(value.has("waves")){
|
||||||
|
JsonValue waves = value.remove("waves");
|
||||||
|
SpawnGroup[] groups = parser.readValue(SpawnGroup[].class, waves);
|
||||||
|
for(SpawnGroup group : groups){
|
||||||
|
group.type = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vars.defaultWaves.get().addAll(groups);
|
||||||
|
}
|
||||||
|
|
||||||
readFields(unit, value, true);
|
readFields(unit, value, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ public class Mods implements Loadable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//make sure the main class exists before loading it; if it doesn't just don't put it there
|
//make sure the main class exists before loading it; if it doesn't just don't put it there
|
||||||
if(mainFile.exists()){
|
if(mainFile.exists() && Core.settings.getBool("mod-" + meta.name.toLowerCase().replace(" ", "-") + "-enabled", true)){
|
||||||
//mobile versions don't support class mods
|
//mobile versions don't support class mods
|
||||||
if(ios){
|
if(ios){
|
||||||
throw new IllegalArgumentException("Java class mods are not supported on iOS.");
|
throw new IllegalArgumentException("Java class mods are not supported on iOS.");
|
||||||
|
|||||||