Merging changes from private branch
This commit is contained in:
@@ -55,6 +55,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
Log.info("[GL] Version: @", graphics.getGLVersion());
|
||||
Log.info("[GL] Max texture size: @", maxTextureSize);
|
||||
Log.info("[GL] Using @ context.", gl30 != null ? "OpenGL 3" : "OpenGL 2");
|
||||
if(gl30 == null) Log.warn("[GL] Your device or video drivers do not support OpenGL 3. This will cause performance issues.");
|
||||
if(NvGpuInfo.hasMemoryInfo()){
|
||||
Log.info("[GL] Total available VRAM: @mb", NvGpuInfo.getMaxMemoryKB()/1024);
|
||||
}
|
||||
@@ -206,6 +207,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
PerfCounter.update.begin();
|
||||
|
||||
int targetfps = Core.settings.getInt("fpscap", 120);
|
||||
boolean changed = lastTargetFps != targetfps && lastTargetFps != -1;
|
||||
boolean limitFps = targetfps > 0 && targetfps <= 240;
|
||||
@@ -256,6 +259,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
Threads.sleep(toSleep / 1000000, (int)(toSleep % 1000000));
|
||||
}
|
||||
}
|
||||
|
||||
PerfCounter.update.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,7 +28,6 @@ import mindustry.net.*;
|
||||
import mindustry.service.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -47,6 +46,10 @@ public class Vars implements Loadable{
|
||||
public static boolean loadedLogger = false, loadedFileLogger = false;
|
||||
/** Name of current Steam player. */
|
||||
public static String steamPlayerName = "";
|
||||
/** Min game version for all mods. */
|
||||
public static final int minModGameVersion = 136;
|
||||
/** Min game version for java mods specifically - this is higher, as Java mods have more breaking changes. */
|
||||
public static final int minJavaModGameVersion = 147;
|
||||
/** If true, the BE server list is always used. */
|
||||
public static boolean forceBeServers = false;
|
||||
/** If true, mod code and scripts do not run. For internal testing only. This WILL break mods if enabled. */
|
||||
@@ -71,7 +74,7 @@ public class Vars implements Loadable{
|
||||
public static final String ghApi = "https://api.github.com";
|
||||
/** URL for discord invite. */
|
||||
public static final String discordURL = "https://discord.gg/mindustry";
|
||||
/** URL the links to the wiki's modding guide.*/
|
||||
/** Link to the wiki's modding guide.*/
|
||||
public static final String modGuideURL = "https://mindustrygame.github.io/wiki/modding/1-modding/";
|
||||
/** URLs to the JSON file containing all the BE servers. Only queried in BE. */
|
||||
public static final String[] serverJsonBeURLs = {"https://raw.githubusercontent.com/Anuken/MindustryServerList/master/servers_be.json", "https://cdn.jsdelivr.net/gh/anuken/mindustryserverlist/servers_be.json"};
|
||||
@@ -111,8 +114,6 @@ public class Vars implements Loadable{
|
||||
public static final float invasionGracePeriod = 20;
|
||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||
public static final float minArmorDamage = 0.1f;
|
||||
/** @deprecated see {@link CoreBlock#landDuration} instead! */
|
||||
public static final @Deprecated float coreLandDuration = 160f;
|
||||
/** size of tiles in units */
|
||||
public static final int tilesize = 8;
|
||||
/** size of one tile payload (^2) */
|
||||
@@ -267,7 +268,7 @@ public class Vars implements Loadable{
|
||||
public static NetServer netServer;
|
||||
public static NetClient netClient;
|
||||
|
||||
public static Player player;
|
||||
public static @Nullable Player player;
|
||||
|
||||
@Override
|
||||
public void loadAsync(){
|
||||
|
||||
@@ -7,6 +7,8 @@ import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.Units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -14,6 +16,7 @@ import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -28,11 +31,13 @@ public class BlockIndexer{
|
||||
private int quadWidth, quadHeight;
|
||||
|
||||
/** Stores all ore quadrants on the map. Maps ID to qX to qY to a list of tiles with that ore. */
|
||||
private IntSeq[][][] ores;
|
||||
private IntSeq[][][] ores, wallOres;
|
||||
/** Stores all damaged tile entities by team. */
|
||||
private Seq<Building>[] damagedTiles = new Seq[Team.all.length];
|
||||
/** All ores present on the map - can be wall or floor. */
|
||||
private Seq<Item> allPresentOres = new Seq<>();
|
||||
/** All ores available on this map. */
|
||||
private ObjectIntMap<Item> allOres = new ObjectIntMap<>();
|
||||
private ObjectIntMap<Item> allOres = new ObjectIntMap<>(), allWallOres = new ObjectIntMap<>();
|
||||
/** Stores teams that are present here as tiles. */
|
||||
private Seq<Team> activeTeams = new Seq<>(Team.class);
|
||||
/** Maps teams to a map of flagged tiles by flag. */
|
||||
@@ -41,6 +46,8 @@ public class BlockIndexer{
|
||||
private boolean[] blocksPresent;
|
||||
/** Array used for returning and reusing. */
|
||||
private Seq<Building> breturnArray = new Seq<>(Building.class);
|
||||
/** Maps block flag to a list of floor tiles that have it. */
|
||||
private Seq<Tile>[] floorMap;
|
||||
|
||||
public BlockIndexer(){
|
||||
clearFlags();
|
||||
@@ -53,15 +60,23 @@ public class BlockIndexer{
|
||||
addIndex(event.tile);
|
||||
});
|
||||
|
||||
Events.on(TileFloorChangeEvent.class, event -> {
|
||||
removeFloorIndex(event.tile, event.previous);
|
||||
addFloorIndex(event.tile, event.floor);
|
||||
});
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
damagedTiles = new Seq[Team.all.length];
|
||||
flagMap = new Seq[Team.all.length][BlockFlag.all.length];
|
||||
floorMap = new Seq[BlockFlag.all.length];
|
||||
activeTeams = new Seq<>(Team.class);
|
||||
|
||||
clearFlags();
|
||||
|
||||
allOres.clear();
|
||||
allWallOres.clear();
|
||||
ores = new IntSeq[content.items().size][][];
|
||||
wallOres = new IntSeq[content.items().size][][];
|
||||
quadWidth = Mathf.ceil(world.width() / (float)quadrantSize);
|
||||
quadHeight = Mathf.ceil(world.height() / (float)quadrantSize);
|
||||
blocksPresent = new boolean[content.blocks().size];
|
||||
@@ -78,28 +93,67 @@ public class BlockIndexer{
|
||||
for(Tile tile : world.tiles){
|
||||
process(tile);
|
||||
|
||||
var drop = tile.drop();
|
||||
addFloorIndex(tile, tile.floor());
|
||||
|
||||
if(drop != null){
|
||||
int qx = (tile.x / quadrantSize);
|
||||
int qy = (tile.y / quadrantSize);
|
||||
|
||||
//add position of quadrant to list
|
||||
if(tile.block() == Blocks.air){
|
||||
if(ores[drop.id] == null){
|
||||
ores[drop.id] = new IntSeq[quadWidth][quadHeight];
|
||||
}
|
||||
if(ores[drop.id][qx][qy] == null){
|
||||
ores[drop.id][qx][qy] = new IntSeq(false, 16);
|
||||
}
|
||||
Item drop;
|
||||
int qx = tile.x / quadrantSize, qy = tile.y / quadrantSize;
|
||||
if(tile.block() == Blocks.air){
|
||||
if((drop = tile.drop()) != null){
|
||||
//add position of quadrant to list
|
||||
if(ores[drop.id] == null) ores[drop.id] = new IntSeq[quadWidth][quadHeight];
|
||||
if(ores[drop.id][qx][qy] == null) ores[drop.id][qx][qy] = new IntSeq(false, 16);
|
||||
ores[drop.id][qx][qy].add(tile.pos());
|
||||
allOres.increment(drop);
|
||||
}
|
||||
}else if((drop = tile.wallDrop()) != null){
|
||||
//add position of quadrant to list
|
||||
if(wallOres[drop.id] == null) wallOres[drop.id] = new IntSeq[quadWidth][quadHeight];
|
||||
if(wallOres[drop.id][qx][qy] == null) wallOres[drop.id][qx][qy] = new IntSeq(false, 16);
|
||||
wallOres[drop.id][qx][qy].add(tile.pos());
|
||||
allWallOres.increment(drop);
|
||||
}
|
||||
}
|
||||
|
||||
updatePresentOres();
|
||||
});
|
||||
}
|
||||
|
||||
public Seq<Item> getAllPresentOres(){
|
||||
return allPresentOres;
|
||||
}
|
||||
|
||||
private void updatePresentOres(){
|
||||
allPresentOres.clear();
|
||||
for(Item item : content.items()){
|
||||
if(hasOre(item) || hasWallOre(item)){
|
||||
allPresentOres.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFloorIndex(Tile tile, Floor floor){
|
||||
if(floor.flags.size == 0) return;
|
||||
|
||||
for(var flag : floor.flags.array){
|
||||
getFlaggedFloors(flag).remove(tile);
|
||||
}
|
||||
}
|
||||
|
||||
private void addFloorIndex(Tile tile, Floor floor){
|
||||
if(floor.flags.size == 0 || !floor.shouldIndex(tile)) return;
|
||||
|
||||
for(var flag : floor.flags.array){
|
||||
getFlaggedFloors(flag).add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public Seq<Tile> getFlaggedFloors(BlockFlag flag){
|
||||
if(floorMap[flag.ordinal()] == null){
|
||||
floorMap[flag.ordinal()] = new Seq<>(false);
|
||||
}
|
||||
return floorMap[flag.ordinal()];
|
||||
}
|
||||
|
||||
public void removeIndex(Tile tile){
|
||||
var team = tile.team();
|
||||
if(tile.build != null && tile.isCenter()){
|
||||
@@ -143,30 +197,37 @@ public class BlockIndexer{
|
||||
public void addIndex(Tile tile){
|
||||
process(tile);
|
||||
|
||||
var drop = tile.drop();
|
||||
if(drop != null && ores != null){
|
||||
int qx = tile.x / quadrantSize;
|
||||
int qy = tile.y / quadrantSize;
|
||||
Item drop = tile.drop(), wallDrop = tile.wallDrop();
|
||||
if(drop == null && wallDrop == null) return;
|
||||
int qx = tile.x / quadrantSize, qy = tile.y / quadrantSize;
|
||||
int pos = tile.pos();
|
||||
|
||||
if(ores[drop.id] == null){
|
||||
ores[drop.id] = new IntSeq[quadWidth][quadHeight];
|
||||
}
|
||||
if(ores[drop.id][qx][qy] == null){
|
||||
ores[drop.id][qx][qy] = new IntSeq(false, 16);
|
||||
}
|
||||
|
||||
int pos = tile.pos();
|
||||
var seq = ores[drop.id][qx][qy];
|
||||
|
||||
if(tile.block() == Blocks.air){
|
||||
//add the index if it is a valid new spot to mine at
|
||||
if(!seq.contains(pos)){
|
||||
seq.add(pos);
|
||||
allOres.increment(drop);
|
||||
if(tile.block() == Blocks.air){
|
||||
if(drop != null){ //floor
|
||||
if(ores[drop.id] == null) ores[drop.id] = new IntSeq[quadWidth][quadHeight];
|
||||
if(ores[drop.id][qx][qy] == null) ores[drop.id][qx][qy] = new IntSeq(false, 16);
|
||||
if(ores[drop.id][qx][qy].addUnique(pos)){
|
||||
int old = allOres.increment(drop); //increment ore count only if not already counted
|
||||
if(old == 0) updatePresentOres();
|
||||
}
|
||||
}else if(seq.contains(pos)){ //otherwise, it likely became blocked, remove it
|
||||
seq.removeValue(pos);
|
||||
allOres.increment(drop, -1);
|
||||
}
|
||||
if(wallDrop != null && wallOres != null && wallOres[wallDrop.id] != null && wallOres[wallDrop.id][qx][qy] != null && wallOres[wallDrop.id][qx][qy].removeValue(pos)){ //wall
|
||||
int old = allWallOres.increment(wallDrop, -1);
|
||||
if(old == 1) updatePresentOres();
|
||||
}
|
||||
}else{
|
||||
if(wallDrop != null){ //wall
|
||||
if(wallOres[wallDrop.id] == null) wallOres[wallDrop.id] = new IntSeq[quadWidth][quadHeight];
|
||||
if(wallOres[wallDrop.id][qx][qy] == null) wallOres[wallDrop.id][qx][qy] = new IntSeq(false, 16);
|
||||
if(wallOres[wallDrop.id][qx][qy].addUnique(pos)){
|
||||
int old = allWallOres.increment(wallDrop); //increment ore count only if not already counted
|
||||
if(old == 0) updatePresentOres();
|
||||
}
|
||||
}
|
||||
|
||||
if(drop != null && ores != null && ores[drop.id] != null&& ores[drop.id][qx][qy] != null && ores[drop.id][qx][qy].removeValue(pos)){ //floor
|
||||
int old = allOres.increment(drop, -1);
|
||||
if(old == 1) updatePresentOres();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +255,11 @@ public class BlockIndexer{
|
||||
return allOres.get(item) > 0;
|
||||
}
|
||||
|
||||
/** @return whether this item is present on this map as a wall ore. */
|
||||
public boolean hasWallOre(Item item){
|
||||
return allWallOres.get(item) > 0;
|
||||
}
|
||||
|
||||
/** Returns all damaged tiles by team. */
|
||||
public Seq<Building> getDamaged(Team team){
|
||||
if(damagedTiles[team.id] == null){
|
||||
@@ -348,7 +414,7 @@ public class BlockIndexer{
|
||||
breturnArray.size = 0;
|
||||
}
|
||||
|
||||
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
|
||||
public Building findEnemyTile(Team team, float x, float y, float range, BuildingPriorityf priority, Boolf<Building> pred){
|
||||
Building target = null;
|
||||
float targetDist = 0;
|
||||
|
||||
@@ -362,10 +428,10 @@ public class BlockIndexer{
|
||||
//if a block has the same priority, the closer one should be targeted
|
||||
float dist = candidate.dst(x, y) - candidate.hitSize() / 2f;
|
||||
if(target == null ||
|
||||
//if its closer and is at least equal priority
|
||||
(dist < targetDist && candidate.block.priority >= target.block.priority) ||
|
||||
// block has higher priority (so range doesnt matter)
|
||||
(candidate.block.priority > target.block.priority)){
|
||||
//if it is closer and is at least equal priority
|
||||
(dist < targetDist && priority.priority(candidate) >= priority.priority(target)) ||
|
||||
// block has higher priority (so range doesn't matter)
|
||||
priority.priority(candidate) > priority.priority(target)){
|
||||
target = candidate;
|
||||
targetDist = dist;
|
||||
}
|
||||
@@ -374,6 +440,10 @@ public class BlockIndexer{
|
||||
return target;
|
||||
}
|
||||
|
||||
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
|
||||
return findEnemyTile(team, x, y, range, UnitSorts.buildingDefault, pred);
|
||||
}
|
||||
|
||||
public Building findTile(Team team, float x, float y, float range, Boolf<Building> pred){
|
||||
return findTile(team, x, y, range, pred, false);
|
||||
}
|
||||
@@ -432,11 +502,43 @@ public class BlockIndexer{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Find the closest ore wall relative to a position. */
|
||||
public Tile findClosestWallOre(float xp, float yp, Item item){
|
||||
//(stolen from foo's client :))))
|
||||
if(wallOres[item.id] != null){
|
||||
float minDst = 0f;
|
||||
Tile closest = null;
|
||||
for(int qx = 0; qx < quadWidth; qx++){
|
||||
for(int qy = 0; qy < quadHeight; qy++){
|
||||
var arr = wallOres[item.id][qx][qy];
|
||||
if(arr != null && arr.size > 0){
|
||||
Tile tile = world.tile(arr.first());
|
||||
if(tile.block() != Blocks.air){
|
||||
float dst = Mathf.dst2(xp, yp, tile.worldx(), tile.worldy());
|
||||
if(closest == null || dst < minDst){
|
||||
closest = tile;
|
||||
minDst = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Find the closest ore block relative to a position. */
|
||||
public Tile findClosestOre(Unit unit, Item item){
|
||||
return findClosestOre(unit.x, unit.y, item);
|
||||
}
|
||||
|
||||
/** Find the closest ore block relative to a position. */
|
||||
public Tile findClosestWallOre(Unit unit, Item item){
|
||||
return findClosestWallOre(unit.x, unit.y, item);
|
||||
}
|
||||
|
||||
private void process(Tile tile){
|
||||
var team = tile.team();
|
||||
//only process entity changes with centered tiles
|
||||
|
||||
@@ -1082,21 +1082,6 @@ public class ControlPathfinder implements Runnable{
|
||||
return raycast(unit.team().id, unit.type.pathCost, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public int nextTargetId(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out){
|
||||
return getPathPosition(unit, pathId, destination, out, null);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean getPathPosition(Unit unit, int pathId, Vec2 destination, Vec2 out, @Nullable boolean[] noResultFound){
|
||||
return getPathPosition(unit, destination, destination, out, noResultFound);
|
||||
}
|
||||
|
||||
public boolean getPathPosition(Unit unit, Vec2 destination, Vec2 out, @Nullable boolean[] noResultFound){
|
||||
return getPathPosition(unit, destination, destination, out, noResultFound);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.TaskQueue;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.core.*;
|
||||
@@ -16,11 +17,14 @@ import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.world.meta.BlockFlag.*;
|
||||
|
||||
public class Pathfinder implements Runnable{
|
||||
private static final long maxUpdate = Time.millisToNanos(8);
|
||||
private static final int neverRefresh = Integer.MAX_VALUE;
|
||||
private static final int updateFPS = 60;
|
||||
private static final int updateInterval = 1000 / updateFPS;
|
||||
|
||||
@@ -30,51 +34,66 @@ public class Pathfinder implements Runnable{
|
||||
static final int impassable = -1;
|
||||
|
||||
public static final int
|
||||
fieldCore = 0;
|
||||
fieldCore = 0,
|
||||
maxFields = 10;
|
||||
|
||||
public static final Seq<Prov<Flowfield>> fieldTypes = Seq.with(
|
||||
EnemyCoreField::new
|
||||
EnemyCoreField::new
|
||||
);
|
||||
|
||||
public static final int
|
||||
costGround = 0,
|
||||
costLegs = 1,
|
||||
costNaval = 2,
|
||||
costHover = 3;
|
||||
costGround = 0,
|
||||
costLegs = 1,
|
||||
costNaval = 2,
|
||||
costNeoplasm = 3,
|
||||
costNone = 4,
|
||||
costHover = 5,
|
||||
|
||||
maxCosts = 8;
|
||||
|
||||
public static final Seq<PathCost> costTypes = Seq.with(
|
||||
//ground
|
||||
(team, tile) ->
|
||||
(PathTile.allDeep(tile) || ((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0) && PathTile.solid(tile)) ? impassable : 1 +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0) +
|
||||
(PathTile.nearLiquid(tile) ? 6 : 0) +
|
||||
(PathTile.deep(tile) ? 6000 : 0) +
|
||||
(PathTile.damages(tile) ? 30 : 0),
|
||||
//ground
|
||||
(team, tile) ->
|
||||
(PathTile.allDeep(tile) || ((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0) && PathTile.solid(tile)) ? impassable : 1 +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0) +
|
||||
(PathTile.nearLiquid(tile) ? 6 : 0) +
|
||||
(PathTile.deep(tile) ? 6000 : 0) +
|
||||
(PathTile.damages(tile) ? 30 : 0),
|
||||
|
||||
//legs
|
||||
(team, tile) ->
|
||||
PathTile.legSolid(tile) ? impassable : 1 +
|
||||
(PathTile.deep(tile) ? 6000 : 0) + //leg units can now drown
|
||||
(PathTile.solid(tile) ? 5 : 0),
|
||||
//legs
|
||||
(team, tile) ->
|
||||
PathTile.legSolid(tile) ? impassable : 1 +
|
||||
(PathTile.deep(tile) ? 6000 : 0) + //leg units can now drown
|
||||
(PathTile.solid(tile) ? 5 : 0),
|
||||
|
||||
//water
|
||||
(team, tile) ->
|
||||
(!PathTile.liquid(tile) ? 6000 : 1) +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
|
||||
(PathTile.deep(tile) ? 0 : 1) +
|
||||
(PathTile.damages(tile) ? 35 : 0),
|
||||
//water
|
||||
(team, tile) ->
|
||||
(!PathTile.liquid(tile) ? 6000 : 1) +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
|
||||
(PathTile.deep(tile) ? 0 : 1) +
|
||||
(PathTile.damages(tile) ? 35 : 0),
|
||||
|
||||
//hover
|
||||
(team, tile) ->
|
||||
(((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0) && PathTile.solid(tile)) ? impassable : 1 +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0)
|
||||
//neoplasm veins
|
||||
(team, tile) ->
|
||||
(PathTile.deep(tile) || (PathTile.team(tile) == 0 && PathTile.solid(tile))) ? impassable : 1 +
|
||||
(PathTile.health(tile) * 3) +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0) +
|
||||
(PathTile.nearLiquid(tile) ? 2 : 0),
|
||||
|
||||
//none (flat cost)
|
||||
(team, tile) -> 1,
|
||||
|
||||
//hover
|
||||
(team, tile) ->
|
||||
(((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0) && PathTile.solid(tile)) ? impassable : 1 +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0)
|
||||
);
|
||||
|
||||
/** tile data, see PathTileStruct - kept as a separate array for threading reasons */
|
||||
int[] tiles = new int[0];
|
||||
int[] tiles = {};
|
||||
|
||||
/** maps team, cost, type to flow field*/
|
||||
Flowfield[][][] cache;
|
||||
@@ -86,6 +105,8 @@ public class Pathfinder implements Runnable{
|
||||
@Nullable Thread thread;
|
||||
IntSeq tmpArray = new IntSeq();
|
||||
|
||||
boolean needsRefresh;
|
||||
|
||||
public Pathfinder(){
|
||||
clearCache();
|
||||
|
||||
@@ -100,6 +121,7 @@ public class Pathfinder implements Runnable{
|
||||
mainList = new Seq<>();
|
||||
clearCache();
|
||||
|
||||
|
||||
for(int i = 0; i < tiles.length; i++){
|
||||
Tile tile = world.tiles.geti(i);
|
||||
tiles[i] = packTile(tile);
|
||||
@@ -153,10 +175,36 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events.run(Trigger.afterGameUpdate, () -> {
|
||||
//only refresh periodically (every 2 frames) to batch flowfield updates
|
||||
//TODO: is it worth switching to a timestamp based system instead that updates every X milliseconds?
|
||||
if(needsRefresh && Core.graphics.getFrameId() % 2 == 0){
|
||||
needsRefresh = false;
|
||||
|
||||
//can't iterate through array so use the map, which should not lead to problems
|
||||
for(Flowfield path : mainList){
|
||||
//paths with a refresh rate should not be updated by tiles changing
|
||||
if(path != null && path.needsRefresh()){
|
||||
synchronized(path.targets){
|
||||
//TODO: this is super slow and forces a refresh for every tile changed!
|
||||
path.updateTargetPositions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//mark every flow field as dirty, so it updates when it's done
|
||||
queue.post(() -> {
|
||||
for(Flowfield data : threadList){
|
||||
data.dirty = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void clearCache(){
|
||||
cache = new Flowfield[256][5][5];
|
||||
cache = new Flowfield[256][maxCosts][maxFields];
|
||||
}
|
||||
|
||||
/** Packs a tile into its internal representation. */
|
||||
@@ -185,19 +233,19 @@ public class Pathfinder implements Runnable{
|
||||
int tid = tile.getTeamID();
|
||||
|
||||
return PathTile.get(
|
||||
tile.build == null || !solid || tile.block() instanceof CoreBlock ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
||||
tid == 0 && tile.build != null && state.rules.coreCapture ? 255 : tid, //use teamid = 255 when core capture is enabled to mark out derelict structures
|
||||
solid,
|
||||
tile.floor().isLiquid,
|
||||
tile.legSolid(),
|
||||
nearLiquid,
|
||||
nearGround,
|
||||
nearSolid,
|
||||
nearLegSolid,
|
||||
tile.floor().isDeep(),
|
||||
tile.floor().damageTaken > 0.00001f,
|
||||
allDeep,
|
||||
tile.block().teamPassable
|
||||
tile.build == null || !solid || tile.block() instanceof CoreBlock ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
||||
tid == 0 && tile.build != null && state.rules.coreCapture ? 255 : tid, //use teamid = 255 when core capture is enabled to mark out derelict structures
|
||||
solid,
|
||||
tile.floor().isLiquid,
|
||||
tile.legSolid(),
|
||||
nearLiquid,
|
||||
nearGround,
|
||||
nearSolid,
|
||||
nearLegSolid,
|
||||
tile.floor().isDeep(),
|
||||
tile.floor().damageTaken > 0.00001f,
|
||||
allDeep,
|
||||
tile.block().teamPassable
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,6 +271,7 @@ public class Pathfinder implements Runnable{
|
||||
thread = null;
|
||||
}
|
||||
queue.clear();
|
||||
needsRefresh = false;
|
||||
}
|
||||
|
||||
/** Update a tile in the internal pathfinding grid.
|
||||
@@ -237,23 +286,10 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
});
|
||||
|
||||
//can't iterate through array so use the map, which should not lead to problems
|
||||
for(Flowfield path : mainList){
|
||||
if(path != null){
|
||||
synchronized(path.targets){
|
||||
path.updateTargetPositions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//mark every flow field as dirty, so it updates when it's done
|
||||
queue.post(() -> {
|
||||
for(Flowfield data : threadList){
|
||||
data.dirty = true;
|
||||
}
|
||||
});
|
||||
|
||||
controlPath.updateTile(tile);
|
||||
|
||||
//queue a refresh sometime in the future
|
||||
needsRefresh = true;
|
||||
}
|
||||
|
||||
/** Thread implementation. */
|
||||
@@ -307,48 +343,55 @@ public class Pathfinder implements Runnable{
|
||||
|
||||
/** Gets next tile to travel to. Main thread only. */
|
||||
public @Nullable Tile getTargetTile(Tile tile, Flowfield path){
|
||||
return getTargetTile(tile, path, true);
|
||||
}
|
||||
|
||||
/** Gets next tile to travel to. Main thread only. */
|
||||
public @Nullable Tile getTargetTile(Tile tile, Flowfield path, boolean diagonals){
|
||||
if(tile == null) return null;
|
||||
|
||||
//uninitialized flowfields are not applicable
|
||||
if(!path.initialized){
|
||||
//also ignore paths with no targets, there is no destination
|
||||
if(!path.initialized || path.targets.size == 0){
|
||||
return tile;
|
||||
}
|
||||
|
||||
//if refresh rate is positive, queue a refresh
|
||||
if(path.refreshRate > 0 && Time.timeSinceMillis(path.lastUpdateTime) > path.refreshRate){
|
||||
if(path.refreshRate > 0 && path.refreshRate != neverRefresh && Time.timeSinceMillis(path.lastUpdateTime) > path.refreshRate && path.frontier.size == 0){
|
||||
path.lastUpdateTime = Time.millis();
|
||||
|
||||
tmpArray.clear();
|
||||
path.getPositions(tmpArray);
|
||||
|
||||
synchronized(path.targets){
|
||||
//make sure the position actually changed
|
||||
if(!(path.targets.size == 1 && tmpArray.size == 1 && path.targets.first() == tmpArray.first())){
|
||||
path.updateTargetPositions();
|
||||
path.updateTargetPositions();
|
||||
|
||||
//queue an update
|
||||
queue.post(() -> updateTargets(path));
|
||||
}
|
||||
//queue an update
|
||||
queue.post(() -> updateTargets(path));
|
||||
}
|
||||
}
|
||||
|
||||
//use complete weights if possible; these contain a complete flow field that is not being updated
|
||||
int[] values = path.hasComplete ? path.completeWeights : path.weights;
|
||||
int apos = tile.array();
|
||||
int res = path.resolution;
|
||||
int ww = path.width;
|
||||
int apos = tile.x/res + tile.y/res * ww;
|
||||
int value = values[apos];
|
||||
|
||||
var points = diagonals ? Geometry.d8 : Geometry.d4;
|
||||
|
||||
Tile current = null;
|
||||
int tl = 0;
|
||||
for(Point2 point : Geometry.d8){
|
||||
int dx = tile.x + point.x, dy = tile.y + point.y;
|
||||
for(Point2 point : points){
|
||||
int dx = tile.x + point.x * res, dy = tile.y + point.y * res;
|
||||
|
||||
Tile other = world.tile(dx, dy);
|
||||
if(other == null) continue;
|
||||
|
||||
int packed = world.packArray(dx, dy);
|
||||
int packed = dx/res + dy/res * ww;
|
||||
|
||||
if(values[packed] < value && (current == null || values[packed] < tl) && path.passable(packed) &&
|
||||
!(point.x != 0 && point.y != 0 && (!path.passable(world.packArray(tile.x + point.x, tile.y)) || !path.passable(world.packArray(tile.x, tile.y + point.y))))){ //diagonal corner trap
|
||||
!(point.x != 0 && point.y != 0 && (!path.passable(((tile.x + point.x)/res + tile.y/res*ww)) || !path.passable((tile.x/res + (tile.y + point.y)/res*ww))))){ //diagonal corner trap
|
||||
current = other;
|
||||
tl = values[packed];
|
||||
}
|
||||
@@ -365,13 +408,21 @@ public class Pathfinder implements Runnable{
|
||||
//increment search, but do not clear the frontier
|
||||
path.search++;
|
||||
|
||||
//search overflow; reset everything.
|
||||
if(path.search >= Short.MAX_VALUE){
|
||||
Arrays.fill(path.searches, (short)0);
|
||||
path.search = 1;
|
||||
}
|
||||
|
||||
synchronized(path.targets){
|
||||
//add targets
|
||||
for(int i = 0; i < path.targets.size; i++){
|
||||
int pos = path.targets.get(i);
|
||||
|
||||
if(pos >= path.weights.length) continue;
|
||||
|
||||
path.weights[pos] = 0;
|
||||
path.searches[pos] = path.search;
|
||||
path.searches[pos] = (short)path.search;
|
||||
path.frontier.addFirst(pos);
|
||||
}
|
||||
}
|
||||
@@ -390,7 +441,7 @@ public class Pathfinder implements Runnable{
|
||||
*/
|
||||
private void registerPath(Flowfield path){
|
||||
path.lastUpdateTime = Time.millis();
|
||||
path.setup(tiles.length);
|
||||
path.setup();
|
||||
|
||||
threadList.add(path);
|
||||
|
||||
@@ -398,9 +449,7 @@ public class Pathfinder implements Runnable{
|
||||
Core.app.post(() -> mainList.add(path));
|
||||
|
||||
//fill with impassables by default
|
||||
for(int i = 0; i < tiles.length; i++){
|
||||
path.weights[i] = impassable;
|
||||
}
|
||||
Arrays.fill(path.weights, impassable);
|
||||
|
||||
//add targets
|
||||
for(int i = 0; i < path.targets.size; i++){
|
||||
@@ -416,6 +465,7 @@ public class Pathfinder implements Runnable{
|
||||
long start = Time.nanos();
|
||||
|
||||
int counter = 0;
|
||||
int w = path.width, h = path.height;
|
||||
|
||||
while(path.frontier.size > 0){
|
||||
int tile = path.frontier.removeLast();
|
||||
@@ -423,7 +473,7 @@ public class Pathfinder implements Runnable{
|
||||
int cost = path.weights[tile];
|
||||
|
||||
//pathfinding overflowed for some reason, time to bail. the next block update will handle this, hopefully
|
||||
if(path.frontier.size >= world.width() * world.height()){
|
||||
if(path.frontier.size >= w * h){
|
||||
path.frontier.clear();
|
||||
return;
|
||||
}
|
||||
@@ -431,12 +481,12 @@ public class Pathfinder implements Runnable{
|
||||
if(cost != impassable){
|
||||
for(Point2 point : Geometry.d4){
|
||||
|
||||
int dx = (tile % wwidth) + point.x, dy = (tile / wwidth) + point.y;
|
||||
int dx = (tile % w) + point.x, dy = (tile / w) + point.y;
|
||||
|
||||
if(dx < 0 || dy < 0 || dx >= wwidth || dy >= wheight) continue;
|
||||
if(dx < 0 || dy < 0 || dx >= w || dy >= h) continue;
|
||||
|
||||
int newPos = tile + point.x + point.y * wwidth;
|
||||
int otherCost = path.cost.getCost(path.team.id, tiles[newPos]);
|
||||
int newPos = dx + dy * w;
|
||||
int otherCost = path.getCost(tiles, newPos);
|
||||
|
||||
if((path.weights[newPos] > cost + otherCost || path.searches[newPos] < path.search) && otherCost != impassable){
|
||||
path.frontier.addFirst(newPos);
|
||||
@@ -523,7 +573,7 @@ public class Pathfinder implements Runnable{
|
||||
* Concrete subclasses must specify a way to fetch costs and destinations.
|
||||
*/
|
||||
public static abstract class Flowfield{
|
||||
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
||||
/** Refresh rate in milliseconds. <= 0 to disable. */
|
||||
protected int refreshRate;
|
||||
/** Team this path is for. Set before using. */
|
||||
protected Team team = Team.derelict;
|
||||
@@ -537,12 +587,16 @@ public class Pathfinder implements Runnable{
|
||||
/** costs of getting to a specific tile */
|
||||
public int[] weights;
|
||||
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
||||
public int[] searches;
|
||||
public short[] searches;
|
||||
/** the last "complete" weights of this tilemap. */
|
||||
public int[] completeWeights;
|
||||
|
||||
/** Scaling factor. For example, resolution = 2 means tiles are twice as large. */
|
||||
public final int resolution;
|
||||
public final int width, height;
|
||||
|
||||
/** search frontier, these are Pos objects */
|
||||
IntQueue frontier = new IntQueue();
|
||||
final IntQueue frontier = new IntQueue();
|
||||
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
|
||||
final IntSeq targets = new IntSeq();
|
||||
/** current search ID */
|
||||
@@ -552,14 +606,44 @@ public class Pathfinder implements Runnable{
|
||||
/** whether this flow field is ready to be used */
|
||||
boolean initialized;
|
||||
|
||||
void setup(int length){
|
||||
public Flowfield(){
|
||||
this(1);
|
||||
}
|
||||
|
||||
public Flowfield(int resolution){
|
||||
this.resolution = resolution;
|
||||
this.width = Mathf.ceil((float)wwidth / resolution);
|
||||
this.height = Mathf.ceil((float)wheight / resolution);
|
||||
}
|
||||
|
||||
void setup(){
|
||||
int length = width * height;
|
||||
|
||||
this.weights = new int[length];
|
||||
this.searches = new int[length];
|
||||
this.searches = new short[length];
|
||||
this.completeWeights = new int[length];
|
||||
this.frontier.ensureCapacity((length) / 4);
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
public int getCost(int[] tiles, int pos){
|
||||
return cost.getCost(team.id, tiles[pos]);
|
||||
}
|
||||
|
||||
public boolean hasTargets(){
|
||||
return targets.size > 0;
|
||||
}
|
||||
|
||||
/** @return the next tile to travel to for this flowfield. Main thread only. */
|
||||
public @Nullable Tile getNextTile(Tile from, boolean diagonals){
|
||||
return pathfinder.getTargetTile(from, this, diagonals);
|
||||
}
|
||||
|
||||
/** @return the next tile to travel to for this flowfield. Main thread only. */
|
||||
public @Nullable Tile getNextTile(Tile from){
|
||||
return pathfinder.getTargetTile(from, this);
|
||||
}
|
||||
|
||||
public boolean hasCompleteWeights(){
|
||||
return hasComplete && completeWeights != null;
|
||||
}
|
||||
@@ -569,6 +653,11 @@ public class Pathfinder implements Runnable{
|
||||
getPositions(targets);
|
||||
}
|
||||
|
||||
/** @return whether this flow field should be refreshed after the current block update */
|
||||
public boolean needsRefresh(){
|
||||
return refreshRate == 0;
|
||||
}
|
||||
|
||||
protected boolean passable(int pos){
|
||||
int amount = cost.getCost(team.id, pathfinder.tiles[pos]);
|
||||
//edge case: naval reports costs of 6000+ for non-liquids, even though they are not technically passable
|
||||
|
||||
@@ -17,7 +17,6 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.defense.turrets.BaseTurret.*;
|
||||
import mindustry.world.blocks.defense.turrets.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
@@ -35,7 +34,7 @@ public class RtsAI{
|
||||
//in order of priority??
|
||||
static final BlockFlag[] flags = {BlockFlag.generator, BlockFlag.factory, BlockFlag.core, BlockFlag.battery, BlockFlag.drill};
|
||||
static final ObjectFloatMap<Building> weights = new ObjectFloatMap<>();
|
||||
static final boolean debug = OS.hasProp("mindustry.debug");
|
||||
static final boolean debug = OS.hasProp("mindustry.debug") && false;
|
||||
|
||||
final Interval timer = new Interval(10);
|
||||
final TeamData data;
|
||||
@@ -210,12 +209,12 @@ public class RtsAI{
|
||||
//defendTarget = aggressor;
|
||||
defendPos = new Vec2(aggressor.x, aggressor.y);
|
||||
defendTarget = aggressor;
|
||||
}else if(false){ //TODO currently ignored, no use defending against nothing
|
||||
//}else if(false){ //TODO currently ignored, no use defending against nothing
|
||||
//should it even go there if there's no aggressor found?
|
||||
Tile closest = defend.findClosestEdge(units.first(), Tile::solid);
|
||||
if(closest != null){
|
||||
defendPos = new Vec2(closest.worldx(), closest.worldy());
|
||||
}
|
||||
// Tile closest = defend.findClosestEdge(units.first(), Tile::solid);
|
||||
// if(closest != null){
|
||||
// defendPos = new Vec2(closest.worldx(), closest.worldy());
|
||||
// }
|
||||
}else{
|
||||
float mindst = Float.MAX_VALUE;
|
||||
Building build = null;
|
||||
|
||||
@@ -3,7 +3,6 @@ package mindustry.ai;
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.ctype.*;
|
||||
@@ -13,10 +12,6 @@ import mindustry.input.*;
|
||||
|
||||
/** Defines a pattern of behavior that an RTS-controlled unit should follow. Shows up in the command UI. */
|
||||
public class UnitCommand extends MappableContent{
|
||||
/** @deprecated now a content type, use the methods in Vars.content instead */
|
||||
@Deprecated
|
||||
public static final Seq<UnitCommand> all = new Seq<>();
|
||||
|
||||
public static UnitCommand moveCommand, repairCommand, rebuildCommand, assistCommand, mineCommand, boostCommand, enterPayloadCommand, loadUnitsCommand, loadBlocksCommand, unloadPayloadCommand, loopPayloadCommand;
|
||||
|
||||
/** Name of UI icon (from Icon class). */
|
||||
@@ -39,8 +34,6 @@ public class UnitCommand extends MappableContent{
|
||||
|
||||
this.icon = icon;
|
||||
this.controller = controller == null ? u -> null : controller;
|
||||
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public UnitCommand(String name, String icon, Binding keybind, Func<Unit, AIController> controller){
|
||||
|
||||
@@ -2,30 +2,23 @@ package mindustry.ai;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
|
||||
public class UnitStance extends MappableContent{
|
||||
/** @deprecated now a content type, use the methods in Vars.content instead */
|
||||
@Deprecated
|
||||
public static final Seq<UnitStance> all = new Seq<>();
|
||||
|
||||
public static UnitStance stop, shoot, holdFire, pursueTarget, patrol, ram;
|
||||
|
||||
/** Name of UI icon (from Icon class). */
|
||||
public final String icon;
|
||||
public String icon;
|
||||
/** Key to press for this stance. */
|
||||
public @Nullable Binding keybind = null;
|
||||
public @Nullable Binding keybind;
|
||||
|
||||
public UnitStance(String name, String icon, Binding keybind){
|
||||
super(name);
|
||||
this.icon = icon;
|
||||
this.keybind = keybind;
|
||||
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public String localized(){
|
||||
|
||||
@@ -82,9 +82,7 @@ public class WaveSpawner{
|
||||
|
||||
eachFlyerSpawn(group.spawn, (spawnX, spawnY) -> {
|
||||
for(int i = 0; i < spawnedf; i++){
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
|
||||
spawnEffect(unit);
|
||||
spawnUnit(group, spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
|
||||
}
|
||||
});
|
||||
}else{
|
||||
@@ -95,9 +93,7 @@ public class WaveSpawner{
|
||||
for(int i = 0; i < spawnedf; i++){
|
||||
Tmp.v1.rnd(spread);
|
||||
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
||||
spawnEffect(unit);
|
||||
spawnUnit(group, spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,6 +102,11 @@ public class WaveSpawner{
|
||||
Time.run(121f, () -> spawning = false);
|
||||
}
|
||||
|
||||
public void spawnUnit(SpawnGroup group, float x, float y){
|
||||
group.createUnit(group.team == null ? state.rules.waveTeam : group.team, x, y,
|
||||
Angles.angle(x, y, world.width()/2f * tilesize, world.height()/2f * tilesize), state.wave - 1, this::spawnEffect);
|
||||
}
|
||||
|
||||
public void doShockwave(float x, float y){
|
||||
Fx.spawnShockwave.at(x, y, state.rules.dropZoneRadius);
|
||||
Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true);
|
||||
@@ -217,15 +218,8 @@ public class WaveSpawner{
|
||||
|
||||
/** Applies the standard wave spawn effects to a unit - invincibility, unmoving. */
|
||||
public void spawnEffect(Unit unit){
|
||||
spawnEffect(unit, unit.angleTo(world.width()/2f * tilesize, world.height()/2f * tilesize));
|
||||
}
|
||||
|
||||
/** Applies the standard wave spawn effects to a unit - invincibility, unmoving. */
|
||||
public void spawnEffect(Unit unit, float rotation){
|
||||
unit.rotation = rotation;
|
||||
unit.apply(StatusEffects.unmoving, 30f);
|
||||
unit.apply(StatusEffects.invincible, 60f);
|
||||
unit.add();
|
||||
unit.unloaded();
|
||||
|
||||
Events.fire(new UnitSpawnEvent(unit));
|
||||
|
||||
@@ -118,9 +118,10 @@ public class BuilderAI extends AIController{
|
||||
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation)));
|
||||
|
||||
if(valid){
|
||||
float range = Math.min(unit.type.buildRange - 20f, 100f);
|
||||
//move toward the plan
|
||||
moveTo(req.tile(), unit.type.buildRange - 20f, 20f);
|
||||
moving = !unit.within(req.tile(), unit.type.buildRange - 10f);
|
||||
moveTo(req.tile(), range - 10f, 20f);
|
||||
moving = !unit.within(req.tile(), range);
|
||||
}else{
|
||||
//discard invalid plan
|
||||
unit.plans.removeFirst();
|
||||
|
||||
@@ -201,7 +201,7 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
targetPos.set(attackTarget);
|
||||
|
||||
if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.pathType() != Pathfinder.costLegs && stance != UnitStance.ram){
|
||||
if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.type.pathCostId != ControlPathfinder.costIdLegs && stance != UnitStance.ram){
|
||||
Tile best = build.findClosestEdge(unit, Tile::solid);
|
||||
if(best != null){
|
||||
targetPos.set(best);
|
||||
@@ -470,7 +470,7 @@ public class CommandAI extends AIController{
|
||||
@Override
|
||||
public boolean retarget(){
|
||||
//retarget faster when there is an explicit target
|
||||
return attackTarget != null ? timer.get(timerTarget, 10) : timer.get(timerTarget, 20);
|
||||
return timer.get(timerTarget, attackTarget != null ? 10f : 20f);
|
||||
}
|
||||
|
||||
public boolean hasCommand(){
|
||||
|
||||
@@ -28,7 +28,7 @@ public class DefenderAI extends AIController{
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
|
||||
//Sort by max health and closer target.
|
||||
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type && u.targetable(unit.team) && u.type.playerControllable,
|
||||
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type && u.targetable(unit.team) && u.playerControllable(),
|
||||
(u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 6400f);
|
||||
if(result != null) return result;
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ public class HugAI extends AIController{
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
if(core != null && unit.within(core, unit.range() / 1.1f + core.block.size * tilesize / 2f)){
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
@@ -17,7 +16,7 @@ public class MinerAI extends AIController{
|
||||
public void updateMovement(){
|
||||
Building core = unit.closestCore();
|
||||
|
||||
if(!(unit.canMine()) || core == null) return;
|
||||
if(!unit.canMine() || core == null) return;
|
||||
|
||||
if(!unit.validMine(unit.mineTile)){
|
||||
unit.mineTile(null);
|
||||
@@ -40,19 +39,17 @@ public class MinerAI extends AIController{
|
||||
mining = false;
|
||||
}else{
|
||||
if(timer.get(timerTarget3, 60) && targetItem != null){
|
||||
ore = indexer.findClosestOre(unit, targetItem);
|
||||
ore = null;
|
||||
if(unit.type.mineFloor) ore = indexer.findClosestOre(unit, targetItem);
|
||||
if(ore == null && unit.type.mineWalls) ore = indexer.findClosestWallOre(unit, targetItem);
|
||||
}
|
||||
|
||||
if(ore != null){
|
||||
moveTo(ore, unit.type.mineRange / 2f, 20f);
|
||||
|
||||
if(ore.block() == Blocks.air && unit.within(ore, unit.type.mineRange)){
|
||||
if(unit.within(ore, unit.type.mineRange) && unit.validMine(ore)){
|
||||
unit.mineTile = ore;
|
||||
}
|
||||
|
||||
if(ore.block() != Blocks.air){
|
||||
mining = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -11,10 +11,11 @@ import mindustry.gen.*;
|
||||
|
||||
public class PhysicsProcess implements AsyncProcess{
|
||||
public static final int
|
||||
layers = 3,
|
||||
layers = 4,
|
||||
layerGround = 0,
|
||||
layerLegs = 1,
|
||||
layerFlying = 2;
|
||||
layerFlying = 2,
|
||||
layerUnderwater = 3;
|
||||
|
||||
private PhysicsWorld physics;
|
||||
private Seq<PhysicRef> refs = new Seq<>(false);
|
||||
@@ -153,15 +154,15 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
|
||||
for(int i = 0; i < bodySize; i++){
|
||||
PhysicsBody body = bodyItems[i];
|
||||
if(body.layer < 0) continue;
|
||||
body.collided = false;
|
||||
trees[body.layer].insert(body);
|
||||
}
|
||||
|
||||
for(int i = 0; i < bodySize; i++){
|
||||
PhysicsBody body = bodyItems[i];
|
||||
|
||||
//for clients, the only body that collides is the local one; all other physics simulations are handled by the server.
|
||||
if(!body.local) continue;
|
||||
if(!body.local || body.layer < 0) continue;
|
||||
|
||||
body.hitbox(rect);
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
//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()) ||
|
||||
(state.rules.attackMode && state.rules.waveTeam.cores().isEmpty())){
|
||||
(state.rules.attackMode && !state.rules.waveTeam.isAlive())){
|
||||
|
||||
if(state.rules.sector.preset != null && state.rules.sector.preset.attackAfterWaves && !state.rules.attackMode){
|
||||
//activate attack mode to destroy cores after waves are done.
|
||||
@@ -309,11 +309,11 @@ public class Logic implements ApplicationListener{
|
||||
Events.fire(new GameOverEvent(state.rules.waveTeam));
|
||||
}else if(state.rules.attackMode){
|
||||
//count # of teams alive
|
||||
int countAlive = state.teams.getActive().count(t -> t.hasCore() && t.team != Team.derelict);
|
||||
int countAlive = state.teams.getActive().count(t -> t.isAlive() && t.team != Team.derelict);
|
||||
|
||||
if((countAlive <= 1 || (!state.rules.pvp && state.rules.defaultTeam.core() == null)) && !state.gameOver){
|
||||
//find team that won
|
||||
TeamData left = state.teams.getActive().find(t -> t.hasCore() && t.team != Team.derelict);
|
||||
TeamData left = state.teams.getActive().find(t -> t.isAlive() && t.team != Team.derelict);
|
||||
Events.fire(new GameOverEvent(left == null ? Team.derelict : left.team));
|
||||
state.gameOver = true;
|
||||
}
|
||||
@@ -413,6 +413,9 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
PerfCounter.frame.end();
|
||||
PerfCounter.frame.begin();
|
||||
|
||||
Events.fire(Trigger.update);
|
||||
universe.updateGlobal();
|
||||
|
||||
@@ -489,7 +492,9 @@ public class Logic implements ApplicationListener{
|
||||
state.envAttrs.add(state.rules.attributes);
|
||||
Groups.weather.each(w -> state.envAttrs.add(w.weather.attrs, w.opacity));
|
||||
|
||||
PerfCounter.entityUpdate.begin();
|
||||
Groups.update();
|
||||
PerfCounter.entityUpdate.end();
|
||||
|
||||
Events.fire(Trigger.afterGameUpdate);
|
||||
}
|
||||
|
||||
@@ -990,6 +990,7 @@ public class NetServer implements ApplicationListener{
|
||||
//write all entities now
|
||||
dataStream.writeInt(entity.id()); //write id
|
||||
dataStream.writeByte(entity.classId() & 0xFF); //write type ID
|
||||
entity.beforeWrite();
|
||||
entity.writeSync(Writes.get(dataStream)); //write entity
|
||||
|
||||
sent++;
|
||||
|
||||
53
core/src/mindustry/core/PerfCounter.java
Normal file
53
core/src/mindustry/core/PerfCounter.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package mindustry.core;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
|
||||
/** Simple per-frame time counter. */
|
||||
public enum PerfCounter{
|
||||
frame,
|
||||
update,
|
||||
entityUpdate,
|
||||
render;
|
||||
|
||||
public static final PerfCounter[] all = values();
|
||||
|
||||
static final int meanWindow = 30;
|
||||
static final int refreshTimeMillis = 500;
|
||||
|
||||
private long valueRefreshTime;
|
||||
private float refreshValue;
|
||||
|
||||
private long beginTime;
|
||||
private boolean began = false;
|
||||
private WindowedMean mean = new WindowedMean(meanWindow);
|
||||
|
||||
public void begin(){
|
||||
began = true;
|
||||
beginTime = Time.nanos();
|
||||
}
|
||||
|
||||
public void end(){
|
||||
if(!began) return;
|
||||
began = false;
|
||||
mean.add(Time.timeSinceNanos(beginTime));
|
||||
}
|
||||
|
||||
/** Value with a periodic refresh interval applied, to prevent jittery UI. */
|
||||
public float valueMs(){
|
||||
if(Time.timeSinceMillis(valueRefreshTime) > refreshTimeMillis){
|
||||
refreshValue = rawValueMs();
|
||||
valueRefreshTime = Time.millis();
|
||||
}
|
||||
return refreshValue;
|
||||
}
|
||||
|
||||
/** Raw value without a refresh interval. This will be unstable. */
|
||||
public float rawValueMs(){
|
||||
return mean.rawMean() / Time.nanosPerMilli;
|
||||
}
|
||||
|
||||
public long rawValueNs(){
|
||||
return (long)mean.rawMean();
|
||||
}
|
||||
}
|
||||
@@ -150,6 +150,7 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
PerfCounter.render.begin();
|
||||
Color.white.set(1f, 1f, 1f, 1f);
|
||||
|
||||
float baseTarget = targetscale;
|
||||
@@ -220,6 +221,8 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
camera.position.sub(camShakeOffset);
|
||||
}
|
||||
|
||||
PerfCounter.render.end();
|
||||
}
|
||||
|
||||
public void updateAllDarkness(){
|
||||
@@ -313,7 +316,6 @@ public class Renderer implements ApplicationListener{
|
||||
Draw.draw(Layer.block - 0.09f, () -> {
|
||||
blocks.floor.beginDraw();
|
||||
blocks.floor.drawLayer(CacheLayer.walls);
|
||||
blocks.floor.endDraw();
|
||||
});
|
||||
|
||||
Draw.drawRange(Layer.blockBuilding, () -> Draw.shader(Shaders.blockbuild, true), Draw::shader);
|
||||
|
||||
@@ -123,7 +123,7 @@ public class World{
|
||||
Tile tile = tiles.get(x, y);
|
||||
if(tile == null) return null;
|
||||
if(tile.build != null){
|
||||
return tile.build.tile();
|
||||
return tile.build.tile;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
@@ -458,10 +458,12 @@ public class World{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void checkMapArea(){
|
||||
public void checkMapArea(int x, int y, int w, int h){
|
||||
for(var build : Groups.build){
|
||||
//reset map-area-based disabled blocks.
|
||||
if(!build.enabled && build.block.autoResetEnabled){
|
||||
//if the map area contracts, disable the block
|
||||
build.checkAllowUpdate();
|
||||
//reset map-area-based disabled blocks that were in the previous map area
|
||||
if(!build.enabled && build.block.autoResetEnabled && Rect.contains(x, y, w, h, build.tile.x, build.tile.y)){
|
||||
build.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,8 @@ public class EditorTile extends Tile{
|
||||
if(block.hasBuilding()){
|
||||
build = entityprov.get().init(this, team, false, rotation);
|
||||
if(block.hasItems) build.items = new ItemModule();
|
||||
if(block.hasLiquids) build.liquids(new LiquidModule());
|
||||
if(block.hasPower) build.power(new PowerModule());
|
||||
if(block.hasLiquids) build.liquids = new LiquidModule();
|
||||
if(block.hasPower) build.power = new PowerModule();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
name(cont, name, remover, indexer);
|
||||
cont.table(t -> t.left().button(
|
||||
b -> b.image(Tex.whiteui).size(iconSmall).update(i -> i.setColor(get.get().color)),
|
||||
Styles.squarei,
|
||||
() -> showTeamSelect(set)
|
||||
).fill().pad(4f)).growX().fillY();
|
||||
});
|
||||
@@ -529,6 +530,8 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
|
||||
public void rebuildObjectives(Seq<MapObjective> objectives){
|
||||
canvas.clearObjectives();
|
||||
objectives.each(MapObjective::validate);
|
||||
|
||||
if(
|
||||
objectives.any() && (
|
||||
// If the objectives were previously programmatically made...
|
||||
@@ -592,9 +595,23 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
public static void showTeamSelect(Cons<Team> cons){
|
||||
showTeamSelect(false, cons);
|
||||
}
|
||||
|
||||
public static void showTeamSelect(boolean allowNull, Cons<Team> cons){
|
||||
BaseDialog dialog = new BaseDialog("");
|
||||
|
||||
dialog.cont.defaults().size(40f).pad(4f);
|
||||
|
||||
if(allowNull){
|
||||
dialog.cont.button(Icon.cancel, Styles.emptyi, () -> {
|
||||
cons.get(null);
|
||||
dialog.hide();
|
||||
}).tooltip("@none");
|
||||
}
|
||||
|
||||
for(var team : Team.baseTeams){
|
||||
dialog.cont.image(Tex.whiteui).size(iconMed).color(team.color).pad(4)
|
||||
dialog.cont.image(Tex.whiteui).color(team.color)
|
||||
.with(i -> i.addListener(new HandCursorListener()))
|
||||
.tooltip(team.localized()).get().clicked(() -> {
|
||||
cons.get(team);
|
||||
|
||||
@@ -288,6 +288,13 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
buildGroups();
|
||||
}).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f).row();
|
||||
|
||||
t.table(a -> {
|
||||
a.add("@waves.team").padRight(8);
|
||||
|
||||
a.button(b -> b.image(Tex.whiteui).size(iconSmall).update(i -> i.setColor(group.team == null ? Color.clear : group.team.color)), Styles.squarei,
|
||||
() -> MapObjectivesDialog.showTeamSelect(true, team -> group.team = team)).size(38f);
|
||||
}).padTop(0).row();
|
||||
|
||||
t.table(a -> {
|
||||
a.add("@waves.spawn").padRight(8);
|
||||
|
||||
|
||||
@@ -70,23 +70,27 @@ public class Damage{
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a dynamic explosion based on specified parameters. */
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage){
|
||||
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, Fx.dynamicExplosion);
|
||||
}
|
||||
|
||||
/** Creates a dynamic explosion based on specified parameters. */
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, Effect explosionFx){
|
||||
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, explosionFx);
|
||||
}
|
||||
|
||||
/** Creates a dynamic explosion based on specified parameters. */
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, Effect explosionFx, float baseShake){
|
||||
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, explosionFx, baseShake);
|
||||
}
|
||||
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam){
|
||||
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, fire, ignoreTeam, Fx.dynamicExplosion);
|
||||
}
|
||||
|
||||
/** Creates a dynamic explosion based on specified parameters. */
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosionFx){
|
||||
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, fire, ignoreTeam, explosionFx, 3f);
|
||||
}
|
||||
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosionFx, float baseShake){
|
||||
if(damage){
|
||||
for(int i = 0; i < Mathf.clamp(power / 700, 0, 8); i++){
|
||||
int length = 5 + Mathf.clamp((int)(Mathf.pow(power, 0.98f) / 500), 1, 18);
|
||||
@@ -123,7 +127,7 @@ public class Damage{
|
||||
Fx.bigShockwave.at(x, y);
|
||||
}
|
||||
|
||||
float shake = Math.min(explosiveness / 4f + 3f, 9f);
|
||||
float shake = Math.min(explosiveness / 4f + baseShake, 9f);
|
||||
Effect.shake(shake, shake, x, y);
|
||||
explosionFx.at(x, y, radius / 8f);
|
||||
}
|
||||
@@ -549,7 +553,12 @@ public class Damage{
|
||||
//this needs to be compensated
|
||||
if(in != null && in.team != team && in.block.size > 1 && in.health > damage){
|
||||
//deal the damage of an entire side, to be equivalent with maximum 'standard' damage
|
||||
in.damage(team, damage * Math.min((in.block.size), baseRadius * 0.4f));
|
||||
float d = damage * Math.min((in.block.size), baseRadius * 0.4f);
|
||||
if(source != null){
|
||||
in.damage(source, team, d);
|
||||
}else{
|
||||
in.damage(team, d);
|
||||
}
|
||||
//no need to continue with the explosion
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ public class EntityCollisions{
|
||||
}
|
||||
}
|
||||
|
||||
private boolean collide(float x1, float y1, float w1, float h1, float vx1, float vy1,
|
||||
public static boolean collide(float x1, float y1, float w1, float h1, float vx1, float vy1,
|
||||
float x2, float y2, float w2, float h2, float vx2, float vy2, Vec2 out){
|
||||
float px = vx1, py = vy1;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
|
||||
public class Lightning{
|
||||
private static final Rand random = new Rand();
|
||||
private static final Rect rect = new Rect();
|
||||
private static final Seq<Unitc> entities = new Seq<>();
|
||||
private static final Seq<Unit> entities = new Seq<>();
|
||||
private static final IntSet hit = new IntSet();
|
||||
private static final int maxChain = 8;
|
||||
private static final float hitRange = 30f;
|
||||
@@ -74,7 +74,7 @@ public class Lightning{
|
||||
});
|
||||
}
|
||||
|
||||
Unitc furthest = Geometry.findFurthest(x, y, entities);
|
||||
Unit furthest = Geometry.findFurthest(x, y, entities);
|
||||
|
||||
if(furthest != null){
|
||||
hit.add(furthest.id());
|
||||
|
||||
@@ -97,6 +97,12 @@ public class Puddles{
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasLiquid(Tile tile, Liquid liquid){
|
||||
if(tile == null) return false;
|
||||
var p = get(tile);
|
||||
return p != null && p.liquid == liquid && p.amount >= 0.5f;
|
||||
}
|
||||
|
||||
public static void remove(Tile tile){
|
||||
if(tile == null) return;
|
||||
|
||||
@@ -126,7 +132,7 @@ public class Puddles{
|
||||
if(Mathf.chance(0.8f * amount)){
|
||||
Fx.steam.at(x, y);
|
||||
}
|
||||
return -0.4f * amount;
|
||||
return -0.7f * amount;
|
||||
}
|
||||
return dest.react(liquid, amount, tile, x, y);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ package mindustry.entities;
|
||||
public class TargetPriority{
|
||||
public static final float
|
||||
//nobody cares about walls
|
||||
wall = -2f,
|
||||
wall = -3f,
|
||||
//anything that has underBullets gets this priority (it's probably still more important than a wall)
|
||||
under = -2f,
|
||||
//transport infrastructure isn't as important as factories
|
||||
transport = -1f,
|
||||
//most blocks
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.entities;
|
||||
|
||||
import arc.math.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.Units.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
@@ -11,4 +12,9 @@ public class UnitSorts{
|
||||
farthest = (u, x, y) -> -u.dst2(x, y),
|
||||
strongest = (u, x, y) -> -u.maxHealth + Mathf.dst2(u.x, u.y, x, y) / 6400f,
|
||||
weakest = (u, x, y) -> u.maxHealth + Mathf.dst2(u.x, u.y, x, y) / 6400f;
|
||||
|
||||
public static BuildingPriorityf
|
||||
|
||||
buildingDefault = b -> b.block.priority,
|
||||
buildingWater = b -> b.block.priority + (b.liquids != null && b.liquids.get(Liquids.water) > 5f ? 10f : 0f);
|
||||
}
|
||||
@@ -95,7 +95,7 @@ public class Units{
|
||||
|
||||
public static int getCap(Team team){
|
||||
//wave team has no cap
|
||||
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam) || state.rules.disableUnitCap){
|
||||
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam) || state.rules.disableUnitCap || team.ignoreUnitCap){
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + team.data().unitCap : state.rules.unitCap);
|
||||
@@ -197,18 +197,8 @@ public class Units{
|
||||
|
||||
/** Returns the nearest enemy tile in a range. */
|
||||
public static Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
|
||||
return findEnemyTile(team, x, y, range, false, pred);
|
||||
}
|
||||
|
||||
/** Returns the nearest enemy tile in a range. */
|
||||
public static Building findEnemyTile(Team team, float x, float y, float range, boolean checkUnder, Boolf<Building> pred){
|
||||
if(team == Team.derelict) return null;
|
||||
|
||||
if(checkUnder){
|
||||
Building target = indexer.findEnemyTile(team, x, y, range, build -> !build.block.underBullets && pred.get(build));
|
||||
if(target != null) return target;
|
||||
}
|
||||
|
||||
return indexer.findEnemyTile(team, x, y, range, pred);
|
||||
}
|
||||
|
||||
@@ -258,7 +248,7 @@ public class Units{
|
||||
if(unit != null){
|
||||
return unit;
|
||||
}else{
|
||||
return findEnemyTile(team, x, y, range, true, tilePred);
|
||||
return findEnemyTile(team, x, y, range, tilePred);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +260,7 @@ public class Units{
|
||||
if(unit != null){
|
||||
return unit;
|
||||
}else{
|
||||
return findEnemyTile(team, x, y, range, true, tilePred);
|
||||
return findEnemyTile(team, x, y, range, tilePred);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +399,7 @@ public class Units{
|
||||
|
||||
/** @return whether any units exist in this rectangle */
|
||||
public static boolean any(float x, float y, float width, float height, Boolf<Unit> filter){
|
||||
return count(x, y, width, height, filter) > 0;
|
||||
return Groups.unit.intersect(x, y, width, height, filter);
|
||||
}
|
||||
|
||||
/** Iterates over all units in a rectangle. */
|
||||
@@ -494,4 +484,8 @@ public class Units{
|
||||
public interface Sortf{
|
||||
float cost(Unit unit, float x, float y);
|
||||
}
|
||||
|
||||
public interface BuildingPriorityf{
|
||||
float priority(Building build);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public abstract class Ability implements Cloneable{
|
||||
protected static final float descriptionWidth = 350f;
|
||||
@@ -13,11 +14,26 @@ public abstract class Ability implements Cloneable{
|
||||
public float data;
|
||||
|
||||
public void update(Unit unit){}
|
||||
|
||||
public void draw(Unit unit){}
|
||||
|
||||
public void death(Unit unit){}
|
||||
|
||||
public void created(Unit unit){}
|
||||
|
||||
public void init(UnitType type){}
|
||||
|
||||
public void displayBars(Unit unit, Table bars){}
|
||||
|
||||
public void display(Table t){
|
||||
t.table(Styles.grayPanel, a -> {
|
||||
a.add("[accent]" + localized()).padBottom(4).center().top().expandX();
|
||||
a.row();
|
||||
a.left().top().defaults().left();
|
||||
addStats(a);
|
||||
}).pad(5).margin(10).growX().top().uniformX();
|
||||
}
|
||||
|
||||
public void addStats(Table t){
|
||||
if(Core.bundle.has(getBundle() + ".description")){
|
||||
t.add(Core.bundle.get(getBundle() + ".description")).wrap().width(descriptionWidth);
|
||||
|
||||
@@ -13,8 +13,8 @@ import static mindustry.Vars.*;
|
||||
|
||||
public class LiquidRegenAbility extends Ability{
|
||||
public Liquid liquid;
|
||||
public float slurpSpeed = 9f;
|
||||
public float regenPerSlurp = 2.9f;
|
||||
public float slurpSpeed = 5f;
|
||||
public float regenPerSlurp = 6f;
|
||||
public float slurpEffectChance = 0.4f;
|
||||
public Effect slurpEffect = Fx.heal;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class LiquidRegenAbility extends Ability{
|
||||
//TODO timer?
|
||||
|
||||
//TODO effects?
|
||||
if(unit.damaged()){
|
||||
if(unit.damaged() && !unit.isFlying()){
|
||||
boolean healed = false;
|
||||
int tx = unit.tileX(), ty = unit.tileY();
|
||||
int rad = Math.max((int)(unit.hitSize / tilesize * 0.6f), 1);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
@@ -9,8 +10,9 @@ import mindustry.gen.*;
|
||||
|
||||
public class MoveEffectAbility extends Ability{
|
||||
public float minVelocity = 0.08f;
|
||||
public float interval = 3f;
|
||||
public float x, y, rotation;
|
||||
public float interval = 3f, chance = 0f;
|
||||
public int amount = 1;
|
||||
public float x, y, rotation, rangeX, rangeY, rangeLengthMin, rangeLengthMax;
|
||||
public boolean rotateEffect = false;
|
||||
public float effectParam = 3f;
|
||||
public boolean teamColor = false;
|
||||
@@ -38,10 +40,17 @@ public class MoveEffectAbility extends Ability{
|
||||
if(Vars.headless) return;
|
||||
|
||||
counter += Time.delta;
|
||||
if(unit.vel.len2() >= minVelocity * minVelocity && (counter >= interval) && !unit.inFogTo(Vars.player.team())){
|
||||
Tmp.v1.trns(unit.rotation - 90f, x, y);
|
||||
if(unit.vel.len2() >= minVelocity * minVelocity && (counter >= interval || (chance > 0 && Mathf.chanceDelta(chance))) && !unit.inFogTo(Vars.player.team())){
|
||||
if(rangeLengthMax > 0){
|
||||
Tmp.v1.trns(unit.rotation - 90f, x, y).add(Tmp.v2.rnd(Mathf.random(rangeLengthMin, rangeLengthMax)));
|
||||
}else{
|
||||
Tmp.v1.trns(unit.rotation - 90f, x + Mathf.range(rangeX), y + Mathf.range(rangeY));
|
||||
}
|
||||
|
||||
counter %= interval;
|
||||
effect.at(Tmp.v1.x + unit.x, Tmp.v1.y + unit.y, (rotateEffect ? unit.rotation : effectParam) + rotation, teamColor ? unit.team.color : color, parentizeEffects ? unit : null);
|
||||
for(int i = 0; i < amount; i++){
|
||||
effect.at(Tmp.v1.x + unit.x, Tmp.v1.y + unit.y, (rotateEffect ? unit.rotation : effectParam) + rotation, teamColor ? unit.team.color : color, parentizeEffects ? unit : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,16 +30,24 @@ public class BulletType extends Content implements Cloneable{
|
||||
|
||||
/** Lifetime in ticks. */
|
||||
public float lifetime = 40f;
|
||||
/** Min/max multipliers for lifetime applied to this bullet when spawned. */
|
||||
public float lifeScaleRandMin = 1f, lifeScaleRandMax = 1f;
|
||||
/** Speed in units/tick. */
|
||||
public float speed = 1f;
|
||||
/** Min/max multipliers for velocity applied to this bullet when spawned. */
|
||||
public float velocityScaleRandMin = 1f, velocityScaleRandMax = 1f;
|
||||
/** Direct damage dealt on hit. */
|
||||
public float damage = 1f;
|
||||
/** Hitbox size. */
|
||||
public float hitSize = 4;
|
||||
/** Clipping hitbox. */
|
||||
public float drawSize = 40f;
|
||||
/** Angle offset applied to bullet when spawned each time. */
|
||||
public float angleOffset = 0f, randomAngleOffset = 0f;
|
||||
/** Drag as fraction of velocity. */
|
||||
public float drag = 0f;
|
||||
/** Acceleration per frame. */
|
||||
public float accel = 0f;
|
||||
/** Whether to pierce units. */
|
||||
public boolean pierce;
|
||||
/** Whether to pierce buildings. */
|
||||
@@ -155,6 +163,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float healAmount = 0f;
|
||||
/** Whether to make fire on impact */
|
||||
public boolean makeFire = false;
|
||||
/** Whether this bullet will always hit blocks under it. */
|
||||
public boolean hitUnder = false;
|
||||
/** Whether to create hit effects on despawn. Forced to true if this bullet has any special effects like splash damage. */
|
||||
public boolean despawnHit = false;
|
||||
/** If true, this bullet will create bullets when it hits anything, not just when it despawns. */
|
||||
@@ -163,6 +173,10 @@ public class BulletType extends Content implements Cloneable{
|
||||
public boolean fragOnAbsorb = true;
|
||||
/** If true, unit armor is ignored in damage calculations. */
|
||||
public boolean pierceArmor = false;
|
||||
/** If true, the bullet will "stick" to enemies and get deactivated on collision. */
|
||||
public boolean sticky = false;
|
||||
/** Extra time added to bullet when it sticks to something. */
|
||||
public float stickyExtraLifetime = 0f;
|
||||
/** Whether status and despawnHit should automatically be set. */
|
||||
public boolean setDefaults = true;
|
||||
/** Amount of shaking produced when this bullet hits something or despawns. */
|
||||
@@ -195,7 +209,7 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float bulletInterval = 20f;
|
||||
/** Number of bullet spawned per interval. */
|
||||
public int intervalBullets = 1;
|
||||
/** Random spread of interval bullets. */
|
||||
/** Random angle added to interval bullets. */
|
||||
public float intervalRandomSpread = 360f;
|
||||
/** Angle spread between individual interval bullets. */
|
||||
public float intervalSpread = 0f;
|
||||
@@ -204,6 +218,9 @@ public class BulletType extends Content implements Cloneable{
|
||||
/** Use a negative value to disable interval bullet delay. */
|
||||
public float intervalDelay = -1f;
|
||||
|
||||
/** If true, this bullet is rendered underwater. Highly experimental! */
|
||||
public boolean underwater = false;
|
||||
|
||||
/** Color used for hit/despawn effects. */
|
||||
public Color hitColor = Color.white;
|
||||
/** Color used for block heal effects. */
|
||||
@@ -212,6 +229,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
public Effect healEffect = Fx.healBlockFull;
|
||||
/** Bullets spawned when this bullet is created. Rarely necessary, used for visuals. */
|
||||
public Seq<BulletType> spawnBullets = new Seq<>();
|
||||
/** Random angle spread of spawn bullets. */
|
||||
public float spawnBulletRandomSpread = 0f;
|
||||
/** Unit spawned _instead of_ this bullet. Useful for missiles. */
|
||||
public @Nullable UnitType spawnUnit;
|
||||
/** Unit spawned when this bullet hits something or despawns due to it hitting the end of its lifetime. */
|
||||
@@ -233,8 +252,12 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float trailChance = -0.0001f;
|
||||
/** Uniform interval in which trail effect is spawned. */
|
||||
public float trailInterval = 0f;
|
||||
/** Min velocity required for trail effect to spawn. */
|
||||
public float trailMinVelocity = 0f;
|
||||
/** Trail effect that is spawned. */
|
||||
public Effect trailEffect = Fx.missileTrail;
|
||||
/** Random offset of trail effect. */
|
||||
public float trailSpread = 0f;
|
||||
/** Rotation/size parameter that is passed to trail. Usually, this controls size. */
|
||||
public float trailParam = 2f;
|
||||
/** Whether the parameter passed to the trail is the bullet rotation, instead of a flat value. */
|
||||
@@ -247,6 +270,14 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float trailWidth = 2f;
|
||||
/** If trailSinMag > 0, these values are applied as a sine curve to trail width. */
|
||||
public float trailSinMag = 0f, trailSinScl = 3f;
|
||||
/** If true, the bullet will attempt to circle around its shooting entity. */
|
||||
public boolean circleShooter = false;
|
||||
/** Radius that the bullet attempts to circle at. */
|
||||
public float circleShooterRadius = 13f;
|
||||
/** Smooth extra radius value for circling. */
|
||||
public float circleShooterRadiusSmooth = 10f;
|
||||
/** Multiplier of speed that is used to adjust velocity when circling. */
|
||||
public float circleShooterRotateSpeed = 0.3f;
|
||||
|
||||
/** Use a negative value to disable splash damage. */
|
||||
public float splashDamageRadius = -1f;
|
||||
@@ -299,6 +330,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float weaveMag = 0f;
|
||||
/** If true, the bullet weave will randomly switch directions on spawn. */
|
||||
public boolean weaveRandom = true;
|
||||
/** Rotation speed of the bullet velocity as it travels. */
|
||||
public float rotateSpeed = 0f;
|
||||
|
||||
/** Number of individual puddles created. */
|
||||
public int puddles;
|
||||
@@ -624,7 +657,7 @@ public class BulletType extends Content implements Cloneable{
|
||||
|
||||
if(spawnBullets.size > 0){
|
||||
for(var bullet : spawnBullets){
|
||||
bullet.create(b, b.x, b.y, b.rotation());
|
||||
bullet.create(b, b.x, b.y, b.rotation() + Mathf.range(spawnBulletRandomSpread));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,18 +711,40 @@ public class BulletType extends Content implements Cloneable{
|
||||
if(weaveMag != 0){
|
||||
b.vel.rotateRadExact((float)Math.sin((b.time + Math.PI * weaveScale/2f) / weaveScale) * weaveMag * (weaveRandom ? (Mathf.randomSeed(b.id, 0, 1) == 1 ? -1 : 1) : 1f) * Time.delta * Mathf.degRad);
|
||||
}
|
||||
|
||||
if(rotateSpeed != 0){
|
||||
b.vel.rotate(rotateSpeed * Time.delta);
|
||||
}
|
||||
|
||||
if(circleShooter && b.owner instanceof Healthc h && h.isValid()){
|
||||
Tmp.v1.set(h).sub(b);
|
||||
Tmp.v1.rotate(90f * Mathf.lerp(0f, 1f, 1f - Mathf.clamp((Tmp.v1.len() - circleShooterRadius) / circleShooterRadiusSmooth)));
|
||||
b.vel.add(Tmp.v1.limit(speed * circleShooterRotateSpeed * Time.delta)).limit(speed);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTrailEffects(Bullet b){
|
||||
if(trailChance > 0){
|
||||
boolean canSpawn = trailMinVelocity <= 0f || b.vel.len2() >= trailMinVelocity * trailMinVelocity;
|
||||
|
||||
if(trailChance > 0 && canSpawn){
|
||||
if(Mathf.chanceDelta(trailChance)){
|
||||
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
|
||||
if(trailSpread > 0){
|
||||
Tmp.v1.rnd(Mathf.random(trailSpread));
|
||||
}else{
|
||||
Tmp.v1.setZero();
|
||||
}
|
||||
trailEffect.at(b.x + Tmp.v1.x, b.y + Tmp.v1.y, trailRotation ? b.rotation() : trailParam, trailColor);
|
||||
}
|
||||
}
|
||||
|
||||
if(trailInterval > 0f){
|
||||
if(trailInterval > 0f && canSpawn){
|
||||
if(b.timer(0, trailInterval)){
|
||||
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
|
||||
if(trailSpread > 0){
|
||||
Tmp.v1.rnd(Mathf.random(trailSpread));
|
||||
}else{
|
||||
Tmp.v1.setZero();
|
||||
}
|
||||
trailEffect.at(b.x + Tmp.v1.x, b.y + Tmp.v1.y, trailRotation ? b.rotation() : trailParam, trailColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -800,6 +855,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
@Nullable Entityc owner, @Nullable Entityc shooter, Team team, float x, float y, float angle, float damage, float velocityScl,
|
||||
float lifetimeScl, Object data, @Nullable Mover mover, float aimX, float aimY, @Nullable Teamc target
|
||||
){
|
||||
angle += angleOffset + Mathf.range(randomAngleOffset);
|
||||
|
||||
if(!Mathf.chance(createChance)) return null;
|
||||
if(ignoreSpawnAngle) angle = 0;
|
||||
if(spawnUnit != null){
|
||||
@@ -846,13 +903,13 @@ public class BulletType extends Content implements Cloneable{
|
||||
bullet.aimX = aimX;
|
||||
bullet.aimY = aimY;
|
||||
|
||||
bullet.initVel(angle, speed * velocityScl);
|
||||
bullet.initVel(angle, speed * velocityScl * (velocityScaleRandMin != 1f || velocityScaleRandMax != 1f ? Mathf.random(velocityScaleRandMin, velocityScaleRandMax) : 1f));
|
||||
if(backMove){
|
||||
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
|
||||
}else{
|
||||
bullet.set(x, y);
|
||||
}
|
||||
bullet.lifetime = lifetime * lifetimeScl;
|
||||
bullet.lifetime = lifetime * lifetimeScl * (lifeScaleRandMin != 1f || lifeScaleRandMax != 1f ? Mathf.random(lifeScaleRandMin, lifeScaleRandMax) : 1f);
|
||||
bullet.data = data;
|
||||
bullet.drag = drag;
|
||||
bullet.hitSize = hitSize;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class InterceptorBulletType extends BasicBulletType{
|
||||
|
||||
public InterceptorBulletType(float speed, float damage){
|
||||
super(speed, damage);
|
||||
}
|
||||
|
||||
public InterceptorBulletType(){
|
||||
}
|
||||
|
||||
public InterceptorBulletType(float speed, float damage, String bulletSprite){
|
||||
super(speed, damage, bulletSprite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Bullet b){
|
||||
super.update(b);
|
||||
|
||||
if(b.data instanceof Bullet other){
|
||||
if(other.isAdded()){
|
||||
|
||||
//check for an overlap between the two bullet trajectories; it is the responsibility of the creator to make sure the bullet is a valid target
|
||||
if(EntityCollisions.collide(
|
||||
b.x, b.y,
|
||||
b.hitSize, b.hitSize,
|
||||
b.deltaX, b.deltaY,
|
||||
other.x, other.y,
|
||||
other.hitSize, other.hitSize,
|
||||
other.deltaX, other.deltaY, Tmp.v1)){
|
||||
|
||||
b.set(Tmp.v1);
|
||||
|
||||
hit(b, b.x, b.y);
|
||||
b.remove();
|
||||
|
||||
if(other.damage > damage){
|
||||
other.damage -= b.damage;
|
||||
}else{
|
||||
other.remove();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
b.data = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
core/src/mindustry/entities/bullet/MultiBulletType.java
Normal file
61
core/src/mindustry/entities/bullet/MultiBulletType.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
/** A fake bullet type that spawns multiple sub-bullets when "fired". */
|
||||
public class MultiBulletType extends BulletType{
|
||||
public BulletType[] bullets = {};
|
||||
/** Amount of times the bullet array is repeated. */
|
||||
public int repeat = 1;
|
||||
|
||||
public MultiBulletType(BulletType... bullets){
|
||||
this.bullets = bullets;
|
||||
}
|
||||
|
||||
public MultiBulletType(int repeat, BulletType... bullets){
|
||||
this.repeat = repeat;
|
||||
this.bullets = bullets;
|
||||
}
|
||||
|
||||
public MultiBulletType(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public float estimateDPS(){
|
||||
float sum = 0f;
|
||||
for(var b : bullets){
|
||||
sum += b.estimateDPS();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float calculateRange(){
|
||||
float max = 0f;
|
||||
for(var b : bullets){
|
||||
max = Math.max(max, b.calculateRange());
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Bullet create(
|
||||
@Nullable Entityc owner, @Nullable Entityc shooter, Team team, float x, float y, float angle, float damage, float velocityScl,
|
||||
float lifetimeScl, Object data, @Nullable Mover mover, float aimX, float aimY, @Nullable Teamc target
|
||||
){
|
||||
angle += angleOffset;
|
||||
|
||||
Bullet last = null;
|
||||
|
||||
for(int i = 0; i < repeat; i++){
|
||||
for(var bullet : bullets){
|
||||
last = bullet.create(owner, shooter, team, x, y, angle, damage, velocityScl, lifetimeScl, data, mover, aimX, aimY, target);
|
||||
}
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package mindustry.entities.bullet;
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
@@ -11,7 +12,7 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class SapBulletType extends BulletType{
|
||||
public float length = 100f;
|
||||
public float length = 100f, lengthRand = 0f;
|
||||
public float sapStrength = 0.5f;
|
||||
public Color color = Color.white.cpy();
|
||||
public float width = 0.4f;
|
||||
@@ -72,7 +73,9 @@ public class SapBulletType extends BulletType{
|
||||
public void init(Bullet b){
|
||||
super.init(b);
|
||||
|
||||
Healthc target = Damage.linecast(b, b.x, b.y, b.rotation(), length);
|
||||
float len = Mathf.random(length, length + lengthRand);
|
||||
|
||||
Healthc target = Damage.linecast(b, b.x, b.y, b.rotation(), len);
|
||||
b.data = target;
|
||||
|
||||
if(target != null){
|
||||
@@ -92,7 +95,7 @@ public class SapBulletType extends BulletType{
|
||||
hit(b, tile.x, tile.y);
|
||||
}
|
||||
}else{
|
||||
b.data = new Vec2().trns(b.rotation(), length).add(b.x, b.y);
|
||||
b.data = new Vec2().trns(b.rotation(), len).add(b.x, b.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,11 @@ abstract class BlockUnitComp implements Unitc{
|
||||
return tile != null && tile.isValid();
|
||||
}
|
||||
|
||||
@Replace
|
||||
public boolean isAdded(){
|
||||
return tile != null && tile.isValid();
|
||||
}
|
||||
|
||||
@Replace
|
||||
public void team(Team team){
|
||||
if(tile != null && this.team != team){
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
|
||||
static final float warpDst = 30f;
|
||||
|
||||
@Import UnitType type;
|
||||
@Import float x, y;
|
||||
@Import Team team;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(!type.bounded) return;
|
||||
|
||||
float bot = 0f, left = 0f, top = world.unitHeight(), right = world.unitWidth();
|
||||
|
||||
//TODO hidden map rules only apply to player teams? should they?
|
||||
if(state.rules.limitMapArea && !team.isAI()){
|
||||
bot = state.rules.limitY * tilesize;
|
||||
left = state.rules.limitX * tilesize;
|
||||
top = state.rules.limitHeight * tilesize + bot;
|
||||
right = state.rules.limitWidth * tilesize + left;
|
||||
}
|
||||
|
||||
if(!net.client() || isLocal()){
|
||||
|
||||
float dx = 0f, dy = 0f;
|
||||
|
||||
//repel unit out of bounds
|
||||
if(x < left) dx += (-(x - left)/warpDst);
|
||||
if(y < bot) dy += (-(y - bot)/warpDst);
|
||||
if(x > right) dx -= (x - right)/warpDst;
|
||||
if(y > top) dy -= (y - top)/warpDst;
|
||||
|
||||
velAddNet(dx * Time.delta, dy * Time.delta);
|
||||
}
|
||||
|
||||
//clamp position if not flying
|
||||
if(isGrounded()){
|
||||
x = Mathf.clamp(x, left, right - tilesize);
|
||||
y = Mathf.clamp(y, bot, top - tilesize);
|
||||
}
|
||||
|
||||
//kill when out of bounds
|
||||
if(x < -finalWorldBounds + left || y < -finalWorldBounds + bot || x >= right + finalWorldBounds || y >= top + finalWorldBounds){
|
||||
kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
||||
}
|
||||
|
||||
if(!(tile.build instanceof ConstructBuild cb)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true, true)){
|
||||
if(Build.checkNoUnitOverlap(current.block, current.x, current.y)){
|
||||
boolean hasAll = infinite || current.isRotation(team) ||
|
||||
//derelict repair
|
||||
|
||||
@@ -16,7 +16,6 @@ import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
@@ -47,7 +46,7 @@ import java.util.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
|
||||
@Component(base = true)
|
||||
@Component(base = true, genInterface = false)
|
||||
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Sized, Senseable, Controllable, Settable{
|
||||
//region vars and initialization
|
||||
static final float timeToSleep = 60f * 1, recentDamageTime = 60f * 5f;
|
||||
@@ -63,7 +62,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
transient Tile tile;
|
||||
transient Block block;
|
||||
transient Seq<Building> proximity = new Seq<>(6);
|
||||
transient Seq<Building> proximity = new Seq<>(true, 6, Building.class);
|
||||
transient int cdump;
|
||||
transient int rotation;
|
||||
transient float payloadRotation;
|
||||
@@ -99,8 +98,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
private transient float timeScale = 1f, timeScaleDuration;
|
||||
private transient float dumpAccum;
|
||||
|
||||
private transient @Nullable SoundLoop sound;
|
||||
|
||||
private transient boolean sleeping;
|
||||
private transient float sleepTime;
|
||||
private transient boolean initialized;
|
||||
@@ -126,6 +123,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
add();
|
||||
}
|
||||
|
||||
checkAllowUpdate();
|
||||
created();
|
||||
|
||||
return self();
|
||||
@@ -136,10 +134,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
this.block = block;
|
||||
this.team = team;
|
||||
|
||||
if(block.loopSound != Sounds.none){
|
||||
sound = new SoundLoop(block.loopSound, block.loopSoundVolume);
|
||||
}
|
||||
|
||||
health = block.health;
|
||||
maxHealth(block.health);
|
||||
timer(new Interval(block.timers));
|
||||
@@ -342,13 +336,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public @Nullable Tile findClosestEdge(Position to, Boolf<Tile> solid){
|
||||
if(to == null) return null;
|
||||
Tile best = null;
|
||||
float mindst = 0f;
|
||||
for(var point : Edges.getEdges(block.size)){
|
||||
Tile other = Vars.world.tile(tile.x + point.x, tile.y + point.y);
|
||||
if(other != null && !solid.get(other) && (best == null || to.dst2(other) < mindst)){
|
||||
best = other;
|
||||
mindst = other.dst2(other);
|
||||
mindst = other.dst2(to);
|
||||
}
|
||||
}
|
||||
return best;
|
||||
@@ -473,6 +468,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return lastDamageTime + recentDamageTime >= Time.time;
|
||||
}
|
||||
|
||||
public void eachEdge(Cons<Tile> cons){
|
||||
for(var edge : block.getEdges()){
|
||||
Tile other = world.tile(tile.x + edge.x, tile.y + edge.y);
|
||||
if(other != null){
|
||||
cons.get(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Building nearby(int dx, int dy){
|
||||
return world.build(tile.x + dx, tile.y + dy);
|
||||
}
|
||||
@@ -809,6 +813,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canBeReplaced(Block other){
|
||||
return other.canReplace(block);
|
||||
}
|
||||
|
||||
public void handleItem(Building source, Item item){
|
||||
items.add(item, 1);
|
||||
}
|
||||
@@ -1066,7 +1074,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void incrementDump(int prox){
|
||||
cdump = ((cdump + 1) % prox);
|
||||
//this is possible if transferring an item changed a block
|
||||
if(prox != 0){
|
||||
cdump = ((cdump + 1) % prox);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used for dumping items. */
|
||||
@@ -1092,6 +1103,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
/** Called after this building is created in the world. May be called multiple times, or when adjacent buildings change. */
|
||||
//TODO ??? this is just onProximityUpdate ?
|
||||
public void onProximityAdded(){
|
||||
if(power != null){
|
||||
updatePowerGraph();
|
||||
@@ -1156,16 +1168,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return getProgressIncrease(1f) / edelta();
|
||||
}
|
||||
|
||||
/** @return whether this block should play its active sound.*/
|
||||
public boolean shouldActiveSound(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return volume cale of active sound. */
|
||||
public float activeSoundVolume(){
|
||||
return 1f;
|
||||
}
|
||||
|
||||
/** @return whether this block should play its idle sound.*/
|
||||
public boolean shouldAmbientSound(){
|
||||
return shouldConsume();
|
||||
@@ -1315,14 +1317,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void onRemoved(){
|
||||
}
|
||||
|
||||
/** Called every frame a unit is on this */
|
||||
/** Called every frame a unit is on this. Hovering/flying/steppy units do not apply. */
|
||||
public void unitOn(Unit unit){
|
||||
}
|
||||
|
||||
/** Called every frame a unit is on this. Applies to any unit. */
|
||||
public void unitOnAny(Unit unit){
|
||||
}
|
||||
|
||||
/** Called when a unit that spawned at this tile is removed. */
|
||||
public void unitRemoved(Unit unit){
|
||||
}
|
||||
|
||||
/** Called when a puddle is on this building. Only called at an interval (40 ticks). */
|
||||
public void puddleOn(Puddle puddle){
|
||||
|
||||
}
|
||||
|
||||
/** Called when arbitrary configuration is applied to a tile. */
|
||||
public void configured(@Nullable Unit builder, @Nullable Object value){
|
||||
//null is of type void.class; anonymous classes use their superclass.
|
||||
@@ -1372,6 +1383,19 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return block.itemCapacity;
|
||||
}
|
||||
|
||||
public void splashLiquid(Liquid liquid, float amount){
|
||||
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
|
||||
|
||||
for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> {
|
||||
Tile other = world.tileWorld(x + Mathf.range(block.size * tilesize / 2), y + Mathf.range(block.size * tilesize / 2));
|
||||
if(other != null){
|
||||
Puddles.deposit(other, liquid, splash);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when a block begins (not finishes!) deconstruction. The building is still present at this point. */
|
||||
public void onDeconstructed(@Nullable Unit builder){
|
||||
//deposit non-incinerable liquid on ground
|
||||
@@ -1383,9 +1407,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Called when the block is destroyed. The tile is still intact at this stage. */
|
||||
public void onDestroyed(){
|
||||
if(sound != null){
|
||||
sound.stop();
|
||||
}
|
||||
|
||||
float explosiveness = block.baseExplosiveness;
|
||||
float flammability = 0f;
|
||||
@@ -1411,22 +1432,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
if(block.hasLiquids && state.rules.damageExplosions){
|
||||
|
||||
liquids.each((liquid, amount) -> {
|
||||
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
|
||||
|
||||
for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> {
|
||||
Tile other = world.tileWorld(x + Mathf.range(block.size * tilesize / 2), y + Mathf.range(block.size * tilesize / 2));
|
||||
if(other != null){
|
||||
Puddles.deposit(other, liquid, splash);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
liquids.each(this::splashLiquid);
|
||||
}
|
||||
|
||||
//cap explosiveness so fluid tanks/vaults don't instakill units
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect);
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect, block.baseShake);
|
||||
|
||||
if(block.createRubble && !floor().solid && !floor().isLiquid){
|
||||
Effect.rubble(x, y, block.size);
|
||||
@@ -1657,8 +1667,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
damage = Damage.applyArmor(damage, block.armor);
|
||||
}
|
||||
|
||||
damage(other.team, damage);
|
||||
Events.fire(bulletDamageEvent.set(self(), other));
|
||||
damage(other, other.team, damage);
|
||||
|
||||
if(health <= 0 && !wasDead){
|
||||
Events.fire(new BuildingBulletDestroyEvent(self(), other));
|
||||
@@ -1681,6 +1690,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
/** Changes this building's team in a safe manner. */
|
||||
public void changeTeam(Team next){
|
||||
if(this.team == next) return;
|
||||
if(block.forceTeam != null) team = block.forceTeam;
|
||||
|
||||
Team last = this.team;
|
||||
boolean was = isValid();
|
||||
@@ -1693,10 +1703,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
indexer.addIndex(tile);
|
||||
Events.fire(teamChangeEvent.set(last, self()));
|
||||
}
|
||||
|
||||
checkAllowUpdate();
|
||||
}
|
||||
|
||||
public boolean canPickup(){
|
||||
return true;
|
||||
return block.canPickup;
|
||||
}
|
||||
|
||||
/** Called right before this building is picked up. */
|
||||
@@ -1764,6 +1776,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
public void onNearbyBuildAdded(Building other){}
|
||||
|
||||
public void consume(){
|
||||
for(Consume cons : block.consumers){
|
||||
cons.trigger(self());
|
||||
@@ -2094,22 +2108,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
stopSound();
|
||||
}
|
||||
|
||||
public void stopSound(){
|
||||
if(sound != null){
|
||||
sound.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void killed(){
|
||||
dead = true;
|
||||
Events.fire(new BlockDestroyEvent(tile));
|
||||
block.destroySound.at(tile);
|
||||
block.destroySound.at(tile, Mathf.random(block.destroyPitchMin, block.destroyPitchMax));
|
||||
onDestroyed();
|
||||
if(tile != emptyTile){
|
||||
tile.remove();
|
||||
@@ -2118,48 +2121,44 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
afterDestroyed();
|
||||
}
|
||||
|
||||
public void checkAllowUpdate(){
|
||||
if(!allowUpdate()){
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Final
|
||||
@Replace
|
||||
@Override
|
||||
public void update(){
|
||||
//TODO should just avoid updating buildings instead
|
||||
if(state.isEditor()) return;
|
||||
|
||||
//TODO refactor to timestamp-based system?
|
||||
if((timeScaleDuration -= Time.delta) <= 0f || !block.canOverdrive){
|
||||
timeScale = 1f;
|
||||
}
|
||||
|
||||
if(!allowUpdate()){
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
if(!headless && !wasVisible && state.rules.fog && !inFogTo(player.team())){
|
||||
visibleFlags |= (1L << player.team().id);
|
||||
wasVisible = true;
|
||||
renderer.blocks.updateShadow(self());
|
||||
renderer.minimap.update(tile);
|
||||
}
|
||||
|
||||
//TODO separate system for sound? AudioSource, etc
|
||||
if(!headless){
|
||||
if(sound != null){
|
||||
sound.update(x, y, shouldActiveSound(), activeSoundVolume());
|
||||
}
|
||||
|
||||
if(block.ambientSound != Sounds.none && shouldAmbientSound()){
|
||||
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
|
||||
}
|
||||
//TODO separate multithreaded system for sound? AudioSource, etc
|
||||
if(!headless && block.ambientSound != Sounds.none && shouldAmbientSound()){
|
||||
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
|
||||
}
|
||||
|
||||
updateConsumption();
|
||||
|
||||
//TODO just handle per-block instead
|
||||
if(enabled || !block.noUpdateDisabled){
|
||||
updateTile();
|
||||
}
|
||||
}
|
||||
|
||||
/** When a block is newly revealed outside of camera view range, it is updated on the minimap. */
|
||||
public void updateFogVisibility(){
|
||||
if(!wasVisible && !inFogTo(player.team())){
|
||||
visibleFlags |= (1L << player.team().id);
|
||||
wasVisible = true;
|
||||
renderer.blocks.updateShadow(self());
|
||||
renderer.minimap.update(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Rect out){
|
||||
out.setCentered(x, y, block.size * tilesize, block.size * tilesize);
|
||||
|
||||
@@ -39,6 +39,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
|
||||
//setting this variable to true prevents lifetime from decreasing for a frame.
|
||||
transient boolean keepAlive;
|
||||
/** Unlike the owner, the shooter is the original entity that created this bullet. For a second-stage missile, the shooter would be the turret, but the owner would be the last missile stage.*/
|
||||
transient Entityc shooter;
|
||||
transient @Nullable Tile aimTile;
|
||||
transient float aimX, aimY;
|
||||
@@ -48,6 +49,9 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
transient @Nullable Trail trail;
|
||||
transient int frags;
|
||||
|
||||
transient Posc stickyTarget;
|
||||
transient float stickyX, stickyY, stickyRotation, stickyRotationOffset;
|
||||
|
||||
@Override
|
||||
public void getCollisions(Cons<QuadTree> consumer){
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
@@ -106,24 +110,43 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
@Override
|
||||
public boolean collides(Hitboxc other){
|
||||
return type.collides && (other instanceof Teamc t && t.team() != team)
|
||||
&& !(other instanceof Flyingc f && !f.checkTarget(type.collidesAir, type.collidesGround))
|
||||
&& !(type.pierce && hasCollided(other.id())); //prevent multiple collisions
|
||||
&& !(other instanceof Unit f && !f.checkTarget(type.collidesAir, type.collidesGround))
|
||||
&& !(type.pierce && hasCollided(other.id())) && stickyTarget == null; //prevent multiple collisions
|
||||
}
|
||||
|
||||
@MethodPriority(100)
|
||||
@Override
|
||||
public void collision(Hitboxc other, float x, float y){
|
||||
type.hit(self(), x, y);
|
||||
|
||||
//must be last.
|
||||
if(!type.pierce){
|
||||
hit = true;
|
||||
remove();
|
||||
if(type.sticky){
|
||||
if(stickyTarget == null){
|
||||
//tunnel into the target a bit for better visuals
|
||||
this.x = x + vel.x;
|
||||
this.y = y + vel.y;
|
||||
stickTo(other);
|
||||
}
|
||||
}else{
|
||||
collided.add(other.id());
|
||||
}
|
||||
type.hit(self(), x, y);
|
||||
|
||||
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
|
||||
//must be last.
|
||||
if(!type.pierce){
|
||||
hit = true;
|
||||
remove();
|
||||
}else{
|
||||
collided.add(other.id());
|
||||
}
|
||||
|
||||
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void stickTo(Posc other){
|
||||
lifetime += type.stickyExtraLifetime;
|
||||
//sticky bullets don't actually hit anything.
|
||||
stickyX = this.x - other.x();
|
||||
stickyY = this.y - other.y();
|
||||
stickyTarget = other;
|
||||
stickyRotationOffset = rotation;
|
||||
stickyRotation = (other instanceof Rotc rot ? rot.rotation() : 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,9 +155,21 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
mover.move(self());
|
||||
}
|
||||
|
||||
if(type.accel != 0){
|
||||
vel.setLength(vel.len() + type.accel * Time.delta);
|
||||
}
|
||||
|
||||
type.update(self());
|
||||
|
||||
if(type.collidesTiles && type.collides && type.collidesGround){
|
||||
if(stickyTarget != null){
|
||||
//only stick to things that still exist in the world
|
||||
if(stickyTarget instanceof Healthc h && h.isValid()){
|
||||
float rotate = (stickyTarget instanceof Rotc rot ? rot.rotation() - stickyRotation : 0f);
|
||||
set(Tmp.v1.set(stickyX, stickyY).rotate(rotate).add(stickyTarget));
|
||||
this.rotation = rotate + stickyRotationOffset;
|
||||
vel.setAngle(this.rotation);
|
||||
}
|
||||
}else if(type.collidesTiles && type.collides && type.collidesGround){
|
||||
tileRaycast(World.toTile(lastX), World.toTile(lastY), tileX(), tileY());
|
||||
}
|
||||
|
||||
@@ -165,6 +200,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
(!build.block.underBullets ||
|
||||
//direct hit on correct tile
|
||||
(aimTile != null && aimTile.build == build) ||
|
||||
//bullet type allows hitting under bullets
|
||||
type.hitUnder ||
|
||||
//same team has no 'under build' mechanics
|
||||
(build.team == team) ||
|
||||
//a piercing bullet overshot the aim tile, it's fine to hit things now
|
||||
@@ -201,31 +238,46 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
&& build.collide(self()) && type.testCollision(self(), build)
|
||||
&& !build.dead() && (type.collidesTeam || build.team != team) && !(type.pierceBuilding && hasCollided(build.id))){
|
||||
|
||||
boolean remove = false;
|
||||
float health = build.health;
|
||||
if(type.sticky){
|
||||
if(build.team != team){
|
||||
//stick to edge of block
|
||||
Vec2 hit = Geometry.raycastRect(lastX, lastY, x, y, Tmp.r1.setCentered(x * tilesize, y * tilesize, tilesize, tilesize));
|
||||
if(hit != null){
|
||||
this.x = hit.x;
|
||||
this.y = hit.y;
|
||||
}
|
||||
|
||||
if(build.team != team){
|
||||
remove = build.collision(self());
|
||||
}
|
||||
stickTo(build);
|
||||
|
||||
if(remove || type.collidesTeam){
|
||||
if(Mathf.dst2(lastX, lastY, x * tilesize, y * tilesize) < Mathf.dst2(lastX, lastY, this.x, this.y)){
|
||||
this.x = x * tilesize;
|
||||
this.y = y * tilesize;
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
boolean remove = false;
|
||||
float health = build.health;
|
||||
|
||||
if(build.team != team){
|
||||
remove = build.collision(self());
|
||||
}
|
||||
|
||||
if(!type.pierceBuilding){
|
||||
hit = true;
|
||||
remove();
|
||||
}else{
|
||||
collided.add(build.id);
|
||||
if(remove || type.collidesTeam){
|
||||
if(Mathf.dst2(lastX, lastY, x * tilesize, y * tilesize) < Mathf.dst2(lastX, lastY, this.x, this.y)){
|
||||
this.x = x * tilesize;
|
||||
this.y = y * tilesize;
|
||||
}
|
||||
|
||||
if(!type.pierceBuilding){
|
||||
hit = true;
|
||||
remove();
|
||||
}else{
|
||||
collided.add(build.id);
|
||||
}
|
||||
}
|
||||
|
||||
type.hitTile(self(), build, x * tilesize, y * tilesize, health, true);
|
||||
|
||||
//stop raycasting when building is hit
|
||||
if(type.pierceBuilding) return;
|
||||
}
|
||||
|
||||
type.hitTile(self(), build, x * tilesize, y * tilesize, health, true);
|
||||
|
||||
//stop raycasting when building is hit
|
||||
if(type.pierceBuilding) return;
|
||||
}
|
||||
|
||||
if(x == x2 && y == y2) break;
|
||||
@@ -247,7 +299,11 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
public void draw(){
|
||||
Draw.z(type.layer);
|
||||
|
||||
type.draw(self());
|
||||
if(type.underwater){
|
||||
Drawf.underwater(() -> type.draw(self()));
|
||||
}else{
|
||||
type.draw(self());
|
||||
}
|
||||
type.drawLight(self());
|
||||
|
||||
Draw.reset();
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -22,7 +20,6 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
@Import float x, y, speedMultiplier, rotation, hitSize;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
@Import Vec2 vel;
|
||||
|
||||
transient Floor lastDeepFloor;
|
||||
transient float lastCrawlSlowdown = 1f;
|
||||
@@ -31,13 +28,7 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return EntityCollisions::legsSolid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int pathType(){
|
||||
return Pathfinder.costLegs;
|
||||
return ignoreSolids() ? null : EntityCollisions::legsSolid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,6 +101,6 @@ abstract class CrawlComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
segmentRot = Angles.clampRange(segmentRot, rotation, type.segmentMaxRot);
|
||||
|
||||
crawlTime += vel.len() * Time.delta;
|
||||
crawlTime += deltaLen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import mindustry.entities.EntityCollisions.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
@Component
|
||||
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
|
||||
abstract class ElevationMoveComp implements Velc, Posc, Hitboxc, Unitc{
|
||||
@Import float x, y;
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return isFlying() ? null : EntityCollisions::solid;
|
||||
return isFlying() || ignoreSolids() ? null : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,12 +59,16 @@ abstract class EntityComp{
|
||||
|
||||
}
|
||||
|
||||
void beforeWrite(){
|
||||
|
||||
}
|
||||
|
||||
void afterRead(){
|
||||
|
||||
}
|
||||
|
||||
/** Called after *all* entities are read. */
|
||||
void afterAllRead(){
|
||||
//called after all entities have been read (useful for ID resolution)
|
||||
void afterReadAll(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
|
||||
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
|
||||
|
||||
@Import float x, y, speedMultiplier, hitSize;
|
||||
@Import Vec2 vel;
|
||||
@Import UnitType type;
|
||||
|
||||
@SyncLocal float elevation;
|
||||
private transient boolean wasFlying;
|
||||
transient boolean hovering;
|
||||
transient float drownTime;
|
||||
transient float splashTimer;
|
||||
transient @Nullable Floor lastDrownFloor;
|
||||
|
||||
boolean checkTarget(boolean targetAir, boolean targetGround){
|
||||
return (isGrounded() && targetGround) || (isFlying() && targetAir);
|
||||
}
|
||||
|
||||
boolean isGrounded(){
|
||||
return elevation < 0.001f;
|
||||
}
|
||||
|
||||
boolean isFlying(){
|
||||
return elevation >= 0.09f;
|
||||
}
|
||||
|
||||
boolean canDrown(){
|
||||
return isGrounded() && !hovering;
|
||||
}
|
||||
|
||||
@Nullable Floor drownFloor(){
|
||||
return canDrown() ? floorOn() : null;
|
||||
}
|
||||
|
||||
boolean emitWalkSound(){
|
||||
return true;
|
||||
}
|
||||
|
||||
void landed(){
|
||||
|
||||
}
|
||||
|
||||
void wobble(){
|
||||
x += Mathf.sin(Time.time + (id() % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
y += Mathf.cos(Time.time + (id() % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
}
|
||||
|
||||
void moveAt(Vec2 vector, float acceleration){
|
||||
Vec2 t = tmp1.set(vector); //target vector
|
||||
tmp2.set(t).sub(vel).limit(acceleration * vector.len() * Time.delta); //delta vector
|
||||
vel.add(tmp2);
|
||||
}
|
||||
|
||||
float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
|
||||
return on.speedMultiplier * speedMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
Floor floor = floorOn();
|
||||
|
||||
if(isFlying() != wasFlying){
|
||||
if(wasFlying){
|
||||
if(tileOn() != null){
|
||||
Fx.unitLand.at(x, y, floorOn().isLiquid ? 1f : 0.5f, tileOn().floor().mapColor);
|
||||
}
|
||||
}
|
||||
|
||||
wasFlying = isFlying();
|
||||
}
|
||||
|
||||
if(!hovering && isGrounded()){
|
||||
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
|
||||
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
|
||||
splashTimer = 0f;
|
||||
|
||||
if(emitWalkSound()){
|
||||
floor.walkSound.at(x, y, Mathf.random(floor.walkSoundPitchMin, floor.walkSoundPitchMax), floor.walkSoundVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDrowning();
|
||||
}
|
||||
|
||||
public void updateDrowning(){
|
||||
Floor floor = drownFloor();
|
||||
|
||||
if(floor != null && floor.isLiquid && floor.drownTime > 0){
|
||||
lastDrownFloor = floor;
|
||||
drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier;
|
||||
if(Mathf.chanceDelta(0.05f)){
|
||||
floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor);
|
||||
}
|
||||
|
||||
if(drownTime >= 0.999f && !net.client()){
|
||||
kill();
|
||||
Events.fire(new UnitDrownEvent(self()));
|
||||
}
|
||||
}else{
|
||||
drownTime -= Time.delta / 50f;
|
||||
}
|
||||
|
||||
drownTime = Mathf.clamp(drownTime);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -19,7 +18,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
abstract class LegsComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
private static final Vec2 straightVec = new Vec2();
|
||||
|
||||
@Import float x, y, rotation, speedMultiplier;
|
||||
@@ -37,13 +36,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int pathType(){
|
||||
return type.allowLegStep ? Pathfinder.costLegs : Pathfinder.costGround;
|
||||
return ignoreSolids() ? null : type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,6 +104,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
|
||||
legs[i] = l;
|
||||
}
|
||||
totalLength = Mathf.random(100f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, ElevationMovec{
|
||||
abstract class MechComp implements Posc, Hitboxc, Unitc, Mechc, ElevationMovec{
|
||||
@Import float x, y, hitSize;
|
||||
@Import UnitType type;
|
||||
|
||||
@@ -68,7 +68,7 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
|
||||
}
|
||||
}
|
||||
}
|
||||
return canDrown() ? floorOn() : null;
|
||||
return floorOn();
|
||||
}
|
||||
|
||||
public float walkExtend(boolean scaled){
|
||||
|
||||
@@ -10,7 +10,7 @@ import mindustry.gen.*;
|
||||
* Will bounce off of other objects that are at similar elevations.
|
||||
* Has mass.*/
|
||||
@Component
|
||||
abstract class PhysicsComp implements Velc, Hitboxc, Flyingc{
|
||||
abstract class PhysicsComp implements Velc, Hitboxc{
|
||||
@Import float hitSize, x, y;
|
||||
@Import Vec2 vel;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
|
||||
|
||||
private static Puddle paramPuddle;
|
||||
private static Cons<Unit> unitCons = unit -> {
|
||||
if(unit.isGrounded() && !unit.hovering){
|
||||
if(unit.isGrounded() && !unit.type.hovering){
|
||||
unit.hitbox(rect2);
|
||||
if(rect.overlaps(rect2)){
|
||||
unit.apply(paramPuddle.liquid.effect, 60 * 2);
|
||||
@@ -104,6 +104,10 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc, Syncc{
|
||||
}
|
||||
|
||||
updateTime = 40f;
|
||||
|
||||
if(tile.build != null){
|
||||
tile.build.puddleOn(self());
|
||||
}
|
||||
}
|
||||
|
||||
if(!headless && liquid.particleEffect != Fx.none){
|
||||
|
||||
158
core/src/mindustry/entities/comp/SegmentComp.java
Normal file
158
core/src/mindustry/entities/comp/SegmentComp.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
@Component
|
||||
abstract class SegmentComp implements Posc, Rotc, Hitboxc, Unitc, Segmentc{
|
||||
@Import float x, y, rotation;
|
||||
@Import UnitType type;
|
||||
@Import Vec2 vel;
|
||||
|
||||
transient @Nullable Segmentc parentSegment, childSegment, headSegment;
|
||||
transient int segmentIndex;
|
||||
|
||||
int parentId;
|
||||
|
||||
public boolean isHead(){
|
||||
return parentSegment == null;
|
||||
}
|
||||
|
||||
public void addChild(Unit other){
|
||||
if(other == self()) return;
|
||||
|
||||
if(childSegment != null){
|
||||
childSegment.parentSegment(null);
|
||||
}
|
||||
|
||||
if(other instanceof Segmentc seg){
|
||||
if(seg.parentSegment() != null){
|
||||
seg.parentSegment().childSegment(null);
|
||||
}
|
||||
|
||||
childSegment = seg;
|
||||
seg.parentSegment(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean ignoreSolids(){
|
||||
return isFlying() || parentSegment != null;
|
||||
}
|
||||
|
||||
//TODO make it phase through things.
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(childSegment != null && !childSegment.isValid()){
|
||||
childSegment = null;
|
||||
}
|
||||
|
||||
if(parentSegment != null && !parentSegment.isValid()){
|
||||
parentSegment = null;
|
||||
}
|
||||
|
||||
if(parentSegment == null){
|
||||
segmentIndex = 0;
|
||||
|
||||
if(childSegment != null){
|
||||
headSegment = this;
|
||||
childSegment.updateSegment(this, this, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public boolean playerControllable(){
|
||||
return type.playerControllable && isHead();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean shouldUpdateController(){
|
||||
return isHead();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean moving(){
|
||||
if(isHead()){
|
||||
return !vel.isZero(0.01f);
|
||||
}else{
|
||||
return deltaLen() / Time.delta >= 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int collisionLayer(){
|
||||
if(parentSegment != null) return -1;
|
||||
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
|
||||
}
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public boolean isCommandable(){
|
||||
return parentSegment == null && controller() instanceof CommandAI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSync(){
|
||||
checkParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterReadAll(){
|
||||
checkParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeWrite(){
|
||||
parentId = parentSegment == null ? -1 : parentSegment.id();
|
||||
}
|
||||
|
||||
public void checkParent(){
|
||||
if(parentId != -1){
|
||||
var parent = Groups.unit.getByID(parentId);
|
||||
if(parent instanceof Segmentc seg){
|
||||
parentSegment = seg;
|
||||
seg.childSegment(this);
|
||||
return;
|
||||
}
|
||||
parentId = -1;
|
||||
}
|
||||
//TODO should this unassign the parent's child too?
|
||||
parentSegment = null;
|
||||
}
|
||||
|
||||
public void updateSegment(Segmentc head, Segmentc parent, int index){
|
||||
rotation = Angles.clampRange(rotation, parent.rotation(), type.segmentRotationRange);
|
||||
segmentIndex = index;
|
||||
headSegment = head;
|
||||
|
||||
float headDelta = head.deltaLen();
|
||||
|
||||
//TODO should depend on the head's speed.
|
||||
if(headDelta > 0.001f){
|
||||
rotation = Mathf.slerpDelta(rotation, parent.rotation(), type.baseRotateSpeed * Mathf.clamp(headDelta / type().speed / Time.delta));
|
||||
}
|
||||
|
||||
Vec2 moveVec = Tmp.v1.trns(rotation + 180f, type.segmentSpacing).add(parent).sub(x, y);
|
||||
float prefSpeed = type.speed * Time.delta * 9999f;
|
||||
move(moveVec.limit(prefSpeed)); //TODO other segments are left behind
|
||||
|
||||
if(childSegment != null){
|
||||
childSegment.updateSegment(head, this, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class StatusComp implements Posc, Flyingc{
|
||||
abstract class StatusComp implements Posc{
|
||||
private Seq<StatusEntry> statuses = new Seq<>(4);
|
||||
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
|
||||
|
||||
@@ -28,12 +28,12 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
@Import float maxHealth;
|
||||
|
||||
/** Apply a status effect for 1 tick (for permanent effects) **/
|
||||
void apply(StatusEffect effect){
|
||||
public void apply(StatusEffect effect){
|
||||
apply(effect, 1);
|
||||
}
|
||||
|
||||
/** Adds a status effect to this unit. */
|
||||
void apply(StatusEffect effect, float duration){
|
||||
public void apply(StatusEffect effect, float duration){
|
||||
if(effect == StatusEffects.none || effect == null || isImmune(effect)) return; //don't apply empty or immune effects
|
||||
|
||||
//unlock status effects regardless of whether they were applied to friendly units
|
||||
@@ -67,18 +67,18 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
}
|
||||
}
|
||||
|
||||
float getDuration(StatusEffect effect){
|
||||
public float getDuration(StatusEffect effect){
|
||||
var entry = statuses.find(e -> e.effect == effect);
|
||||
return entry == null ? 0 : entry.time;
|
||||
}
|
||||
|
||||
void clearStatuses(){
|
||||
public void clearStatuses(){
|
||||
statuses.each(e -> e.effect.onRemoved(self()));
|
||||
statuses.clear();
|
||||
}
|
||||
|
||||
/** Removes a status effect. */
|
||||
void unapply(StatusEffect effect){
|
||||
public void unapply(StatusEffect effect){
|
||||
statuses.remove(e -> {
|
||||
if(e.effect == effect){
|
||||
e.effect.onRemoved(self());
|
||||
@@ -89,13 +89,15 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
});
|
||||
}
|
||||
|
||||
boolean isBoss(){
|
||||
public boolean isBoss(){
|
||||
return hasEffect(StatusEffects.boss);
|
||||
}
|
||||
|
||||
abstract boolean isImmune(StatusEffect effect);
|
||||
public boolean isImmune(StatusEffect effect){
|
||||
return type.immunities.contains(effect);
|
||||
}
|
||||
|
||||
Color statusColor(){
|
||||
public Color statusColor(){
|
||||
if(statuses.size == 0){
|
||||
return Tmp.c1.set(Color.white);
|
||||
}
|
||||
@@ -168,6 +170,8 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
applyDynamicStatus().armorOverride = armor;
|
||||
}
|
||||
|
||||
public abstract boolean isGrounded();
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
Floor floor = floorOn();
|
||||
@@ -237,7 +241,7 @@ abstract class StatusComp implements Posc, Flyingc{
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasEffect(StatusEffect effect){
|
||||
public boolean hasEffect(StatusEffect effect){
|
||||
return applied.get(effect.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec{
|
||||
abstract class TankComp implements Posc, Hitboxc, Unitc, ElevationMovec{
|
||||
@Import float x, y, hitSize, rotation, speedMultiplier;
|
||||
@Import boolean hovering, disarmed;
|
||||
@Import boolean disarmed;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
|
||||
@@ -27,6 +27,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
|
||||
transient float treadTime;
|
||||
transient boolean walked;
|
||||
transient Floor lastDeepFloor;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -51,6 +52,9 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
}
|
||||
}
|
||||
|
||||
lastDeepFloor = null;
|
||||
boolean anyNonDeep = false;
|
||||
|
||||
//calculate overlapping tiles so it slows down when going "over" walls
|
||||
int r = Math.max((int)(hitSize * 0.6f / tilesize), 0);
|
||||
|
||||
@@ -62,6 +66,12 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
solids ++;
|
||||
}
|
||||
|
||||
if(t.floor().isDeep()){
|
||||
lastDeepFloor = t.floor();
|
||||
}else{
|
||||
anyNonDeep = true;
|
||||
}
|
||||
|
||||
//TODO should this apply to the player team(s)? currently PvE due to balancing
|
||||
if(type.crushDamage > 0 && !disarmed && (walked || deltaLen() >= 0.01f) && t != null
|
||||
//damage radius is 1 tile smaller to prevent it from just touching walls as it passes
|
||||
@@ -76,6 +86,10 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
}
|
||||
}
|
||||
|
||||
if(anyNonDeep){
|
||||
lastDeepFloor = null;
|
||||
}
|
||||
|
||||
lastSlowdown = Mathf.lerp(1f, type.crawlSlowdown, Mathf.clamp((float)solids / total / type.crawlSlowdownFrac));
|
||||
|
||||
//trigger animation only when walking manually
|
||||
@@ -89,7 +103,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
@Override
|
||||
@Replace
|
||||
public float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() || hovering ? Blocks.air.asFloor() : floorOn();
|
||||
Floor on = isFlying() || type.hovering ? Blocks.air.asFloor() : floorOn();
|
||||
//TODO take into account extra blocks
|
||||
return on.speedMultiplier * speedMultiplier * lastSlowdown;
|
||||
}
|
||||
@@ -97,17 +111,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
|
||||
@Replace
|
||||
@Override
|
||||
public @Nullable Floor drownFloor(){
|
||||
//tanks can only drown when all the nearby floors are deep
|
||||
//TODO implement properly
|
||||
if(hitSize >= 12 && canDrown()){
|
||||
for(Point2 p : Geometry.d8){
|
||||
Floor f = world.floorWorld(x + p.x * tilesize, y + p.y * tilesize);
|
||||
if(!f.isDeep()){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return canDrown() ? floorOn() : null;
|
||||
return canDrown() ? lastDeepFloor : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
39
core/src/mindustry/entities/comp/UnderwaterMoveComp.java
Normal file
39
core/src/mindustry/entities/comp/UnderwaterMoveComp.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
@Component
|
||||
abstract class UnderwaterMoveComp implements WaterMovec{
|
||||
@Import UnitType type;
|
||||
|
||||
@MethodPriority(10f)
|
||||
@Replace
|
||||
public void draw(){
|
||||
//TODO draw status effects?
|
||||
|
||||
Drawf.underwater(() -> {
|
||||
type.draw(self());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int collisionLayer(){
|
||||
return PhysicsProcess.layerUnderwater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hittable(){
|
||||
return false && type.hittable(self());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean targetable(Team targeter){
|
||||
return false && type.targetable(self(), targeter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.async.*;
|
||||
@@ -34,10 +33,12 @@ import static mindustry.Vars.*;
|
||||
import static mindustry.logic.GlobalVars.*;
|
||||
|
||||
@Component(base = true)
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
|
||||
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
|
||||
static final float warpDst = 30f;
|
||||
|
||||
@Import boolean hovering, dead, disarmed;
|
||||
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
|
||||
@Import boolean dead, disarmed;
|
||||
@Import float x, y, rotation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
|
||||
@Import Team team;
|
||||
@Import int id;
|
||||
@Import @Nullable Tile mineTile;
|
||||
@@ -62,6 +63,48 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
private transient boolean wasPlayer;
|
||||
private transient boolean wasHealed;
|
||||
|
||||
@SyncLocal float elevation;
|
||||
private transient boolean wasFlying;
|
||||
transient float drownTime;
|
||||
transient float splashTimer;
|
||||
transient @Nullable Floor lastDrownFloor;
|
||||
|
||||
public boolean checkTarget(boolean targetAir, boolean targetGround){
|
||||
return (isGrounded() && targetGround) || (isFlying() && targetAir);
|
||||
}
|
||||
|
||||
public boolean isGrounded(){
|
||||
return elevation < 0.001f;
|
||||
}
|
||||
|
||||
public boolean isFlying(){
|
||||
return elevation >= 0.09f;
|
||||
}
|
||||
|
||||
public boolean canDrown(){
|
||||
return isGrounded() && type.canDrown;
|
||||
}
|
||||
|
||||
public @Nullable Floor drownFloor(){
|
||||
return floorOn();
|
||||
}
|
||||
|
||||
public void wobble(){
|
||||
x += Mathf.sin(Time.time + (id % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
y += Mathf.cos(Time.time + (id % 10) * 12, 25f, 0.05f) * Time.delta * elevation;
|
||||
}
|
||||
|
||||
public void moveAt(Vec2 vector, float acceleration){
|
||||
Vec2 t = tmp1.set(vector); //target vector
|
||||
tmp2.set(t).sub(vel).limit(acceleration * vector.len() * Time.delta); //delta vector
|
||||
vel.add(tmp2);
|
||||
}
|
||||
|
||||
public float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() || type.hovering ? Blocks.air.asFloor() : floorOn();
|
||||
return on.speedMultiplier * speedMultiplier;
|
||||
}
|
||||
|
||||
/** Called when this unit was unloaded from a factory or spawn point. */
|
||||
public void unloaded(){
|
||||
|
||||
@@ -352,12 +395,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean canDrown(){
|
||||
return isGrounded() && !hovering && type.canDrown;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean canShoot(){
|
||||
@@ -420,11 +457,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
|
||||
}
|
||||
|
||||
/** @return pathfinder path type for calculating costs. This is used for wave AI only. (TODO: remove) */
|
||||
public int pathType(){
|
||||
return Pathfinder.costGround;
|
||||
}
|
||||
|
||||
public void lookAt(float angle){
|
||||
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta * speedMultiplier());
|
||||
}
|
||||
@@ -445,8 +477,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
return controller instanceof CommandAI;
|
||||
}
|
||||
|
||||
public boolean canTarget(Unit other){
|
||||
return other != null && other.checkTarget(type.targetAir, type.targetGround);
|
||||
public boolean canTarget(Teamc other){
|
||||
return other != null && (other instanceof Unit u ? u.checkTarget(type.targetAir, type.targetGround) : (other instanceof Building b && type.targetGround));
|
||||
}
|
||||
|
||||
public CommandAI command(){
|
||||
@@ -475,9 +507,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
this.drag = type.drag;
|
||||
this.armor = type.armor;
|
||||
this.hitSize = type.hitSize;
|
||||
this.hovering = type.hovering;
|
||||
|
||||
if(controller == null) controller(type.createController(self()));
|
||||
if(mounts().length != type.weapons.size) setupWeapons(type);
|
||||
if(abilities.length != type.abilities.size){
|
||||
abilities = new Ability[type.abilities.size];
|
||||
@@ -485,6 +515,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
abilities[i] = type.abilities.get(i).copy();
|
||||
}
|
||||
}
|
||||
if(controller == null) controller(type.createController(self()));
|
||||
}
|
||||
|
||||
public boolean playerControllable(){
|
||||
return type.playerControllable;
|
||||
}
|
||||
|
||||
public boolean targetable(Team targeter){
|
||||
@@ -504,7 +539,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
|
||||
@Override
|
||||
public void afterRead(){
|
||||
afterSync();
|
||||
setType(this.type);
|
||||
controller.unit(self());
|
||||
//reset controller state
|
||||
if(!(controller instanceof AIController ai && ai.keepState())){
|
||||
controller(type.createController(self()));
|
||||
@@ -512,7 +548,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterAllRead(){
|
||||
public void afterReadAll(){
|
||||
controller.afterRead(self());
|
||||
}
|
||||
|
||||
@@ -555,11 +591,98 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDrowning(){
|
||||
Floor floor = drownFloor();
|
||||
|
||||
if(floor != null && floor.isLiquid && floor.drownTime > 0 && canDrown()){
|
||||
lastDrownFloor = floor;
|
||||
drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier;
|
||||
if(Mathf.chanceDelta(0.05f)){
|
||||
floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor);
|
||||
}
|
||||
|
||||
if(drownTime >= 0.999f && !net.client()){
|
||||
kill();
|
||||
Events.fire(new UnitDrownEvent(self()));
|
||||
}
|
||||
}else{
|
||||
drownTime -= Time.delta / 50f;
|
||||
}
|
||||
|
||||
drownTime = Mathf.clamp(drownTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
|
||||
type.update(self());
|
||||
|
||||
//update bounds
|
||||
|
||||
if(type.bounded){
|
||||
float bot = 0f, left = 0f, top = world.unitHeight(), right = world.unitWidth();
|
||||
|
||||
//TODO hidden map rules only apply to player teams? should they?
|
||||
if(state.rules.limitMapArea && !team.isAI()){
|
||||
bot = state.rules.limitY * tilesize;
|
||||
left = state.rules.limitX * tilesize;
|
||||
top = state.rules.limitHeight * tilesize + bot;
|
||||
right = state.rules.limitWidth * tilesize + left;
|
||||
}
|
||||
|
||||
if(!net.client() || isLocal()){
|
||||
|
||||
float dx = 0f, dy = 0f;
|
||||
|
||||
//repel unit out of bounds
|
||||
if(x < left) dx += (-(x - left)/warpDst);
|
||||
if(y < bot) dy += (-(y - bot)/warpDst);
|
||||
if(x > right) dx -= (x - right)/warpDst;
|
||||
if(y > top) dy -= (y - top)/warpDst;
|
||||
|
||||
velAddNet(dx * Time.delta, dy * Time.delta);
|
||||
}
|
||||
|
||||
//clamp position if not flying
|
||||
if(isGrounded()){
|
||||
x = Mathf.clamp(x, left, right - tilesize);
|
||||
y = Mathf.clamp(y, bot, top - tilesize);
|
||||
}
|
||||
|
||||
//kill when out of bounds
|
||||
if(x < -finalWorldBounds + left || y < -finalWorldBounds + bot || x >= right + finalWorldBounds || y >= top + finalWorldBounds){
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
//update drown/flying state
|
||||
|
||||
Floor floor = floorOn();
|
||||
Tile tile = tileOn();
|
||||
|
||||
if(isFlying() != wasFlying){
|
||||
if(wasFlying){
|
||||
if(tile != null){
|
||||
Fx.unitLand.at(x, y, floor.isLiquid ? 1f : 0.5f, tile.floor().mapColor);
|
||||
}
|
||||
}
|
||||
|
||||
wasFlying = isFlying();
|
||||
}
|
||||
|
||||
if(!type.hovering && isGrounded() && type.emitWalkEffect){
|
||||
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
|
||||
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
|
||||
splashTimer = 0f;
|
||||
|
||||
if(type.emitWalkSound){
|
||||
floor.walkSound.at(x, y, Mathf.random(floor.walkSoundPitchMin, floor.walkSoundPitchMax), floor.walkSoundVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateDrowning();
|
||||
|
||||
if(wasHealed && healTime <= -1f){
|
||||
healTime = 1f;
|
||||
}
|
||||
@@ -647,8 +770,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
Tile tile = tileOn();
|
||||
Floor floor = floorOn();
|
||||
if(tile != null && tile.build != null){
|
||||
tile.build.unitOnAny(self());
|
||||
}
|
||||
|
||||
if(tile != null && isGrounded() && !type.hovering){
|
||||
//unit block update
|
||||
@@ -673,7 +797,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
|
||||
//AI only updates on the server
|
||||
if(!net.client() && !dead){
|
||||
if(!net.client() && !dead && shouldUpdateController()){
|
||||
controller.updateUnit();
|
||||
}
|
||||
|
||||
@@ -688,6 +812,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldUpdateController(){
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return a preview UI icon for this unit. */
|
||||
public TextureRegion icon(){
|
||||
return type.uiIcon;
|
||||
@@ -770,11 +898,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
type.display(self(), table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmune(StatusEffect effect){
|
||||
return type.immunities.contains(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
type.draw(self());
|
||||
|
||||
@@ -39,6 +39,10 @@ abstract class VelComp implements Posc{
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean ignoreSolids(){
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return whether this entity can move through a location*/
|
||||
boolean canPass(int tileX, int tileY){
|
||||
SolidPred s = solidity();
|
||||
|
||||
38
core/src/mindustry/entities/comp/WaterCrawlComp.java
Normal file
38
core/src/mindustry/entities/comp/WaterCrawlComp.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.EntityCollisions.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
@Component
|
||||
abstract class WaterCrawlComp implements Posc, Velc, Hitboxc, Unitc, Crawlc{
|
||||
@Import float x, y, rotation, speedMultiplier;
|
||||
@Import UnitType type;
|
||||
|
||||
@Replace
|
||||
public SolidPred solidity(){
|
||||
return isFlying() || ignoreSolids() ? null : EntityCollisions::waterSolid;
|
||||
}
|
||||
|
||||
@Replace
|
||||
public boolean onSolid(){
|
||||
return EntityCollisions.waterSolid(tileX(), tileY());
|
||||
}
|
||||
|
||||
@Replace
|
||||
public float floorSpeedMultiplier(){
|
||||
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
|
||||
return (on.shallow ? 1f : 1.3f) * speedMultiplier;
|
||||
}
|
||||
|
||||
public boolean onLiquid(){
|
||||
Tile tile = tileOn();
|
||||
return tile != null && tile.floor().isLiquid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -18,7 +17,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Unitc{
|
||||
@Import float x, y, rotation, speedMultiplier;
|
||||
@Import UnitType type;
|
||||
|
||||
@@ -38,19 +37,6 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public int pathType(){
|
||||
return Pathfinder.costNaval;
|
||||
}
|
||||
|
||||
//don't want obnoxious splashing
|
||||
@Override
|
||||
@Replace
|
||||
public boolean emitWalkSound(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(){
|
||||
tleft.clear();
|
||||
@@ -59,6 +45,7 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
//TODO: move to UnitType
|
||||
float z = Draw.z();
|
||||
|
||||
Draw.z(Layer.debris);
|
||||
@@ -76,7 +63,7 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public SolidPred solidity(){
|
||||
return isFlying() ? null : EntityCollisions::waterSolid;
|
||||
return isFlying() || ignoreSolids() ? null : EntityCollisions::waterSolid;
|
||||
}
|
||||
|
||||
@Replace
|
||||
|
||||
@@ -23,6 +23,8 @@ public class RegionPart extends DrawPart{
|
||||
public boolean mirror = false;
|
||||
/** If true, an outline is drawn under the part. */
|
||||
public boolean outline = true;
|
||||
/** If true, this part has an outline created 'in-place'. Currently vanilla only, do not use this! */
|
||||
public boolean replaceOutline = false;
|
||||
/** If true, the base + outline regions are drawn. Set to false for heat-only regions. */
|
||||
public boolean drawRegion = true;
|
||||
/** If true, the heat region produces light. */
|
||||
@@ -38,7 +40,8 @@ public class RegionPart extends DrawPart{
|
||||
public Blending blending = Blending.normal;
|
||||
public float layer = -1, layerOffset = 0f, heatLayerOffset = 1f, turretHeatLayer = Layer.turretHeat;
|
||||
public float outlineLayerOffset = -0.001f;
|
||||
public float x, y, xScl = 1f, yScl = 1f, rotation;
|
||||
//note that origin DOES NOT AFFECT child parts
|
||||
public float x, y, xScl = 1f, yScl = 1f, rotation, originX, originY;
|
||||
public float moveX, moveY, growX, growY, moveRot;
|
||||
public float heatLightOpacity = 0.3f;
|
||||
public @Nullable Color color, colorTo, mixColor, mixColorTo;
|
||||
@@ -99,16 +102,21 @@ public class RegionPart extends DrawPart{
|
||||
float sign = (i == 0 ? 1 : -1) * params.sideMultiplier;
|
||||
Tmp.v1.set((x + mx) * sign, y + my).rotateRadExact((params.rotation - 90) * Mathf.degRad);
|
||||
|
||||
Draw.xscl *= sign;
|
||||
|
||||
if(originX != 0f || originY != 0f){
|
||||
//correct for offset caused by origin shift
|
||||
Tmp.v1.sub(Tmp.v2.set(-originX * Draw.xscl, -originY * Draw.yscl).rotate(params.rotation - 90f).add(originX * Draw.xscl, originY * Draw.yscl));
|
||||
}
|
||||
|
||||
float
|
||||
rx = params.x + Tmp.v1.x,
|
||||
ry = params.y + Tmp.v1.y,
|
||||
rot = mr * sign + params.rotation - 90;
|
||||
|
||||
Draw.xscl *= sign;
|
||||
|
||||
if(outline && drawRegion){
|
||||
Draw.z(prevZ + outlineLayerOffset);
|
||||
Draw.rect(outlines[Math.min(i, regions.length - 1)], rx, ry, rot);
|
||||
rect(outlines[Math.min(i, regions.length - 1)], rx, ry, rot);
|
||||
Draw.z(prevZ);
|
||||
}
|
||||
|
||||
@@ -126,7 +134,7 @@ public class RegionPart extends DrawPart{
|
||||
}
|
||||
|
||||
Draw.blend(blending);
|
||||
Draw.rect(region, rx, ry, rot);
|
||||
rect(region, rx, ry, rot);
|
||||
Draw.blend();
|
||||
if(color != null) Draw.color();
|
||||
}
|
||||
@@ -134,7 +142,7 @@ public class RegionPart extends DrawPart{
|
||||
if(heat.found()){
|
||||
float hprog = heatProgress.getClamp(params, clampProgress);
|
||||
heatColor.write(Tmp.c1).a(hprog * heatColor.a);
|
||||
Drawf.additive(heat, Tmp.c1, rx, ry, rot, turretShading ? turretHeatLayer : Draw.z() + heatLayerOffset);
|
||||
Drawf.additive(heat, Tmp.c1, 1f, rx, ry, rot, turretShading ? turretHeatLayer : Draw.z() + heatLayerOffset, originX, originY);
|
||||
if(heatLight) Drawf.light(rx, ry, light.found() ? light : heat, rot, Tmp.c1, heatLightOpacity * hprog);
|
||||
}
|
||||
|
||||
@@ -167,6 +175,11 @@ public class RegionPart extends DrawPart{
|
||||
Draw.scl(preXscl, preYscl);
|
||||
}
|
||||
|
||||
void rect(TextureRegion region, float x, float y, float rotation){
|
||||
float w = region.width * region.scl() * Draw.xscl, h = region.height * region.scl() * Draw.yscl;
|
||||
Draw.rect(region, x, y, w, h, w / 2f + originX * Draw.xscl, h / 2f + originY * Draw.yscl, rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(String name){
|
||||
String realName = this.name == null ? name + suffix : this.name;
|
||||
|
||||
@@ -6,6 +6,20 @@ import arc.util.*;
|
||||
public class ShootHelix extends ShootPattern{
|
||||
public float scl = 2f, mag = 1.5f, offset = Mathf.PI * 1.25f;
|
||||
|
||||
public ShootHelix(float scl, float mag){
|
||||
this.scl = scl;
|
||||
this.mag = mag;
|
||||
}
|
||||
|
||||
public ShootHelix(float scl, float mag, float offset){
|
||||
this.scl = scl;
|
||||
this.mag = mag;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public ShootHelix(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shoot(int totalShots, BulletHandler handler, @Nullable Runnable barrelIncrementer){
|
||||
for(int i = 0; i < shots; i++){
|
||||
|
||||
@@ -14,6 +14,10 @@ public class ShootSpread extends ShootPattern{
|
||||
public ShootSpread(){
|
||||
}
|
||||
|
||||
public static ShootSpread circle(int points){
|
||||
return new ShootSpread(points, 360f / points);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shoot(int totalShots, BulletHandler handler, @Nullable Runnable barrelIncrementer){
|
||||
for(int i = 0; i < shots; i++){
|
||||
|
||||
@@ -21,11 +21,12 @@ public class AIController implements UnitController{
|
||||
|
||||
protected Unit unit;
|
||||
protected Interval timer = new Interval(4);
|
||||
protected AIController fallback;
|
||||
protected @Nullable AIController fallback;
|
||||
protected float noTargetTime;
|
||||
|
||||
/** main target that is being faced */
|
||||
protected Teamc target;
|
||||
protected @Nullable Teamc target;
|
||||
protected @Nullable Teamc bomberTarget;
|
||||
|
||||
{
|
||||
resetTimers();
|
||||
@@ -124,15 +125,27 @@ public class AIController implements UnitController{
|
||||
}
|
||||
|
||||
public void pathfind(int pathTarget){
|
||||
int costType = unit.pathType();
|
||||
pathfind(pathTarget, true);
|
||||
}
|
||||
|
||||
public void pathfind(int pathTarget, boolean stopAtTargetTile){
|
||||
int costType = unit.type.flowfieldPathType;
|
||||
|
||||
Tile tile = unit.tileOn();
|
||||
if(tile == null) return;
|
||||
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
|
||||
Tile targetTile = pathfinder.getField(unit.team, costType, pathTarget).getNextTile(tile);
|
||||
|
||||
if(tile == targetTile || !unit.canPass(targetTile.x, targetTile.y)) return;
|
||||
if((tile == targetTile && stopAtTargetTile) || !unit.canPass(targetTile.x, targetTile.y)) return;
|
||||
|
||||
unit.movePref(vec.trns(unit.angleTo(targetTile.worldx(), targetTile.worldy()), prefSpeed()));
|
||||
unit.movePref(alterPathfind(vec.set(targetTile.worldx(), targetTile.worldy()).sub(tile.worldx(), tile.worldy()).setLength(prefSpeed())));
|
||||
}
|
||||
|
||||
public Vec2 alterPathfind(Vec2 vec){
|
||||
return vec;
|
||||
}
|
||||
|
||||
public void targetInvalidated(){
|
||||
//TODO: try this for normal units, reset the target timer
|
||||
}
|
||||
|
||||
public void updateWeapons(){
|
||||
@@ -146,6 +159,9 @@ public class AIController implements UnitController{
|
||||
noTargetTime += Time.delta;
|
||||
|
||||
if(invalid(target)){
|
||||
if(target != null && !target.isAdded()){
|
||||
targetInvalidated();
|
||||
}
|
||||
target = null;
|
||||
}else{
|
||||
noTargetTime = 0f;
|
||||
@@ -185,6 +201,13 @@ public class AIController implements UnitController{
|
||||
if(mount.target != null){
|
||||
shoot = mount.target.within(mountX, mountY, wrange + (mount.target instanceof Sized s ? s.hitSize()/2f : 0f)) && shouldShoot();
|
||||
|
||||
if(unit.type.autoDropBombs && !shoot){
|
||||
if(bomberTarget == null || !bomberTarget.isAdded() || !bomberTarget.within(unit, unit.hitSize/2f + ((Sized)bomberTarget).hitSize()/2f)){
|
||||
bomberTarget = Units.closestTarget(unit.team, unit.x, unit.y, unit.hitSize, u -> !u.isFlying(), t -> true);
|
||||
}
|
||||
shoot = bomberTarget != null;
|
||||
}
|
||||
|
||||
Vec2 to = Predict.intercept(unit, mount.target, weapon.bullet.speed);
|
||||
mount.aimX = to.x;
|
||||
mount.aimY = to.y;
|
||||
|
||||
@@ -31,8 +31,4 @@ public interface UnitController{
|
||||
default void afterRead(Unit unit){
|
||||
|
||||
}
|
||||
|
||||
default boolean isBeingControlled(Unit player){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import mindustry.net.*;
|
||||
import mindustry.net.Packets.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
public class EventType{
|
||||
@@ -82,6 +83,8 @@ public class EventType{
|
||||
public static class BlockInfoEvent{}
|
||||
/** Called *after* all content has been initialized. */
|
||||
public static class ContentInitEvent{}
|
||||
/** Called *after* all content has been added to the atlas, but before its pixmaps are disposed. */
|
||||
public static class AtlasPackEvent{}
|
||||
/** Called *after* all mod content has been loaded, but before it has been initialized. */
|
||||
public static class ModContentLoadEvent{}
|
||||
/** Called when the client game is first loaded. */
|
||||
@@ -395,6 +398,22 @@ public class EventType{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a tile changes its floor. Do not cache or use with a timer.
|
||||
* Do not modify any tiles inside listener code.
|
||||
* */
|
||||
public static class TileFloorChangeEvent{
|
||||
public Tile tile;
|
||||
public Floor previous, floor;
|
||||
|
||||
public TileFloorChangeEvent set(Tile tile, Floor previous, Floor floor){
|
||||
this.tile = tile;
|
||||
this.previous = previous;
|
||||
this.floor = floor;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a building's team changes.
|
||||
* Event object is reused, do not nest!
|
||||
|
||||
@@ -36,6 +36,7 @@ public final class FogControl implements CustomChunk{
|
||||
|
||||
private boolean justLoaded = false;
|
||||
private boolean loadedStatic = false;
|
||||
private int lastEntityUpdateIndex = 0;
|
||||
|
||||
public FogControl(){
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
@@ -131,6 +132,7 @@ public final class FogControl implements CustomChunk{
|
||||
}
|
||||
|
||||
void stop(){
|
||||
lastEntityUpdateIndex = 0;
|
||||
fog = null;
|
||||
//I don't care whether the fog thread crashes here, it's about to die anyway
|
||||
staticEvents.clear();
|
||||
@@ -214,6 +216,31 @@ public final class FogControl implements CustomChunk{
|
||||
//clear to prepare for queuing fog radius from units and buildings
|
||||
dynamicEventQueue.clear();
|
||||
|
||||
//update fog visibility manually
|
||||
if(state.rules.fog && !headless && Groups.build.size() > 0){
|
||||
|
||||
int size = Groups.build.size();
|
||||
int chunkSize = 5; //fraction of entity list to iterate each frame
|
||||
int chunks = Math.min(chunkSize, size);
|
||||
|
||||
int iterated = Math.max(1, size / chunks);
|
||||
int steps = 0;
|
||||
int i = lastEntityUpdateIndex % size;
|
||||
|
||||
while(steps < iterated){
|
||||
Groups.build.index(i).updateFogVisibility();
|
||||
|
||||
steps ++;
|
||||
i ++;
|
||||
|
||||
if(i >= size){
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lastEntityUpdateIndex = i;
|
||||
}
|
||||
|
||||
for(var team : state.teams.present){
|
||||
//AI teams do not have fog
|
||||
if(!team.team.isOnlyAI()){
|
||||
|
||||
@@ -277,6 +277,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
String className = getClass().getSimpleName().replace("Objective", "");
|
||||
return Core.bundle == null ? className : Core.bundle.get("objective." + className.toLowerCase() + ".name", className);
|
||||
}
|
||||
|
||||
/** Validate fields after reading to make sure none of them are null. */
|
||||
public void validate(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Research a specific piece of content in the tech tree. */
|
||||
@@ -298,6 +303,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.research", content.emoji(), content.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(content == null) content = Items.copper;
|
||||
}
|
||||
}
|
||||
|
||||
/** Produce a specific piece of content in the tech tree (essentially research with different text). */
|
||||
@@ -319,6 +329,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.produce", content.emoji(), content.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(content == null) content = Items.copper;
|
||||
}
|
||||
}
|
||||
|
||||
/** Have a certain amount of item in your core. */
|
||||
@@ -342,6 +357,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.item", state.rules.defaultTeam.items().get(item), amount, item.emoji(), item.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(item == null) item = Items.copper;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a certain item in your core (through a block, not manually.) */
|
||||
@@ -365,6 +385,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.coreitem", state.stats.coreItemCount.get(item), amount, item.emoji(), item.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(item == null) item = Items.copper;
|
||||
}
|
||||
}
|
||||
|
||||
/** Build a certain amount of a block. */
|
||||
@@ -388,6 +413,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.build", count - state.stats.placedBlockCount.get(block, 0), block.emoji(), block.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(block == null) block = Blocks.conveyor;
|
||||
}
|
||||
}
|
||||
|
||||
/** Produce a certain amount of a unit. */
|
||||
@@ -411,6 +441,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.buildunit", count - state.rules.defaultTeam.data().countType(unit), unit.emoji(), unit.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(unit == null) unit = UnitTypes.dagger;
|
||||
}
|
||||
}
|
||||
|
||||
/** Produce a certain amount of units. */
|
||||
@@ -524,6 +559,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.destroyblock", block.emoji(), block.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(block == null) block = Blocks.router;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DestroyBlocksObjective extends MapObjective{
|
||||
@@ -559,6 +599,11 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public String text(){
|
||||
return Core.bundle.format("objective.destroyblocks", progress(), positions.length, block.emoji(), block.localizedName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(){
|
||||
if(block == null) block = Blocks.router;
|
||||
}
|
||||
}
|
||||
|
||||
/** Command any unit to do anything. Always compete in headless mode. */
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package mindustry.game;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Json.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.io.versions.*;
|
||||
import mindustry.type.*;
|
||||
@@ -48,6 +48,8 @@ public class SpawnGroup implements JsonSerializable, Cloneable{
|
||||
public @Nullable StatusEffect effect;
|
||||
/** Items this unit spawns with. Null to disable. */
|
||||
public @Nullable ItemStack items;
|
||||
/** Team that units spawned use. Null for default wave team. */
|
||||
public @Nullable Team team;
|
||||
|
||||
public SpawnGroup(UnitType type){
|
||||
this.type = type;
|
||||
@@ -75,12 +77,9 @@ public class SpawnGroup implements JsonSerializable, Cloneable{
|
||||
return Math.max(shields + shieldScaling*(wave - begin), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unit, and assigns correct values based on this group's data.
|
||||
* This method does not add() the unit.
|
||||
*/
|
||||
public Unit createUnit(Team team, int wave){
|
||||
Unit unit = type.create(team);
|
||||
/** Creates a unit, and assigns correct values based on this group's data. */
|
||||
public Unit createUnit(Team team, float x, float y, float rotation, int wave, Cons<Unit> cons){
|
||||
Unit unit = type.spawn(team, x, y, rotation, cons);
|
||||
|
||||
if(effect != null){
|
||||
unit.apply(effect, 999999f);
|
||||
@@ -104,6 +103,11 @@ public class SpawnGroup implements JsonSerializable, Cloneable{
|
||||
return unit;
|
||||
}
|
||||
|
||||
/** Creates a unit, and assigns correct values based on this group's data. */
|
||||
public Unit createUnit(Team team, int wave){
|
||||
return createUnit(team, 0f, 0f, 0f, wave, u -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Json json){
|
||||
if(type == null) type = UnitTypes.dagger;
|
||||
@@ -120,7 +124,7 @@ public class SpawnGroup implements JsonSerializable, Cloneable{
|
||||
if(spawn != -1) json.writeValue("spawn", spawn);
|
||||
if(payloads != null && payloads.any()) json.writeValue("payloads", payloads.map(u -> u.name).toArray(String.class));
|
||||
if(items != null && items.amount > 0) json.writeValue("items", items);
|
||||
|
||||
if(team != null) json.writeValue("team", team.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,6 +144,7 @@ public class SpawnGroup implements JsonSerializable, Cloneable{
|
||||
spawn = data.getInt("spawn", -1);
|
||||
if(data.has("payloads")) payloads = Seq.with(json.readValue(String[].class, data.get("payloads"))).map(content::unit).removeAll(t -> t == null);
|
||||
if(data.has("items")) items = json.readValue(ItemStack.class, data.get("items"));
|
||||
if(data.has("team")) team = Team.get(data.getInt("team"));
|
||||
|
||||
|
||||
//old boss effect ID
|
||||
|
||||
@@ -19,6 +19,7 @@ public class Team implements Comparable<Team>, Senseable{
|
||||
public final Color color = new Color();
|
||||
public final Color[] palette = {new Color(), new Color(), new Color()};
|
||||
public final int[] palettei = new int[3];
|
||||
public boolean ignoreUnitCap = false;
|
||||
public String emoji = "";
|
||||
public boolean hasPalette;
|
||||
public String name;
|
||||
@@ -50,6 +51,8 @@ public class Team implements Comparable<Team>, Senseable{
|
||||
new Team(i, "team#" + i, Color.HSVtoRGB(360f * Mathf.random(), 100f * Mathf.random(0.4f, 1f), 100f * Mathf.random(0.6f, 1f), 1f));
|
||||
}
|
||||
Mathf.rand.setSeed(new Rand().nextLong());
|
||||
|
||||
neoplastic.ignoreUnitCap = true;
|
||||
}
|
||||
|
||||
public static Team get(int id){
|
||||
@@ -95,10 +98,16 @@ public class Team implements Comparable<Team>, Senseable{
|
||||
return data().core();
|
||||
}
|
||||
|
||||
/** @return whether this team has any buildings on this map; in waves mode, this is always true for the enemy team. */
|
||||
public boolean active(){
|
||||
return state.teams.isActive(this);
|
||||
}
|
||||
|
||||
/** @return whether this team has any active cores. Not the same as active()! */
|
||||
public boolean isAlive(){
|
||||
return data().isAlive();
|
||||
}
|
||||
|
||||
/** @return whether this team is supposed to be AI-controlled. */
|
||||
public boolean isAI(){
|
||||
return (state.rules.waves || state.rules.attackMode) && this != state.rules.defaultTeam && !state.rules.pvp;
|
||||
@@ -114,12 +123,6 @@ public class Team implements Comparable<Team>, Senseable{
|
||||
return isAI() && !rules().rtsAi;
|
||||
}
|
||||
|
||||
/** @deprecated There is absolutely no reason to use this. */
|
||||
@Deprecated
|
||||
public boolean isEnemy(Team other){
|
||||
return this != other;
|
||||
}
|
||||
|
||||
public Seq<CoreBuild> cores(){
|
||||
return state.teams.cores(this);
|
||||
}
|
||||
@@ -161,6 +164,7 @@ public class Team implements Comparable<Team>, Senseable{
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
if(sensor == LAccess.id) return id;
|
||||
return 0;
|
||||
if(sensor == LAccess.color) return color.toDoubleBits();
|
||||
return Double.NaN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,15 @@ public class Teams{
|
||||
return active;
|
||||
}
|
||||
|
||||
public void updateActive(Team team){
|
||||
TeamData data = get(team);
|
||||
//register in active list if needed
|
||||
if(data.active() && !active.contains(data)){
|
||||
active.add(data);
|
||||
updateEnemies();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerCore(CoreBuild core){
|
||||
TeamData data = get(core.team);
|
||||
//add core if not present
|
||||
@@ -407,13 +416,18 @@ public class Teams{
|
||||
}
|
||||
|
||||
public boolean active(){
|
||||
return (team == state.rules.waveTeam && state.rules.waves) || cores.size > 0;
|
||||
return (team == state.rules.waveTeam && state.rules.waves) || cores.size > 0 || buildings.size > 0 || (team == Team.neoplastic && units.size > 0);
|
||||
}
|
||||
|
||||
public boolean hasCore(){
|
||||
return cores.size > 0;
|
||||
}
|
||||
|
||||
/** @return whether this team has any cores (standard team), or any hearts (neoplasm). */
|
||||
public boolean isAlive(){
|
||||
return hasCore();
|
||||
}
|
||||
|
||||
public boolean noCores(){
|
||||
return cores.isEmpty();
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ public class BlockRenderer{
|
||||
private IntSet procLinks = new IntSet(), procLights = new IntSet();
|
||||
|
||||
private BlockQuadtree blockTree = new BlockQuadtree(new Rect(0, 0, 1, 1));
|
||||
private BlockLightQuadtree blockLightTree = new BlockLightQuadtree(new Rect(0, 0, 1, 1));
|
||||
private FloorQuadtree floorTree = new FloorQuadtree(new Rect(0, 0, 1, 1));
|
||||
|
||||
public BlockRenderer(){
|
||||
@@ -64,7 +65,9 @@ public class BlockRenderer{
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
blockTree = new BlockQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
blockLightTree = new BlockLightQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
floorTree = new FloorQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
|
||||
shadowEvents.clear();
|
||||
updateFloors.clear();
|
||||
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
||||
@@ -93,7 +96,7 @@ public class BlockRenderer{
|
||||
tile.build.wasVisible = true;
|
||||
}
|
||||
|
||||
if(tile.block().hasShadow && (tile.build == null || tile.build.wasVisible)){
|
||||
if(tile.block().displayShadow(tile) && (tile.build == null || tile.build.wasVisible)){
|
||||
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
|
||||
}
|
||||
}
|
||||
@@ -116,7 +119,10 @@ public class BlockRenderer{
|
||||
Events.on(TilePreChangeEvent.class, event -> {
|
||||
if(blockTree == null || floorTree == null) return;
|
||||
|
||||
if(indexBlock(event.tile)) blockTree.remove(event.tile);
|
||||
if(indexBlock(event.tile)){
|
||||
blockTree.remove(event.tile);
|
||||
blockLightTree.remove(event.tile);
|
||||
}
|
||||
if(indexFloor(event.tile)) floorTree.remove(event.tile);
|
||||
});
|
||||
|
||||
@@ -209,7 +215,10 @@ public class BlockRenderer{
|
||||
}
|
||||
|
||||
void recordIndex(Tile tile){
|
||||
if(indexBlock(tile)) blockTree.insert(tile);
|
||||
if(indexBlock(tile)){
|
||||
blockTree.insert(tile);
|
||||
blockLightTree.insert(tile);
|
||||
}
|
||||
if(indexFloor(tile)) floorTree.insert(tile);
|
||||
}
|
||||
|
||||
@@ -295,7 +304,7 @@ public class BlockRenderer{
|
||||
for(Tile tile : shadowEvents){
|
||||
if(tile == null) continue;
|
||||
//draw white/shadow color depending on blend
|
||||
Draw.color((!tile.block().hasShadow || (state.rules.fog && tile.build != null && !tile.build.wasVisible)) ? Color.white : blendShadowColor);
|
||||
Draw.color((!tile.block().displayShadow(tile) || (state.rules.fog && tile.build != null && !tile.build.wasVisible)) ? Color.white : blendShadowColor);
|
||||
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
|
||||
}
|
||||
|
||||
@@ -354,16 +363,17 @@ public class BlockRenderer{
|
||||
//draw floor lights
|
||||
floorTree.intersect(bounds, lightview::add);
|
||||
|
||||
blockLightTree.intersect(bounds, tile -> {
|
||||
if(tile.block().emitLight && (tile.build == null || procLights.add(tile.build.pos()))){
|
||||
lightview.add(tile);
|
||||
}
|
||||
});
|
||||
|
||||
blockTree.intersect(bounds, tile -> {
|
||||
if(tile.build == null || procLinks.add(tile.build.id)){
|
||||
tileview.add(tile);
|
||||
}
|
||||
|
||||
//lights are drawn even in the expanded range
|
||||
if(((tile.build != null && procLights.add(tile.build.pos())) || tile.block().emitLight)){
|
||||
lightview.add(tile);
|
||||
}
|
||||
|
||||
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
|
||||
for(Building other : tile.build.getPowerConnections(outArray2)){
|
||||
if(other.block instanceof PowerNode && procLinks.add(other.id)){ //TODO need a generic way to render connections!
|
||||
@@ -519,6 +529,24 @@ public class BlockRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
static class BlockLightQuadtree extends QuadTree<Tile>{
|
||||
|
||||
public BlockLightQuadtree(Rect bounds){
|
||||
super(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Tile tile){
|
||||
var block = tile.block();
|
||||
tmp.setCentered(tile.worldx() + block.offset, tile.worldy() + block.offset, block.lightClipSize, block.lightClipSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuadTree<Tile> newChild(Rect rect){
|
||||
return new BlockLightQuadtree(rect);
|
||||
}
|
||||
}
|
||||
|
||||
static class FloorQuadtree extends QuadTree<Tile>{
|
||||
|
||||
public FloorQuadtree(Rect bounds){
|
||||
@@ -528,7 +556,7 @@ public class BlockRenderer{
|
||||
@Override
|
||||
public void hitbox(Tile tile){
|
||||
var floor = tile.floor();
|
||||
tmp.setCentered(tile.worldx(), tile.worldy(), floor.clipSize, floor.clipSize);
|
||||
tmp.setCentered(tile.worldx(), tile.worldy(), floor.lightClipSize, floor.lightClipSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ public class CacheLayer{
|
||||
public static CacheLayer[] all = {};
|
||||
|
||||
public int id;
|
||||
public boolean liquid;
|
||||
|
||||
/** Registers cache layers that will render before the 'normal' layer. */
|
||||
public static void add(CacheLayer... layers){
|
||||
@@ -66,7 +67,7 @@ public class CacheLayer{
|
||||
slag = new ShaderLayer(Shaders.slag),
|
||||
arkycite = new ShaderLayer(Shaders.arkycite),
|
||||
cryofluid = new ShaderLayer(Shaders.cryofluid),
|
||||
space = new ShaderLayer(Shaders.space),
|
||||
space = new ShaderLayer(Shaders.space, false),
|
||||
normal = new CacheLayer(),
|
||||
walls = new CacheLayer()
|
||||
);
|
||||
@@ -86,7 +87,11 @@ public class CacheLayer{
|
||||
public @Nullable Shader shader;
|
||||
|
||||
public ShaderLayer(Shader shader){
|
||||
//shader will be null on headless backend, but that's ok
|
||||
this(shader, true);
|
||||
}
|
||||
|
||||
public ShaderLayer(@Nullable Shader shader, boolean liquid){
|
||||
this.liquid = liquid;
|
||||
this.shader = shader;
|
||||
}
|
||||
|
||||
@@ -94,7 +99,6 @@ public class CacheLayer{
|
||||
public void begin(){
|
||||
if(!Core.settings.getBool("animatedwater")) return;
|
||||
|
||||
renderer.blocks.floor.endc();
|
||||
renderer.effectBuffer.begin();
|
||||
Core.graphics.clear(Color.clear);
|
||||
renderer.blocks.floor.beginc();
|
||||
@@ -104,11 +108,8 @@ public class CacheLayer{
|
||||
public void end(){
|
||||
if(!Core.settings.getBool("animatedwater")) return;
|
||||
|
||||
renderer.blocks.floor.endc();
|
||||
renderer.effectBuffer.end();
|
||||
|
||||
renderer.effectBuffer.blit(shader);
|
||||
|
||||
renderer.blocks.floor.beginc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ public class Drawf{
|
||||
}
|
||||
}
|
||||
|
||||
public static void underwater(Runnable run){
|
||||
renderer.blocks.floor.drawUnderwater(run);
|
||||
}
|
||||
|
||||
//TODO offset unused
|
||||
public static void flame(float x, float y, int divisions, float rotation, float length, float width, float pan){
|
||||
float len1 = length * pan, len2 = length * (1f - pan);
|
||||
@@ -130,6 +134,17 @@ public class Drawf{
|
||||
additive(region, color, 1f, x, y, rotation, layer);
|
||||
}
|
||||
|
||||
public static void additive(TextureRegion region, Color color, float alpha, float x, float y, float width, float height, float layer){
|
||||
float pz = Draw.z();
|
||||
Draw.z(layer);
|
||||
Draw.color(color, alpha * color.a);
|
||||
Draw.blend(Blending.additive);
|
||||
Draw.rect(region, x, y, width, height, 0f);
|
||||
Draw.blend();
|
||||
Draw.color();
|
||||
Draw.z(pz);
|
||||
}
|
||||
|
||||
public static void additive(TextureRegion region, Color color, float alpha, float x, float y, float rotation, float layer){
|
||||
float pz = Draw.z();
|
||||
Draw.z(layer);
|
||||
@@ -141,6 +156,17 @@ public class Drawf{
|
||||
Draw.z(pz);
|
||||
}
|
||||
|
||||
public static void additive(TextureRegion region, Color color, float alpha, float x, float y, float rotation, float layer, float originX, float originY){
|
||||
float pz = Draw.z(), w = region.width * region.scl() * Draw.xscl, h = region.height * region.scl() * Draw.yscl;
|
||||
Draw.z(layer);
|
||||
Draw.color(color, alpha * color.a);
|
||||
Draw.blend(Blending.additive);
|
||||
Draw.rect(region, x, y, w, h, w / 2f + originX * region.scl() * Draw.xscl, h / 2f + originY * region.scl() * Draw.yscl, rotation);
|
||||
Draw.blend();
|
||||
Draw.color();
|
||||
Draw.z(pz);
|
||||
}
|
||||
|
||||
public static void limitLine(Position start, Position dest, float len1, float len2, Color color){
|
||||
if(start.within(dest, len1 + len2)){
|
||||
return;
|
||||
@@ -267,7 +293,7 @@ public class Drawf{
|
||||
}
|
||||
|
||||
public static void selected(Building tile, Color color){
|
||||
selected(tile.tile(), color);
|
||||
selected(tile.tile, color);
|
||||
}
|
||||
|
||||
public static void selected(Tile tile, Color color){
|
||||
|
||||
@@ -42,21 +42,29 @@ public class FloorRenderer{
|
||||
private static final boolean dynamic = false;
|
||||
|
||||
private float[] vertices = new float[maxSprites * vertexSize * 4];
|
||||
private short[] indices = new short[maxSprites * 6];
|
||||
private int vidx;
|
||||
private FloorRenderBatch batch = new FloorRenderBatch();
|
||||
private Shader shader;
|
||||
private Texture texture;
|
||||
private TextureRegion error;
|
||||
|
||||
private Mesh[][][] cache;
|
||||
private IndexData indexData;
|
||||
private ChunkMesh[][][] cache;
|
||||
private IntSet drawnLayerSet = new IntSet();
|
||||
private IntSet recacheSet = new IntSet();
|
||||
private IntSeq drawnLayers = new IntSeq();
|
||||
private ObjectSet<CacheLayer> used = new ObjectSet<>();
|
||||
|
||||
private Seq<Runnable> underwaterDraw = new Seq<>(Runnable.class);
|
||||
//alpha value of pixels cannot exceed the alpha of the surface they're being drawn on
|
||||
private Blending underwaterBlend = new Blending(
|
||||
Gl.srcAlpha, Gl.oneMinusSrcAlpha,
|
||||
Gl.dstAlpha, Gl.oneMinusSrcAlpha
|
||||
);
|
||||
|
||||
public FloorRenderer(){
|
||||
short j = 0;
|
||||
short[] indices = new short[maxSprites * 6];
|
||||
for(int i = 0; i < indices.length; i += 6, j += 4){
|
||||
indices[i] = j;
|
||||
indices[i + 1] = (short)(j + 1);
|
||||
@@ -66,6 +74,14 @@ public class FloorRenderer{
|
||||
indices[i + 5] = j;
|
||||
}
|
||||
|
||||
indexData = new IndexBufferObject(true, indices.length){
|
||||
@Override
|
||||
public void dispose(){
|
||||
//there is never a need to dispose this index buffer
|
||||
}
|
||||
};
|
||||
indexData.set(indices, 0, indices.length);
|
||||
|
||||
shader = new Shader(
|
||||
"""
|
||||
attribute vec4 a_position;
|
||||
@@ -121,6 +137,8 @@ public class FloorRenderer{
|
||||
drawnLayers.clear();
|
||||
drawnLayerSet.clear();
|
||||
|
||||
Rect bounds = camera.bounds(Tmp.r3);
|
||||
|
||||
//preliminary layer check
|
||||
for(int x = minx; x <= maxx; x++){
|
||||
for(int y = miny; y <= maxy; y++){
|
||||
@@ -131,11 +149,11 @@ public class FloorRenderer{
|
||||
cacheChunk(x, y);
|
||||
}
|
||||
|
||||
Mesh[] chunk = cache[x][y];
|
||||
ChunkMesh[] chunk = cache[x][y];
|
||||
|
||||
//loop through all layers, and add layer index if it exists
|
||||
for(int i = 0; i < layers; i++){
|
||||
if(chunk[i] != null && i != CacheLayer.walls.id){
|
||||
if(chunk[i] != null && i != CacheLayer.walls.id && chunk[i].bounds.overlaps(bounds)){
|
||||
drawnLayerSet.add(i);
|
||||
}
|
||||
}
|
||||
@@ -149,16 +167,13 @@ public class FloorRenderer{
|
||||
|
||||
drawnLayers.sort();
|
||||
|
||||
Draw.flush();
|
||||
beginDraw();
|
||||
|
||||
for(int i = 0; i < drawnLayers.size; i++){
|
||||
CacheLayer layer = CacheLayer.all[drawnLayers.get(i)];
|
||||
|
||||
drawLayer(layer);
|
||||
drawLayer(CacheLayer.all[drawnLayers.get(i)]);
|
||||
}
|
||||
|
||||
endDraw();
|
||||
underwaterDraw.clear();
|
||||
}
|
||||
|
||||
public void beginc(){
|
||||
@@ -167,29 +182,6 @@ public class FloorRenderer{
|
||||
|
||||
//only ever use the base environment texture
|
||||
texture.bind(0);
|
||||
|
||||
//enable all mesh attributes; TODO remove once the attribute cache bug is fixed
|
||||
if(Core.gl30 == null){
|
||||
for(VertexAttribute attribute : attributes){
|
||||
int loc = shader.getAttributeLocation(attribute.alias);
|
||||
if(loc != -1) Gl.enableVertexAttribArray(loc);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void endc(){
|
||||
//disable all mesh attributes; TODO remove once the attribute cache bug is fixed
|
||||
if(Core.gl30 == null){
|
||||
for(VertexAttribute attribute : attributes){
|
||||
int loc = shader.getAttributeLocation(attribute.alias);
|
||||
if(loc != -1) Gl.disableVertexAttribArray(loc);
|
||||
}
|
||||
}
|
||||
|
||||
//unbind last buffer
|
||||
Gl.bindBuffer(Gl.arrayBuffer, 0);
|
||||
Gl.bindBuffer(Gl.elementArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void checkChanges(){
|
||||
@@ -205,6 +197,10 @@ public class FloorRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
public void drawUnderwater(Runnable run){
|
||||
underwaterDraw.add(run);
|
||||
}
|
||||
|
||||
public void beginDraw(){
|
||||
if(cache == null){
|
||||
return;
|
||||
@@ -217,14 +213,6 @@ public class FloorRenderer{
|
||||
Gl.enable(Gl.blend);
|
||||
}
|
||||
|
||||
public void endDraw(){
|
||||
if(cache == null){
|
||||
return;
|
||||
}
|
||||
|
||||
endc();
|
||||
}
|
||||
|
||||
public void drawLayer(CacheLayer layer){
|
||||
if(cache == null){
|
||||
return;
|
||||
@@ -240,6 +228,8 @@ public class FloorRenderer{
|
||||
|
||||
layer.begin();
|
||||
|
||||
Rect bounds = camera.bounds(Tmp.r3);
|
||||
|
||||
for(int x = minx; x <= maxx; x++){
|
||||
for(int y = miny; y <= maxy; y++){
|
||||
|
||||
@@ -249,33 +239,29 @@ public class FloorRenderer{
|
||||
|
||||
var mesh = cache[x][y][layer.id];
|
||||
|
||||
//this *must* be a vertexbufferobject on gles2, so cast it and render it directly
|
||||
if(mesh != null && mesh.vertices instanceof VertexBufferObject vbo && mesh.indices instanceof IndexBufferObject ibo){
|
||||
|
||||
//bindi the buffer and update its contents, but do not unnecessarily enable all the attributes again
|
||||
vbo.bind();
|
||||
//set up vertex attribute pointers for this specific VBO
|
||||
int offset = 0;
|
||||
for(VertexAttribute attribute : attributes){
|
||||
int location = shader.getAttributeLocation(attribute.alias);
|
||||
int aoffset = offset;
|
||||
offset += attribute.size;
|
||||
if(location < 0) continue;
|
||||
|
||||
Gl.vertexAttribPointer(location, attribute.components, attribute.type, attribute.normalized, vertexSize * 4, aoffset);
|
||||
}
|
||||
|
||||
ibo.bind();
|
||||
|
||||
mesh.vertices.render(mesh.indices, Gl.triangles, 0, mesh.getNumIndices());
|
||||
}else if(mesh != null){
|
||||
//TODO this should be the default branch!
|
||||
mesh.bind(shader);
|
||||
mesh.render(shader, Gl.triangles);
|
||||
if(mesh != null && mesh.bounds.overlaps(bounds)){
|
||||
mesh.render(shader, Gl.triangles, 0, mesh.getMaxVertices() * 6 / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//every underwater object needs to be drawn once per cache layer, which sucks.
|
||||
if(layer.liquid && underwaterDraw.size > 0){
|
||||
|
||||
Draw.blend(underwaterBlend);
|
||||
|
||||
var items = underwaterDraw.items;
|
||||
int len = underwaterDraw.size;
|
||||
for(int i = 0; i < len; i++){
|
||||
items[i].run();
|
||||
}
|
||||
|
||||
Draw.flush();
|
||||
Draw.blend(Blending.normal);
|
||||
Blending.normal.apply();
|
||||
beginDraw();
|
||||
}
|
||||
|
||||
layer.end();
|
||||
}
|
||||
|
||||
@@ -298,7 +284,7 @@ public class FloorRenderer{
|
||||
}
|
||||
|
||||
if(cache[cx][cy].length == 0){
|
||||
cache[cx][cy] = new Mesh[CacheLayer.all.length];
|
||||
cache[cx][cy] = new ChunkMesh[CacheLayer.all.length];
|
||||
}
|
||||
|
||||
var meshes = cache[cx][cy];
|
||||
@@ -315,7 +301,7 @@ public class FloorRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
private Mesh cacheChunkLayer(int cx, int cy, CacheLayer layer){
|
||||
private ChunkMesh cacheChunkLayer(int cx, int cy, CacheLayer layer){
|
||||
vidx = 0;
|
||||
|
||||
Batch current = Core.batch;
|
||||
@@ -345,13 +331,13 @@ public class FloorRenderer{
|
||||
Core.batch = current;
|
||||
|
||||
int floats = vidx;
|
||||
//every 4 vertices need 6 indices
|
||||
int vertCount = floats / vertexSize, indCount = vertCount * 6/4;
|
||||
ChunkMesh mesh = new ChunkMesh(true, floats / vertexSize, 0, attributes,
|
||||
cx * tilesize * chunksize - tilesize/2f, cy * tilesize * chunksize - tilesize/2f,
|
||||
(cx+1) * tilesize * chunksize + tilesize/2f, (cy+1) * tilesize * chunksize + tilesize/2f);
|
||||
|
||||
Mesh mesh = new Mesh(true, vertCount, indCount, attributes);
|
||||
mesh.setVertices(vertices, 0, vidx);
|
||||
mesh.setAutoBind(false);
|
||||
mesh.setIndices(indices, 0, indCount);
|
||||
//all vertices are shared
|
||||
mesh.indices = indexData;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
@@ -372,7 +358,7 @@ public class FloorRenderer{
|
||||
|
||||
recacheSet.clear();
|
||||
int chunksx = Mathf.ceil((float)(world.width()) / chunksize), chunksy = Mathf.ceil((float)(world.height()) / chunksize);
|
||||
cache = new Mesh[chunksx][chunksy][dynamic ? 0 : CacheLayer.all.length];
|
||||
cache = new ChunkMesh[chunksx][chunksy][dynamic ? 0 : CacheLayer.all.length];
|
||||
|
||||
texture = Core.atlas.find("grass1").texture;
|
||||
error = Core.atlas.find("env-error");
|
||||
@@ -391,7 +377,28 @@ public class FloorRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
static class ChunkMesh extends Mesh{
|
||||
Rect bounds = new Rect();
|
||||
|
||||
ChunkMesh(boolean isStatic, int maxVertices, int maxIndices, VertexAttribute[] attributes, float minX, float minY, float maxX, float maxY){
|
||||
super(isStatic, maxVertices, maxIndices, attributes);
|
||||
|
||||
bounds.set(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
}
|
||||
|
||||
class FloorRenderBatch extends Batch{
|
||||
//TODO: alternate clipping approach, can be more accurate
|
||||
/*
|
||||
float minX, minY, maxX, maxY;
|
||||
|
||||
void reset(){
|
||||
minX = Float.POSITIVE_INFINITY;
|
||||
minY = Float.POSITIVE_INFINITY;
|
||||
maxX = 0f;
|
||||
maxY = 0f;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation){
|
||||
|
||||
@@ -32,6 +32,8 @@ public class LightRenderer{
|
||||
public void add(float x, float y, float radius, Color color, float opacity){
|
||||
if(!enabled() || radius <= 0f) return;
|
||||
|
||||
//TODO: clipping.
|
||||
|
||||
float res = Color.toFloatBits(color.r, color.g, color.b, opacity);
|
||||
|
||||
if(circles.size <= circleIndex) circles.add(new CircleLight());
|
||||
|
||||
@@ -11,6 +11,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.noise.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
@@ -239,35 +240,17 @@ public class MenuRenderer implements Disposable{
|
||||
}
|
||||
|
||||
private void drawFlyers(){
|
||||
Draw.color(0f, 0f, 0f, 0.4f);
|
||||
|
||||
TextureRegion icon = flyerType.fullIcon;
|
||||
flyerType.sample.elevation = 1f;
|
||||
flyerType.sample.team = Team.sharded;
|
||||
flyerType.sample.rotation = flyerRot;
|
||||
flyerType.sample.heal();
|
||||
|
||||
flyers((x, y) -> {
|
||||
Draw.rect(icon, x - 12f, y - 13f, flyerRot - 90);
|
||||
flyerType.sample.set(x, y);
|
||||
flyerType.drawShadow(flyerType.sample);
|
||||
flyerType.draw(flyerType.sample);
|
||||
});
|
||||
|
||||
float size = Math.max(icon.width, icon.height) * icon.scl() * 1.6f;
|
||||
|
||||
flyers((x, y) -> {
|
||||
Draw.rect("circle-shadow", x, y, size, size);
|
||||
});
|
||||
Draw.color();
|
||||
|
||||
flyers((x, y) -> {
|
||||
float engineOffset = flyerType.engineOffset, engineSize = flyerType.engineSize, rotation = flyerRot;
|
||||
|
||||
Draw.color(Pal.engine);
|
||||
Fill.circle(x + Angles.trnsx(rotation + 180, engineOffset), y + Angles.trnsy(rotation + 180, engineOffset),
|
||||
engineSize + Mathf.absin(Time.time, 2f, engineSize / 4f));
|
||||
|
||||
Draw.color(Color.white);
|
||||
Fill.circle(x + Angles.trnsx(rotation + 180, engineOffset - 1f), y + Angles.trnsy(rotation + 180, engineOffset - 1f),
|
||||
(engineSize + Mathf.absin(Time.time, 2f, engineSize / 4f)) / 2f);
|
||||
Draw.color();
|
||||
|
||||
Draw.rect(icon, x, y, flyerRot - 90);
|
||||
});
|
||||
}
|
||||
|
||||
private void flyers(Floatc2 cons){
|
||||
|
||||
@@ -30,8 +30,6 @@ public class MinimapRenderer{
|
||||
private Rect rect = new Rect();
|
||||
private float zoom = 4;
|
||||
|
||||
private float lastX, lastY, lastW, lastH, lastScl;
|
||||
private boolean worldSpace;
|
||||
private IntSet updates = new IntSet();
|
||||
private float updateCounter = 0f;
|
||||
|
||||
@@ -123,13 +121,7 @@ public class MinimapRenderer{
|
||||
region = new TextureRegion(texture);
|
||||
}
|
||||
|
||||
public void drawEntities(float x, float y, float w, float h, float scaling, boolean fullView){
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
lastW = w;
|
||||
lastH = h;
|
||||
lastScl = scaling;
|
||||
worldSpace = fullView;
|
||||
public void drawEntities(float x, float y, float w, float h, boolean fullView){
|
||||
|
||||
if(!fullView){
|
||||
updateUnitArray();
|
||||
@@ -150,12 +142,12 @@ public class MinimapRenderer{
|
||||
|
||||
float scaleFactor;
|
||||
var trans = Tmp.m1.idt();
|
||||
trans.translate(lastX, lastY);
|
||||
if(!worldSpace){
|
||||
trans.scl(Tmp.v1.set(scaleFactor = lastW / rect.width, lastH / rect.height));
|
||||
trans.translate(x, y);
|
||||
if(!fullView){
|
||||
trans.scl(Tmp.v1.set(scaleFactor = w / rect.width, h / rect.height));
|
||||
trans.translate(-rect.x, -rect.y);
|
||||
}else{
|
||||
trans.scl(Tmp.v1.set(scaleFactor = lastW / world.unitWidth(), lastH / world.unitHeight()));
|
||||
trans.scl(Tmp.v1.set(scaleFactor = w / world.unitWidth(), h / world.unitHeight()));
|
||||
}
|
||||
trans.translate(tilesize / 2f, tilesize / 2f);
|
||||
Draw.trans(trans);
|
||||
|
||||
@@ -131,7 +131,7 @@ public class OverlayRenderer{
|
||||
Building build = (select instanceof BlockUnitc b ? b.tile() : select instanceof Building b ? b : null);
|
||||
TextureRegion region = build != null ? build.block.fullIcon : Core.atlas.white();
|
||||
|
||||
if(!(select instanceof Unitc)){
|
||||
if(select instanceof BlockUnitc){
|
||||
Draw.rect(region, select.getX(), select.getY());
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ public class Pal{
|
||||
neoplasm1 = Color.valueOf("f98f4a"),
|
||||
neoplasmMid = Color.valueOf("e05438"),
|
||||
neoplasm2 = Color.valueOf("9e172c"),
|
||||
neoplasmAcid = Color.valueOf("8ead44"),
|
||||
neoplasmAcidGlow = Color.valueOf("68e43e"),
|
||||
|
||||
logicBlocks = Color.valueOf("d4816b"),
|
||||
logicControl = Color.valueOf("6bb2b2"),
|
||||
|
||||
@@ -18,8 +18,6 @@ public class PlanetParams{
|
||||
public Vec3 camUp = new Vec3(0f, 1f, 0f);
|
||||
/** the unit length direction vector of the camera **/
|
||||
public Vec3 camDir = new Vec3(0, 0, -1);
|
||||
/** The sun/main planet of the solar system from which everything is rendered. Deprecated use planet.solarSystem instead */
|
||||
public @Deprecated Planet solarSystem = Planets.sun;
|
||||
/** Planet being looked at. */
|
||||
public Planet planet = Planets.serpulo;
|
||||
|
||||
|
||||
@@ -474,7 +474,7 @@ public class DesktopInput extends InputHandler{
|
||||
cursorType = cursor.build.getCursor();
|
||||
}
|
||||
|
||||
if(canRepairDerelict(cursor)){
|
||||
if(canRepairDerelict(cursor) && !player.dead() && player.unit().canBuild()){
|
||||
cursorType = ui.repairCursor;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,6 +312,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
//only assign a group when this is not a queued command
|
||||
if(ai.commandQueue.size == 0 && unitIds.length > 1){
|
||||
int layer = unit.collisionLayer();
|
||||
|
||||
if(layer == -1) layer = 0;
|
||||
|
||||
if(groups[layer] == null){
|
||||
groups[layer] = new UnitGroup();
|
||||
}
|
||||
@@ -418,7 +421,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, itemTransferRange) || player.dead() || amount <= 0) return;
|
||||
|
||||
if(net.server() && (!Units.canInteract(player, build) ||
|
||||
!netServer.admins.allowAction(player, ActionType.withdrawItem, build.tile(), action -> {
|
||||
!netServer.admins.allowAction(player, ActionType.withdrawItem, build.tile, action -> {
|
||||
action.item = item;
|
||||
action.itemAmount = amount;
|
||||
}))){
|
||||
@@ -606,7 +609,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(build == null) return;
|
||||
|
||||
if(net.server() && (!Units.canInteract(player, build) ||
|
||||
!netServer.admins.allowAction(player, ActionType.rotate, build.tile(), action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
|
||||
!netServer.admins.allowAction(player, ActionType.rotate, build.tile, action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
|
||||
throw new ValidateException(player, "Player cannot rotate a block.");
|
||||
}
|
||||
|
||||
@@ -693,7 +696,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(unit == null){ //just clear the unit (is this used?)
|
||||
player.clearUnit();
|
||||
//make sure it's AI controlled, so players can't overwrite each other
|
||||
}else if(unit.isAI() && unit.team == player.team() && !unit.dead && unit.type.playerControllable){
|
||||
}else if(unit.isAI() && unit.team == player.team() && !unit.dead && unit.playerControllable()){
|
||||
if(net.client() && player.isLocal()){
|
||||
player.justSwitchFrom = player.unit();
|
||||
player.justSwitchTo = unit;
|
||||
@@ -878,7 +881,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
if(controlledType != null && player.dead() && controlledType.playerControllable){
|
||||
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.playerControllable() && !u.dead);
|
||||
|
||||
if(unit != null){
|
||||
//only trying controlling once a second to prevent packet spam
|
||||
@@ -1853,7 +1856,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
public @Nullable Unit selectedUnit(){
|
||||
Unit unit = Units.closest(player.team(), Core.input.mouseWorld().x, Core.input.mouseWorld().y, 40f, u -> u.isAI() && u.type.playerControllable);
|
||||
Unit unit = Units.closest(player.team(), Core.input.mouseWorld().x, Core.input.mouseWorld().y, 40f, u -> u.isAI() && u.playerControllable());
|
||||
if(unit != null){
|
||||
unit.hitbox(Tmp.r1);
|
||||
Tmp.r1.grow(6f);
|
||||
@@ -2053,7 +2056,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
return ignoreUnits ? Build.validPlaceIgnoreUnits(type, player.team(), x, y, rotation, true) : Build.validPlace(type, player.team(), x, y, rotation);
|
||||
return ignoreUnits ? Build.validPlaceIgnoreUnits(type, player.team(), x, y, rotation, true, true) : Build.validPlace(type, player.team(), x, y, rotation);
|
||||
}
|
||||
|
||||
public boolean validBreak(int x, int y){
|
||||
|
||||
@@ -706,7 +706,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
payloadTarget = null;
|
||||
|
||||
//control a unit/block detected on first tap of double-tap
|
||||
if(unitTapped != null && state.rules.possessionAllowed && unitTapped.isAI() && unitTapped.team == player.team() && !unitTapped.dead && unitTapped.type.playerControllable){
|
||||
if(unitTapped != null && state.rules.possessionAllowed && unitTapped.isAI() && unitTapped.team == player.team() && !unitTapped.dead && unitTapped.playerControllable()){
|
||||
Call.unitControl(player, unitTapped);
|
||||
recentRespawnTimer = 1f;
|
||||
}else if(buildingTapped != null && state.rules.possessionAllowed){
|
||||
|
||||
@@ -308,6 +308,7 @@ public class JsonIO{
|
||||
}
|
||||
|
||||
exec.all.add(obj);
|
||||
obj.validate();
|
||||
}
|
||||
|
||||
// Second iteration to map the parents.
|
||||
|
||||
@@ -382,6 +382,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
writeChunk(stream, true, out -> {
|
||||
out.writeByte(entity.classId());
|
||||
out.writeInt(entity.id());
|
||||
entity.beforeWrite();
|
||||
entity.write(Writes.get(out));
|
||||
});
|
||||
}
|
||||
@@ -436,8 +437,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
//entityMapping is null in older save versions, so use the default
|
||||
var mapping = this.entityMapping == null ? EntityMapping.idMap : this.entityMapping;
|
||||
|
||||
Seq<Entityc> entities = new Seq<>();
|
||||
|
||||
int amount = stream.readInt();
|
||||
for(int j = 0; j < amount; j++){
|
||||
readChunk(stream, true, in -> {
|
||||
@@ -450,7 +449,6 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
int id = in.readInt();
|
||||
|
||||
Entityc entity = (Entityc)mapping[typeid].get();
|
||||
entities.add(entity);
|
||||
EntityGroup.checkNextId(id);
|
||||
entity.id(id);
|
||||
entity.read(Reads.get(in));
|
||||
@@ -458,9 +456,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
});
|
||||
}
|
||||
|
||||
for(var e : entities){
|
||||
e.afterAllRead();
|
||||
}
|
||||
Groups.all.each(Entityc::afterReadAll);
|
||||
}
|
||||
|
||||
public void readEntityMapping(DataInput stream) throws IOException{
|
||||
|
||||
@@ -1434,7 +1434,7 @@ public class LExecutor{
|
||||
if(type.obj() instanceof UnitType type && !type.internal && !type.hidden && t != null && Units.canCreate(t, type)){
|
||||
//random offset to prevent stacking
|
||||
var unit = type.spawn(t, World.unconv(x.numf()) + Mathf.range(0.01f), World.unconv(y.numf()) + Mathf.range(0.01f));
|
||||
spawner.spawnEffect(unit, rotation.numf());
|
||||
spawner.spawnEffect(unit);
|
||||
result.setobj(unit);
|
||||
}
|
||||
}
|
||||
@@ -1603,11 +1603,12 @@ public class LExecutor{
|
||||
}else if(full){
|
||||
//disable the rule, covers the whole map
|
||||
if(set){
|
||||
int prevX = state.rules.limitX, prevY = state.rules.limitY, prevW = state.rules.limitWidth, prevH = state.rules.limitHeight;
|
||||
state.rules.limitMapArea = false;
|
||||
if(!headless){
|
||||
renderer.updateAllDarkness();
|
||||
}
|
||||
world.checkMapArea();
|
||||
world.checkMapArea(prevX, prevY, prevW, prevH);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1616,12 +1617,20 @@ public class LExecutor{
|
||||
}
|
||||
|
||||
if(set){
|
||||
int prevX = state.rules.limitX, prevY = state.rules.limitY, prevW = state.rules.limitWidth, prevH = state.rules.limitHeight;
|
||||
if(!state.rules.limitMapArea){
|
||||
//it was never on in the first place, so the old bounds don't apply
|
||||
prevW = 0;
|
||||
prevH = 0;
|
||||
prevX = -1;
|
||||
prevY = -1;
|
||||
}
|
||||
state.rules.limitMapArea = true;
|
||||
state.rules.limitX = x;
|
||||
state.rules.limitY = y;
|
||||
state.rules.limitWidth = w;
|
||||
state.rules.limitHeight = h;
|
||||
world.checkMapArea();
|
||||
world.checkMapArea(prevX, prevY, prevW, prevH);
|
||||
|
||||
if(!headless){
|
||||
renderer.updateAllDarkness();
|
||||
@@ -1933,9 +1942,7 @@ public class LExecutor{
|
||||
for(int i = 0; i < spawned; i++){
|
||||
Tmp.v1.rnd(spread);
|
||||
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
||||
Vars.spawner.spawnEffect(unit);
|
||||
spawner.spawnUnit(group, spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,8 @@ public class ContentParser{
|
||||
put(BulletType.class, (type, data) -> {
|
||||
if(data.isString()){
|
||||
return field(Bullets.class, data);
|
||||
}else if(data.isArray()){
|
||||
return new MultiBulletType(parser.readValue(BulletType[].class, data));
|
||||
}
|
||||
Class<?> bc = resolve(data.getString("type", ""), BasicBulletType.class);
|
||||
data.remove("type");
|
||||
|
||||
@@ -366,7 +366,7 @@ public class Mods implements Loadable{
|
||||
}
|
||||
Log.debug("Time to generate icons: @", Time.elapsed());
|
||||
|
||||
//dispose old atlas data
|
||||
//replace old atlas data
|
||||
Core.atlas = packer.flush(filter, new TextureAtlas(){
|
||||
PixmapRegion fake = new PixmapRegion(new Pixmap(1, 1));
|
||||
boolean didWarn = false;
|
||||
@@ -392,6 +392,8 @@ public class Mods implements Loadable{
|
||||
Log.debug("Total pages: @", Core.atlas.getTextures().size);
|
||||
|
||||
packer.printStats();
|
||||
|
||||
Events.fire(new AtlasPackEvent());
|
||||
}
|
||||
|
||||
packer.dispose();
|
||||
@@ -1151,7 +1153,7 @@ public class Mods implements Loadable{
|
||||
!skipModLoading() &&
|
||||
Core.settings.getBool("mod-" + baseName + "-enabled", true) &&
|
||||
Version.isAtLeast(meta.minGameVersion) &&
|
||||
(meta.getMinMajor() >= 136 || headless) &&
|
||||
(meta.getMinMajor() >= minJavaModGameVersion || headless) &&
|
||||
!skipModCode &&
|
||||
initialize
|
||||
){
|
||||
@@ -1249,7 +1251,7 @@ public class Mods implements Loadable{
|
||||
|
||||
/** @return whether this is a java class mod. */
|
||||
public boolean isJava(){
|
||||
return meta.java || main != null;
|
||||
return meta.java || main != null || meta.main != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -1292,10 +1294,10 @@ public class Mods implements Loadable{
|
||||
return blacklistedMods.contains(name);
|
||||
}
|
||||
|
||||
/** @return whether this mod is outdated, e.g. not compatible with v7. */
|
||||
/** @return whether this mod is outdated, i.e. not compatible with v8/v7. */
|
||||
public boolean isOutdated(){
|
||||
//must be at least 136 to indicate v7 compat
|
||||
return getMinMajor() < 136;
|
||||
return getMinMajor() < (isJava() ? minJavaModGameVersion : minModGameVersion);
|
||||
}
|
||||
|
||||
public int getMinMajor(){
|
||||
@@ -1397,11 +1399,6 @@ public class Mods implements Loadable{
|
||||
/** If set, load the mod content in this order by content names */
|
||||
public String[] contentOrder;
|
||||
|
||||
public String displayName(){
|
||||
//useless, kept for legacy reasons
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String shortDescription(){
|
||||
return Strings.truncate(subtitle == null ? (description == null || description.length() > maxModSubtitleLength ? "" : description) : subtitle, maxModSubtitleLength, "...");
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class GameService{
|
||||
if(campaign() && e.unit != null && e.unit.isLocal() && !e.breaking){
|
||||
SStat.blocksBuilt.add();
|
||||
|
||||
if(e.tile.block() == Blocks.router && e.tile.build.proximity().contains(t -> t.block == Blocks.router)){
|
||||
if(e.tile.block() == Blocks.router && e.tile.build.proximity.contains(t -> t.block == Blocks.router)){
|
||||
chainRouters.complete();
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
lightRadius = -1f,
|
||||
/** light color opacity*/
|
||||
lightOpacity = 0.6f,
|
||||
/** scale of soft shadow - its size is calculated based off of region size */
|
||||
softShadowScl = 1f,
|
||||
/** fog view radius in tiles. <0 for automatic radius. */
|
||||
fogRadius = -1f,
|
||||
|
||||
@@ -159,6 +161,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
faceTarget = true,
|
||||
/** AI flag: if true, this flying unit circles around its target like a bomber */
|
||||
circleTarget = false,
|
||||
/** AI flag: if true, this unit will drop bombs under itself even when it is not next to its 'real' target. used for carpet bombers */
|
||||
autoDropBombs = false,
|
||||
/** if true, this unit can boost into the air if a player/processors controls it*/
|
||||
canBoost = false,
|
||||
/** if true, this unit will always boost when using builder AI */
|
||||
@@ -199,7 +203,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
allowLegStep = false,
|
||||
/** for legged units, setting this to false forces it to be on the ground physics layer. */
|
||||
legPhysicsLayer = true,
|
||||
/** if true, this unit cannot drown, and will not be affected by the floor under it. */
|
||||
/** if true, this unit will not be affected by the floor under it. */
|
||||
hovering = false,
|
||||
/** if true, this unit can move in any direction regardless of rotation. if false, this unit can only move in the direction it is facing. */
|
||||
omniMovement = true,
|
||||
@@ -219,6 +223,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
hidden = false,
|
||||
/** if true, this unit is for internal use only and does not have a sprite generated. */
|
||||
internal = false,
|
||||
/** For certain units, generating sprites is still necessary, despite being internal. */
|
||||
internalGenerateSprites = false,
|
||||
/** If false, this unit is not pushed away from map edges. */
|
||||
bounded = true,
|
||||
/** if true, this unit is detected as naval - do NOT assign this manually! Initialized in init() */
|
||||
@@ -234,6 +240,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
hoverable = true,
|
||||
/** if true, this modded unit always has a -outline region generated for its base. Normally, outlines are ignored if there are no top = false weapons. */
|
||||
alwaysCreateOutline = false,
|
||||
/** for vanilla content only - if false, skips the full icon generation step. */
|
||||
generateFullIcon = true,
|
||||
/** if true, this unit has a square shadow. */
|
||||
squareShape = false,
|
||||
/** if true, this unit will draw its building beam towards blocks. */
|
||||
@@ -248,6 +256,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
drawShields = true,
|
||||
/** if false, the unit body is not drawn. */
|
||||
drawBody = true,
|
||||
/** if false, the soft shadow is not drawn. */
|
||||
drawSoftShadow = true,
|
||||
/** if false, the unit is not drawn on the minimap. */
|
||||
drawMinimap = true;
|
||||
|
||||
@@ -300,6 +310,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
/** override for engine trail color */
|
||||
public @Nullable Color trailColor;
|
||||
|
||||
/** Cost type ID for flow field/enemy AI pathfinding. */
|
||||
public int flowfieldPathType = -1;
|
||||
/** Function used for calculating cost of moving with ControlPathfinder. Does not affect "normal" flow field pathfinding. */
|
||||
public @Nullable PathCost pathCost;
|
||||
/** ID for path cost, to be used in the control path finder. This is the value that actually matters; do not assign manually. Set in init(). */
|
||||
@@ -386,12 +398,18 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
/** how straight the leg outward angles are (0 = circular, 1 = horizontal line) */
|
||||
legStraightness = 0f;
|
||||
|
||||
/** If true, the base (further away) leg region is drawn under instead of over. */
|
||||
public boolean legBaseUnder = false;
|
||||
/** If true, legs are locked to the base of the unit instead of being on an implicit rotating "mount". */
|
||||
public boolean lockLegBase = false;
|
||||
/** If true, legs always try to move around even when the unit is not moving (leads to more natural behavior) */
|
||||
public boolean legContinuousMove;
|
||||
/** TODO neither of these appear to do much */
|
||||
public boolean flipBackLegs = true, flipLegSide = false;
|
||||
/** Whether to emit a splashing noise in water. */
|
||||
public boolean emitWalkSound = true;
|
||||
/** Whether to emit a splashing effect in water (fasle implies emitWalkSound false). */
|
||||
public boolean emitWalkEffect = true;
|
||||
|
||||
//MECH UNITS
|
||||
|
||||
@@ -417,6 +435,14 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
/** number of independent segments */
|
||||
public int segments = 0;
|
||||
/** TODO wave support - for multi-unit segmented units, this is the number of independent units that are spawned */
|
||||
public int segmentUnits = 1;
|
||||
/** unit spawned in segments; if null, the same unit is used */
|
||||
public @Nullable UnitType segmentUnit;
|
||||
/** unit spawned at the end; if null, the segment unit is used */
|
||||
public @Nullable UnitType segmentEndUnit;
|
||||
/** true - parent segments are on higher layers; false - parent segments are on lower layers than head*/
|
||||
public boolean segmentLayerOrder = true;
|
||||
/** magnitude of sine offset between segments */
|
||||
public float segmentMag = 2f,
|
||||
/** scale of sine offset between segments */
|
||||
@@ -427,6 +453,10 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
segmentRotSpeed = 1f,
|
||||
/** maximum difference between segment angles */
|
||||
segmentMaxRot = 30f,
|
||||
/** spacing between separate unit segments (only used for multi-unit worms) */
|
||||
segmentSpacing = -1f,
|
||||
/** rotation between segments is clamped to this range */
|
||||
segmentRotationRange = 80f,
|
||||
/** speed multiplier this unit will have when crawlSlowdownFrac is met. */
|
||||
crawlSlowdown = 0.5f,
|
||||
/** damage dealt to blocks under this tank/crawler every frame. */
|
||||
@@ -485,13 +515,49 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
return unit;
|
||||
}
|
||||
|
||||
public Unit spawn(Team team, float x, float y){
|
||||
/** @param cons Callback that gets called with every unit that is spawned. This is used for multi-unit segmented units. */
|
||||
public Unit spawn(Team team, float x, float y, float rotation, @Nullable Cons<Unit> cons){
|
||||
float offsetX = 0f, offsetY = 0f;
|
||||
if(segmentUnits > 1 && sample instanceof Segmentc){
|
||||
Tmp.v1.trns(rotation, segmentSpacing * segmentUnits / 2f);
|
||||
offsetX = Tmp.v1.x;
|
||||
offsetY = Tmp.v1.y;
|
||||
}
|
||||
|
||||
Unit out = create(team);
|
||||
out.set(x, y);
|
||||
out.rotation = rotation;
|
||||
out.set(x + offsetX, y + offsetY);
|
||||
out.add();
|
||||
if(cons != null) cons.get(out);
|
||||
|
||||
if(segmentUnits > 1 && out instanceof Segmentc){
|
||||
Unit last = out;
|
||||
UnitType segType = segmentUnit == null ? this : segmentUnit;
|
||||
for(int i = 0; i < segmentUnits; i++){
|
||||
UnitType type = i == segmentUnits - 1 && segmentEndUnit != null ? segmentEndUnit : segType;
|
||||
|
||||
Unit next = type.create(team);
|
||||
Tmp.v1.trns(rotation, segmentSpacing * (i + 1));
|
||||
next.set(x - Tmp.v1.x + offsetX, y - Tmp.v1.y + offsetY);
|
||||
next.rotation = rotation;
|
||||
next.add();
|
||||
((Segmentc)last).addChild(next);
|
||||
|
||||
if(cons != null) cons.get(next);
|
||||
last = next;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public Unit spawn(Team team, float x, float y, float rotation){
|
||||
return spawn(team, x, y, rotation, null);
|
||||
}
|
||||
|
||||
public Unit spawn(Team team, float x, float y){
|
||||
return spawn(team, x, y, 0f);
|
||||
}
|
||||
|
||||
public Unit spawn(float x, float y){
|
||||
return spawn(state.rules.defaultTeam, x, y);
|
||||
}
|
||||
@@ -557,7 +623,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
}
|
||||
|
||||
if(payloadCapacity > 0 && unit instanceof Payloadc payload){
|
||||
bars.add(new Bar("stat.payloadcapacity", Pal.items, () -> payload.payloadUsed() / unit.type().payloadCapacity));
|
||||
bars.add(new Bar("stat.payloadcapacity", Pal.items, () -> payload.payloadUsed() / payloadCapacity));
|
||||
bars.row();
|
||||
|
||||
var count = new float[]{-1};
|
||||
@@ -699,12 +765,13 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
Unit example = constructor.get();
|
||||
|
||||
allowLegStep = example instanceof Legsc;
|
||||
allowLegStep = example instanceof Legsc || example instanceof Crawlc;
|
||||
|
||||
//water preset
|
||||
if(example instanceof WaterMovec){
|
||||
if(example instanceof WaterMovec || example instanceof WaterCrawlc){
|
||||
naval = true;
|
||||
canDrown = false;
|
||||
emitWalkSound = false;
|
||||
omniMovement = false;
|
||||
immunities.add(StatusEffects.wet);
|
||||
if(shadowElevation < 0f){
|
||||
@@ -712,10 +779,19 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
}
|
||||
}
|
||||
|
||||
if(flowfieldPathType == -1){
|
||||
flowfieldPathType =
|
||||
naval ? Pathfinder.costNaval :
|
||||
allowLegStep ? Pathfinder.costLegs :
|
||||
flying ? Pathfinder.costNone :
|
||||
hovering ? Pathfinder.costHover :
|
||||
Pathfinder.costGround;
|
||||
}
|
||||
|
||||
if(pathCost == null){
|
||||
pathCost =
|
||||
naval ? ControlPathfinder.costNaval :
|
||||
allowLegStep || example instanceof Crawlc ? ControlPathfinder.costLegs :
|
||||
allowLegStep ? ControlPathfinder.costLegs :
|
||||
hovering ? ControlPathfinder.costHover :
|
||||
ControlPathfinder.costGround;
|
||||
}
|
||||
@@ -783,6 +859,10 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
mechStride = 4f + (hitSize -8f)/2.1f;
|
||||
}
|
||||
|
||||
if(segmentSpacing < 0){
|
||||
segmentSpacing = hitSize;
|
||||
}
|
||||
|
||||
if(aimDst < 0){
|
||||
aimDst = weapons.contains(w -> !w.rotate) ? hitSize * 2f : hitSize / 2f;
|
||||
}
|
||||
@@ -1229,19 +1309,27 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
//region drawing
|
||||
|
||||
public void draw(Unit unit){
|
||||
float scl = xscl;
|
||||
if(unit.inFogTo(Vars.player.team())) return;
|
||||
|
||||
unit.drawBuilding();
|
||||
drawMining(unit);
|
||||
if(buildSpeed > 0f){
|
||||
unit.drawBuilding();
|
||||
}
|
||||
|
||||
if(unit.mining()){
|
||||
drawMining(unit);
|
||||
}
|
||||
|
||||
boolean isPayload = !unit.isAdded();
|
||||
|
||||
Mechc mech = unit instanceof Mechc ? (Mechc)unit : null;
|
||||
float z = isPayload ? Draw.z() : (unit.elevation > 0.5f ? flyingLayer : groundLayer) + Mathf.clamp(hitSize / 4000f, 0, 0.01f);
|
||||
|
||||
if(unit.controller().isBeingControlled(player.unit())){
|
||||
drawControl(unit);
|
||||
}
|
||||
Mechc mech = unit instanceof Mechc m ? m : null;
|
||||
Segmentc seg = unit instanceof Segmentc c ? c : null;
|
||||
float z =
|
||||
isPayload ? Draw.z() :
|
||||
//dead flying units are assumed to be falling, and to prevent weird clipping issue with the dark "fog", they always draw above it
|
||||
unit.elevation > 0.5f || (flying && unit.dead) ? (flyingLayer) :
|
||||
seg != null ? groundLayer + seg.segmentIndex() / 4000f * Mathf.sign(segmentLayerOrder) + (!segmentLayerOrder ? 0.01f : 0f) :
|
||||
groundLayer + Mathf.clamp(hitSize / 4000f, 0, 0.01f);
|
||||
|
||||
if(!isPayload && (unit.isFlying() || shadowElevation > 0)){
|
||||
Draw.z(Math.min(Layer.darkness, z - 1f));
|
||||
@@ -1276,7 +1364,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
drawPayload((Unit & Payloadc)unit);
|
||||
}
|
||||
|
||||
drawSoftShadow(unit);
|
||||
if(drawSoftShadow) drawSoftShadow(unit);
|
||||
|
||||
Draw.z(z);
|
||||
|
||||
@@ -1294,6 +1382,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
Draw.z(z);
|
||||
if(drawBody) drawBody(unit);
|
||||
if(drawCell && !(unit instanceof Crawlc)) drawCell(unit);
|
||||
Draw.scl(scl); //TODO this is a hack for neoplasm turrets
|
||||
drawWeapons(unit);
|
||||
if(drawItems) drawItems(unit);
|
||||
if(!isPayload){
|
||||
@@ -1394,15 +1483,6 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
);
|
||||
}
|
||||
|
||||
public void drawControl(Unit unit){
|
||||
Draw.z(unit.isFlying() ? Layer.flyingUnitLow : Layer.groundUnit - 2);
|
||||
|
||||
Draw.color(Pal.accent, Color.white, Mathf.absin(4f, 0.3f));
|
||||
Lines.poly(unit.x, unit.y, 4, unit.hitSize + 1.5f);
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public void drawShadow(Unit unit){
|
||||
float e = Mathf.clamp(unit.elevation, shadowElevation, 1f) * shadowElevationScl * (1f - unit.drownTime);
|
||||
float x = unit.x + shadowTX * e, y = unit.y + shadowTY * e;
|
||||
@@ -1428,7 +1508,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
public void drawSoftShadow(float x, float y, float rotation, float alpha){
|
||||
Draw.color(0, 0, 0, 0.4f * alpha);
|
||||
float rad = 1.6f;
|
||||
float size = Math.max(region.width, region.height) * region.scl();
|
||||
float size = Math.max(region.width, region.height) * region.scl() * softShadowScl;
|
||||
Draw.rect(softShadowRegion, x, y, size * rad * Draw.xscl, size * rad * Draw.yscl, rotation - 90);
|
||||
Draw.color();
|
||||
}
|
||||
@@ -1528,6 +1608,11 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
public void drawBody(Unit unit){
|
||||
applyColor(unit);
|
||||
|
||||
if(unit instanceof UnderwaterMovec){
|
||||
Draw.alpha(1f);
|
||||
Draw.mixcol(unit.floorOn().mapColor.write(Tmp.c1).mul(0.9f), 1f);
|
||||
}
|
||||
|
||||
Draw.rect(region, unit.x, unit.y, unit.rotation - 90);
|
||||
|
||||
Draw.reset();
|
||||
@@ -1613,11 +1698,19 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
Draw.rect(footRegion, leg.base.x, leg.base.y, position.angleTo(leg.base));
|
||||
}
|
||||
|
||||
Lines.stroke(legRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legRegion, position.x, position.y, leg.joint.x, leg.joint.y, false);
|
||||
if(legBaseUnder){
|
||||
Lines.stroke(legBaseRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legBaseRegion, leg.joint.x + Tmp.v1.x, leg.joint.y + Tmp.v1.y, leg.base.x, leg.base.y, false);
|
||||
|
||||
Lines.stroke(legBaseRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legBaseRegion, leg.joint.x + Tmp.v1.x, leg.joint.y + Tmp.v1.y, leg.base.x, leg.base.y, false);
|
||||
Lines.stroke(legRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legRegion, position.x, position.y, leg.joint.x, leg.joint.y, false);
|
||||
}else{
|
||||
Lines.stroke(legRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legRegion, position.x, position.y, leg.joint.x, leg.joint.y, false);
|
||||
|
||||
Lines.stroke(legBaseRegion.height * legRegion.scl() * flips);
|
||||
Lines.line(legBaseRegion, leg.joint.x + Tmp.v1.x, leg.joint.y + Tmp.v1.y, leg.base.x, leg.base.y, false);
|
||||
}
|
||||
|
||||
if(jointRegion.found()){
|
||||
Draw.rect(jointRegion, leg.joint.x, leg.joint.y);
|
||||
@@ -1640,27 +1733,29 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
//TODO
|
||||
public void drawCrawl(Crawlc crawl){
|
||||
Unit unit = (Unit)crawl;
|
||||
applyColor(unit);
|
||||
|
||||
//change to 2 TODO
|
||||
float crawlTime =
|
||||
crawl instanceof Segmentc seg && seg.headSegment() instanceof Crawlc head ? head.crawlTime() + seg.segmentIndex() * segmentPhase * segments :
|
||||
crawl.crawlTime();
|
||||
|
||||
for(int p = 0; p < 2; p++){
|
||||
TextureRegion[] regions = p == 0 ? segmentOutlineRegions : segmentRegions;
|
||||
|
||||
for(int i = 0; i < segments; i++){
|
||||
float trns = Mathf.sin(crawl.crawlTime() + i * segmentPhase, segmentScl, segmentMag);
|
||||
float trns = Mathf.sin(crawlTime + i * segmentPhase, segmentScl, segmentMag);
|
||||
|
||||
//at segment 0, rotation = segmentRot, but at the last segment it is rotation
|
||||
float rot = Mathf.slerp(crawl.segmentRot(), unit.rotation, i / (float)(segments - 1));
|
||||
float tx = Angles.trnsx(rot, trns), ty = Angles.trnsy(rot, trns);
|
||||
|
||||
//shadow
|
||||
Draw.color(0f, 0f, 0f, 0.2f);
|
||||
//Draw.color(0f, 0f, 0f, 0.2f);
|
||||
//Draw.rect(regions[i], unit.x + tx + 2f, unit.y + ty - 2f, rot - 90);
|
||||
|
||||
applyColor(unit);
|
||||
//applyColor(unit);
|
||||
|
||||
//TODO merge outlines?
|
||||
Draw.rect(regions[i], unit.x + tx, unit.y + ty, rot - 90);
|
||||
|
||||
@@ -46,7 +46,7 @@ public class Weapon implements Cloneable{
|
||||
public boolean rotate = false;
|
||||
/** Whether to show the sprite of the weapon in the database. */
|
||||
public boolean showStatSprite = true;
|
||||
/** rotation at which this weapon starts at. TODO buggy!*/
|
||||
/** rotation at which this weapon starts at. */
|
||||
public float baseRotation = 0f;
|
||||
/** whether to draw the outline on top. */
|
||||
public boolean top = true;
|
||||
@@ -92,14 +92,16 @@ public class Weapon implements Cloneable{
|
||||
public float shootX = 0f, shootY = 3f;
|
||||
/** offsets of weapon position on unit */
|
||||
public float x = 5f, y = 0f;
|
||||
/** Random spread on the X axis. */
|
||||
public float xRand = 0f;
|
||||
/** Random spread on the X/Y axis. */
|
||||
public float xRand = 0f, yRand = 0f;
|
||||
/** pattern used for bullets */
|
||||
public ShootPattern shoot = new ShootPattern();
|
||||
/** radius of shadow drawn under the weapon; <0 to disable */
|
||||
public float shadow = -1f;
|
||||
/** fraction of velocity that is random */
|
||||
public float velocityRnd = 0f;
|
||||
/** extra velocity that is added as a fraction */
|
||||
public float extraVelocity = 0f;
|
||||
/** The half-radius of the cone in which shooting will start. */
|
||||
public float shootCone = 5f;
|
||||
/** Cone in which the weapon can rotate relative to its mount. */
|
||||
@@ -234,7 +236,9 @@ public class Weapon implements Cloneable{
|
||||
}
|
||||
}
|
||||
|
||||
Draw.xscl = -Mathf.sign(flipSprite);
|
||||
float prev = Draw.xscl;
|
||||
|
||||
Draw.xscl *= -Mathf.sign(flipSprite);
|
||||
|
||||
//fix color
|
||||
unit.type.applyColor(unit);
|
||||
@@ -255,7 +259,7 @@ public class Weapon implements Cloneable{
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
Draw.xscl = 1f;
|
||||
Draw.xscl = prev;
|
||||
|
||||
if(parts.size > 0){
|
||||
//TODO does it need an outline?
|
||||
@@ -479,17 +483,18 @@ public class Weapon implements Cloneable{
|
||||
mount.charging = false;
|
||||
float
|
||||
xSpread = Mathf.range(xRand),
|
||||
ySpread = Mathf.range(yRand),
|
||||
weaponRotation = unit.rotation - 90 + (rotate ? mount.rotation : baseRotation),
|
||||
mountX = unit.x + Angles.trnsx(unit.rotation - 90, x, y),
|
||||
mountY = unit.y + Angles.trnsy(unit.rotation - 90, x, y),
|
||||
bulletX = mountX + Angles.trnsx(weaponRotation, this.shootX + xOffset + xSpread, this.shootY + yOffset),
|
||||
bulletY = mountY + Angles.trnsy(weaponRotation, this.shootX + xOffset + xSpread, this.shootY + yOffset),
|
||||
bulletX = mountX + Angles.trnsx(weaponRotation, this.shootX + xOffset + xSpread, this.shootY + yOffset + ySpread),
|
||||
bulletY = mountY + Angles.trnsy(weaponRotation, this.shootX + xOffset + xSpread, this.shootY + yOffset + ySpread),
|
||||
shootAngle = bulletRotation(unit, mount, bulletX, bulletY) + angleOffset,
|
||||
lifeScl = bullet.scaleLife ? Mathf.clamp(Mathf.dst(bulletX, bulletY, mount.aimX, mount.aimY) / bullet.range) : 1f,
|
||||
angle = shootAngle + Mathf.range(inaccuracy + bullet.inaccuracy);
|
||||
|
||||
Entityc shooter = unit.controller() instanceof MissileAI ai ? ai.shooter : unit; //Pass the missile's shooter down to its bullets
|
||||
mount.bullet = bullet.create(unit, shooter, unit.team, bulletX, bulletY, angle, -1f, (1f - velocityRnd) + Mathf.random(velocityRnd), lifeScl, null, mover, mount.aimX, mount.aimY, mount.target);
|
||||
mount.bullet = bullet.create(unit, shooter, unit.team, bulletX, bulletY, angle, -1f, (1f - velocityRnd) + Mathf.random(velocityRnd) + extraVelocity, lifeScl, null, mover, mount.aimX, mount.aimY, mount.target);
|
||||
handleBullet(unit, mount, mount.bullet);
|
||||
|
||||
if(!continuous){
|
||||
|
||||
@@ -18,6 +18,7 @@ public class MissileUnitType extends UnitType{
|
||||
logicControllable = false;
|
||||
isEnemy = false;
|
||||
useUnitCap = false;
|
||||
drawCell = false;
|
||||
allowedInPayloads = false;
|
||||
controller = u -> new MissileAI();
|
||||
flying = true;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package mindustry.type.weapons;
|
||||
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
/** Fires a bullet to intercept enemy bullets. The fired bullet MUST be of type InterceptorBulletType. */
|
||||
public class PointDefenseBulletWeapon extends Weapon{
|
||||
public float damageTargetWeight = 10f;
|
||||
|
||||
public PointDefenseBulletWeapon(String name){
|
||||
super(name);
|
||||
}
|
||||
|
||||
public PointDefenseBulletWeapon(){
|
||||
}
|
||||
|
||||
{
|
||||
autoTarget = true;
|
||||
controllable = false;
|
||||
rotate = true;
|
||||
useAmmo = false;
|
||||
useAttackRange = false;
|
||||
targetInterval = targetSwitchInterval = 5f;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Teamc findTarget(Unit unit, float x, float y, float range, boolean air, boolean ground){
|
||||
return Groups.bullet.intersect(x - range, y - range, range*2, range*2).min(b -> b.team != unit.team && b.type().hittable && !(b.type.collidesAir && !b.type.collidesTiles), b -> b.dst2(x, y) - b.damage * damageTargetWeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkTarget(Unit unit, Teamc target, float x, float y, float range){
|
||||
return !(target.within(unit, range) && target.team() != unit.team && target instanceof Bullet b && b.type != null && b.type.hittable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleBullet(Unit unit, WeaponMount mount, Bullet bullet){
|
||||
super.handleBullet(unit, mount, bullet);
|
||||
|
||||
if(mount.target instanceof Bullet b){
|
||||
bullet.data = b;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -105,11 +105,38 @@ public class Fonts{
|
||||
});
|
||||
}
|
||||
|
||||
public static void loadContentIcons(){
|
||||
Seq<Font> fonts = Seq.with(Fonts.def, Fonts.outline);
|
||||
Texture uitex = Core.atlas.find("logo").texture;
|
||||
public static void registerIcon(String name, String regionName, int ch, TextureRegion region){
|
||||
int size = (int)(Fonts.def.getData().lineHeight/Fonts.def.getData().scaleY);
|
||||
|
||||
unicodeIcons.put(name, ch);
|
||||
stringIcons.put(name, ((char)ch) + "");
|
||||
unicodeToName.put(ch, regionName);
|
||||
|
||||
Vec2 out = Scaling.fit.apply(region.width, region.height, size, size);
|
||||
|
||||
Glyph glyph = new Glyph();
|
||||
glyph.id = ch;
|
||||
glyph.srcX = 0;
|
||||
glyph.srcY = 0;
|
||||
glyph.width = (int)out.x;
|
||||
glyph.height = (int)out.y;
|
||||
glyph.u = region.u;
|
||||
glyph.v = region.v2;
|
||||
glyph.u2 = region.u2;
|
||||
glyph.v2 = region.v;
|
||||
glyph.xoffset = 0;
|
||||
glyph.yoffset = -size;
|
||||
glyph.xadvance = size;
|
||||
glyph.kerning = null;
|
||||
glyph.fixedWidth = true;
|
||||
glyph.page = 0;
|
||||
Fonts.def.getData().setGlyph(ch, glyph);
|
||||
Fonts.outline.getData().setGlyph(ch, glyph);
|
||||
}
|
||||
|
||||
public static void loadContentIcons(){
|
||||
Texture uitex = Core.atlas.find("logo").texture;
|
||||
|
||||
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
|
||||
String line;
|
||||
while((line = reader.readLine()) != null){
|
||||
@@ -123,29 +150,7 @@ public class Fonts{
|
||||
continue;
|
||||
}
|
||||
|
||||
unicodeIcons.put(nametex[0], ch);
|
||||
stringIcons.put(nametex[0], ((char)ch) + "");
|
||||
unicodeToName.put(ch, texture);
|
||||
|
||||
Vec2 out = Scaling.fit.apply(region.width, region.height, size, size);
|
||||
|
||||
Glyph glyph = new Glyph();
|
||||
glyph.id = ch;
|
||||
glyph.srcX = 0;
|
||||
glyph.srcY = 0;
|
||||
glyph.width = (int)out.x;
|
||||
glyph.height = (int)out.y;
|
||||
glyph.u = region.u;
|
||||
glyph.v = region.v2;
|
||||
glyph.u2 = region.u2;
|
||||
glyph.v2 = region.v;
|
||||
glyph.xoffset = 0;
|
||||
glyph.yoffset = -size;
|
||||
glyph.xadvance = size;
|
||||
glyph.kerning = null;
|
||||
glyph.fixedWidth = true;
|
||||
glyph.page = 0;
|
||||
fonts.each(f -> f.getData().setGlyph(ch, glyph));
|
||||
registerIcon(nametex[0], texture, ch, region);
|
||||
}
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
@@ -153,11 +158,30 @@ public class Fonts{
|
||||
|
||||
stringIcons.put("alphachan", stringIcons.get("alphaaaa"));
|
||||
|
||||
//TODO: mod emojis can't work because most mod icons are not on the UI page!
|
||||
/*
|
||||
if(Vars.mods.list().contains(m -> m.shouldBeEnabled())){
|
||||
ContentType[] types = {ContentType.liquid, ContentType.item, ContentType.block, ContentType.status, ContentType.unit};
|
||||
int startChar = 0xE000 + 1;
|
||||
|
||||
for(var type : types){
|
||||
for(var cont : Vars.content.getBy(type)){
|
||||
if(!cont.isVanilla() && cont instanceof UnlockableContent u && u.uiIcon.found()){
|
||||
int id = startChar;
|
||||
|
||||
registerIcon(u.name, u.uiIcon instanceof AtlasRegion atlas ? atlas.name : u.name, id, u.uiIcon);
|
||||
|
||||
startChar ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
team.emoji = stringIcons.get(team.name, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void loadContentIconsHeadless(){
|
||||
try(var reader = Core.files.internal("icons/icons.properties").reader(Vars.bufferSize)){
|
||||
String line;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class Minimap extends Table{
|
||||
|
||||
if(renderer.minimap.getTexture() != null){
|
||||
Draw.alpha(parentAlpha);
|
||||
renderer.minimap.drawEntities(x, y, width, height, 0.75f, false);
|
||||
renderer.minimap.drawEntities(x, y, width, height, false);
|
||||
}
|
||||
|
||||
clipEnd();
|
||||
|
||||
@@ -119,7 +119,7 @@ public class DatabaseDialog extends BaseDialog{
|
||||
for(int i = 0; i < array.size; i++){
|
||||
UnlockableContent unlock = array.get(i);
|
||||
|
||||
Image image = unlocked(unlock) ? new Image(unlock.uiIcon).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
|
||||
Image image = unlocked(unlock) ? new Image(new TextureRegionDrawable(unlock.uiIcon), mobile ? Color.white : Color.lightGray).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
|
||||
|
||||
//banned cross
|
||||
if(state.isGame() && (unlock instanceof UnitType u && u.isBanned() || unlock instanceof Block b && state.rules.isBanned(b))){
|
||||
|
||||
@@ -365,7 +365,7 @@ public class ModsDialog extends BaseDialog{
|
||||
|
||||
private @Nullable String getStateDetails(LoadedMod item){
|
||||
if(item.isOutdated()){
|
||||
return "@mod.outdatedv7.details";
|
||||
return Core.bundle.format("mod.outdated.details", item.isJava() ? minJavaModGameVersion : minModGameVersion);
|
||||
}else if(item.isBlacklisted()){
|
||||
return "@mod.blacklisted.details";
|
||||
}else if(!item.isSupported()){
|
||||
|
||||
@@ -179,6 +179,7 @@ public class ConsoleFragment extends Table{
|
||||
public String injectConsoleVariables(){
|
||||
return
|
||||
"var unit = Vars.player.unit();" +
|
||||
"var player = Vars.player;" +
|
||||
"var team = Vars.player.team();" +
|
||||
"var core = Vars.player.core();" +
|
||||
"var items = Vars.player.team().items();" +
|
||||
|
||||
@@ -687,44 +687,6 @@ public class HudFragment{
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated see {@link CoreBuild#beginLaunch(CoreBlock)} */
|
||||
@Deprecated
|
||||
public void showLaunch(){
|
||||
float margin = 30f;
|
||||
|
||||
Image image = new Image();
|
||||
image.color.a = 0f;
|
||||
image.touchable = Touchable.disabled;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.delay((coreLandDuration - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
|
||||
image.update(() -> {
|
||||
image.toFront();
|
||||
ui.loadfrag.toFront();
|
||||
if(state.isMenu()){
|
||||
image.remove();
|
||||
}
|
||||
});
|
||||
Core.scene.add(image);
|
||||
}
|
||||
|
||||
/** @deprecated see {@link CoreBuild#beginLaunch(CoreBlock)} */
|
||||
@Deprecated
|
||||
public void showLand(){
|
||||
Image image = new Image();
|
||||
image.color.a = 1f;
|
||||
image.touchable = Touchable.disabled;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.fadeOut(35f / 60f), Actions.remove());
|
||||
image.update(() -> {
|
||||
image.toFront();
|
||||
ui.loadfrag.toFront();
|
||||
if(state.isMenu()){
|
||||
image.remove();
|
||||
}
|
||||
});
|
||||
Core.scene.add(image);
|
||||
}
|
||||
|
||||
private void toggleMenus(){
|
||||
if(flip != null){
|
||||
flip.getStyle().imageUp = shown ? Icon.downOpen : Icon.upOpen;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class MinimapFragment{
|
||||
Draw.rect(reg, w/2f + panx*zoom, h/2f + pany*zoom, size, size * ratio);
|
||||
|
||||
Rect bounds = getRectBounds();
|
||||
renderer.minimap.drawEntities(bounds.x, bounds.y, bounds.width, bounds.height, zoom, true);
|
||||
renderer.minimap.drawEntities(bounds.x, bounds.y, bounds.width, bounds.height, true);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
|
||||
@@ -332,7 +332,7 @@ public class PlacementFragment{
|
||||
};
|
||||
|
||||
//top table with hover info
|
||||
frame.table(Tex.buttonEdge2,top -> {
|
||||
frame.table(Tex.buttonEdge2, top -> {
|
||||
topTable = top;
|
||||
top.add(new Table()).growX().update(topTable -> {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user