Merge branch 'master' into balancing_burst-drill-optional-multiplier
This commit is contained in:
@@ -35,7 +35,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
|
||||
@Override
|
||||
public void setup(){
|
||||
String dataDir = OS.env("MINDUSTRY_DATA_DIR");
|
||||
String dataDir = System.getProperty("mindustry.data.dir", OS.env("MINDUSTRY_DATA_DIR"));
|
||||
if(dataDir != null){
|
||||
Core.settings.setDataDirectory(files.absolute(dataDir));
|
||||
}
|
||||
@@ -55,6 +55,9 @@ 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(NvGpuInfo.hasMemoryInfo()){
|
||||
Log.info("[GL] Total available VRAM: @mb", NvGpuInfo.getMaxMemoryKB()/1024);
|
||||
}
|
||||
if(maxTextureSize < 4096) Log.warn("[GL] Your maximum texture size is below the recommended minimum of 4096. This will cause severe performance issues.");
|
||||
Log.info("[JAVA] Version: @", OS.javaVersion);
|
||||
if(Core.app.isAndroid()){
|
||||
@@ -62,7 +65,9 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
}
|
||||
long ram = Runtime.getRuntime().maxMemory();
|
||||
boolean gb = ram >= 1024 * 1024 * 1024;
|
||||
Log.info("[RAM] Available: @ @", Strings.fixed(gb ? ram / 1024f / 1024 / 1024f : ram / 1024f / 1024f, 1), gb ? "GB" : "MB");
|
||||
if(!OS.isIos){
|
||||
Log.info("[RAM] Available: @ @", Strings.fixed(gb ? ram / 1024f / 1024 / 1024f : ram / 1024f / 1024f, 1), gb ? "GB" : "MB");
|
||||
}
|
||||
|
||||
Time.setDeltaProvider(() -> {
|
||||
float result = Core.graphics.getDeltaTime() * 60f;
|
||||
|
||||
@@ -45,10 +45,10 @@ public class Vars implements Loadable{
|
||||
public static boolean loadLocales = true;
|
||||
/** Whether the logger is loaded. */
|
||||
public static boolean loadedLogger = false, loadedFileLogger = false;
|
||||
/** Whether to enable various experimental features (e.g. spawn positions for spawn groups) TODO change */
|
||||
public static boolean experimental = true;
|
||||
/** Name of current Steam player. */
|
||||
public static String steamPlayerName = "";
|
||||
/** If true, the BE server list is always used. */
|
||||
public static boolean forceBeServers = false;
|
||||
/** Default accessible content types used for player-selectable icons. */
|
||||
public static final ContentType[] defaultContentIcons = {ContentType.item, ContentType.liquid, ContentType.block, ContentType.unit};
|
||||
/** Default rule environment. */
|
||||
@@ -71,11 +71,12 @@ public class Vars implements Loadable{
|
||||
public static final String discordURL = "https://discord.gg/mindustry";
|
||||
/** URL the links to the wiki's modding guide.*/
|
||||
public static final String modGuideURL = "https://mindustrygame.github.io/wiki/modding/1-modding/";
|
||||
/** URL to the JSON file containing all the BE servers. Only queried in BE. */
|
||||
public static final String serverJsonBeURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_be.json";
|
||||
/** URL to the JSON file containing all the stable servers. */
|
||||
//TODO merge with v6 list upon release
|
||||
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_v7.json";
|
||||
/** 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"};
|
||||
/** URLs to the JSON file containing all the stable servers. */
|
||||
public static final String[] serverJsonURLs = {"https://raw.githubusercontent.com/Anuken/MindustryServerList/master/servers_v8.json", "https://cdn.jsdelivr.net/gh/anuken/mindustryserverlist/servers_v8.json"};
|
||||
/** URLs to the JSON files containing the list of mods. */
|
||||
public static final String[] modJsonURLs = {"https://raw.githubusercontent.com/Anuken/MindustryMods/master/mods.json", "https://cdn.jsdelivr.net/gh/anuken/mindustrymods/mods.json"};
|
||||
/** URL of the github issue report template.*/
|
||||
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?labels=bug&template=bug_report.md";
|
||||
/** list of built-in servers.*/
|
||||
@@ -94,6 +95,8 @@ public class Vars implements Loadable{
|
||||
public static final float finalWorldBounds = 250;
|
||||
/** default range for building */
|
||||
public static final float buildingRange = 220f;
|
||||
/** scaling for unit circle collider radius, based on hitbox size */
|
||||
public static final float unitCollisionRadiusScale = 0.6f;
|
||||
/** range for moving items */
|
||||
public static final float itemTransferRange = 220f;
|
||||
/** range for moving items for logic units */
|
||||
@@ -145,7 +148,7 @@ public class Vars implements Loadable{
|
||||
"modeSurvival", "commandRally", "commandAttack",
|
||||
};
|
||||
/** maximum TCP packet size */
|
||||
public static final int maxTcpSize = 900;
|
||||
public static final int maxTcpSize = 1100;
|
||||
/** default server port */
|
||||
public static final int port = 6567;
|
||||
/** multicast discovery port.*/
|
||||
@@ -170,6 +173,8 @@ public class Vars implements Loadable{
|
||||
public static boolean confirmExit = true;
|
||||
/** if true, UI is not drawn */
|
||||
public static boolean disableUI;
|
||||
/** if true, most autosaving is disabled. internal use only! */
|
||||
public static boolean disableSave;
|
||||
/** if true, game is set up in mobile mode, even on desktop. used for debugging */
|
||||
public static boolean testMobile;
|
||||
/** whether the game is running on a mobile device */
|
||||
|
||||
@@ -18,15 +18,15 @@ public class Astar{
|
||||
private static float[] costs;
|
||||
private static byte[][] rotations;
|
||||
|
||||
public static Seq<Tile> pathfind(Tile from, Tile to, TileHueristic th, Boolf<Tile> passable){
|
||||
public static Seq<Tile> pathfind(Tile from, Tile to, TileHeuristic th, Boolf<Tile> passable){
|
||||
return pathfind(from.x, from.y, to.x, to.y, th, manhattan, passable);
|
||||
}
|
||||
|
||||
public static Seq<Tile> pathfind(int startX, int startY, int endX, int endY, TileHueristic th, Boolf<Tile> passable){
|
||||
public static Seq<Tile> pathfind(int startX, int startY, int endX, int endY, TileHeuristic th, Boolf<Tile> passable){
|
||||
return pathfind(startX, startY, endX, endY, th, manhattan, passable);
|
||||
}
|
||||
|
||||
public static Seq<Tile> pathfind(int startX, int startY, int endX, int endY, TileHueristic th, DistanceHeuristic dh, Boolf<Tile> passable){
|
||||
public static Seq<Tile> pathfind(int startX, int startY, int endX, int endY, TileHeuristic th, DistanceHeuristic dh, Boolf<Tile> passable){
|
||||
Tiles tiles = world.tiles;
|
||||
|
||||
Tile start = tiles.getn(startX, startY);
|
||||
@@ -94,7 +94,7 @@ public class Astar{
|
||||
float cost(int x1, int y1, int x2, int y2);
|
||||
}
|
||||
|
||||
public interface TileHueristic{
|
||||
public interface TileHeuristic{
|
||||
float cost(Tile tile);
|
||||
|
||||
default float cost(Tile from, Tile tile){
|
||||
|
||||
@@ -258,7 +258,7 @@ public class BaseBuilderAI{
|
||||
|
||||
//queue it
|
||||
for(Stile tile : result.tiles){
|
||||
data.plans.add(new BlockPlan(cx + tile.x, cy + tile.y, tile.rotation, tile.block.id, tile.config));
|
||||
data.plans.add(new BlockPlan(cx + tile.x, cy + tile.y, tile.rotation, tile.block, tile.config));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -58,8 +58,8 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
costNaval = (team, tile) ->
|
||||
//impassable same-team neutral block, or non-liquid
|
||||
(PathTile.solid(tile) && ((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0)) || !PathTile.liquid(tile) ? impassable :
|
||||
1 +
|
||||
(PathTile.solid(tile) && ((PathTile.team(tile) == team && !PathTile.teamPassable(tile)) || PathTile.team(tile) == 0)) ? impassable :
|
||||
(!PathTile.liquid(tile) ? 6000 : 1) +
|
||||
//impassable synthetic enemy block
|
||||
((PathTile.team(tile) != team && PathTile.team(tile) != 0) && PathTile.solid(tile) ? wallImpassableCap : 0) +
|
||||
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 6 : 0);
|
||||
@@ -124,7 +124,7 @@ public class ControlPathfinder implements Runnable{
|
||||
//TODO: very dangerous usage;
|
||||
//TODO - it is accessed from the main thread
|
||||
//TODO - it is written to on the pathfinding thread
|
||||
//maps position in world in (x + y * width format) | type (bitpacked to long) to a cache of flow fields
|
||||
//maps position in world in (x + y * width format) | path type | team (bitpacked to long with FieldIndex.get) to a cache of flow fields
|
||||
LongMap<FieldCache> fields = new LongMap<>();
|
||||
//MAIN THREAD ONLY
|
||||
Seq<FieldCache> fieldList = new Seq<>(false);
|
||||
@@ -188,6 +188,7 @@ public class ControlPathfinder implements Runnable{
|
||||
final IntQueue frontier = new IntQueue();
|
||||
//maps cluster index to field weights; 0 means uninitialized
|
||||
final IntMap<int[]> fields = new IntMap<>();
|
||||
//packed (goalPos | costId | team) long key to use in the global fields map
|
||||
final long mapKey;
|
||||
|
||||
//main thread only!
|
||||
@@ -200,7 +201,7 @@ public class ControlPathfinder implements Runnable{
|
||||
this.team = team;
|
||||
this.goalPos = goalPos;
|
||||
this.costId = costId;
|
||||
this.mapKey = Pack.longInt(goalPos, costId);
|
||||
this.mapKey = FieldIndex.get(goalPos, costId, team);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,24 +233,7 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
Events.on(TileChangeEvent.class, e -> {
|
||||
|
||||
e.tile.getLinkedTiles(t -> {
|
||||
int x = t.x, y = t.y, mx = x % clusterSize, my = y % clusterSize, cx = x / clusterSize, cy = y / clusterSize, cluster = cx + cy * cwidth;
|
||||
|
||||
//is at the edge of a cluster; this means the portals may have changed.
|
||||
if(mx == 0 || my == 0 || mx == clusterSize - 1 || my == clusterSize - 1){
|
||||
|
||||
if(mx == 0) queueClusterUpdate(cx - 1, cy); //left
|
||||
if(my == 0) queueClusterUpdate(cx, cy - 1); //bottom
|
||||
if(mx == clusterSize - 1) queueClusterUpdate(cx + 1, cy); //right
|
||||
if(my == clusterSize - 1) queueClusterUpdate(cx, cy + 1); //top
|
||||
|
||||
queueClusterUpdate(cx, cy);
|
||||
//TODO: recompute edge clusters too.
|
||||
}else{
|
||||
//there is no need to recompute portals for block updates that are not on the edge.
|
||||
queue.post(() -> clustersToInnerUpdate.add(cluster));
|
||||
}
|
||||
});
|
||||
updateTile(e.tile);
|
||||
|
||||
//TODO: recalculate affected flow fields? or just all of them? how to reflow?
|
||||
});
|
||||
@@ -258,7 +242,7 @@ public class ControlPathfinder implements Runnable{
|
||||
Events.run(Trigger.update, () -> {
|
||||
for(var req : unitRequests.values()){
|
||||
//skipped N update -> drop it
|
||||
if(req.lastUpdateId <= state.updateId - 10){
|
||||
if(req.lastUpdateId <= state.updateId - 10 || !req.unit.isAdded()){
|
||||
req.invalidated = true;
|
||||
//concurrent modification!
|
||||
queue.post(() -> threadPathRequests.remove(req));
|
||||
@@ -358,6 +342,29 @@ public class ControlPathfinder implements Runnable{
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTile(Tile tile){
|
||||
tile.getLinkedTiles(this::updateSingleTile);
|
||||
}
|
||||
|
||||
public void updateSingleTile(Tile t){
|
||||
int x = t.x, y = t.y, mx = x % clusterSize, my = y % clusterSize, cx = x / clusterSize, cy = y / clusterSize, cluster = cx + cy * cwidth;
|
||||
|
||||
//is at the edge of a cluster; this means the portals may have changed.
|
||||
if(mx == 0 || my == 0 || mx == clusterSize - 1 || my == clusterSize - 1){
|
||||
|
||||
if(mx == 0) queueClusterUpdate(cx - 1, cy); //left
|
||||
if(my == 0) queueClusterUpdate(cx, cy - 1); //bottom
|
||||
if(mx == clusterSize - 1) queueClusterUpdate(cx + 1, cy); //right
|
||||
if(my == clusterSize - 1) queueClusterUpdate(cx, cy + 1); //top
|
||||
|
||||
queueClusterUpdate(cx, cy);
|
||||
//TODO: recompute edge clusters too.
|
||||
}else{
|
||||
//there is no need to recompute portals for block updates that are not on the edge.
|
||||
queue.post(() -> clustersToInnerUpdate.add(cluster));
|
||||
}
|
||||
}
|
||||
|
||||
void queueClusterUpdate(int cx, int cy){
|
||||
if(cx >= 0 && cy >= 0 && cx < cwidth && cy < cheight){
|
||||
queue.post(() -> clustersToUpdate.add(cx + cy * cwidth));
|
||||
@@ -534,7 +541,7 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
void updateInnerEdges(int team, PathCost cost, int cx, int cy, Cluster cluster){
|
||||
int minX = cx * clusterSize, minY = cy * clusterSize, maxX = Math.min(minX + clusterSize - 1, wwidth - 1), maxY = Math.min(minY + clusterSize - 1, wheight - 1);
|
||||
|
||||
|
||||
usedEdges.clear();
|
||||
|
||||
//clear all connections, since portals changed, they need to be recomputed.
|
||||
@@ -548,7 +555,7 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
for(int i = 0; i < portals.size; i++){
|
||||
usedEdges.add(Point2.pack(direction, i));
|
||||
|
||||
|
||||
int
|
||||
portal = portals.items[i],
|
||||
from = Point2.x(portal), to = Point2.y(portal),
|
||||
@@ -1020,10 +1027,12 @@ public class ControlPathfinder implements Runnable{
|
||||
//no result found, bail out.
|
||||
if(nodePath == null){
|
||||
request.notFound = true;
|
||||
//stop following the old path, it's not relevant now, it's just not possible to reach the destination anymore
|
||||
request.oldCache = null;
|
||||
return;
|
||||
}
|
||||
|
||||
FieldCache cache = fields.get(Pack.longInt(goalPos, costId));
|
||||
FieldCache cache = fields.get(FieldIndex.get(goalPos, costId, team));
|
||||
//if true, extra values are added on the sides of existing field cells that face new cells.
|
||||
boolean addingFrontier = true;
|
||||
|
||||
@@ -1093,6 +1102,10 @@ public class ControlPathfinder implements Runnable{
|
||||
}
|
||||
|
||||
public boolean getPathPosition(Unit unit, Vec2 destination, Vec2 mainDestination, Vec2 out, @Nullable boolean[] noResultFound){
|
||||
if(noResultFound != null){
|
||||
noResultFound[0] = false;
|
||||
}
|
||||
|
||||
int costId = unit.type.pathCostId;
|
||||
PathCost cost = idToCost(costId);
|
||||
|
||||
@@ -1139,7 +1152,7 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
boolean any = false;
|
||||
|
||||
long fieldKey = Pack.longInt(destPos, costId);
|
||||
long fieldKey = FieldIndex.get(destPos, costId, team);
|
||||
|
||||
//use existing request if it exists.
|
||||
if(request != null && request.destination == destPos){
|
||||
@@ -1147,14 +1160,19 @@ public class ControlPathfinder implements Runnable{
|
||||
|
||||
Tile tileOn = unit.tileOn(), initialTileOn = tileOn;
|
||||
//TODO: should fields be accessible from this thread?
|
||||
FieldCache fieldCache = fields.get(fieldKey);
|
||||
FieldCache fieldCache = null;
|
||||
try{
|
||||
fieldCache = fields.get(fieldKey);
|
||||
}catch(ArrayIndexOutOfBoundsException ignored){ //TODO fix this, rare crash due to remove() elsewhere
|
||||
}
|
||||
if(fieldCache == null) fieldCache = request.oldCache;
|
||||
|
||||
if(fieldCache != null && tileOn != null){
|
||||
FieldCache old = request.oldCache;
|
||||
FieldCache targetCache = old != null ? old : fieldCache;
|
||||
boolean requeue = old == null;
|
||||
//nullify the old field to be GCed, as it cannot be relevant anymore (this path is complete)
|
||||
if(fieldCache.frontier.isEmpty() && old != null){
|
||||
if(fieldCache != request.oldCache && fieldCache.frontier.isEmpty() && old != null){
|
||||
request.oldCache = null;
|
||||
}
|
||||
|
||||
@@ -1245,7 +1263,11 @@ public class ControlPathfinder implements Runnable{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}else if(request == null){
|
||||
}else{
|
||||
//destroy the old one immediately, it's invalid now
|
||||
if(request != null){
|
||||
request.lastUpdateId = -1000;
|
||||
}
|
||||
|
||||
//queue new request.
|
||||
unitRequests.put(unit, request = new PathRequest(unit, team, costId, destPos));
|
||||
@@ -1258,9 +1280,7 @@ public class ControlPathfinder implements Runnable{
|
||||
recalculatePath(f);
|
||||
});
|
||||
|
||||
out.set(destination);
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(noResultFound != null){
|
||||
@@ -1445,7 +1465,7 @@ public class ControlPathfinder implements Runnable{
|
||||
int index = cx + cy * cwidth;
|
||||
|
||||
for(var req : threadPathRequests){
|
||||
long mapKey = Pack.longInt(req.destination, pathCost);
|
||||
long mapKey = FieldIndex.get(req.destination, pathCost, team);
|
||||
var field = fields.get(mapKey);
|
||||
if((field != null && field.fields.containsKey(index)) || req.notFound){
|
||||
invalidRequests.add(req);
|
||||
@@ -1531,7 +1551,7 @@ public class ControlPathfinder implements Runnable{
|
||||
continue;
|
||||
}
|
||||
|
||||
long mapKey = Pack.longInt(request.destination, request.costId);
|
||||
long mapKey = FieldIndex.get(request.destination, request.costId, request.team);
|
||||
|
||||
var field = fields.get(mapKey);
|
||||
|
||||
@@ -1539,7 +1559,7 @@ public class ControlPathfinder implements Runnable{
|
||||
//it's only worth recalculating a path when the current frontier has finished; otherwise the unit will be following something incomplete.
|
||||
if(field.frontier.isEmpty()){
|
||||
|
||||
//remove the field, to be recalculated next update one recalculatePath is processed
|
||||
//remove the field, to be recalculated next update once recalculatePath is processed
|
||||
fields.remove(field.mapKey);
|
||||
Core.app.post(() -> fieldList.remove(field));
|
||||
|
||||
@@ -1547,6 +1567,10 @@ public class ControlPathfinder implements Runnable{
|
||||
for(var otherRequest : threadPathRequests){
|
||||
if(otherRequest.destination == request.destination){
|
||||
otherRequest.oldCache = field;
|
||||
|
||||
if(otherRequest != request){
|
||||
queue.post(() -> recalculatePath(otherRequest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1580,6 +1604,15 @@ public class ControlPathfinder implements Runnable{
|
||||
}
|
||||
}
|
||||
|
||||
@Struct
|
||||
static class FieldIndexStruct{
|
||||
int pos;
|
||||
@StructField(8)
|
||||
int costId;
|
||||
@StructField(8)
|
||||
int team;
|
||||
}
|
||||
|
||||
@Struct
|
||||
static class IntraEdgeStruct{
|
||||
@StructField(8)
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.ai;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
@@ -16,6 +17,7 @@ import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.world.meta.BlockFlag.*;
|
||||
|
||||
public class Pathfinder implements Runnable{
|
||||
private static final long maxUpdate = Time.millisToNanos(8);
|
||||
@@ -37,7 +39,8 @@ public class Pathfinder implements Runnable{
|
||||
public static final int
|
||||
costGround = 0,
|
||||
costLegs = 1,
|
||||
costNaval = 2;
|
||||
costNaval = 2,
|
||||
costHover = 3;
|
||||
|
||||
public static final Seq<PathCost> costTypes = Seq.with(
|
||||
//ground
|
||||
@@ -61,7 +64,13 @@ public class Pathfinder implements Runnable{
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
|
||||
(PathTile.deep(tile) ? 0 : 1) +
|
||||
(PathTile.damages(tile) ? 35 : 0)
|
||||
(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)
|
||||
);
|
||||
|
||||
/** tile data, see PathTileStruct - kept as a separate array for threading reasons */
|
||||
@@ -243,6 +252,8 @@ public class Pathfinder implements Runnable{
|
||||
data.dirty = true;
|
||||
}
|
||||
});
|
||||
|
||||
controlPath.updateTile(tile);
|
||||
}
|
||||
|
||||
/** Thread implementation. */
|
||||
@@ -452,8 +463,34 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
|
||||
public static class EnemyCoreField extends Flowfield{
|
||||
private final static BlockFlag[] randomTargets = {storage, generator, launchPad, factory, repair, battery, reactor, drill};
|
||||
private Rand rand = new Rand();
|
||||
|
||||
@Override
|
||||
protected void getPositions(IntSeq out){
|
||||
if(state.rules.randomWaveAI && team == state.rules.waveTeam){
|
||||
rand.setSeed(state.rules.waves ? state.wave : (int)(state.tick / (5400)) + hashCode());
|
||||
|
||||
//maximum amount of different target flag types they will attack
|
||||
int max = 1;
|
||||
|
||||
for(int attempt = 0; attempt < 5 && max > 0; attempt++){
|
||||
var targets = indexer.getEnemy(team, randomTargets[rand.random(randomTargets.length - 1)]);
|
||||
if(!targets.isEmpty()){
|
||||
boolean any = false;
|
||||
for(Building other : targets){
|
||||
if((other.items != null && other.items.any()) || other.status() != BlockStatus.noInput){
|
||||
out.add(other.tile.array());
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
if(any){
|
||||
max --;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Building other : indexer.getEnemy(team, BlockFlag.core)){
|
||||
out.add(other.tile.array());
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ public class RtsAI{
|
||||
//other can never be destroyed | other destroys self instantly
|
||||
if(Float.isInfinite(timeDestroyOther) || Mathf.zero(timeDestroySelf)) return 0f;
|
||||
//self can never be destroyed | self destroys other instantly
|
||||
if(Float.isInfinite(timeDestroySelf) || Mathf.zero(timeDestroyOther)) return 1f;
|
||||
if(Float.isInfinite(timeDestroySelf) || Mathf.zero(timeDestroyOther)) return 100000f;
|
||||
|
||||
//examples:
|
||||
// self 10 sec / other 10 sec -> can destroy target with 100 % losses -> returns 1
|
||||
|
||||
@@ -17,7 +17,7 @@ public class UnitCommand extends MappableContent{
|
||||
@Deprecated
|
||||
public static final Seq<UnitCommand> all = new Seq<>();
|
||||
|
||||
public static UnitCommand moveCommand, repairCommand, rebuildCommand, assistCommand, mineCommand, boostCommand, enterPayloadCommand, loadUnitsCommand, loadBlocksCommand, unloadPayloadCommand;
|
||||
public static UnitCommand moveCommand, repairCommand, rebuildCommand, assistCommand, mineCommand, boostCommand, enterPayloadCommand, loadUnitsCommand, loadBlocksCommand, unloadPayloadCommand, loopPayloadCommand;
|
||||
|
||||
/** Name of UI icon (from Icon class). */
|
||||
public final String icon;
|
||||
@@ -110,5 +110,10 @@ public class UnitCommand extends MappableContent{
|
||||
drawTarget = true;
|
||||
resetTarget = false;
|
||||
}};
|
||||
loopPayloadCommand = new UnitCommand("loopPayload", "resize", Binding.unit_command_loop_payload, null){{
|
||||
switchToMove = false;
|
||||
drawTarget = true;
|
||||
resetTarget = false;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class UnitGroup{
|
||||
public int collisionLayer;
|
||||
public volatile float[] positions, originalPositions;
|
||||
public volatile boolean valid;
|
||||
|
||||
|
||||
public void calculateFormation(Vec2 dest, int collisionLayer){
|
||||
this.collisionLayer = collisionLayer;
|
||||
|
||||
@@ -72,7 +72,7 @@ public class UnitGroup{
|
||||
positions[a * 2] = v1.x;
|
||||
positions[a * 2 + 1] = v1.y;
|
||||
|
||||
float rad = units.get(a).hitSize/2f;
|
||||
float rad = units.get(a).hitSize * Vars.unitCollisionRadiusScale;
|
||||
|
||||
maxDst = Math.max(maxDst, v1.dst(0f, 0f) + rad);
|
||||
totalArea += Mathf.PI * rad * rad;
|
||||
|
||||
@@ -66,12 +66,22 @@ public class WaveSpawner{
|
||||
if(group.type == null) continue;
|
||||
|
||||
int spawned = group.getSpawned(state.wave - 1);
|
||||
if(spawned == 0) continue;
|
||||
|
||||
if(state.isCampaign()){
|
||||
//when spawning a boss, round down, so 1.5x (hard) * 1 boss does not result in 2 bosses
|
||||
spawned = Math.max(1, group.effect == StatusEffects.boss ?
|
||||
(int)(spawned * state.getPlanet().campaignRules.difficulty.enemySpawnMultiplier) :
|
||||
Mathf.round(spawned * state.getPlanet().campaignRules.difficulty.enemySpawnMultiplier));
|
||||
}
|
||||
|
||||
int spawnedf = spawned;
|
||||
|
||||
if(group.type.flying){
|
||||
float spread = margin / 1.5f;
|
||||
|
||||
eachFlyerSpawn(group.spawn, (spawnX, spawnY) -> {
|
||||
for(int i = 0; i < spawned; i++){
|
||||
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);
|
||||
@@ -82,7 +92,7 @@ public class WaveSpawner{
|
||||
|
||||
eachGroundSpawn(group.spawn, (spawnX, spawnY, doShockwave) -> {
|
||||
|
||||
for(int i = 0; i < spawned; i++){
|
||||
for(int i = 0; i < spawnedf; i++){
|
||||
Tmp.v1.rnd(spread);
|
||||
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
@@ -153,7 +163,7 @@ public class WaveSpawner{
|
||||
|
||||
private void eachFlyerSpawn(int filterPos, Floatc2 cons){
|
||||
boolean airUseSpawns = state.rules.airUseSpawns;
|
||||
|
||||
|
||||
for(Tile tile : spawns){
|
||||
if(filterPos != -1 && filterPos != tile.pos()) continue;
|
||||
|
||||
|
||||
@@ -179,12 +179,12 @@ public class BuilderAI extends AIController{
|
||||
BlockPlan block = blocks.first();
|
||||
|
||||
//check if it's already been placed
|
||||
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block().id == block.block){
|
||||
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block() == block.block){
|
||||
blocks.removeFirst();
|
||||
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation) && (!alwaysFlee || !nearEnemy(block.x, block.y))){ //it's valid
|
||||
}else if(Build.validPlace(block.block, unit.team(), block.x, block.y, block.rotation) && (!alwaysFlee || !nearEnemy(block.x, block.y))){ //it's valid
|
||||
lastPlan = block;
|
||||
//add build plan
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, block.block, block.config));
|
||||
//shift build plan to tail so next unit builds something else
|
||||
blocks.addLast(blocks.removeFirst());
|
||||
}else{
|
||||
@@ -195,7 +195,7 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
|
||||
if(!unit.type.flying){
|
||||
unit.updateBoosting(moving || unit.floorOn().isDuct || unit.floorOn().damageTaken > 0f);
|
||||
unit.updateBoosting(unit.type.boostWhenBuilding || moving || unit.floorOn().isDuct || unit.floorOn().damageTaken > 0f || unit.floorOn().isDeep());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,12 @@ public class CommandAI extends AIController{
|
||||
protected static final Vec2 vecOut = new Vec2(), vecMovePos = new Vec2();
|
||||
protected static final boolean[] noFound = {false};
|
||||
protected static final UnitPayload tmpPayload = new UnitPayload(null);
|
||||
protected static final int transferStateNone = 0, transferStateLoad = 1, transferStateUnload = 2;
|
||||
|
||||
public Seq<Position> commandQueue = new Seq<>(5);
|
||||
public @Nullable Vec2 targetPos;
|
||||
public @Nullable Teamc attackTarget;
|
||||
/** Group of units that were all commanded to reach the same point.. */
|
||||
/** Group of units that were all commanded to reach the same point. */
|
||||
public @Nullable UnitGroup group;
|
||||
public int groupIndex = 0;
|
||||
/** All encountered unreachable buildings of this AI. Why a sequence? Because contains() is very rarely called on it. */
|
||||
@@ -36,6 +37,8 @@ public class CommandAI extends AIController{
|
||||
protected Vec2 lastTargetPos;
|
||||
protected boolean blockingUnit;
|
||||
protected float timeSpentBlocked;
|
||||
protected float payloadPickupCooldown;
|
||||
protected int transferState = transferStateNone;
|
||||
|
||||
/** Stance, usually related to firing mode. */
|
||||
public UnitStance stance = UnitStance.shoot;
|
||||
@@ -52,7 +55,7 @@ public class CommandAI extends AIController{
|
||||
|
||||
/** Attempts to assign a command to this unit. If not supported by the unit type, does nothing. */
|
||||
public void command(UnitCommand command){
|
||||
if(Structs.contains(unit.type.commands, command)){
|
||||
if(unit.type.commands.contains(command)){
|
||||
//clear old state.
|
||||
unit.mineTile = null;
|
||||
unit.clearBuilding();
|
||||
@@ -85,8 +88,8 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
|
||||
//assign defaults
|
||||
if(command == null && unit.type.commands.length > 0){
|
||||
command = unit.type.defaultCommand == null ? unit.type.commands[0] : unit.type.defaultCommand;
|
||||
if(command == null && unit.type.commands.size > 0){
|
||||
command = unit.type.defaultCommand == null ? unit.type.commands.first() : unit.type.defaultCommand;
|
||||
}
|
||||
|
||||
//update command controller based on index.
|
||||
@@ -113,9 +116,18 @@ public class CommandAI extends AIController{
|
||||
attackTarget = null;
|
||||
}
|
||||
|
||||
void tryPickupUnit(Payloadc pay){
|
||||
Unit target = Units.closest(unit.team, unit.x, unit.y, unit.type.hitSize * 2f, u -> u.isAI() && u != unit && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize));
|
||||
if(target != null){
|
||||
Call.pickedUnitPayload(unit, target);
|
||||
}
|
||||
}
|
||||
|
||||
public void defaultBehavior(){
|
||||
|
||||
if(!net.client() && unit instanceof Payloadc pay){
|
||||
payloadPickupCooldown -= Time.delta;
|
||||
|
||||
//auto-drop everything
|
||||
if(command == UnitCommand.unloadPayloadCommand && pay.hasPayload()){
|
||||
Call.payloadDropped(unit, unit.x, unit.y);
|
||||
@@ -123,10 +135,7 @@ public class CommandAI extends AIController{
|
||||
|
||||
//try to pick up what's under it
|
||||
if(command == UnitCommand.loadUnitsCommand){
|
||||
Unit target = Units.closest(unit.team, unit.x, unit.y, unit.type.hitSize * 2f, u -> u.isAI() && u != unit && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize));
|
||||
if(target != null){
|
||||
Call.pickedUnitPayload(unit, target);
|
||||
}
|
||||
tryPickupUnit(pay);
|
||||
}
|
||||
|
||||
//try to pick up a block
|
||||
@@ -218,8 +227,14 @@ public class CommandAI extends AIController{
|
||||
vecMovePos.add(group.positions[groupIndex * 2], group.positions[groupIndex * 2 + 1]);
|
||||
}
|
||||
|
||||
Building targetBuild = world.buildWorld(targetPos.x, targetPos.y);
|
||||
|
||||
//TODO: should the unit stop when it finds a target?
|
||||
if(stance == UnitStance.patrol && target != null && unit.within(target, unit.type.range - 2f) && !unit.type.circleTarget){
|
||||
if(
|
||||
(stance == UnitStance.patrol && target != null && unit.within(target, unit.type.range - 2f) && !unit.type.circleTarget) ||
|
||||
(command == UnitCommand.enterPayloadCommand && unit.within(targetPos, 4f) || (targetBuild != null && unit.within(targetBuild, targetBuild.block.size * tilesize/2f * 0.9f))) ||
|
||||
(command == UnitCommand.loopPayloadCommand && unit.within(targetPos, 10f))
|
||||
){
|
||||
move = false;
|
||||
}
|
||||
|
||||
@@ -260,6 +275,13 @@ public class CommandAI extends AIController{
|
||||
vecOut.set(vecMovePos);
|
||||
}else{
|
||||
move = controlPath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
|
||||
|
||||
//TODO: what to do when there's a target and it can't be reached?
|
||||
/*
|
||||
if(noFound[0] && attackTarget != null && attackTarget.within(unit, unit.type.range * 2f)){
|
||||
move = true;
|
||||
vecOut.set(targetPos);
|
||||
}*/
|
||||
}
|
||||
|
||||
//rare case where unit must be perfectly aligned (happens with 1-tile gaps)
|
||||
@@ -321,10 +343,54 @@ public class CommandAI extends AIController{
|
||||
|
||||
void finishPath(){
|
||||
//the enter payload command never finishes until they are actually accepted
|
||||
if(command == UnitCommand.enterPayloadCommand && commandQueue.size == 0 && targetPos != null && world.buildWorld(targetPos.x, targetPos.y) != null && world.buildWorld(targetPos.x, targetPos.y).block.acceptsPayloads){
|
||||
if(command == UnitCommand.enterPayloadCommand && commandQueue.size == 0 && targetPos != null && world.buildWorld(targetPos.x, targetPos.y) != null && world.buildWorld(targetPos.x, targetPos.y).block.acceptsUnitPayloads){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!net.client() && command == UnitCommand.loopPayloadCommand && unit instanceof Payloadc pay){
|
||||
|
||||
if(transferState == transferStateNone){
|
||||
transferState = pay.hasPayload() ? transferStateUnload : transferStateLoad;
|
||||
}
|
||||
|
||||
if(payloadPickupCooldown > 0f) return;
|
||||
|
||||
if(transferState == transferStateUnload){
|
||||
//drop until there's a failure
|
||||
int prev = -1;
|
||||
while(pay.hasPayload() && prev != pay.payloads().size){
|
||||
prev = pay.payloads().size;
|
||||
Call.payloadDropped(unit, unit.x, unit.y);
|
||||
}
|
||||
|
||||
//wait for everything to unload before running code below
|
||||
if(pay.hasPayload()){
|
||||
return;
|
||||
}
|
||||
payloadPickupCooldown = 60f;
|
||||
}else if(transferState == transferStateLoad){
|
||||
//pick up units until there's a failure
|
||||
int prev = -1;
|
||||
while(prev != pay.payloads().size){
|
||||
prev = pay.payloads().size;
|
||||
tryPickupUnit(pay);
|
||||
}
|
||||
|
||||
//wait to load things before running code below
|
||||
if(!pay.hasPayload()){
|
||||
return;
|
||||
}
|
||||
payloadPickupCooldown = 60f;
|
||||
}
|
||||
|
||||
//it will never finish
|
||||
if(commandQueue.size == 0){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
transferState = transferStateNone;
|
||||
|
||||
Vec2 prev = targetPos;
|
||||
targetPos = null;
|
||||
|
||||
@@ -336,7 +402,7 @@ public class CommandAI extends AIController{
|
||||
commandPosition(position);
|
||||
}
|
||||
|
||||
if(prev != null && stance == UnitStance.patrol){
|
||||
if(prev != null && (stance == UnitStance.patrol || command == UnitCommand.loopPayloadCommand)){
|
||||
commandQueue.add(prev.cpy());
|
||||
}
|
||||
|
||||
@@ -351,10 +417,15 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Unit unit){
|
||||
clearCommands();
|
||||
}
|
||||
|
||||
public void commandQueue(Position location){
|
||||
if(targetPos == null && attackTarget == null){
|
||||
if(location instanceof Teamc target){
|
||||
commandTarget(target, this.stopAtTarget);
|
||||
if(location instanceof Teamc t){
|
||||
commandTarget(t, this.stopAtTarget);
|
||||
}else if(location instanceof Vec2 position){
|
||||
commandPosition(position);
|
||||
}
|
||||
@@ -392,7 +463,7 @@ public class CommandAI extends AIController{
|
||||
|
||||
@Override
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
return !nearAttackTarget(x, y, range) ? super.findTarget(x, y, range, air, ground) : attackTarget;
|
||||
return !nearAttackTarget(x, y, range) ? super.findTarget(x, y, range, air, ground) : Units.isHittable(attackTarget, air, ground) ? attackTarget : null;
|
||||
}
|
||||
|
||||
public boolean nearAttackTarget(float x, float y, float range){
|
||||
@@ -445,52 +516,4 @@ public class CommandAI extends AIController{
|
||||
this.stopAtTarget = stopAtTarget;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
//TODO ひどい
|
||||
(does not work)
|
||||
|
||||
public static float cohesionScl = 0.3f;
|
||||
public static float cohesionRad = 3f, separationRad = 1.1f, separationScl = 1f, flockMult = 0.5f;
|
||||
|
||||
Vec2 calculateFlock(){
|
||||
if(local.isEmpty()) return flockVec.setZero();
|
||||
|
||||
flockVec.setZero();
|
||||
separation.setZero();
|
||||
cohesion.setZero();
|
||||
massCenter.set(unit);
|
||||
|
||||
float rad = unit.hitSize;
|
||||
float sepDst = rad * separationRad, cohDst = rad * cohesionRad;
|
||||
|
||||
//"cohesed" isn't even a word smh
|
||||
int separated = 0, cohesed = 1;
|
||||
|
||||
for(var other : local){
|
||||
float dst = other.dst(unit);
|
||||
if(dst < sepDst){
|
||||
separation.add(Tmp.v1.set(unit).sub(other).scl(1f / sepDst));
|
||||
separated ++;
|
||||
}
|
||||
|
||||
if(dst < cohDst){
|
||||
massCenter.add(other);
|
||||
cohesed ++;
|
||||
}
|
||||
}
|
||||
|
||||
if(separated > 0){
|
||||
separation.scl(1f / separated);
|
||||
flockVec.add(separation.scl(separationScl));
|
||||
}
|
||||
|
||||
if(cohesed > 1){
|
||||
massCenter.scl(1f / cohesed);
|
||||
flockVec.add(Tmp.v1.set(massCenter).sub(unit).limit(cohesionScl * unit.type.speed));
|
||||
//seek mass center?
|
||||
}
|
||||
|
||||
return flockVec;
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.world.meta.BlockFlag.*;
|
||||
|
||||
//TODO very strange idle behavior sometimes
|
||||
public class FlyingAI extends AIController{
|
||||
final static Rand rand = new Rand();
|
||||
final static BlockFlag[] randomTargets = {core, storage, generator, launchPad, factory, repair, battery, reactor, drill};
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
@@ -28,6 +31,30 @@ public class FlyingAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
|
||||
if(state.rules.randomWaveAI){
|
||||
if(unit.team == Team.derelict) return null;
|
||||
var list = enemy ? indexer.getEnemy(unit.team, flag) : indexer.getFlagged(unit.team, flag);
|
||||
if(list.isEmpty()) return null;
|
||||
|
||||
Building closest = null;
|
||||
float cdist = 0f;
|
||||
for(Building t : list){
|
||||
if((t.items != null && t.items.any()) || t.status() != BlockStatus.noInput){
|
||||
float dst = t.dst2(x, y);
|
||||
if(closest == null || dst < cdist){
|
||||
closest = t;
|
||||
cdist = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}else{
|
||||
return super.targetFlag(x, y, flag, enemy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
var result = findMainTarget(x, y, range, air, ground);
|
||||
@@ -44,14 +71,27 @@ public class FlyingAI extends AIController{
|
||||
return core;
|
||||
}
|
||||
|
||||
for(var flag : unit.type.targetFlags){
|
||||
if(flag == null){
|
||||
Teamc result = target(x, y, range, air, ground);
|
||||
if(result != null) return result;
|
||||
}else if(ground){
|
||||
Teamc result = targetFlag(x, y, flag, true);
|
||||
if(state.rules.randomWaveAI){
|
||||
//when there are no waves, it's just random based on the unit
|
||||
rand.setSeed(unit.type.id + (state.rules.waves ? state.wave : unit.id));
|
||||
//try a few random flags first
|
||||
for(int attempt = 0; attempt < 5; attempt++){
|
||||
Teamc result = targetFlag(x, y, randomTargets[rand.random(randomTargets.length - 1)], true);
|
||||
if(result != null) return result;
|
||||
}
|
||||
//try the closest target
|
||||
Teamc result = target(x, y, range, air, ground);
|
||||
if(result != null) return result;
|
||||
}else{
|
||||
for(var flag : unit.type.targetFlags){
|
||||
if(flag == null){
|
||||
Teamc result = target(x, y, range, air, ground);
|
||||
if(result != null) return result;
|
||||
}else if(ground){
|
||||
Teamc result = targetFlag(x, y, flag, true);
|
||||
if(result != null) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return core;
|
||||
|
||||
@@ -10,6 +10,11 @@ import mindustry.gen.*;
|
||||
public class MissileAI extends AIController{
|
||||
public @Nullable Unit shooter;
|
||||
|
||||
@Override
|
||||
protected void resetTimers(){
|
||||
timer.reset(timerTarget, 5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
unloadPayloads();
|
||||
@@ -33,7 +38,7 @@ public class MissileAI extends AIController{
|
||||
|
||||
@Override
|
||||
public Teamc target(float x, float y, float range, boolean air, boolean ground){
|
||||
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground && (!t.block.underBullets || (shooter != null && t == Vars.world.buildWorld(shooter.aimX, shooter.aimY))));
|
||||
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground) && !u.isMissile(), t -> ground && (!t.block.underBullets || (shooter != null && t == Vars.world.buildWorld(shooter.aimX, shooter.aimY))));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,7 +45,7 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
body.x = entity.x;
|
||||
body.y = entity.y;
|
||||
body.mass = entity.mass();
|
||||
body.radius = entity.hitSize / 2f;
|
||||
body.radius = entity.hitSize * Vars.unitCollisionRadiusScale;
|
||||
|
||||
PhysicRef ref = new PhysicRef(entity, body);
|
||||
refs.add(ref);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ import mindustry.entities.bullet.*;
|
||||
public class Bullets{
|
||||
public static BulletType
|
||||
|
||||
placeholder, spaceLiquid, damageLightning, damageLightningGround, fireball;
|
||||
placeholder, spaceLiquid, damageLightning, damageLightningGround, damageLightningAir, fireball;
|
||||
|
||||
public static void load(){
|
||||
|
||||
@@ -37,6 +37,10 @@ public class Bullets{
|
||||
damageLightningGround = damageLightning.copy();
|
||||
damageLightningGround.collidesAir = false;
|
||||
|
||||
damageLightningAir = damageLightning.copy();
|
||||
damageLightningAir.collidesGround = false;
|
||||
damageLightningAir.collidesTiles = false;
|
||||
|
||||
fireball = new FireBulletType(1f, 4){{
|
||||
hittable = false;
|
||||
}};
|
||||
|
||||
@@ -137,6 +137,10 @@ public class ErekirTechTree{
|
||||
node(eruptionDrill, Seq.with(new OnSector(stronghold)), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(largeCliffCrusher, Seq.with(new OnSector(stronghold)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -220,7 +224,9 @@ public class ErekirTechTree{
|
||||
});
|
||||
|
||||
node(heatRouter, () -> {
|
||||
node(smallHeatRedirector, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Fx{
|
||||
public static final Effect
|
||||
|
||||
none = new Effect(0, 0f, e -> {}),
|
||||
|
||||
|
||||
blockCrash = new Effect(90f, e -> {
|
||||
if(!(e.data instanceof Block block)) return;
|
||||
|
||||
@@ -445,6 +445,20 @@ public class Fx{
|
||||
}
|
||||
}),
|
||||
|
||||
titanExplosionLarge = new Effect(45f, 220f, e -> {
|
||||
color(e.color);
|
||||
stroke(e.fout() * 3f);
|
||||
float circleRad = 6f + e.finpow() * 110f;
|
||||
Lines.circle(e.x, e.y, circleRad);
|
||||
|
||||
rand.setSeed(e.id);
|
||||
for(int i = 0; i < 21; i++){
|
||||
float angle = rand.random(360f);
|
||||
float lenRand = rand.random(0.5f, 1f);
|
||||
Lines.lineAngle(e.x, e.y, angle, e.foutpow() * 50f * rand.random(1f, 0.6f) + 2f, e.finpow() * 100f * lenRand + 6f);
|
||||
}
|
||||
}),
|
||||
|
||||
titanSmoke = new Effect(300f, 300f, b -> {
|
||||
float intensity = 3f;
|
||||
|
||||
@@ -465,6 +479,34 @@ public class Fx{
|
||||
}
|
||||
}),
|
||||
|
||||
titanSmokeLarge = new Effect(400f, 400f, b -> {
|
||||
float intensity = 4f;
|
||||
|
||||
color(b.color, 0.65f);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rand.setSeed(b.id*2 + i);
|
||||
float lenScl = rand.random(0.5f, 1f);
|
||||
int fi = i;
|
||||
b.scaled(b.lifetime * lenScl, e -> {
|
||||
randLenVectors(e.id + fi - 1, e.fin(Interp.pow10Out), (int)(2.9f * intensity), 26f * intensity, (x, y, in, out) -> {
|
||||
float fout = e.fout(Interp.pow5Out) * rand.random(0.5f, 1f);
|
||||
float rad = fout * ((2f + intensity) * 2.35f);
|
||||
|
||||
Fill.circle(e.x + x, e.y + y, rad);
|
||||
Drawf.light(e.x + x, e.y + y, rad * 2.5f, b.color, 0.5f);
|
||||
});
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
smokeAoeCloud = new Effect(60f * 3f, 250f, e -> {
|
||||
color(e.color, 0.65f);
|
||||
|
||||
randLenVectors(e.id, 80, 90f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 6f * Mathf.clamp(e.fin() / 0.1f) * Mathf.clamp(e.fout() / 0.1f));
|
||||
});
|
||||
}),
|
||||
|
||||
missileTrailSmoke = new Effect(180f, 300f, b -> {
|
||||
float intensity = 2f;
|
||||
|
||||
@@ -485,6 +527,26 @@ public class Fx{
|
||||
}
|
||||
}).layer(Layer.bullet - 1f),
|
||||
|
||||
missileTrailSmokeSmall = new Effect(120f, 200f, b -> {
|
||||
float intensity = 1.3f;
|
||||
|
||||
color(b.color, 0.7f);
|
||||
for(int i = 0; i < 3; i++){
|
||||
rand.setSeed(b.id*2 + i);
|
||||
float lenScl = rand.random(0.5f, 1f);
|
||||
int fi = i;
|
||||
b.scaled(b.lifetime * lenScl, e -> {
|
||||
randLenVectors(e.id + fi - 1, e.fin(Interp.pow10Out), (int)(2.9f * intensity), 13f * intensity, (x, y, in, out) -> {
|
||||
float fout = e.fout(Interp.pow5Out) * rand.random(0.5f, 1f);
|
||||
float rad = fout * ((2f + intensity) * 2.35f);
|
||||
|
||||
Fill.circle(e.x + x, e.y + y, rad);
|
||||
Drawf.light(e.x + x, e.y + y, rad * 2.5f, b.color, 0.5f);
|
||||
});
|
||||
});
|
||||
}
|
||||
}).layer(Layer.bullet - 1f),
|
||||
|
||||
neoplasmSplat = new Effect(400f, 300f, b -> {
|
||||
float intensity = 3f;
|
||||
|
||||
@@ -523,6 +585,24 @@ public class Fx{
|
||||
}
|
||||
}),
|
||||
|
||||
scatheExplosionSmall = new Effect(40f, 160f, e -> {
|
||||
color(e.color);
|
||||
stroke(e.fout() * 4f);
|
||||
float circleRad = 6f + e.finpow() * 40f;
|
||||
Lines.circle(e.x, e.y, circleRad);
|
||||
|
||||
rand.setSeed(e.id);
|
||||
for(int i = 0; i < 16; i++){
|
||||
float angle = rand.random(360f);
|
||||
float lenRand = rand.random(0.5f, 1f);
|
||||
Tmp.v1.trns(angle, circleRad);
|
||||
|
||||
for(int s : Mathf.signs){
|
||||
Drawf.tri(e.x + Tmp.v1.x, e.y + Tmp.v1.y, e.foutpow() * 30f, e.fout() * 25f * lenRand + 6f, angle + 90f + s * 90f);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
scatheLight = new Effect(60f, 160f, e -> {
|
||||
float circleRad = 6f + e.finpow() * 60f;
|
||||
|
||||
@@ -530,6 +610,13 @@ public class Fx{
|
||||
Fill.circle(e.x, e.y, circleRad);
|
||||
}).layer(Layer.bullet + 2f),
|
||||
|
||||
scatheLightSmall = new Effect(60f, 160f, e -> {
|
||||
float circleRad = 6f + e.finpow() * 40f;
|
||||
|
||||
color(e.color, e.foutpow());
|
||||
Fill.circle(e.x, e.y, circleRad);
|
||||
}).layer(Layer.bullet + 2f),
|
||||
|
||||
scatheSlash = new Effect(40f, 160f, e -> {
|
||||
Draw.color(e.color);
|
||||
for(int s : Mathf.signs){
|
||||
@@ -761,7 +848,7 @@ public class Fx{
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
hitLaserBlast = new Effect(12, e -> {
|
||||
color(e.color);
|
||||
stroke(e.fout() * 1.5f);
|
||||
@@ -1114,7 +1201,7 @@ public class Fx{
|
||||
stroke(2f * e.fout());
|
||||
Lines.circle(e.x, e.y, 5f * e.fout());
|
||||
}),
|
||||
|
||||
|
||||
forceShrink = new Effect(20, e -> {
|
||||
color(e.color, e.fout());
|
||||
if(renderer.animateShields){
|
||||
@@ -1703,6 +1790,18 @@ public class Fx{
|
||||
}
|
||||
}),
|
||||
|
||||
shootSmokeMissileColor = new Effect(130f, 300f, e -> {
|
||||
color(e.color);
|
||||
alpha(0.5f);
|
||||
rand.setSeed(e.id);
|
||||
for(int i = 0; i < 35; i++){
|
||||
v.trns(e.rotation + 180f + rand.range(21f), rand.random(e.finpow() * 90f)).add(rand.range(3f), rand.range(3f));
|
||||
e.scaled(e.lifetime * rand.random(0.2f, 1f), b -> {
|
||||
Fill.circle(e.x + v.x, e.y + v.y, b.fout() * 9f + 0.3f);
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
regenParticle = new Effect(100f, e -> {
|
||||
color(Pal.regen);
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class Items{
|
||||
}};
|
||||
|
||||
scrap = new Item("scrap", Color.valueOf("777777")){{
|
||||
|
||||
cost = 0.5f;
|
||||
}};
|
||||
|
||||
silicon = new Item("silicon", Color.valueOf("53565c")){{
|
||||
|
||||
@@ -65,7 +65,6 @@ public class Planets{
|
||||
clearSectorOnLose = true;
|
||||
defaultCore = Blocks.coreBastion;
|
||||
iconColor = Color.valueOf("ff9266");
|
||||
hiddenItems.addAll(Items.serpuloItems).removeAll(Items.erekirItems);
|
||||
enemyBuildSpeedMultiplier = 0.4f;
|
||||
|
||||
//TODO disallowed for now
|
||||
@@ -86,6 +85,8 @@ public class Planets{
|
||||
r.coreDestroyClear = true;
|
||||
r.onlyDepositCore = true;
|
||||
};
|
||||
campaignRuleDefaults.fog = true;
|
||||
campaignRuleDefaults.showSpawns = true;
|
||||
|
||||
unlockedOnLand.add(Blocks.coreBastion);
|
||||
}};
|
||||
@@ -144,6 +145,7 @@ public class Planets{
|
||||
r.waveTeam = Team.crux;
|
||||
r.placeRangeCheck = false;
|
||||
r.showSpawns = false;
|
||||
r.coreDestroyClear = true;
|
||||
};
|
||||
iconColor = Color.valueOf("7d4dff");
|
||||
atmosphereColor = Color.valueOf("3c1b8f");
|
||||
@@ -152,7 +154,6 @@ public class Planets{
|
||||
startSector = 15;
|
||||
alwaysUnlocked = true;
|
||||
landCloudColor = Pal.spore.cpy().a(0.5f);
|
||||
hiddenItems.addAll(Items.erekirItems).removeAll(Items.serpuloItems);
|
||||
}};
|
||||
|
||||
verilus = makeAsteroid("verlius", sun, Blocks.stoneWall, Blocks.iceWall, 0.5f, 12, 2f, gen -> {
|
||||
|
||||
@@ -7,10 +7,12 @@ import static mindustry.content.Planets.*;
|
||||
public class SectorPresets{
|
||||
public static SectorPreset
|
||||
groundZero,
|
||||
craters, biomassFacility, frozenForest, ruinousShores, windsweptIslands, stainedMountains, tarFields,
|
||||
fungalPass, extractionOutpost, saltFlats, overgrowth,
|
||||
craters, biomassFacility, taintedWoods, frozenForest, ruinousShores, facility32m, windsweptIslands, stainedMountains, tarFields,
|
||||
frontier, fungalPass, infestedCanyons, atolls, mycelialBastion, extractionOutpost, saltFlats, testingGrounds, overgrowth, //polarAerodrome,
|
||||
impact0078, desolateRift, nuclearComplex, planetaryTerminal,
|
||||
coastline, navalFortress,
|
||||
coastline, navalFortress, weatheredChannels, seaPort,
|
||||
|
||||
geothermalStronghold, cruxscape,
|
||||
|
||||
onset, aegis, lake, intersect, basin, atlas, split, marsh, peaks, ravine, caldera,
|
||||
stronghold, crevice, siege, crossroads, karst, origin;
|
||||
@@ -32,6 +34,11 @@ public class SectorPresets{
|
||||
difficulty = 5;
|
||||
}};
|
||||
|
||||
testingGrounds = new SectorPreset("testingGrounds", serpulo, 3){{
|
||||
difficulty = 7;
|
||||
captureWave = 33;
|
||||
}};
|
||||
|
||||
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
|
||||
captureWave = 15;
|
||||
difficulty = 2;
|
||||
@@ -42,6 +49,11 @@ public class SectorPresets{
|
||||
difficulty = 3;
|
||||
}};
|
||||
|
||||
taintedWoods = new SectorPreset("taintedWoods", serpulo, 221){{
|
||||
captureWave = 33;
|
||||
difficulty = 5;
|
||||
}};
|
||||
|
||||
craters = new SectorPreset("craters", serpulo, 18){{
|
||||
captureWave = 20;
|
||||
difficulty = 2;
|
||||
@@ -52,6 +64,15 @@ public class SectorPresets{
|
||||
difficulty = 3;
|
||||
}};
|
||||
|
||||
seaPort = new SectorPreset("seaPort", serpulo, 47){{
|
||||
difficulty = 4;
|
||||
}};
|
||||
|
||||
facility32m = new SectorPreset("facility32m", serpulo, 64){{
|
||||
captureWave = 25;
|
||||
difficulty = 4;
|
||||
}};
|
||||
|
||||
windsweptIslands = new SectorPreset("windsweptIslands", serpulo, 246){{
|
||||
captureWave = 30;
|
||||
difficulty = 4;
|
||||
@@ -66,19 +87,45 @@ public class SectorPresets{
|
||||
difficulty = 5;
|
||||
}};
|
||||
|
||||
//TODO: removed for now
|
||||
//polarAerodrome = new SectorPreset("polarAerodrome", serpulo, 68){{
|
||||
// difficulty = 7;
|
||||
//}};
|
||||
|
||||
coastline = new SectorPreset("coastline", serpulo, 108){{
|
||||
captureWave = 30;
|
||||
difficulty = 5;
|
||||
}};
|
||||
|
||||
navalFortress = new SectorPreset("navalFortress", serpulo, 216){{
|
||||
weatheredChannels = new SectorPreset("weatheredChannels", serpulo, 39){{
|
||||
captureWave = 40;
|
||||
difficulty = 9;
|
||||
}};
|
||||
|
||||
navalFortress = new SectorPreset("navalFortress", serpulo, 216){{
|
||||
difficulty = 8;
|
||||
}};
|
||||
|
||||
frontier = new SectorPreset("frontier", serpulo, 203){{
|
||||
difficulty = 4;
|
||||
}};
|
||||
|
||||
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
|
||||
difficulty = 4;
|
||||
}};
|
||||
|
||||
infestedCanyons = new SectorPreset("infestedCanyons", serpulo, 210){{
|
||||
difficulty = 4;
|
||||
}};
|
||||
|
||||
atolls = new SectorPreset("atolls", serpulo, 1){{
|
||||
difficulty = 7;
|
||||
}};
|
||||
|
||||
mycelialBastion = new SectorPreset("mycelialBastion", serpulo, 260){{
|
||||
difficulty = 8;
|
||||
}};
|
||||
|
||||
overgrowth = new SectorPreset("overgrowth", serpulo, 134){{
|
||||
difficulty = 5;
|
||||
}};
|
||||
@@ -108,6 +155,14 @@ public class SectorPresets{
|
||||
isLastSector = true;
|
||||
}};
|
||||
|
||||
geothermalStronghold = new SectorPreset("geothermalStronghold", serpulo, 264){{
|
||||
difficulty = 10;
|
||||
}};
|
||||
|
||||
cruxscape = new SectorPreset("cruxscape", serpulo, 54){{
|
||||
difficulty = 10;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
//region erekir
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.content;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static mindustry.content.Blocks.*;
|
||||
import static mindustry.content.SectorPresets.craters;
|
||||
@@ -122,7 +123,7 @@ public class SerpuloTechTree{
|
||||
});
|
||||
|
||||
node(pyratiteMixer, () -> {
|
||||
node(blastMixer, () -> {
|
||||
node(blastMixer, Seq.with(new SectorComplete(facility32m)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -138,7 +139,7 @@ public class SerpuloTechTree{
|
||||
});
|
||||
});
|
||||
|
||||
node(plastaniumCompressor, Seq.with(new SectorComplete(windsweptIslands)), () -> {
|
||||
node(plastaniumCompressor, Seq.with(new SectorComplete(windsweptIslands), new OnSector(tarFields)), () -> {
|
||||
node(phaseWeaver, Seq.with(new SectorComplete(tarFields)), () -> {
|
||||
|
||||
});
|
||||
@@ -261,12 +262,21 @@ public class SerpuloTechTree{
|
||||
node(duo, () -> {
|
||||
node(copperWall, () -> {
|
||||
node(copperWallLarge, () -> {
|
||||
node(scrapWall, () -> {
|
||||
node(scrapWallLarge, () -> {
|
||||
node(scrapWallHuge, () -> {
|
||||
node(scrapWallGigantic);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(titaniumWall, () -> {
|
||||
node(titaniumWallLarge);
|
||||
|
||||
node(door, () -> {
|
||||
node(doorLarge);
|
||||
});
|
||||
|
||||
node(plastaniumWall, () -> {
|
||||
node(plastaniumWallLarge, () -> {
|
||||
|
||||
@@ -359,11 +369,12 @@ public class SerpuloTechTree{
|
||||
});
|
||||
});
|
||||
|
||||
node(crawler, () -> {
|
||||
//override research requirements to have graphite, not coal
|
||||
node(crawler, ItemStack.with(Items.silicon, 400, Items.graphite, 400), () -> {
|
||||
node(atrax, () -> {
|
||||
node(spiroct, () -> {
|
||||
node(arkyid, () -> {
|
||||
node(toxopid, () -> {
|
||||
node(toxopid, Seq.with(new SectorComplete(mycelialBastion)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -397,7 +408,7 @@ public class SerpuloTechTree{
|
||||
});
|
||||
});
|
||||
|
||||
node(navalFactory, Seq.with(new SectorComplete(ruinousShores)), () -> {
|
||||
node(navalFactory, Seq.with(new OnSector(windsweptIslands)), () -> {
|
||||
node(risso, () -> {
|
||||
node(minke, () -> {
|
||||
node(bryde, () -> {
|
||||
@@ -425,8 +436,8 @@ public class SerpuloTechTree{
|
||||
});
|
||||
|
||||
node(additiveReconstructor, Seq.with(new SectorComplete(biomassFacility)), () -> {
|
||||
node(multiplicativeReconstructor, () -> {
|
||||
node(exponentialReconstructor, Seq.with(new SectorComplete(overgrowth)), () -> {
|
||||
node(multiplicativeReconstructor, Seq.with(new SectorComplete(overgrowth)), () -> {
|
||||
node(exponentialReconstructor, () -> {
|
||||
node(tetrativeReconstructor, () -> {
|
||||
|
||||
});
|
||||
@@ -446,6 +457,16 @@ public class SerpuloTechTree{
|
||||
new Research(mender),
|
||||
new Research(combustionGenerator)
|
||||
), () -> {
|
||||
node(frontier, Seq.with(
|
||||
new Research(groundFactory),
|
||||
new Research(airFactory),
|
||||
new Research(thermalGenerator),
|
||||
new Research(dagger),
|
||||
new Research(mono)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(ruinousShores, Seq.with(
|
||||
new SectorComplete(craters),
|
||||
new Research(graphitePress),
|
||||
@@ -459,6 +480,18 @@ public class SerpuloTechTree{
|
||||
new Research(siliconSmelter),
|
||||
new Research(steamGenerator)
|
||||
), () -> {
|
||||
node(seaPort, Seq.with(
|
||||
new SectorComplete(biomassFacility),
|
||||
new Research(navalFactory),
|
||||
new Research(risso),
|
||||
new Research(retusa),
|
||||
new Research(steamGenerator),
|
||||
new Research(cultivator),
|
||||
new Research(coalCentrifuge)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(tarFields, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(coalCentrifuge),
|
||||
@@ -487,28 +520,73 @@ public class SerpuloTechTree{
|
||||
new Research(risso),
|
||||
new Research(minke),
|
||||
new Research(bryde),
|
||||
new Research(sei),
|
||||
new Research(omura),
|
||||
new Research(spectre),
|
||||
new Research(launchPad),
|
||||
new Research(massDriver),
|
||||
new Research(impactReactor),
|
||||
new Research(additiveReconstructor),
|
||||
new Research(exponentialReconstructor)
|
||||
new Research(exponentialReconstructor),
|
||||
new Research(tetrativeReconstructor)
|
||||
), () -> {
|
||||
node(geothermalStronghold, Seq.with(
|
||||
new Research(omura),
|
||||
new Research(navanax),
|
||||
new Research(eclipse),
|
||||
new Research(oct),
|
||||
new Research(reign),
|
||||
new Research(corvus),
|
||||
new Research(toxopid)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(cruxscape, Seq.with(
|
||||
new Research(omura),
|
||||
new Research(navanax),
|
||||
new Research(eclipse),
|
||||
new Research(oct),
|
||||
new Research(reign),
|
||||
new Research(corvus),
|
||||
new Research(toxopid)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(extractionOutpost, Seq.with(
|
||||
new SectorComplete(stainedMountains),
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(groundFactory),
|
||||
new Research(nova),
|
||||
new Research(airFactory),
|
||||
new Research(mono)
|
||||
node(facility32m, Seq.with(
|
||||
new Research(pneumaticDrill),
|
||||
new SectorComplete(stainedMountains)
|
||||
), () -> {
|
||||
node(extractionOutpost, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new SectorComplete(facility32m),
|
||||
new Research(groundFactory),
|
||||
new Research(nova),
|
||||
new Research(airFactory),
|
||||
new Research(mono)
|
||||
), () -> {
|
||||
//TODO: removed for now
|
||||
/*node(polarAerodrome, Seq.with(
|
||||
new SectorComplete(fungalPass),
|
||||
new SectorComplete(desolateRift),
|
||||
new SectorComplete(overgrowth),
|
||||
new Research(multiplicativeReconstructor),
|
||||
new Research(zenith),
|
||||
new Research(swarmer),
|
||||
new Research(cyclone),
|
||||
new Research(blastDrill),
|
||||
new Research(blastDrill),
|
||||
new Research(massDriver)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
node(saltFlats, Seq.with(
|
||||
@@ -518,21 +596,41 @@ public class SerpuloTechTree{
|
||||
new Research(airFactory),
|
||||
new Research(door)
|
||||
), () -> {
|
||||
node(testingGrounds, Seq.with(
|
||||
new Research(cryofluidMixer),
|
||||
new Research(Liquids.cryofluid),
|
||||
new Research(waterExtractor),
|
||||
new Research(ripple)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(coastline, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new SectorComplete(saltFlats),
|
||||
new Research(navalFactory),
|
||||
new Research(payloadConveyor)
|
||||
), () -> {
|
||||
|
||||
node(navalFortress, Seq.with(
|
||||
new SectorComplete(coastline),
|
||||
new SectorComplete(extractionOutpost),
|
||||
new Research(coreNucleus),
|
||||
new Research(massDriver),
|
||||
new Research(oxynoe),
|
||||
new Research(minke),
|
||||
new Research(bryde),
|
||||
new Research(cyclone),
|
||||
new Research(ripple)
|
||||
), () -> {
|
||||
node(weatheredChannels, Seq.with(
|
||||
new SectorComplete(impact0078),
|
||||
new Research(bryde),
|
||||
new Research(surgeSmelter),
|
||||
new Research(overdriveProjector)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -548,7 +646,22 @@ public class SerpuloTechTree{
|
||||
new Research(UnitTypes.mace),
|
||||
new Research(UnitTypes.flare)
|
||||
), () -> {
|
||||
node(mycelialBastion, Seq.with(
|
||||
new Research(atrax),
|
||||
new Research(spiroct),
|
||||
new Research(multiplicativeReconstructor),
|
||||
new Research(exponentialReconstructor)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(atolls, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(multiplicativeReconstructor),
|
||||
new Research(mega)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -559,6 +672,14 @@ public class SerpuloTechTree{
|
||||
new Research(scatter),
|
||||
new Research(graphitePress)
|
||||
), () -> {
|
||||
node(taintedWoods, Seq.with(
|
||||
new SectorComplete(biomassFacility),
|
||||
new Research(Items.sporePod),
|
||||
new Research(wave)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(stainedMountains, Seq.with(
|
||||
new SectorComplete(biomassFacility),
|
||||
new Research(pneumaticDrill),
|
||||
@@ -569,6 +690,16 @@ public class SerpuloTechTree{
|
||||
new Research(groundFactory),
|
||||
new Research(door)
|
||||
), () -> {
|
||||
node(infestedCanyons, Seq.with(
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(navalFactory),
|
||||
new Research(risso),
|
||||
new Research(minke),
|
||||
new Research(additiveReconstructor)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(nuclearComplex, Seq.with(
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(thermalGenerator),
|
||||
|
||||
@@ -61,12 +61,12 @@ public class StatusEffects{
|
||||
color = Pal.lightishGray;
|
||||
speedMultiplier = 0.4f;
|
||||
|
||||
init(() -> opposite(fast));
|
||||
init(() -> opposite(fast));
|
||||
}};
|
||||
|
||||
fast = new StatusEffect("fast"){{
|
||||
color = Pal.boostTo;
|
||||
speedMultiplier = 1.6f;
|
||||
color = Pal.boostTo;
|
||||
speedMultiplier = 1.6f;
|
||||
|
||||
init(() -> opposite(slow));
|
||||
}};
|
||||
@@ -89,7 +89,7 @@ public class StatusEffects{
|
||||
opposite(burning, melting);
|
||||
});
|
||||
}};
|
||||
|
||||
|
||||
muddy = new StatusEffect("muddy"){{
|
||||
color = Color.valueOf("46382a");
|
||||
speedMultiplier = 0.94f;
|
||||
|
||||
@@ -139,6 +139,16 @@ public class TechTree{
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the specified database tab to all the content in this tree. */
|
||||
public void addDatabaseTab(UnlockableContent tab){
|
||||
each(node -> node.content.databaseTabs.add(tab));
|
||||
}
|
||||
|
||||
/** Adds the specified planet to the shownPlanets of all the content in this tree. */
|
||||
public void addPlanet(Planet planet){
|
||||
each(node -> node.content.shownPlanets.add(planet));
|
||||
}
|
||||
|
||||
public Drawable icon(){
|
||||
return icon == null ? new TextureRegionDrawable(content.uiIcon) : icon;
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ public class UnitTypes{
|
||||
|
||||
reign = new UnitType("reign"){{
|
||||
speed = 0.4f;
|
||||
hitSize = 26f;
|
||||
hitSize = 30f;
|
||||
rotateSpeed = 1.65f;
|
||||
health = 24000;
|
||||
armor = 18f;
|
||||
@@ -322,7 +322,7 @@ public class UnitTypes{
|
||||
speed = 0.55f;
|
||||
hitSize = 8f;
|
||||
health = 120f;
|
||||
buildSpeed = 0.35f;
|
||||
buildSpeed = 0.3f;
|
||||
armor = 1f;
|
||||
|
||||
abilities.add(new RepairFieldAbility(10f, 60f * 4, 60f));
|
||||
@@ -617,6 +617,7 @@ public class UnitTypes{
|
||||
|
||||
weapons.add(new Weapon(){{
|
||||
shootOnDeath = true;
|
||||
targetUnderBlocks = false;
|
||||
reload = 24f;
|
||||
shootCone = 180f;
|
||||
ejectEffect = Fx.none;
|
||||
@@ -1011,7 +1012,7 @@ public class UnitTypes{
|
||||
accel = 0.08f;
|
||||
drag = 0.016f;
|
||||
flying = true;
|
||||
hitSize = 10f;
|
||||
hitSize = 11f;
|
||||
targetAir = false;
|
||||
engineOffset = 7.8f;
|
||||
range = 140f;
|
||||
@@ -1041,6 +1042,7 @@ public class UnitTypes{
|
||||
|
||||
status = StatusEffects.blasted;
|
||||
statusDuration = 60f;
|
||||
damage = splashDamage * 0.5f;
|
||||
}};
|
||||
}});
|
||||
}};
|
||||
@@ -1254,6 +1256,7 @@ public class UnitTypes{
|
||||
controller = u -> new MinerAI();
|
||||
|
||||
defaultCommand = UnitCommand.mineCommand;
|
||||
allowChangeCommands = false;
|
||||
|
||||
flying = true;
|
||||
drag = 0.06f;
|
||||
@@ -1445,6 +1448,7 @@ public class UnitTypes{
|
||||
healPercent = 15f;
|
||||
splashDamage = 220f;
|
||||
splashDamageRadius = 80f;
|
||||
damage = splashDamage * 0.7f;
|
||||
}};
|
||||
}});
|
||||
}};
|
||||
@@ -1831,7 +1835,6 @@ public class UnitTypes{
|
||||
//region naval support
|
||||
retusa = new UnitType("retusa"){{
|
||||
speed = 0.9f;
|
||||
targetAir = false;
|
||||
drag = 0.14f;
|
||||
hitSize = 11f;
|
||||
health = 270;
|
||||
@@ -1861,6 +1864,23 @@ public class UnitTypes{
|
||||
}};
|
||||
}});
|
||||
|
||||
weapons.add(new Weapon("retusa-weapon"){{
|
||||
shootSound = Sounds.lasershoot;
|
||||
reload = 22f;
|
||||
x = 4.5f;
|
||||
y = -3.5f;
|
||||
rotateSpeed = 5f;
|
||||
mirror = true;
|
||||
rotate = true;
|
||||
bullet = new LaserBoltBulletType(5.2f, 12){{
|
||||
lifetime = 30f;
|
||||
healPercent = 5.5f;
|
||||
collidesTeam = true;
|
||||
backColor = Pal.heal;
|
||||
frontColor = Color.white;
|
||||
}};
|
||||
}});
|
||||
|
||||
weapons.add(new Weapon(){{
|
||||
mirror = false;
|
||||
rotate = true;
|
||||
@@ -1912,7 +1932,7 @@ public class UnitTypes{
|
||||
trailWidth = 3f;
|
||||
trailLength = 8;
|
||||
|
||||
splashDamage = 33f;
|
||||
splashDamage = 40f;
|
||||
splashDamageRadius = 32f;
|
||||
}};
|
||||
}});
|
||||
@@ -2346,7 +2366,8 @@ public class UnitTypes{
|
||||
//region core
|
||||
|
||||
alpha = new UnitType("alpha"){{
|
||||
aiController = BuilderAI::new;
|
||||
aiController = () -> new BuilderAI(true, 400f);
|
||||
controller = u -> u.team.isAI() ? aiController.get() : new CommandAI();
|
||||
isEnemy = false;
|
||||
|
||||
lowAltitude = true;
|
||||
@@ -2384,7 +2405,8 @@ public class UnitTypes{
|
||||
}};
|
||||
|
||||
beta = new UnitType("beta"){{
|
||||
aiController = BuilderAI::new;
|
||||
aiController = () -> new BuilderAI(true, 400f);
|
||||
controller = u -> u.team.isAI() ? aiController.get() : new CommandAI();
|
||||
isEnemy = false;
|
||||
|
||||
flying = true;
|
||||
@@ -2425,7 +2447,8 @@ public class UnitTypes{
|
||||
}};
|
||||
|
||||
gamma = new UnitType("gamma"){{
|
||||
aiController = BuilderAI::new;
|
||||
aiController = () -> new BuilderAI(true, 400f);
|
||||
controller = u -> u.team.isAI() ? aiController.get() : new CommandAI();
|
||||
isEnemy = false;
|
||||
|
||||
lowAltitude = true;
|
||||
@@ -2646,7 +2669,7 @@ public class UnitTypes{
|
||||
width = 5f;
|
||||
height = 7f;
|
||||
lifetime = 15f;
|
||||
hitSize = 4f;
|
||||
hitSize = 4f;
|
||||
pierceCap = 3;
|
||||
pierce = true;
|
||||
pierceBuilding = true;
|
||||
@@ -3524,7 +3547,7 @@ public class UnitTypes{
|
||||
trailWidth = 2.2f;
|
||||
trailLength = 7;
|
||||
trailChance = -1f;
|
||||
|
||||
|
||||
collidesAir = false;
|
||||
|
||||
despawnEffect = Fx.none;
|
||||
|
||||
@@ -27,6 +27,7 @@ import static mindustry.Vars.*;
|
||||
public class ContentLoader{
|
||||
private ObjectMap<String, MappableContent>[] contentNameMap = new ObjectMap[ContentType.all.length];
|
||||
private Seq<Content>[] contentMap = new Seq[ContentType.all.length];
|
||||
private ObjectMap<String, MappableContent> nameMap = new ObjectMap<>();
|
||||
private MappableContent[][] temporaryMapper;
|
||||
private @Nullable LoadedMod currentMod;
|
||||
private @Nullable Content lastAdded;
|
||||
@@ -81,13 +82,14 @@ public class ContentLoader{
|
||||
for(int k = 0; k < contentMap.length; k++){
|
||||
Log.debug("[@]: loaded @", ContentType.all[k].name(), contentMap[k].size);
|
||||
}
|
||||
Log.debug("Total content loaded: @", Seq.with(ContentType.all).mapInt(c -> contentMap[c.ordinal()].size).sum());
|
||||
Log.debug("Total content loaded: @", Seq.with(ContentType.all).sum(c -> contentMap[c.ordinal()].size));
|
||||
Log.debug("-------------------");
|
||||
}
|
||||
|
||||
/** Calls Content#init() on everything. Use only after all modules have been created. */
|
||||
public void init(){
|
||||
initialize(Content::init);
|
||||
initialize(Content::postInit);
|
||||
if(logicVars != null) logicVars.init();
|
||||
Events.fire(new ContentInitEvent());
|
||||
}
|
||||
@@ -171,6 +173,13 @@ public class ContentLoader{
|
||||
|
||||
public void handleMappableContent(MappableContent content){
|
||||
if(contentNameMap[content.getContentType().ordinal()].containsKey(content.name)){
|
||||
var list = contentMap[content.getContentType().ordinal()];
|
||||
|
||||
//this method is only called when registering content, and after handleContent.
|
||||
//If this is the last registered content, and it is invalid, make sure to remove it from the list to prevent invalid stuff from being registered
|
||||
if(list.size > 0 && list.peek() == content){
|
||||
list.pop();
|
||||
}
|
||||
throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')");
|
||||
}
|
||||
if(currentMod != null){
|
||||
@@ -180,12 +189,18 @@ public class ContentLoader{
|
||||
}
|
||||
}
|
||||
contentNameMap[content.getContentType().ordinal()].put(content.name, content);
|
||||
nameMap.put(content.name, content);
|
||||
}
|
||||
|
||||
public void setTemporaryMapper(MappableContent[][] temporaryMapper){
|
||||
this.temporaryMapper = temporaryMapper;
|
||||
}
|
||||
|
||||
/** @return the last registered content with the specified name. Note that the content loader makes no attempt to resolve name conflicts. This method can be unreliable. */
|
||||
public @Nullable MappableContent byName(String name){
|
||||
return nameMap.get(name);
|
||||
}
|
||||
|
||||
public Seq<Content>[] getContentMap(){
|
||||
return contentMap;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
@@ -30,7 +30,6 @@ import mindustry.net.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -75,6 +74,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
ui.showInfo("@mods.initfailed");
|
||||
});
|
||||
}
|
||||
checkAutoUnlocks();
|
||||
});
|
||||
|
||||
Events.on(StateChangeEvent.class, event -> {
|
||||
@@ -404,7 +404,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
ui.planet.hide();
|
||||
SaveSlot slot = sector.save;
|
||||
sector.planet.setLastSector(sector);
|
||||
if(slot != null && !clearSectors && (!sector.planet.clearSectorOnLose || sector.info.hasCore)){
|
||||
if(slot != null && !clearSectors && (!(sector.planet.clearSectorOnLose || sector.info.hasWorldProcessor) || sector.info.hasCore)){
|
||||
|
||||
try{
|
||||
boolean hadNoCore = !sector.info.hasCore;
|
||||
@@ -417,7 +417,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
//if there is no base, simulate a new game and place the right loadout at the spawn position
|
||||
if(state.rules.defaultTeam.cores().isEmpty() || hadNoCore){
|
||||
|
||||
if(sector.planet.clearSectorOnLose){
|
||||
if(sector.planet.clearSectorOnLose || sector.info.hasWorldProcessor){
|
||||
playNewSector(origin, sector, reloader);
|
||||
}else{
|
||||
//no spawn set -> delete the sector save
|
||||
@@ -441,6 +441,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
state.wave = 1;
|
||||
//set up default wave time
|
||||
state.wavetime = state.rules.initialWaveSpacing <= 0f ? (state.rules.waveSpacing * (sector.preset == null ? 2f : sector.preset.startWaveTimeMultiplier)) : state.rules.initialWaveSpacing;
|
||||
state.wavetime *= sector.planet.campaignRules.difficulty.waveTimeMultiplier;
|
||||
//reset captured state
|
||||
sector.info.wasCaptured = false;
|
||||
|
||||
@@ -457,7 +458,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
for(var plan : state.rules.waveTeam.data().plans){
|
||||
Tile tile = world.tile(plan.x, plan.y);
|
||||
if(tile != null){
|
||||
tile.setBlock(content.block(plan.block), state.rules.waveTeam, plan.rotation);
|
||||
tile.setBlock(plan.block, state.rules.waveTeam, plan.rotation);
|
||||
if(plan.config != null && tile.build != null){
|
||||
tile.build.configureAny(plan.config);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class GameState{
|
||||
}
|
||||
|
||||
public @Nullable Planet getPlanet(){
|
||||
return rules.sector != null ? rules.sector.planet : null;
|
||||
return rules.sector != null ? rules.sector.planet : rules.planet;
|
||||
}
|
||||
|
||||
public boolean isEditor(){
|
||||
|
||||
@@ -92,7 +92,7 @@ public class Logic implements ApplicationListener{
|
||||
if(wavesPassed > 0){
|
||||
//simulate wave counter moving forward
|
||||
state.wave += wavesPassed;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
state.wavetime = state.rules.waveSpacing * state.getPlanet().campaignRules.difficulty.waveTimeMultiplier;
|
||||
|
||||
SectorDamage.applyCalculatedDamage();
|
||||
}
|
||||
@@ -131,6 +131,7 @@ public class Logic implements ApplicationListener{
|
||||
//enable building AI on campaign unless the preset disables it
|
||||
|
||||
state.rules.coreIncinerates = true;
|
||||
state.rules.allowEditWorldProcessors = false;
|
||||
state.rules.waveTeam.rules().infiniteResources = true;
|
||||
state.rules.waveTeam.rules().buildSpeedMultiplier *= state.getPlanet().enemyBuildSpeedMultiplier;
|
||||
|
||||
@@ -140,10 +141,6 @@ public class Logic implements ApplicationListener{
|
||||
core.items.set(item, core.block.itemCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
//set up hidden items
|
||||
state.rules.hiddenBuildItems.clear();
|
||||
state.rules.hiddenBuildItems.addAll(state.rules.sector.planet.hiddenItems);
|
||||
}
|
||||
|
||||
//save settings
|
||||
@@ -213,8 +210,7 @@ public class Logic implements ApplicationListener{
|
||||
var bounds = tile.block().bounds(tile.x, tile.y, Tmp.r1);
|
||||
while(it.hasNext()){
|
||||
BlockPlan b = it.next();
|
||||
Block block = content.block(b.block);
|
||||
if(bounds.overlaps(block.bounds(b.x, b.y, Tmp.r2))){
|
||||
if(bounds.overlaps(b.block.bounds(b.x, b.y, Tmp.r2))){
|
||||
b.removed = true;
|
||||
it.remove();
|
||||
}
|
||||
@@ -225,7 +221,7 @@ public class Logic implements ApplicationListener{
|
||||
public void play(){
|
||||
state.set(State.playing);
|
||||
//grace period of 2x wave time before game starts
|
||||
state.wavetime = state.rules.initialWaveSpacing <= 0 ? state.rules.waveSpacing * 2 : state.rules.initialWaveSpacing;
|
||||
state.wavetime = (state.rules.initialWaveSpacing <= 0 ? state.rules.waveSpacing * 2 : state.rules.initialWaveSpacing) * (state.isCampaign() ? state.getPlanet().campaignRules.difficulty.waveTimeMultiplier : 1f);;
|
||||
Events.fire(new PlayEvent());
|
||||
|
||||
//add starting items
|
||||
@@ -274,7 +270,7 @@ public class Logic implements ApplicationListener{
|
||||
public void runWave(){
|
||||
spawner.spawnEnemies();
|
||||
state.wave++;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
state.wavetime = state.rules.waveSpacing * (state.isCampaign() ? state.getPlanet().campaignRules.difficulty.waveTimeMultiplier : 1f);
|
||||
|
||||
Events.fire(new WaveEvent());
|
||||
}
|
||||
@@ -398,8 +394,8 @@ public class Logic implements ApplicationListener{
|
||||
public static void researched(Content content){
|
||||
if(!(content instanceof UnlockableContent u)) return;
|
||||
|
||||
boolean was = u.unlockedNow();
|
||||
state.rules.researched.add(u.name);
|
||||
boolean was = u.unlockedNowHost();
|
||||
state.rules.researched.add(u);
|
||||
|
||||
if(!was){
|
||||
Events.fire(new UnlockEvent(u));
|
||||
|
||||
@@ -10,6 +10,7 @@ import arc.util.*;
|
||||
import arc.util.CommandHandler.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.JsonValue.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.core.GameState.*;
|
||||
@@ -18,6 +19,7 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.net.Administration.*;
|
||||
import mindustry.net.*;
|
||||
@@ -32,10 +34,12 @@ import java.util.zip.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class NetClient implements ApplicationListener{
|
||||
private static final long entitySnapshotTimeout = 1000 * 20;
|
||||
private static final float dataTimeout = 60 * 30;
|
||||
/** ticks between syncs, e.g. 5 means 60/5 = 12 syncs/sec*/
|
||||
private static final float playerSyncTime = 4;
|
||||
private static final Reads dataReads = new Reads(null);
|
||||
private static final JsonValue tmpJsonMap = new JsonValue(ValueType.object);
|
||||
|
||||
private long ping;
|
||||
private Interval timer = new Interval(5);
|
||||
@@ -47,6 +51,8 @@ public class NetClient implements ApplicationListener{
|
||||
private boolean quietReset = false;
|
||||
/** Counter for data timeout. */
|
||||
private float timeoutTime = 0f;
|
||||
/** Timestamp for last UDP state snapshot received. */
|
||||
private long lastSnapshotTimestamp;
|
||||
/** Last sent client snapshot ID. */
|
||||
private int lastSent;
|
||||
|
||||
@@ -57,6 +63,8 @@ public class NetClient implements ApplicationListener{
|
||||
private DataInputStream dataStream = new DataInputStream(byteStream);
|
||||
/** Packet handlers for custom types of messages. */
|
||||
private ObjectMap<String, Seq<Cons<String>>> customPacketHandlers = new ObjectMap<>();
|
||||
/** Packet handlers for custom types of messages, in binary. */
|
||||
private ObjectMap<String, Seq<Cons<byte[]>>> customBinaryPacketHandlers = new ObjectMap<>();
|
||||
|
||||
public NetClient(){
|
||||
|
||||
@@ -147,10 +155,34 @@ public class NetClient implements ApplicationListener{
|
||||
return customPacketHandlers.get(type, Seq::new);
|
||||
}
|
||||
|
||||
public void addBinaryPacketHandler(String type, Cons<byte[]> handler){
|
||||
customBinaryPacketHandlers.get(type, Seq::new).add(handler);
|
||||
}
|
||||
|
||||
public Seq<Cons<byte[]>> getBinaryPacketHandlers(String type){
|
||||
return customBinaryPacketHandlers.get(type, Seq::new);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.server, variants = Variant.both)
|
||||
public static void clientBinaryPacketReliable(String type, byte[] contents){
|
||||
var arr = netClient.customBinaryPacketHandlers.get(type);
|
||||
if(arr != null){
|
||||
for(var c : arr){
|
||||
c.get(contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.server, variants = Variant.both, unreliable = true)
|
||||
public static void clientBinaryPacketUnreliable(String type, byte[] contents){
|
||||
clientBinaryPacketReliable(type, contents);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.server, variants = Variant.both)
|
||||
public static void clientPacketReliable(String type, String contents){
|
||||
if(netClient.customPacketHandlers.containsKey(type)){
|
||||
for(Cons<String> c : netClient.customPacketHandlers.get(type)){
|
||||
var arr = netClient.customPacketHandlers.get(type);
|
||||
if(arr != null){
|
||||
for(Cons<String> c : arr){
|
||||
c.get(contents);
|
||||
}
|
||||
}
|
||||
@@ -340,6 +372,18 @@ public class NetClient implements ApplicationListener{
|
||||
state.rules = rules;
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void setRule(String rule, String jsonData){
|
||||
try{
|
||||
//readField searches for the specified value, so create a fake parent for it.
|
||||
tmpJsonMap.child = null;
|
||||
tmpJsonMap.addChild(rule, new JsonReader().parse(jsonData));
|
||||
JsonIO.json.readField(state.rules, rule, tmpJsonMap);
|
||||
}catch(Throwable error){
|
||||
Log.err("Failed to read rule", error);
|
||||
}
|
||||
}
|
||||
|
||||
//NOTE: avoid using this, runs into packet/buffer size limitations
|
||||
@Remote(variants = Variant.both)
|
||||
public static void setObjectives(MapObjectives executor){
|
||||
@@ -437,6 +481,7 @@ public class NetClient implements ApplicationListener{
|
||||
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
|
||||
public static void entitySnapshot(short amount, byte[] data){
|
||||
try{
|
||||
netClient.lastSnapshotTimestamp = Time.millis();
|
||||
netClient.byteStream.setBytes(data);
|
||||
DataInputStream input = netClient.dataStream;
|
||||
|
||||
@@ -534,7 +579,18 @@ public class NetClient implements ApplicationListener{
|
||||
if(!net.client()) return;
|
||||
|
||||
if(state.isGame()){
|
||||
if(!connecting) sync();
|
||||
if(!connecting){
|
||||
sync();
|
||||
|
||||
//timeout if UDP snapshot packets are not received for a while
|
||||
if(lastSnapshotTimestamp > 0 && Time.timeSinceMillis(lastSnapshotTimestamp) > entitySnapshotTimeout){
|
||||
Log.err("Timed out after not received UDP snapshots.");
|
||||
quiet = true;
|
||||
ui.showErrorMessage("@disconnect.snapshottimeout");
|
||||
net.disconnect();
|
||||
lastSnapshotTimestamp = 0;
|
||||
}
|
||||
}
|
||||
}else if(!connecting){
|
||||
net.disconnect();
|
||||
}else{ //...must be connecting
|
||||
@@ -571,6 +627,7 @@ public class NetClient implements ApplicationListener{
|
||||
Core.app.post(Call::connectConfirm);
|
||||
Time.runTask(40f, platform::updateRPC);
|
||||
Core.app.post(ui.loadfrag::hide);
|
||||
lastSnapshotTimestamp = Time.millis();
|
||||
}
|
||||
|
||||
private void reset(){
|
||||
@@ -581,6 +638,7 @@ public class NetClient implements ApplicationListener{
|
||||
quietReset = false;
|
||||
quiet = false;
|
||||
lastSent = 0;
|
||||
lastSnapshotTimestamp = 0;
|
||||
|
||||
Groups.clear();
|
||||
ui.chatfrag.clearMessages();
|
||||
|
||||
@@ -117,6 +117,8 @@ public class NetServer implements ApplicationListener{
|
||||
private DataOutputStream dataStream = new DataOutputStream(syncStream);
|
||||
/** Packet handlers for custom types of messages. */
|
||||
private ObjectMap<String, Seq<Cons2<Player, String>>> customPacketHandlers = new ObjectMap<>();
|
||||
/** Packet handlers for custom types of messages - binary version. */
|
||||
private ObjectMap<String, Seq<Cons2<Player, byte[]>>> customBinaryPacketHandlers = new ObjectMap<>();
|
||||
/** Packet handlers for logic client data */
|
||||
private ObjectMap<String, Seq<Cons2<Player, Object>>> logicClientDataHandlers = new ObjectMap<>();
|
||||
|
||||
@@ -423,7 +425,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
});
|
||||
|
||||
clientCommands.<Player>register("vote", "<y/n/c>", "Vote to kick the current player. Admin can cancel the voting with 'c'.", (arg, player) -> {
|
||||
clientCommands.<Player>register("vote", "<y/n/c>", "Vote to kick the current player. Admins can cancel the voting with 'c'.", (arg, player) -> {
|
||||
if(currentlyKicking == null){
|
||||
player.sendMessage("[scarlet]Nobody is being voted on.");
|
||||
}else{
|
||||
@@ -517,6 +519,14 @@ public class NetServer implements ApplicationListener{
|
||||
return customPacketHandlers.get(type, Seq::new);
|
||||
}
|
||||
|
||||
public void addBinaryPacketHandler(String type, Cons2<Player, byte[]> handler){
|
||||
customBinaryPacketHandlers.get(type, Seq::new).add(handler);
|
||||
}
|
||||
|
||||
public Seq<Cons2<Player, byte[]>> getBinaryPacketHandlers(String type){
|
||||
return customBinaryPacketHandlers.get(type, Seq::new);
|
||||
}
|
||||
|
||||
public void addLogicDataHandler(String type, Cons2<Player, Object> handler){
|
||||
logicClientDataHandlers.get(type, Seq::new).add(handler);
|
||||
}
|
||||
@@ -589,6 +599,20 @@ public class NetServer implements ApplicationListener{
|
||||
serverPacketReliable(player, type, contents);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
public static void serverBinaryPacketReliable(Player player, String type, byte[] contents){
|
||||
if(netServer.customPacketHandlers.containsKey(type)){
|
||||
for(var c : netServer.customBinaryPacketHandlers.get(type)){
|
||||
c.get(player, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client, unreliable = true)
|
||||
public static void serverBinaryPacketUnreliable(Player player, String type, byte[] contents){
|
||||
serverBinaryPacketReliable(player, type, contents);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
public static void clientLogicDataReliable(Player player, String channel, Object value){
|
||||
Seq<Cons2<Player, Object>> handlers = netServer.logicClientDataHandlers.get(channel);
|
||||
@@ -674,7 +698,7 @@ public class NetServer implements ApplicationListener{
|
||||
//auto-skip done requests
|
||||
if(req.breaking && tile.block() == Blocks.air){
|
||||
continue;
|
||||
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || (tile.build != null && tile.build.rotation == req.rotation))){
|
||||
}else if(!req.breaking && tile.block() == req.block && tile.team() != Team.derelict && (!req.block.rotate || (tile.build != null && tile.build.rotation == req.rotation))){
|
||||
continue;
|
||||
}else if(con.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
|
||||
continue;
|
||||
@@ -1058,7 +1082,7 @@ public class NetServer implements ApplicationListener{
|
||||
try{
|
||||
writeEntitySnapshot(player);
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -339,6 +339,8 @@ public class Renderer implements ApplicationListener{
|
||||
Draw.draw(Layer.effect + 0.02f, bloom::render);
|
||||
}
|
||||
|
||||
control.input.drawCommanded();
|
||||
|
||||
Draw.draw(Layer.plans, overlays::drawBottom);
|
||||
|
||||
if(animateShields && Shaders.shield != null){
|
||||
@@ -548,6 +550,7 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
public void showLaunch(CoreBuild landCore, CoreBlock coreType){
|
||||
control.input.config.hideConfig();
|
||||
control.input.planConfig.hide();
|
||||
control.input.inv.hide();
|
||||
|
||||
this.landCore = landCore;
|
||||
|
||||
@@ -158,7 +158,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
Core.scene.draw();
|
||||
|
||||
if(Core.input.keyTap(KeyCode.mouseLeft) && Core.scene.hasField()){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
Element e = Core.scene.getHoverElement();
|
||||
if(!(e instanceof TextField)){
|
||||
Core.scene.setKeyboardFocus(null);
|
||||
}
|
||||
@@ -628,6 +628,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
int option = 0;
|
||||
for(var optionsRow : options){
|
||||
if(optionsRow.length == 0) continue;
|
||||
Table buttonRow = table.row().table().get().row();
|
||||
int fullWidth = 400 - (optionsRow.length - 1) * 8; // adjust to count padding as well
|
||||
int width = fullWidth / optionsRow.length;
|
||||
|
||||
@@ -12,6 +12,8 @@ public class Version{
|
||||
public static String type = "unknown";
|
||||
/** Build modifier, e.g. 'alpha' or 'release' */
|
||||
public static String modifier = "unknown";
|
||||
/** Git commit hash (short) */
|
||||
public static String commitHash = "unknown";
|
||||
/** Number specifying the major version, e.g. '4' */
|
||||
public static int number;
|
||||
/** Build number, e.g. '43'. set to '-1' for custom builds. */
|
||||
@@ -32,6 +34,7 @@ public class Version{
|
||||
type = map.get("type");
|
||||
number = Integer.parseInt(map.get("number", "4"));
|
||||
modifier = map.get("modifier");
|
||||
commitHash = map.get("commitHash");
|
||||
if(map.get("build").contains(".")){
|
||||
String[] split = map.get("build").split("\\.");
|
||||
try{
|
||||
@@ -73,6 +76,6 @@ public class Version{
|
||||
if(build == -1){
|
||||
return "custom build";
|
||||
}
|
||||
return (type.equals("official") ? modifier : type) + " build " + build + (revision == 0 ? "" : "." + revision);
|
||||
return (type.equals("official") ? modifier : type) + " build " + build + (revision == 0 ? "" : "." + revision) + (commitHash.equals("unknown") ? "" : " (" + commitHash + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,8 +321,6 @@ public class World{
|
||||
state.rules.cloudColor = sector.planet.landCloudColor;
|
||||
state.rules.env = sector.planet.defaultEnv;
|
||||
state.rules.planet = sector.planet;
|
||||
state.rules.hiddenBuildItems.clear();
|
||||
state.rules.hiddenBuildItems.addAll(sector.planet.hiddenItems);
|
||||
sector.planet.applyRules(state.rules);
|
||||
sector.info.resources = content.toSeq();
|
||||
sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||
|
||||
@@ -25,6 +25,9 @@ public abstract class Content implements Comparable<Content>{
|
||||
/** Called after all content and modules are created. Do not use to load regions or texture data! */
|
||||
public void init(){}
|
||||
|
||||
/** Called after init(). */
|
||||
public void postInit(){}
|
||||
|
||||
/**
|
||||
* Called after all content is created, only on non-headless versions.
|
||||
* Use for loading regions or other image data.
|
||||
|
||||
@@ -9,6 +9,7 @@ import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.graphics.*;
|
||||
@@ -35,8 +36,6 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
public boolean hideDetails = true;
|
||||
/** If false, all icon generation is disabled for this content; createIcons is not called. */
|
||||
public boolean generateIcons = true;
|
||||
/** Special logic icon ID. */
|
||||
public int iconId = 0;
|
||||
/** How big the content appears in certain selection menus */
|
||||
public float selectionSize = 24f;
|
||||
/** Icon of the content to use in UI. */
|
||||
@@ -45,11 +44,24 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
public TextureRegion fullIcon;
|
||||
/** Override for the full icon. Useful for mod content with duplicate icons. Overrides any other full icon.*/
|
||||
public String fullOverride = "";
|
||||
/** If true, this content will appear in all database tabs. */
|
||||
public boolean allDatabaseTabs = false;
|
||||
/**
|
||||
* Planets that this content is made for. If empty, a planet is decided based on item requirements.
|
||||
* Currently, this is only meaningful for blocks.
|
||||
* */
|
||||
public ObjectSet<Planet> shownPlanets = new ObjectSet<>();
|
||||
/**
|
||||
* Content - usually a planet - that dictates which database tab(s) this content will appear in.
|
||||
* If nothing is defined, it will use the values in shownPlanets.
|
||||
* If shownPlanets is also empty, it will use Serpulo as the "default" tab.
|
||||
* */
|
||||
public ObjectSet<UnlockableContent> databaseTabs = new ObjectSet<>();
|
||||
/** The tech tree node for this content, if applicable. Null if not part of a tech tree. */
|
||||
public @Nullable TechNode techNode;
|
||||
/** Tech nodes for all trees that this content is part of. */
|
||||
public Seq<TechNode> techNodes = new Seq<>();
|
||||
/** Unlock state. Loaded from settings. Do not modify outside of the constructor. */
|
||||
/** Unlock state. Loaded from settings. Do not modify outside the constructor. */
|
||||
protected boolean unlocked;
|
||||
|
||||
public UnlockableContent(String name){
|
||||
@@ -61,10 +73,17 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
this.unlocked = Core.settings != null && Core.settings.getBool(this.name + "-unlocked", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(){
|
||||
super.postInit();
|
||||
|
||||
databaseTabs.addAll(shownPlanets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadIcon(){
|
||||
fullIcon =
|
||||
Core.atlas.find(fullOverride,
|
||||
Core.atlas.find(fullOverride == null ? "" : fullOverride,
|
||||
Core.atlas.find(getContentType().name() + "-" + name + "-full",
|
||||
Core.atlas.find(name + "-full",
|
||||
Core.atlas.find(name,
|
||||
@@ -74,6 +93,10 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
uiIcon = Core.atlas.find(getContentType().name() + "-" + name + "-ui", fullIcon);
|
||||
}
|
||||
|
||||
public boolean isOnPlanet(@Nullable Planet planet){
|
||||
return planet == null || planet == Planets.sun || shownPlanets.isEmpty() || shownPlanets.contains(planet);
|
||||
}
|
||||
|
||||
public int getLogicId(){
|
||||
return logicVars.lookupLogicId(this);
|
||||
}
|
||||
@@ -200,15 +223,24 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
}
|
||||
|
||||
public boolean unlockedNowHost(){
|
||||
if(!state.isCampaign()) return true;
|
||||
return !state.isCampaign() || unlockedHost();
|
||||
}
|
||||
|
||||
/** @return in multiplayer, whether this is unlocked for the host player, otherwise, whether it is unlocked for the local player (same as unlocked()) */
|
||||
public boolean unlockedHost(){
|
||||
return net != null && net.client() ?
|
||||
alwaysUnlocked || state.rules.researched.contains(name) :
|
||||
alwaysUnlocked || state.rules.researched.contains(this) :
|
||||
unlocked || alwaysUnlocked;
|
||||
}
|
||||
|
||||
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||
public boolean unlockedNow(){
|
||||
return unlocked() || !state.isCampaign();
|
||||
}
|
||||
|
||||
public boolean unlocked(){
|
||||
return net != null && net.client() ?
|
||||
alwaysUnlocked || unlocked || state.rules.researched.contains(name) :
|
||||
alwaysUnlocked || unlocked || state.rules.researched.contains(this) :
|
||||
unlocked || alwaysUnlocked;
|
||||
}
|
||||
|
||||
@@ -220,11 +252,6 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
}
|
||||
}
|
||||
|
||||
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||
public boolean unlockedNow(){
|
||||
return unlocked() || !state.isCampaign();
|
||||
}
|
||||
|
||||
public boolean locked(){
|
||||
return !unlocked();
|
||||
}
|
||||
|
||||
@@ -172,21 +172,18 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
menu.cont.row();
|
||||
}
|
||||
|
||||
//wip feature
|
||||
if(experimental){
|
||||
menu.cont.button("@editor.sectorgenerate", Icon.terrain, () -> {
|
||||
menu.hide();
|
||||
sectorGenDialog.show();
|
||||
}).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
menu.cont.row();
|
||||
}
|
||||
menu.cont.button("@editor.sectorgenerate", Icon.terrain, () -> {
|
||||
menu.hide();
|
||||
sectorGenDialog.show();
|
||||
}).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.button("@quit", Icon.exit, () -> {
|
||||
tryExit();
|
||||
menu.hide();
|
||||
}).padTop(!steam && !experimental ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
}).padTop(1).size(swidth * 2f + 10, 60f);
|
||||
|
||||
resizeDialog = new MapResizeDialog((width, height, shiftX, shiftY) -> {
|
||||
if(!(editor.width() == width && editor.height() == height && shiftX == 0 && shiftY == 0)){
|
||||
@@ -271,6 +268,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
));
|
||||
world.endMapLoad();
|
||||
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
Core.camera.position.set(player);
|
||||
player.clearUnit();
|
||||
|
||||
for(var unit : Groups.unit){
|
||||
@@ -695,28 +693,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
editor.undo();
|
||||
}
|
||||
|
||||
//more undocumented features, fantastic
|
||||
if(Core.input.keyTap(KeyCode.t)){
|
||||
|
||||
//clears all 'decoration' from the map
|
||||
for(int x = 0; x < editor.width(); x++){
|
||||
for(int y = 0; y < editor.height(); y++){
|
||||
Tile tile = editor.tile(x, y);
|
||||
if(tile.block().breakable && tile.block() instanceof Prop){
|
||||
tile.setBlock(Blocks.air);
|
||||
editor.renderer.updatePoint(x, y);
|
||||
}
|
||||
|
||||
if(tile.overlay() != Blocks.air && tile.overlay() != Blocks.spawn){
|
||||
tile.setOverlay(Blocks.air);
|
||||
editor.renderer.updatePoint(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editor.flushOp();
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(KeyCode.y)){
|
||||
editor.redo();
|
||||
}
|
||||
@@ -737,7 +713,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
private void addBlockSelection(Table cont){
|
||||
blockSelection = new Table();
|
||||
pane = new ScrollPane(blockSelection);
|
||||
pane = new ScrollPane(blockSelection, Styles.smallPane);
|
||||
pane.setFadeScrollBars(false);
|
||||
pane.setOverscroll(true, false);
|
||||
pane.exited(() -> {
|
||||
@@ -754,7 +730,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
cont.row();
|
||||
cont.table(Tex.underline, extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
|
||||
cont.row();
|
||||
cont.add(pane).expandY().top().left();
|
||||
cont.add(pane).expandY().growX().top().left();
|
||||
|
||||
rebuildBlockSelection("");
|
||||
}
|
||||
@@ -784,7 +760,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|| (!searchText.isEmpty() && !block.localizedName.toLowerCase().contains(searchText.toLowerCase()))
|
||||
) continue;
|
||||
|
||||
ImageButton button = new ImageButton(Tex.whiteui, Styles.squareTogglei);
|
||||
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearNoneTogglei);
|
||||
button.getStyle().imageUp = new TextureRegionDrawable(region);
|
||||
button.clicked(() -> editor.drawBlock = block);
|
||||
button.resizeImage(8 * 4f);
|
||||
@@ -793,7 +769,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
if(i == 0) editor.drawBlock = block;
|
||||
|
||||
if(++i % 4 == 0){
|
||||
if(++i % 6 == 0){
|
||||
blockSelection.row();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ public class MapObjectivesCanvas extends WidgetGroup{
|
||||
() -> obj,
|
||||
res -> {}
|
||||
);
|
||||
}).width(400f).fillY()).grow();
|
||||
}).width(Math.min(Core.graphics.getWidth() * 0.95f / Scl.scl(1f) - Scl.scl(20f), 700f)).fillY()).grow();
|
||||
|
||||
dialog.addCloseButton();
|
||||
dialog.show();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.geom.*;
|
||||
@@ -44,7 +45,7 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
name(cont, name, remover, indexer);
|
||||
|
||||
if(field != null && field.isAnnotationPresent(Multiline.class)){
|
||||
cont.area(get.get(), set).height(85f).growX();
|
||||
cont.area(get.get(), set).height(100f).growX();
|
||||
}else{
|
||||
cont.field(get.get(), set).growX();
|
||||
}
|
||||
@@ -465,10 +466,42 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
buttons.defaults().size(160f, 64f).pad(2f);
|
||||
buttons.button("@back", Icon.left, MapObjectivesDialog.this::hide);
|
||||
buttons.button("@add", Icon.add, () -> getProvider(MapObjective.class).get(new TypeInfo(MapObjective.class), canvas::query));
|
||||
buttons.button("@waves.edit", Icon.edit, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@waves.edit");
|
||||
dialog.addCloseButton();
|
||||
dialog.setFillParent(false);
|
||||
dialog.cont.table(Tex.button, t -> {
|
||||
var style = Styles.cleart;
|
||||
t.defaults().size(280f, 64f).pad(2f);
|
||||
|
||||
t.button("@waves.copy", Icon.copy, style, () -> {
|
||||
ui.showInfoFade("@copied");
|
||||
Core.app.setClipboardText(JsonIO.write(new MapObjectives(canvas.objectives)));
|
||||
dialog.hide();
|
||||
}).disabled(b -> canvas.objectives.isEmpty()).marginLeft(12f).row();
|
||||
|
||||
t.button("@waves.load", Icon.download, style, () -> {
|
||||
try{
|
||||
rebuildObjectives(new Seq<>(JsonIO.read(MapObjectives.class, Core.app.getClipboardText()).all));
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
ui.showErrorMessage("@waves.invalid");
|
||||
}
|
||||
dialog.hide();
|
||||
}).disabled(Core.app.getClipboardText() == null || !Core.app.getClipboardText().startsWith("[")).marginLeft(12f).row();
|
||||
|
||||
t.button("@clear", Icon.none, style, () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
|
||||
rebuildObjectives(new Seq<>());
|
||||
dialog.hide();
|
||||
})).marginLeft(12f).row();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
|
||||
if(mobile){
|
||||
buttons.button("@cancel", Icon.cancel, canvas::stopQuery).disabled(b -> !canvas.isQuerying());
|
||||
buttons.button("@ok", Icon.ok, canvas::placeQuery).disabled(b -> !canvas.isQuerying());
|
||||
buttons.button("@cancel", Icon.cancel, canvas::stopQuery).visible(() -> canvas.isQuerying());
|
||||
buttons.button("@ok", Icon.ok, canvas::placeQuery).visible(() -> canvas.isQuerying());
|
||||
}
|
||||
|
||||
setFillParent(true);
|
||||
@@ -490,22 +523,27 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
public void show(Seq<MapObjective> objectives, Cons<Seq<MapObjective>> out){
|
||||
this.out = out;
|
||||
|
||||
rebuildObjectives(objectives);
|
||||
show();
|
||||
}
|
||||
|
||||
public void rebuildObjectives(Seq<MapObjective> objectives){
|
||||
canvas.clearObjectives();
|
||||
if(
|
||||
objectives.any() && (
|
||||
// If the objectives were previously programmatically made...
|
||||
objectives.contains(obj -> obj.editorX == -1 || obj.editorY == -1) ||
|
||||
// ... or some idiot somehow made it not work...
|
||||
objectives.contains(obj -> !canvas.tilemap.createTile(obj))
|
||||
objectives.any() && (
|
||||
// If the objectives were previously programmatically made...
|
||||
objectives.contains(obj -> obj.editorX == -1 || obj.editorY == -1) ||
|
||||
// ... or some idiot somehow made it not work...
|
||||
objectives.contains(obj -> !canvas.tilemap.createTile(obj))
|
||||
)){
|
||||
// ... then rebuild the structure.
|
||||
canvas.clearObjectives();
|
||||
|
||||
// This is definitely NOT a good way to do it, but only insane people or people from the distant past would actually encounter this anyway.
|
||||
int w = objWidth + 2,
|
||||
len = objectives.size * w,
|
||||
columns = objectives.size,
|
||||
rows = 1;
|
||||
len = objectives.size * w,
|
||||
columns = objectives.size,
|
||||
rows = 1;
|
||||
|
||||
if(len > bounds){
|
||||
rows = len / bounds;
|
||||
@@ -525,7 +563,6 @@ public class MapObjectivesDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
canvas.objectives.set(objectives);
|
||||
show();
|
||||
}
|
||||
|
||||
public static <T extends UnlockableContent> void showContentSelect(@Nullable ContentType type, Cons<T> cons, Boolf<T> check){
|
||||
|
||||
@@ -19,7 +19,7 @@ import mindustry.world.blocks.logic.LogicBlock.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapProcessorsDialog extends BaseDialog{
|
||||
private IconSelectDialog iconSelect = new IconSelectDialog();
|
||||
private IconSelectDialog iconSelect = new IconSelectDialog(true);
|
||||
private TextField search;
|
||||
private Seq<Building> processors = new Seq<>();
|
||||
private Table list;
|
||||
|
||||
@@ -329,7 +329,7 @@ public class MapView extends Element implements GestureListener{
|
||||
return Core.scene != null && Core.scene.getKeyboardFocus() != null
|
||||
&& Core.scene.getKeyboardFocus().isDescendantOf(ui.editor)
|
||||
&& ui.editor.isShown() && tool == EditorTool.zoom &&
|
||||
Core.scene.hit(Core.input.mouse().x, Core.input.mouse().y, true) == this;
|
||||
Core.scene.getHoverElement() == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -93,6 +93,7 @@ public class SectorGenerateDialog extends BaseDialog{
|
||||
var preset = sectorobj.preset;
|
||||
sectorobj.preset = null;
|
||||
|
||||
logic.reset(); //TODO: is this a good idea? all rules and map state are cleared, but it fixes inconsistent gen
|
||||
world.loadSector(sectorobj, seed, false);
|
||||
|
||||
sectorobj.preset = preset;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
@@ -17,7 +22,6 @@ import mindustry.ui.*;
|
||||
|
||||
public class WaveGraph extends Table{
|
||||
public Seq<SpawnGroup> groups = new Seq<>();
|
||||
public int from = 0, to = 20;
|
||||
|
||||
private Mode mode = Mode.counts;
|
||||
private int[][] values;
|
||||
@@ -26,47 +30,114 @@ public class WaveGraph extends Table{
|
||||
private float maxHealth;
|
||||
private Table colors;
|
||||
private ObjectSet<UnitType> hidden = new ObjectSet<>();
|
||||
private StringBuilder countStr = new StringBuilder();
|
||||
|
||||
private float pan;
|
||||
private float zoom = 1f;
|
||||
private int from = 0, to = 20;
|
||||
private int lastFrom = -1, lastTo = -1;
|
||||
private float lastZoom = -1f;
|
||||
|
||||
private float defaultSpace = Scl.scl(40f);
|
||||
private FloatSeq points = new FloatSeq(40);
|
||||
|
||||
public WaveGraph(){
|
||||
background(Tex.pane);
|
||||
|
||||
scrolled((scroll) -> {
|
||||
zoom -= scroll * 2f / 10f * zoom;
|
||||
clampZoom();
|
||||
});
|
||||
|
||||
touchable = Touchable.enabled;
|
||||
addListener(new InputListener(){
|
||||
|
||||
@Override
|
||||
public void enter(InputEvent event, float x, float y, int pointer, Element fromActor){
|
||||
requestScroll();
|
||||
}
|
||||
});
|
||||
|
||||
addListener(new ElementGestureListener(){
|
||||
@Override
|
||||
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY){
|
||||
pan -= deltaX/zoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zoom(InputEvent event, float initialDistance, float distance){
|
||||
if(lastZoom < 0) lastZoom = zoom;
|
||||
|
||||
zoom = distance / initialDistance * lastZoom;
|
||||
clampZoom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
lastZoom = zoom;
|
||||
}
|
||||
});
|
||||
|
||||
rect((x, y, width, height) -> {
|
||||
Lines.stroke(Scl.scl(3f));
|
||||
countStr.setLength(0);
|
||||
|
||||
Vec2 mouse = stageToLocalCoordinates(Core.input.mouse());
|
||||
|
||||
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
|
||||
Font font = Fonts.outline;
|
||||
|
||||
lay.setText(font, "1");
|
||||
|
||||
int maxY = switch(mode){
|
||||
case counts -> nextStep(max);
|
||||
case health -> nextStep((int)maxHealth);
|
||||
case totals -> nextStep(maxTotal);
|
||||
};
|
||||
|
||||
float fh = lay.height;
|
||||
float offsetX = Scl.scl(lay.width * (maxY + "").length() * 2), offsetY = Scl.scl(22f) + fh + Scl.scl(5f);
|
||||
lay.setText(font, "1");
|
||||
|
||||
float graphX = x + offsetX, graphY = y + offsetY, graphW = width - offsetX, graphH = height - offsetY;
|
||||
float spacing = graphW / (values.length - 1);
|
||||
float spacing = zoom * defaultSpace;
|
||||
pan = Math.max(pan, (width/2f)/zoom-defaultSpace);
|
||||
|
||||
float fh = lay.height;
|
||||
float offsetX = 0f, offsetY = Scl.scl(22f) + fh + Scl.scl(5f);
|
||||
float graphX = x + offsetX - pan * zoom + width/2f, graphY = y + offsetY, graphW = width - offsetX, graphH = height - offsetY;
|
||||
|
||||
float left = (x-graphX)/spacing, right = (x + width - graphX)/spacing;
|
||||
|
||||
//int radius = Mathf.ceil(graphW / spacing / 2f);
|
||||
|
||||
from = (int)left - 1;
|
||||
to = (int)right + 1;
|
||||
|
||||
if(lastFrom != from || lastTo != to){
|
||||
rebuild();
|
||||
}
|
||||
|
||||
lastFrom = from;
|
||||
lastTo = to;
|
||||
|
||||
if(!clipBegin(x + offsetX, y + offsetY, graphW, graphH)) return;
|
||||
|
||||
int selcol = Rect.contains(x, y, width, height, mouse.x, mouse.y) ? Mathf.round((mouse.x - graphX - (from * spacing)) / spacing) : -1;
|
||||
if(selcol + from <= -1) selcol = -1;
|
||||
|
||||
if(mode == Mode.counts){
|
||||
for(UnitType type : used.orderedItems()){
|
||||
Draw.color(color(type));
|
||||
Draw.alpha(parentAlpha);
|
||||
|
||||
Lines.beginLine();
|
||||
beginLine();
|
||||
|
||||
for(int i = 0; i < values.length; i++){
|
||||
int val = values[i][type.id];
|
||||
float cx = graphX + i * spacing, cy = graphY + val * graphH / maxY;
|
||||
Lines.linePoint(cx, cy);
|
||||
float cx = graphX + (i+from) * spacing, cy = graphY + val * graphH / maxY;
|
||||
linePoint(cx, cy);
|
||||
}
|
||||
|
||||
Lines.endLine();
|
||||
endLine();
|
||||
}
|
||||
}else if(mode == Mode.totals){
|
||||
Lines.beginLine();
|
||||
beginLine();
|
||||
|
||||
Draw.color(Pal.accent);
|
||||
for(int i = 0; i < values.length; i++){
|
||||
@@ -75,13 +146,13 @@ public class WaveGraph extends Table{
|
||||
sum += values[i][type.id];
|
||||
}
|
||||
|
||||
float cx = graphX + i * spacing, cy = graphY + sum * graphH / maxY;
|
||||
Lines.linePoint(cx, cy);
|
||||
float cx = graphX + (i+from) * spacing, cy = graphY + sum * graphH / maxY;
|
||||
linePoint(cx, cy);
|
||||
}
|
||||
|
||||
Lines.endLine();
|
||||
endLine();
|
||||
}else if(mode == Mode.health){
|
||||
Lines.beginLine();
|
||||
beginLine();
|
||||
|
||||
Draw.color(Pal.health);
|
||||
for(int i = 0; i < values.length; i++){
|
||||
@@ -90,13 +161,32 @@ public class WaveGraph extends Table{
|
||||
sum += (type.health) * values[i][type.id];
|
||||
}
|
||||
|
||||
float cx = graphX + i * spacing, cy = graphY + sum * graphH / maxY;
|
||||
Lines.linePoint(cx, cy);
|
||||
float cx = graphX + (i+from) * spacing, cy = graphY + sum * graphH / maxY;
|
||||
linePoint(cx, cy);
|
||||
}
|
||||
|
||||
Lines.endLine();
|
||||
endLine();
|
||||
}
|
||||
|
||||
|
||||
if(selcol >= 0 && selcol < values.length){
|
||||
Draw.color(1f, 0f, 0f, 0.2f);
|
||||
Fill.crect((selcol+from) * spacing + graphX - spacing/2f, graphY, spacing, graphH);
|
||||
Draw.color();
|
||||
font.getData().setScale(1.5f);
|
||||
for(UnitType type : used.orderedItems()){
|
||||
int amount = values[Mathf.clamp(selcol, 0, values.length - 1)][type.id];
|
||||
if(amount > 0){
|
||||
countStr.append(type.emoji()).append(" ").append(amount).append("\n");
|
||||
}
|
||||
}
|
||||
float pad = Scl.scl(5f);
|
||||
font.draw(countStr, (selcol+from) * spacing + graphX - spacing/2f + pad, graphY + graphH - pad);
|
||||
font.getData().setScale(1f);
|
||||
}
|
||||
|
||||
clipEnd();
|
||||
|
||||
//how many numbers can fit here
|
||||
float totalMarks = Mathf.clamp(maxY, 1, 10);
|
||||
|
||||
@@ -106,13 +196,13 @@ public class WaveGraph extends Table{
|
||||
Draw.alpha(0.1f);
|
||||
|
||||
for(int i = 0; i < maxY; i += markSpace){
|
||||
float cy = graphY + i * graphH / maxY, cx = graphX;
|
||||
float cy = graphY + i * graphH / maxY, cx = x;
|
||||
|
||||
Lines.line(cx, cy, cx + graphW, cy);
|
||||
|
||||
lay.setText(font, "" + i);
|
||||
|
||||
font.draw("" + i, cx, cy + lay.height / 2f, Align.right);
|
||||
font.draw("" + i, cx, cy + lay.height / 2f, Align.left);
|
||||
}
|
||||
Draw.alpha(1f);
|
||||
|
||||
@@ -120,10 +210,12 @@ public class WaveGraph extends Table{
|
||||
font.setColor(Color.lightGray);
|
||||
|
||||
for(int i = 0; i < values.length; i++){
|
||||
float cy = y + fh, cx = graphX + graphW / (values.length - 1) * i;
|
||||
float cy = y + fh, cx = graphX + spacing * (i + from);
|
||||
|
||||
Lines.line(cx, cy, cx, cy + len);
|
||||
if(i == values.length / 2){
|
||||
if(cx >= x + offsetX && cx <= x + offsetX + graphW){
|
||||
Lines.line(cx, cy, cx, cy + len);
|
||||
}
|
||||
if(i == selcol){
|
||||
font.draw("" + (i + from + 1), cx, cy - Scl.scl(2f), Align.center);
|
||||
}
|
||||
}
|
||||
@@ -152,6 +244,28 @@ public class WaveGraph extends Table{
|
||||
}).growX();
|
||||
}
|
||||
|
||||
private void clampZoom(){
|
||||
zoom = Mathf.clamp(zoom, 0.5f / Scl.scl(1f), 40f / Scl.scl(1f));
|
||||
}
|
||||
|
||||
private void linePoint(float x, float y){
|
||||
points.add(x, y);
|
||||
}
|
||||
|
||||
private void beginLine(){
|
||||
points.clear();
|
||||
}
|
||||
|
||||
private void endLine(){
|
||||
var items = points.items;
|
||||
for(int i = 0; i < points.size - 2; i += 2){
|
||||
Lines.line(items[i], items[i + 1], items[i + 2], items[i + 3], false);
|
||||
Fill.circle(items[i], items[i + 1], Lines.getStroke()/2f);
|
||||
}
|
||||
Fill.circle(items[points.size - 2], items[points.size - 1], Lines.getStroke());
|
||||
points.clear();
|
||||
}
|
||||
|
||||
public void rebuild(){
|
||||
values = new int[to - from + 1][Vars.content.units().size];
|
||||
used.clear();
|
||||
@@ -177,6 +291,8 @@ public class WaveGraph extends Table{
|
||||
maxHealth = Math.max(maxHealth, healthsum);
|
||||
}
|
||||
|
||||
used.orderedItems().sort();
|
||||
|
||||
ObjectSet<UnitType> usedCopy = new ObjectSet<>(used);
|
||||
|
||||
colors.clear();
|
||||
@@ -198,7 +314,7 @@ public class WaveGraph extends Table{
|
||||
t.button(b -> {
|
||||
Color tcolor = color(type).cpy();
|
||||
b.image().size(32f).update(i -> i.setColor(b.isChecked() ? Tmp.c1.set(tcolor).mul(0.5f) : tcolor)).get().act(1);
|
||||
b.image(type.uiIcon).size(32f).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
|
||||
b.image(type.uiIcon).size(32f).scaling(Scaling.fit).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
|
||||
b.margin(0f);
|
||||
}, Styles.fullTogglet, () -> {
|
||||
if(!hidden.add(type)){
|
||||
@@ -212,6 +328,8 @@ public class WaveGraph extends Table{
|
||||
}
|
||||
}).scrollY(false);
|
||||
|
||||
colors.act(0.000001f);
|
||||
|
||||
for(UnitType type : hidden){
|
||||
used.remove(type);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import static mindustry.Vars.*;
|
||||
import static mindustry.game.SpawnGroup.*;
|
||||
|
||||
public class WaveInfoDialog extends BaseDialog{
|
||||
private int start = 0, displayed = 20;
|
||||
Seq<SpawnGroup> groups = new Seq<>();
|
||||
private @Nullable SpawnGroup expandedGroup;
|
||||
|
||||
@@ -36,7 +35,6 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
private @Nullable UnitType filterType;
|
||||
private Sort sort = Sort.begin;
|
||||
private boolean reverseSort = false;
|
||||
private float updateTimer, updatePeriod = 1f;
|
||||
private boolean checkedSpawns;
|
||||
private WaveGraph graph = new WaveGraph();
|
||||
|
||||
@@ -49,7 +47,6 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
});
|
||||
hidden(() -> state.rules.spawns = groups);
|
||||
|
||||
onResize(this::setup);
|
||||
addCloseButton();
|
||||
|
||||
buttons.button("@waves.edit", Icon.edit, () -> {
|
||||
@@ -71,7 +68,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
groups = maps.readWaves(Core.app.getClipboardText());
|
||||
buildGroups();
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
ui.showErrorMessage("@waves.invalid");
|
||||
}
|
||||
dialog.hide();
|
||||
@@ -93,57 +90,11 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
dialog.show();
|
||||
}).size(250f, 64f);
|
||||
|
||||
buttons.defaults().width(60f);
|
||||
|
||||
buttons.button("<", () -> {}).update(t -> {
|
||||
if(t.getClickListener().isPressed()){
|
||||
shift(-1);
|
||||
}
|
||||
});
|
||||
buttons.button(">", () -> {}).update(t -> {
|
||||
if(t.getClickListener().isPressed()){
|
||||
shift(1);
|
||||
}
|
||||
});
|
||||
|
||||
buttons.button("-", () -> {}).update(t -> {
|
||||
if(t.getClickListener().isPressed()){
|
||||
view(-1);
|
||||
}
|
||||
});
|
||||
buttons.button("+", () -> {}).update(t -> {
|
||||
if(t.getClickListener().isPressed()){
|
||||
view(1);
|
||||
}
|
||||
});
|
||||
|
||||
if(experimental){
|
||||
buttons.button(Core.bundle.get("waves.random"), Icon.refresh, () -> {
|
||||
groups.clear();
|
||||
groups = Waves.generate(1f / 10f);
|
||||
buildGroups();
|
||||
}).width(200f);
|
||||
}
|
||||
}
|
||||
|
||||
void view(int amount){
|
||||
updateTimer += Time.delta;
|
||||
if(updateTimer >= updatePeriod){
|
||||
displayed += amount;
|
||||
if(displayed < 5) displayed = 5;
|
||||
updateTimer = 0f;
|
||||
updateWaves();
|
||||
}
|
||||
}
|
||||
|
||||
void shift(int amount){
|
||||
updateTimer += Time.delta;
|
||||
if(updateTimer >= updatePeriod){
|
||||
start += amount;
|
||||
if(start < 0) start = 0;
|
||||
updateTimer = 0f;
|
||||
updateWaves();
|
||||
}
|
||||
buttons.button(Core.bundle.get("waves.random"), Icon.refresh, () -> {
|
||||
groups.clear();
|
||||
groups = Waves.generate(1f / 10f);
|
||||
buildGroups();
|
||||
}).width(200f);
|
||||
}
|
||||
|
||||
void setup(){
|
||||
@@ -156,7 +107,6 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
s.image(Icon.zoom).padRight(8);
|
||||
s.field(search < 0 ? "" : (search + 1) + "", TextFieldFilter.digitsOnly, text -> {
|
||||
search = groups.any() ? Strings.parseInt(text, 0) - 1 : -1;
|
||||
start = Math.max(search - (displayed / 2) - (displayed % 2), 0);
|
||||
buildGroups();
|
||||
}).growX().maxTextLength(8).get().setMessageText("@waves.search");
|
||||
s.button(Icon.units, Styles.emptyi, () -> showUnits(type -> filterType = type, true)).size(46f).tooltip("@waves.filter")
|
||||
@@ -222,7 +172,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
t.button(b -> {
|
||||
b.left();
|
||||
b.image(group.type.uiIcon).size(32f).padRight(3).scaling(Scaling.fit);
|
||||
b.add(group.type.localizedName).color(Pal.accent);
|
||||
b.add(group.type.localizedName).ellipsis(true).width(110f).left().color(Pal.accent);
|
||||
|
||||
b.add().growX();
|
||||
|
||||
@@ -493,8 +443,6 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
|
||||
void updateWaves(){
|
||||
graph.groups = groups;
|
||||
graph.from = start;
|
||||
graph.to = start + displayed;
|
||||
graph.rebuild();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -101,9 +103,13 @@ public class Damage{
|
||||
float damagePerWave = explosiveness / 2f;
|
||||
|
||||
for(int i = 0; i < waves; i++){
|
||||
var shields = ignoreTeam == null ? null : indexer.getEnemy(ignoreTeam, BlockFlag.shield);
|
||||
int f = i;
|
||||
Time.run(i * 2f, () -> {
|
||||
damage(ignoreTeam, x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), damagePerWave, false);
|
||||
if(shields == null || shields.isEmpty() || !shields.contains(b -> b instanceof ExplosionShield s && s.absorbExplosion(x, y, damagePerWave))){
|
||||
damage(ignoreTeam, x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), damagePerWave, false);
|
||||
}
|
||||
|
||||
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
|
||||
});
|
||||
}
|
||||
@@ -166,7 +172,7 @@ public class Damage{
|
||||
public static float findPierceLength(Bullet b, int pierceCap, float length){
|
||||
return findPierceLength(b, pierceCap, b.type.laserAbsorb, length);
|
||||
}
|
||||
|
||||
|
||||
public static float findPierceLength(Bullet b, int pierceCap, boolean laser, float length){
|
||||
vec.trnsExact(b.rotation(), length);
|
||||
rect.setPosition(b.x, b.y).setSize(vec.x, vec.y).normalize().grow(3f);
|
||||
@@ -358,7 +364,7 @@ public class Damage{
|
||||
*/
|
||||
public static Healthc linecast(Bullet hitter, float x, float y, float angle, float length){
|
||||
vec.trns(angle, length);
|
||||
|
||||
|
||||
tmpBuilding = null;
|
||||
|
||||
if(hitter.type.collidesGround){
|
||||
@@ -644,7 +650,7 @@ public class Damage{
|
||||
this.target = target;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void reset(){
|
||||
target = null;
|
||||
|
||||
@@ -83,6 +83,7 @@ public class Units{
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
public static void unitDespawn(Unit unit){
|
||||
if(unit == null) return;
|
||||
Fx.unitDespawn.at(unit.x, unit.y, 0, unit);
|
||||
unit.remove();
|
||||
}
|
||||
@@ -94,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)){
|
||||
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam) || state.rules.disableUnitCap){
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + team.data().unitCap : state.rules.unitCap);
|
||||
@@ -111,6 +112,10 @@ public class Units{
|
||||
return player == null || tile == null || tile.interactable(player.team()) || state.rules.editor;
|
||||
}
|
||||
|
||||
public static boolean isHittable(@Nullable Posc target, boolean air, boolean ground){
|
||||
return target != null && (target instanceof Buildingc ? ground : (target instanceof Unit u && u.checkTarget(air, ground)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a target.
|
||||
* @param target The target to validate
|
||||
@@ -474,7 +479,7 @@ public class Units{
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
for(int i = 0; i < data.size; i++){
|
||||
var other = data.items[i];
|
||||
if(other.team != team){
|
||||
if(other.team != team && other.team != Team.derelict){
|
||||
if(other.tree().any(x, y, width, height)){
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public abstract class Ability implements Cloneable{
|
||||
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 addStats(Table t){
|
||||
|
||||
@@ -14,6 +14,7 @@ import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -68,7 +69,7 @@ public class EnergyFieldAbility extends Ability{
|
||||
t.add(Core.bundle.format("bullet.damage", damage));
|
||||
if(status != StatusEffects.none){
|
||||
t.row();
|
||||
t.add((status.hasEmoji() ? status.emoji() : "") + "[stat]" + status.localizedName);
|
||||
t.add((status.hasEmoji() ? status.emoji() : "") + "[stat]" + status.localizedName).with(l -> StatValues.withTooltip(l, status));
|
||||
}
|
||||
if(displayHeal){
|
||||
t.row();
|
||||
@@ -135,7 +136,7 @@ public class EnergyFieldAbility extends Ability{
|
||||
|
||||
if(hitBuildings && targetGround){
|
||||
Units.nearbyBuildings(rx, ry, range, b -> {
|
||||
if((b.team != Team.derelict || state.rules.coreCapture) && (b.team != unit.team || b.damaged())){
|
||||
if((b.team != Team.derelict || state.rules.coreCapture) && ((b.team != unit.team && b.block.targetable) || b.damaged()) && !b.block.privileged){
|
||||
all.add(b);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -105,6 +105,15 @@ public class ForceFieldAbility extends Ability{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void death(Unit unit){
|
||||
|
||||
//self-destructing units can have a shield on death
|
||||
if(unit.shield > 0f && !wasBroken){
|
||||
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.type.shieldColor(unit), this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Unit unit){
|
||||
checkRadius(unit);
|
||||
@@ -131,6 +140,11 @@ public class ForceFieldAbility extends Ability{
|
||||
bars.add(new Bar("stat.shieldhealth", Pal.accent, () -> unit.shield / max)).row();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void created(Unit unit){
|
||||
unit.shield = max;
|
||||
}
|
||||
|
||||
public void checkRadius(Unit unit){
|
||||
//timer2 is used to store radius scale as an effect
|
||||
realRad = radiusScale * radius;
|
||||
|
||||
@@ -11,7 +11,6 @@ import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public class ShieldArcAbility extends Ability{
|
||||
@@ -20,9 +19,9 @@ public class ShieldArcAbility extends Ability{
|
||||
private static Vec2 paramPos = new Vec2();
|
||||
private static final Cons<Bullet> shieldConsumer = b -> {
|
||||
if(b.team != paramUnit.team && b.type.absorbable && paramField.data > 0 &&
|
||||
!b.within(paramPos, paramField.radius - paramField.width/2f) &&
|
||||
Tmp.v1.set(b).add(b.vel).within(paramPos, paramField.radius + paramField.width/2f) &&
|
||||
Angles.within(paramPos.angleTo(b), paramUnit.rotation + paramField.angleOffset, paramField.angle / 2f)){
|
||||
!(b.within(paramPos, paramField.radius - paramField.width/2f) && paramPos.within(b.x - b.deltaX, b.y - b.deltaY, paramField.radius - paramField.width/2f)) &&
|
||||
(Tmp.v1.set(b).add(b.deltaX, b.deltaY).within(paramPos, paramField.radius + paramField.width/2f) || b.within(paramPos, paramField.radius + paramField.width/2f)) &&
|
||||
(Angles.within(paramPos.angleTo(b), paramUnit.rotation + paramField.angleOffset, paramField.angle / 2f) || Angles.within(paramPos.angleTo(b.x + b.deltaX, b.y + b.deltaY), paramUnit.rotation + paramField.angleOffset, paramField.angle / 2f))){
|
||||
|
||||
b.absorb();
|
||||
Fx.absorb.at(b);
|
||||
@@ -60,7 +59,7 @@ public class ShieldArcAbility extends Ability{
|
||||
public boolean drawArc = true;
|
||||
/** If not null, will be drawn on top. */
|
||||
public @Nullable String region;
|
||||
/** Color override of the shield. Uses unit shield colour by default. */
|
||||
/** Color override of the shield. Uses unit shield colour by default. */
|
||||
public @Nullable Color color;
|
||||
/** If true, sprite position will be influenced by x/y. */
|
||||
public boolean offsetRegion = false;
|
||||
@@ -80,7 +79,7 @@ public class ShieldArcAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
|
||||
|
||||
if(data < max){
|
||||
data += Time.delta * regen;
|
||||
}
|
||||
@@ -102,7 +101,7 @@ public class ShieldArcAbility extends Ability{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(UnitType type){
|
||||
public void created(Unit unit){
|
||||
data = max;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ public class ShieldRegenFieldAbility extends Ability{
|
||||
t.row();
|
||||
t.add(abilityStat("firingrate", Strings.autoFixed(60f / reload, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("pulseregen", Strings.autoFixed(amount, 2)));
|
||||
t.row();
|
||||
t.add(abilityStat("shield", Strings.autoFixed(max, 2)));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -19,6 +21,7 @@ public class StatusFieldAbility extends Ability{
|
||||
public Effect activeEffect = Fx.overdriveWave;
|
||||
public float effectX, effectY;
|
||||
public boolean parentizeEffects, effectSizeParam = true;
|
||||
public Color color = Pal.accent;
|
||||
|
||||
protected float timer;
|
||||
|
||||
@@ -52,7 +55,7 @@ public class StatusFieldAbility extends Ability{
|
||||
});
|
||||
|
||||
float x = unit.x + Angles.trnsx(unit.rotation, effectY, effectX), y = unit.y + Angles.trnsy(unit.rotation, effectY, effectX);
|
||||
activeEffect.at(x, y, effectSizeParam ? range : unit.rotation, parentizeEffects ? unit : null);
|
||||
activeEffect.at(x, y, effectSizeParam ? range : unit.rotation, color, parentizeEffects ? unit : null);
|
||||
|
||||
timer = 0f;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ArtilleryBulletType extends BasicBulletType{
|
||||
super.update(b);
|
||||
|
||||
if(b.timer(0, (3 + b.fslope() * 2f) * trailMult)){
|
||||
trailEffect.at(b.x, b.y, b.fslope() * trailSize, backColor);
|
||||
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : b.fslope() * trailSize, backColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,10 @@ public class BulletType extends Content implements Cloneable{
|
||||
public StatusEffect status = StatusEffects.none;
|
||||
/** Intensity of applied status effect in terms of duration. */
|
||||
public float statusDuration = 60 * 8f;
|
||||
/** Turret only. If false, blocks will not be targeted. */
|
||||
public boolean targetBlocks = true;
|
||||
/** Turret only. If false, missiles will not be targeted. */
|
||||
public boolean targetMissiles = true;
|
||||
/** Whether this bullet type collides with tiles. */
|
||||
public boolean collidesTiles = true;
|
||||
/** Whether this bullet type collides with tiles that are of the same team. */
|
||||
@@ -137,6 +141,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float rangeOverride = -1f;
|
||||
/** When used in a turret with multiple ammo types, this can be set to a non-zero value to influence range. */
|
||||
public float rangeChange = 0f;
|
||||
/** When used in turrets with limitRange() applied, this adds extra range to the bullets that extends past targeting range. Only particularly relevant in vanilla. */
|
||||
public float extraRangeMargin = 0f;
|
||||
/** Range initialized in init(). */
|
||||
public float range = 0f;
|
||||
/** % of block health healed **/
|
||||
@@ -346,7 +352,7 @@ public class BulletType extends Content implements Cloneable{
|
||||
return spawnUnit.estimateDps();
|
||||
}
|
||||
|
||||
float sum = damage * (pierce ? pierceCap == -1 ? 2 : Mathf.clamp(pierceCap, 1, 2) : 1f) * splashDamage*0.75f;
|
||||
float sum = (damage + splashDamage*0.75f) * (pierce ? pierceCap == -1 ? 2 : Mathf.clamp(pierceCap, 1, 2) : 1f);
|
||||
if(fragBullet != null && fragBullet != this){
|
||||
sum += fragBullet.estimateDPS() * fragBullets / 2f;
|
||||
}
|
||||
@@ -521,7 +527,7 @@ public class BulletType extends Content implements Cloneable{
|
||||
if(fragBullet != null && (fragOnAbsorb || !b.absorbed) && !(b.frags >= pierceFragCap && pierceFragCap > 0)){
|
||||
for(int i = 0; i < fragBullets; i++){
|
||||
float len = Mathf.random(fragOffsetMin, fragOffsetMax);
|
||||
float a = b.rotation() + Mathf.range(fragRandomSpread / 2) + fragAngle + ((i - fragBullets/2) * fragSpread);
|
||||
float a = b.rotation() + Mathf.range(fragRandomSpread / 2) + fragAngle + fragSpread * i - (fragBullets - 1) * fragSpread / 2f;
|
||||
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
|
||||
}
|
||||
b.frags++;
|
||||
@@ -549,7 +555,7 @@ public class BulletType extends Content implements Cloneable{
|
||||
if(!fragOnHit){
|
||||
createFrags(b, b.x, b.y);
|
||||
}
|
||||
|
||||
|
||||
despawnEffect.at(b.x, b.y, b.rotation(), hitColor);
|
||||
despawnSound.at(b);
|
||||
|
||||
@@ -675,7 +681,7 @@ public class BulletType extends Content implements Cloneable{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void updateTrail(Bullet b){
|
||||
if(!headless && trailLength > 0){
|
||||
if(b.trail == null){
|
||||
@@ -710,13 +716,16 @@ public class BulletType extends Content implements Cloneable{
|
||||
}
|
||||
|
||||
if(lightningType == null){
|
||||
lightningType = !collidesAir ? Bullets.damageLightningGround : Bullets.damageLightning;
|
||||
lightningType =
|
||||
!collidesAir ? Bullets.damageLightningGround :
|
||||
!collidesGround ? Bullets.damageLightningAir :
|
||||
Bullets.damageLightning;
|
||||
}
|
||||
|
||||
if(lightRadius <= -1){
|
||||
lightRadius = Math.max(18, hitSize * 5f);
|
||||
}
|
||||
|
||||
|
||||
drawSize = Math.max(drawSize, trailLength * speed * 2f);
|
||||
range = calculateRange();
|
||||
}
|
||||
@@ -748,15 +757,15 @@ public class BulletType extends Content implements Cloneable{
|
||||
}
|
||||
|
||||
public @Nullable Bullet create(Bullet parent, float x, float y, float angle){
|
||||
return create(parent.owner, parent.team, x, y, angle);
|
||||
return create(parent.owner, parent.shooter, parent.team, x, y, angle, -1, 1f, 1f, null, null, -1f, -1f);
|
||||
}
|
||||
|
||||
public @Nullable Bullet create(Bullet parent, float x, float y, float angle, float velocityScl, float lifeScale){
|
||||
return create(parent.owner, parent.team, x, y, angle, velocityScl, lifeScale);
|
||||
return create(parent.owner, parent.shooter, parent.team, x, y, angle, -1, velocityScl, lifeScale, null, null, -1f, -1f);
|
||||
}
|
||||
|
||||
public @Nullable Bullet create(Bullet parent, float x, float y, float angle, float velocityScl){
|
||||
return create(parent.owner(), parent.team, x, y, angle, velocityScl);
|
||||
return create(parent.owner, parent.shooter, parent.team, x, y, angle, -1, velocityScl, 1f, null, null, -1f, -1f);
|
||||
}
|
||||
|
||||
public @Nullable Bullet create(@Nullable Entityc owner, Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl, Object data){
|
||||
@@ -772,6 +781,13 @@ public class BulletType extends Content implements Cloneable{
|
||||
}
|
||||
|
||||
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){
|
||||
return create(owner, shooter, team, x, y, angle, damage, velocityScl, lifetimeScl, data, mover, aimX, aimY, null);
|
||||
}
|
||||
|
||||
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
|
||||
){
|
||||
if(!Mathf.chance(createChance)) return null;
|
||||
if(ignoreSpawnAngle) angle = 0;
|
||||
if(spawnUnit != null){
|
||||
@@ -807,12 +823,13 @@ public class BulletType extends Content implements Cloneable{
|
||||
Bullet bullet = Bullet.create();
|
||||
bullet.type = this;
|
||||
bullet.owner = owner;
|
||||
bullet.shooter = (shooter == null ? owner : shooter);
|
||||
bullet.team = team;
|
||||
bullet.time = 0f;
|
||||
bullet.originX = x;
|
||||
bullet.originY = y;
|
||||
if(!(aimX == -1f && aimY == -1f)){
|
||||
bullet.aimTile = world.tileWorld(aimX, aimY);
|
||||
bullet.aimTile = target instanceof Building b ? b.tile : world.tileWorld(aimX, aimY);
|
||||
}
|
||||
bullet.aimX = aimX;
|
||||
bullet.aimY = aimY;
|
||||
|
||||
@@ -37,6 +37,7 @@ public class ContinuousLaserBulletType extends ContinuousBulletType{
|
||||
incendSpread = 5;
|
||||
incendChance = 0.4f;
|
||||
lightColor = Color.orange;
|
||||
lightOpacity = 0.7f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +67,7 @@ public class ContinuousLaserBulletType extends ContinuousBulletType{
|
||||
|
||||
Tmp.v1.trns(b.rotation(), realLength * 1.1f);
|
||||
|
||||
Drawf.light(b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, 0.7f);
|
||||
Drawf.light(b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, lightOpacity);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
||||
10
core/src/mindustry/entities/bullet/EmptyBulletType.java
Normal file
10
core/src/mindustry/entities/bullet/EmptyBulletType.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
public class EmptyBulletType extends BulletType{
|
||||
|
||||
public EmptyBulletType(){
|
||||
hittable = collidesGround = collidesAir = collidesTiles = false;
|
||||
speed = 0f;
|
||||
keepVelocity = false;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -8,8 +7,6 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class LightningBulletType extends BulletType{
|
||||
public Color lightningColor = Pal.lancerLaser;
|
||||
public int lightningLength = 25, lightningLengthRand = 0;
|
||||
|
||||
public LightningBulletType(){
|
||||
damage = 1f;
|
||||
@@ -21,6 +18,9 @@ public class LightningBulletType extends BulletType{
|
||||
hittable = false;
|
||||
//for stats
|
||||
status = StatusEffects.shocked;
|
||||
lightningLength = 25;
|
||||
lightningLengthRand = 0;
|
||||
lightningColor = Pal.lancerLaser;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,10 +33,6 @@ public class LightningBulletType extends BulletType{
|
||||
return super.estimateDPS() * Math.max(lightningLength / 10f, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Bullet b){
|
||||
super.init(b);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
@@ -63,7 +62,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
||||
Tile tile = world.tile(plan.x, plan.y);
|
||||
boolean isSameDerelict = (tile != null && tile.build != null && tile.block() == plan.block && tile.build.tileX() == plan.x && tile.build.tileY() == plan.y && tile.team() == Team.derelict);
|
||||
if(tile == null || (plan.breaking && tile.block() == Blocks.air) || (!plan.breaking && ((tile.build != null && tile.build.rotation == plan.rotation && !isSameDerelict) || !plan.block.rotate) &&
|
||||
//th block must be the same, but not derelict and the same
|
||||
//the block must be the same, but not derelict and the same
|
||||
((tile.block() == plan.block && !isSameDerelict) ||
|
||||
//same floor or overlay
|
||||
(plan.block != null && (plan.block.isOverlay() && plan.block == tile.overlay() || (plan.block.isFloor() && plan.block == tile.floor())))))){
|
||||
@@ -137,17 +136,31 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
||||
}
|
||||
|
||||
if(!(tile.build instanceof ConstructBuild cb)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
|
||||
boolean hasAll = infinite || current.isRotation(team) ||
|
||||
if(!current.initialized && !current.breaking && Build.validPlaceIgnoreUnits(current.block, team, current.x, current.y, current.rotation, true)){
|
||||
if(Build.checkNoUnitOverlap(current.block, current.x, current.y)){
|
||||
boolean hasAll = infinite || current.isRotation(team) ||
|
||||
//derelict repair
|
||||
(tile.team() == Team.derelict && tile.block() == current.block && tile.build != null && tile.block().allowDerelictRepair && state.rules.derelictRepair) ||
|
||||
//make sure there's at least 1 item of each type first
|
||||
!Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
|
||||
!Structs.contains(current.block.requirements, i -> !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
|
||||
|
||||
if(hasAll){
|
||||
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
|
||||
if(hasAll){
|
||||
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
|
||||
|
||||
if(current.block.instantBuild){
|
||||
if(plans.size > 0){
|
||||
plans.removeFirst();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}else{
|
||||
current.stuck = true;
|
||||
}
|
||||
}else{
|
||||
current.stuck = true;
|
||||
//there's a unit blocking the plan, skip it
|
||||
plans.removeFirst();
|
||||
plans.addLast(current);
|
||||
continue;
|
||||
}
|
||||
}else if(!current.initialized && current.breaking && Build.validBreak(team, current.x, current.y)){
|
||||
Call.beginBreak(self(), team, current.x, current.y);
|
||||
@@ -186,11 +199,10 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
|
||||
|
||||
/** Draw all current build plans. Does not draw the beam effect, only the positions. */
|
||||
void drawBuildPlans(){
|
||||
Boolf<BuildPlan> skip = plan -> plan.progress > 0.01f || (buildPlan() == plan && plan.initialized && (within(plan.x * tilesize, plan.y * tilesize, type.buildRange) || state.isEditor()));
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
for(BuildPlan plan : plans){
|
||||
if(skip.get(plan)) continue;
|
||||
if(plan.progress > 0.01f || (buildPlan() == plan && plan.initialized && (within(plan.x * tilesize, plan.y * tilesize, type.buildRange) || state.isEditor()))) continue;
|
||||
if(i == 0){
|
||||
drawPlan(plan, 1f);
|
||||
}else{
|
||||
|
||||
@@ -338,7 +338,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
data.plans.addFirst(new BlockPlan(tile.x, tile.y, (short)rotation, toAdd.id, overrideConfig == null ? config() : overrideConfig));
|
||||
data.plans.addFirst(new BlockPlan(tile.x, tile.y, (short)rotation, toAdd, overrideConfig == null ? config() : overrideConfig));
|
||||
}
|
||||
|
||||
public @Nullable Tile findClosestEdge(Position to, Boolf<Tile> solid){
|
||||
@@ -1217,6 +1217,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void payloadDraw(){
|
||||
if(block.isAir()) return;
|
||||
draw();
|
||||
}
|
||||
|
||||
@@ -1370,6 +1371,10 @@ 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;
|
||||
float power = 0f;
|
||||
|
||||
@@ -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;
|
||||
transient Entityc shooter;
|
||||
transient @Nullable Tile aimTile;
|
||||
transient float aimX, aimY;
|
||||
transient float originX, originY;
|
||||
@@ -248,7 +249,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
|
||||
type.draw(self());
|
||||
type.drawLight(self());
|
||||
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ abstract class HealthComp implements Entityc, Posc{
|
||||
}
|
||||
|
||||
void damage(float amount){
|
||||
if(Float.isNaN(health)) health = 0f;
|
||||
|
||||
health -= amount;
|
||||
hitTime = 1f;
|
||||
if(health <= 0 && !dead){
|
||||
@@ -86,6 +88,7 @@ abstract class HealthComp implements Entityc, Posc{
|
||||
|
||||
void clampHealth(){
|
||||
health = Math.min(health, maxHealth);
|
||||
if(Float.isNaN(health)) health = 0f;
|
||||
}
|
||||
|
||||
/** Heals by a flat amount. */
|
||||
|
||||
@@ -90,6 +90,8 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
|
||||
void pickup(Unit unit){
|
||||
if(unit.isAdded()) unit.team.data().updateCount(unit.type, 1);
|
||||
|
||||
unit.remove();
|
||||
addPayload(new UnitPayload(unit));
|
||||
Fx.unitPickup.at(unit);
|
||||
@@ -129,7 +131,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
|
||||
//drop off payload on an acceptor if possible
|
||||
if(on != null && on.build != null && on.build.acceptPayload(on.build, payload)){
|
||||
if(on != null && on.build != null && on.build.team == team && on.build.acceptPayload(on.build, payload)){
|
||||
Fx.unitDrop.at(on.build);
|
||||
on.build.handlePayload(on.build, payload);
|
||||
return true;
|
||||
@@ -146,8 +148,12 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
boolean dropUnit(UnitPayload payload){
|
||||
Unit u = payload.unit;
|
||||
|
||||
//add random offset to prevent unit stacking
|
||||
Tmp.v1.rnd(Mathf.random(2f));
|
||||
|
||||
//can't drop ground units
|
||||
if(!u.canPass(tileX(), tileY()) || Units.count(x, y, u.physicSize(), o -> o.isGrounded()) > 1){
|
||||
//allow stacking for small units for now - otherwise, unit transfer would get annoying
|
||||
if(!u.canPass(World.toTile(x + Tmp.v1.x), World.toTile(y + Tmp.v1.y)) || Units.count(x, y, u.physicSize(), o -> o.isGrounded() && o.hitSize > 14f) > 1){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -156,8 +162,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
//clients do not drop payloads
|
||||
if(Vars.net.client()) return true;
|
||||
|
||||
u.set(this);
|
||||
u.trns(Tmp.v1.rnd(Mathf.random(2f)));
|
||||
u.set(x + Tmp.v1.x, y + Tmp.v1.y);
|
||||
u.rotation(rotation);
|
||||
//reset the ID to a new value to make sure it's synced
|
||||
u.id = EntityGroup.nextId();
|
||||
|
||||
@@ -45,6 +45,8 @@ abstract class ShieldComp implements Healthc, Posc{
|
||||
protected void rawDamage(float amount){
|
||||
boolean hadShields = shield > 0.0001f;
|
||||
|
||||
if(Float.isNaN(health)) health = 0f;
|
||||
|
||||
if(hadShields){
|
||||
shieldAlpha = 1f;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@ import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.payloads.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
import static mindustry.logic.GlobalVars.*;
|
||||
@@ -443,6 +445,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMissile(){
|
||||
return this instanceof TimedKillc;
|
||||
}
|
||||
|
||||
public int count(){
|
||||
return team.data().countType(type);
|
||||
}
|
||||
@@ -713,7 +719,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
|
||||
//if this unit crash landed (was flying), damage stuff in a radius
|
||||
if(type.flying && !spawnedByCore && type.createWreck && state.rules.unitCrashDamage(team) > 0){
|
||||
Damage.damage(team, x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f * state.rules.unitCrashDamage(team), true, false, true);
|
||||
var shields = indexer.getEnemy(team, BlockFlag.shield);
|
||||
float crashDamage = Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f * state.rules.unitCrashDamage(team);
|
||||
if(shields.isEmpty() || !shields.contains(b -> b instanceof ExplosionShield s && s.absorbExplosion(x, y, crashDamage))){
|
||||
Damage.damage(team, x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, crashDamage, true, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
if(!headless && type.createScorch){
|
||||
|
||||
@@ -96,9 +96,13 @@ public abstract class DrawPart{
|
||||
}
|
||||
|
||||
default float getClamp(PartParams p){
|
||||
return Mathf.clamp(get(p));
|
||||
return getClamp(p, true);
|
||||
}
|
||||
|
||||
|
||||
default float getClamp(PartParams p, boolean clamp){
|
||||
return clamp ? Mathf.clamp(get(p)) : get(p);
|
||||
}
|
||||
|
||||
default PartProgress inv(){
|
||||
return p -> 1f - get(p);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class FlarePart extends DrawPart{
|
||||
public float x, y, rotation, rotMove, spinSpeed;
|
||||
public boolean followRotation;
|
||||
public Color color1 = Pal.techBlue, color2 = Color.white;
|
||||
public boolean clampProgress = true;
|
||||
public PartProgress progress = PartProgress.warmup;
|
||||
public float layer = Layer.effect;
|
||||
|
||||
@@ -20,7 +21,7 @@ public class FlarePart extends DrawPart{
|
||||
float z = Draw.z();
|
||||
if(layer > 0) Draw.z(layer);
|
||||
|
||||
float prog = progress.getClamp(params);
|
||||
float prog = progress.getClamp(params, clampProgress);
|
||||
int i = params.sideOverride == -1 ? 0 : params.sideOverride;
|
||||
|
||||
float sign = (i == 0 ? 1 : -1) * params.sideMultiplier;
|
||||
|
||||
@@ -20,6 +20,7 @@ public class HaloPart extends DrawPart{
|
||||
public Color color = Color.white;
|
||||
public @Nullable Color colorTo;
|
||||
public boolean mirror = false;
|
||||
public boolean clampProgress = true;
|
||||
public PartProgress progress = PartProgress.warmup;
|
||||
public float layer = -1f, layerOffset = 0f;
|
||||
|
||||
@@ -32,7 +33,7 @@ public class HaloPart extends DrawPart{
|
||||
Draw.z(Draw.z() + layerOffset);
|
||||
|
||||
float
|
||||
prog = progress.getClamp(params),
|
||||
prog = progress.getClamp(params, clampProgress),
|
||||
baseRot = Time.time * rotateSpeed,
|
||||
rad = radiusTo < 0 ? radius : Mathf.lerp(radius, radiusTo, prog),
|
||||
triLen = triLengthTo < 0 ? triLength : Mathf.lerp(triLength, triLengthTo, prog),
|
||||
|
||||
@@ -15,7 +15,7 @@ public class RegionPart extends DrawPart{
|
||||
public String suffix = "";
|
||||
/** Overrides suffix if set. */
|
||||
public @Nullable String name;
|
||||
public TextureRegion heat;
|
||||
public TextureRegion heat, light;
|
||||
public TextureRegion[] regions = {};
|
||||
public TextureRegion[] outlines = {};
|
||||
|
||||
@@ -27,6 +27,8 @@ public class RegionPart extends DrawPart{
|
||||
public boolean drawRegion = true;
|
||||
/** If true, the heat region produces light. */
|
||||
public boolean heatLight = false;
|
||||
/** Whether to clamp progress to (0-1). If false, allows usage of interps that go past the range, but may have unwanted visual bugs depending on values. */
|
||||
public boolean clampProgress = true;
|
||||
/** Progress function for determining position/rotation. */
|
||||
public PartProgress progress = PartProgress.warmup;
|
||||
/** Progress function for scaling. */
|
||||
@@ -67,14 +69,14 @@ public class RegionPart extends DrawPart{
|
||||
Draw.z(Draw.z() + layerOffset);
|
||||
|
||||
float prevZ = Draw.z();
|
||||
float prog = progress.getClamp(params), sclProg = growProgress.getClamp(params);
|
||||
float prog = progress.getClamp(params, clampProgress), sclProg = growProgress.getClamp(params, clampProgress);
|
||||
float mx = moveX * prog, my = moveY * prog, mr = moveRot * prog + rotation,
|
||||
gx = growX * sclProg, gy = growY * sclProg;
|
||||
|
||||
if(moves.size > 0){
|
||||
for(int i = 0; i < moves.size; i++){
|
||||
var move = moves.get(i);
|
||||
float p = move.progress.getClamp(params);
|
||||
float p = move.progress.getClamp(params, clampProgress);
|
||||
mx += move.x * p;
|
||||
my += move.y * p;
|
||||
mr += move.rot * p;
|
||||
@@ -130,10 +132,10 @@ public class RegionPart extends DrawPart{
|
||||
}
|
||||
|
||||
if(heat.found()){
|
||||
float hprog = heatProgress.getClamp(params);
|
||||
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);
|
||||
if(heatLight) Drawf.light(rx, ry, heat, rot, Tmp.c1, heatLightOpacity * hprog);
|
||||
if(heatLight) Drawf.light(rx, ry, light.found() ? light : heat, rot, Tmp.c1, heatLightOpacity * hprog);
|
||||
}
|
||||
|
||||
Draw.xscl *= sign;
|
||||
@@ -187,6 +189,7 @@ public class RegionPart extends DrawPart{
|
||||
}
|
||||
|
||||
heat = Core.atlas.find(realName + "-heat");
|
||||
light = Core.atlas.find(realName + "-light");
|
||||
for(var child : children){
|
||||
child.turretShading = turretShading;
|
||||
child.load(name);
|
||||
|
||||
@@ -15,6 +15,7 @@ public class ShapePart extends DrawPart{
|
||||
public Color color = Color.white;
|
||||
public @Nullable Color colorTo;
|
||||
public boolean mirror = false;
|
||||
public boolean clampProgress = true;
|
||||
public PartProgress progress = PartProgress.warmup;
|
||||
public float layer = -1f, layerOffset = 0f;
|
||||
|
||||
@@ -26,7 +27,7 @@ public class ShapePart extends DrawPart{
|
||||
|
||||
Draw.z(Draw.z() + layerOffset);
|
||||
|
||||
float prog = progress.getClamp(params),
|
||||
float prog = progress.getClamp(params, clampProgress),
|
||||
baseRot = Time.time * rotateSpeed,
|
||||
rad = radiusTo < 0 ? radius : Mathf.lerp(radius, radiusTo, prog),
|
||||
str = strokeTo < 0 ? stroke : Mathf.lerp(stroke, strokeTo, prog);
|
||||
|
||||
@@ -4,7 +4,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -29,8 +28,12 @@ public class AIController implements UnitController{
|
||||
protected Teamc target;
|
||||
|
||||
{
|
||||
timer.reset(0, Mathf.random(40f));
|
||||
timer.reset(1, Mathf.random(60f));
|
||||
resetTimers();
|
||||
}
|
||||
|
||||
protected void resetTimers(){
|
||||
timer.reset(timerTarget, Mathf.random(40f));
|
||||
timer.reset(timerTarget2, Mathf.random(60f));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,7 +130,7 @@ public class AIController implements UnitController{
|
||||
if(tile == null) return;
|
||||
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
|
||||
|
||||
if(tile == targetTile || (costType == Pathfinder.costNaval && !targetTile.floor().isLiquid)) return;
|
||||
if(tile == targetTile || !unit.canPass(targetTile.x, targetTile.y)) return;
|
||||
|
||||
unit.movePref(vec.trns(unit.angleTo(targetTile.worldx(), targetTile.worldy()), prefSpeed()));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.math.geom.QuadTree.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
@@ -64,7 +65,6 @@ public class BuildPlan implements Position, QuadTreeObject{
|
||||
public BuildPlan(){
|
||||
|
||||
}
|
||||
|
||||
public boolean placeable(Team team){
|
||||
return Build.validPlace(block, team, x, y, rotation);
|
||||
}
|
||||
@@ -152,6 +152,17 @@ public class BuildPlan implements Position, QuadTreeObject{
|
||||
return y*tilesize + (block == null ? 0 : block.offset);
|
||||
}
|
||||
|
||||
public boolean isDone(){
|
||||
Tile tile = world.tile(x, y);
|
||||
if(tile == null) return true;
|
||||
Block tblock = tile.block();
|
||||
if(breaking){
|
||||
return tblock == Blocks.air || tblock == tile.floor();
|
||||
}else{
|
||||
return tblock == block && (tile.build == null || tile.build.rotation == rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Tile tile(){
|
||||
return world.tile(x, y);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package mindustry.entities.units;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public interface UnitController{
|
||||
void unit(Unit unit);
|
||||
Unit unit();
|
||||
@Nullable Unit unit();
|
||||
|
||||
default void hit(Bullet bullet){
|
||||
|
||||
|
||||
22
core/src/mindustry/game/CampaignRules.java
Normal file
22
core/src/mindustry/game/CampaignRules.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package mindustry.game;
|
||||
|
||||
import mindustry.type.*;
|
||||
|
||||
public class CampaignRules{
|
||||
public Difficulty difficulty = Difficulty.normal;
|
||||
public boolean fog;
|
||||
public boolean showSpawns;
|
||||
public boolean sectorInvasion;
|
||||
public boolean randomWaveAI;
|
||||
|
||||
public void apply(Planet planet, Rules rules){
|
||||
rules.staticFog = rules.fog = fog;
|
||||
rules.showSpawns = showSpawns;
|
||||
rules.randomWaveAI = randomWaveAI;
|
||||
rules.objectiveTimerMultiplier = difficulty.waveTimeMultiplier;
|
||||
rules.teams.get(rules.waveTeam).blockHealthMultiplier = difficulty.enemyHealthMultiplier;
|
||||
rules.teams.get(rules.waveTeam).unitHealthMultiplier = difficulty.enemyHealthMultiplier;
|
||||
rules.teams.get(rules.waveTeam).unitCostMultiplier = 1f / difficulty.enemySpawnMultiplier;
|
||||
rules.teams.get(rules.waveTeam).unitBuildSpeedMultiplier = difficulty.enemySpawnMultiplier;
|
||||
}
|
||||
}
|
||||
27
core/src/mindustry/game/Difficulty.java
Normal file
27
core/src/mindustry/game/Difficulty.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package mindustry.game;
|
||||
|
||||
import arc.*;
|
||||
|
||||
public enum Difficulty{
|
||||
//TODO these need tweaks
|
||||
casual(0.75f, 0.5f, 2f),
|
||||
easy(1f, 0.75f, 1.5f),
|
||||
normal(1f, 1f, 1f),
|
||||
hard(1.25f, 1.5f, 0.8f),
|
||||
eradication(1.5f, 2f, 0.6f);
|
||||
|
||||
public static final Difficulty[] all = values();
|
||||
|
||||
//TODO add more fields
|
||||
public float enemyHealthMultiplier, enemySpawnMultiplier, waveTimeMultiplier;
|
||||
|
||||
Difficulty(float enemyHealthMultiplier, float enemySpawnMultiplier, float waveTimeMultiplier){
|
||||
this.enemySpawnMultiplier = enemySpawnMultiplier;
|
||||
this.waveTimeMultiplier = waveTimeMultiplier;
|
||||
this.enemyHealthMultiplier = enemyHealthMultiplier;
|
||||
}
|
||||
|
||||
public String localized(){
|
||||
return Core.bundle.get("difficulty." + name());
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,13 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
JsonIO.classTag(name, type);
|
||||
}
|
||||
|
||||
public MapObjectives(Seq<MapObjective> all){
|
||||
this.all.addAll(all);
|
||||
}
|
||||
|
||||
public MapObjectives(){
|
||||
}
|
||||
|
||||
/** Adds all given objectives to the executor as root objectives. */
|
||||
public void add(MapObjective... objectives){
|
||||
for(var objective : objectives) flatten(objective);
|
||||
@@ -164,6 +171,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
|
||||
/** Base abstract class for any in-map objective. */
|
||||
public static abstract class MapObjective{
|
||||
public boolean hidden;
|
||||
public @Nullable @Multiline String details;
|
||||
public @Unordered String[] flagsAdded = {};
|
||||
public @Unordered String[] flagsRemoved = {};
|
||||
@@ -441,7 +449,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
|
||||
@Override
|
||||
public boolean update(){
|
||||
return (countup += Time.delta) >= duration;
|
||||
return (countup += Time.delta) >= duration * state.rules.objectiveTimerMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -453,7 +461,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
@Override
|
||||
public String text(){
|
||||
if(text != null){
|
||||
int i = (int)((duration - countup) / 60f);
|
||||
int i = (int)((duration * state.rules.objectiveTimerMultiplier - countup) / 60f);
|
||||
StringBuilder timeString = new StringBuilder();
|
||||
|
||||
int m = i / 60;
|
||||
@@ -1125,6 +1133,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
||||
public void setTexture(String textureName){
|
||||
this.textureName = textureName;
|
||||
|
||||
if(headless) return;
|
||||
if(fetchedRegion == null) fetchedRegion = new TextureRegion();
|
||||
lookupRegion(textureName, fetchedRegion);
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ public class Objectives{
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return content.unlocked();
|
||||
return content.unlockedHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String display(){
|
||||
return Core.bundle.format("requirement.research",
|
||||
//TODO broken for multi tech nodes.
|
||||
(content.techNode == null || content.techNode.parent == null || content.techNode.parent.content.unlocked()) ?
|
||||
(content.techNode == null || content.techNode.parent == null || content.techNode.parent.content.unlockedHost()) ?
|
||||
(content.emoji() + " " + content.localizedName) : "???");
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,13 @@ public class Objectives{
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return content.unlocked();
|
||||
return content.unlockedHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String display(){
|
||||
return Core.bundle.format("requirement.produce",
|
||||
content.unlocked() ? (content.emoji() + " " + content.localizedName) : "???");
|
||||
content.unlockedHost() ? (content.emoji() + " " + content.localizedName) : "???");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import arc.util.serialization.*;
|
||||
import arc.util.serialization.Json.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.graphics.g3d.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.type.*;
|
||||
@@ -61,6 +62,8 @@ public class Rules{
|
||||
public boolean fire = true;
|
||||
/** Whether units use and require ammo. */
|
||||
public boolean unitAmmo = false;
|
||||
/** EXPERIMENTAL! If true, air and ground units target random things each wave instead of only the core/generators. */
|
||||
public boolean randomWaveAI = false;
|
||||
/** EXPERIMENTAL! If true, blocks will update in units and share power. */
|
||||
public boolean unitPayloadUpdate = false;
|
||||
/** If true, units' payloads are destroy()ed when the unit is destroyed. */
|
||||
@@ -85,6 +88,8 @@ public class Rules{
|
||||
public boolean ghostBlocks = true;
|
||||
/** Whether to allow units to build with logic. */
|
||||
public boolean logicUnitBuild = true;
|
||||
/** If true, world processors can be edited and placed on this map. */
|
||||
public boolean allowEditWorldProcessors = false;
|
||||
/** If true, world processors no longer update. Used for testing. */
|
||||
public boolean disableWorldProcessors = false;
|
||||
/** How much health blocks start with. */
|
||||
@@ -97,6 +102,8 @@ public class Rules{
|
||||
public float buildSpeedMultiplier = 1f;
|
||||
/** Multiplier for percentage of materials refunded when deconstructing. */
|
||||
public float deconstructRefundMultiplier = 0.5f;
|
||||
/** Multiplier for time in timer objectives. */
|
||||
public float objectiveTimerMultiplier = 1f;
|
||||
/** No-build zone around enemy core radius. */
|
||||
public float enemyCoreBuildRadius = 400f;
|
||||
/** If true, no-build zones are calculated based on the closest core. */
|
||||
@@ -131,6 +138,8 @@ public class Rules{
|
||||
public int winWave = 0;
|
||||
/** Base unit cap. Can still be increased by blocks. */
|
||||
public int unitCap = 0;
|
||||
/** If true, the unit cap is disabled. */
|
||||
public boolean disableUnitCap;
|
||||
/** Environment drag multiplier. */
|
||||
public float dragMultiplier = 1f;
|
||||
/** Environmental flags that dictate visuals & how blocks function. */
|
||||
@@ -152,9 +161,7 @@ public class Rules{
|
||||
/** Reveals blocks normally hidden by build visibility. */
|
||||
public ObjectSet<Block> revealedBlocks = new ObjectSet<>();
|
||||
/** Unlocked content names. Only used in multiplayer when the campaign is enabled. */
|
||||
public ObjectSet<String> researched = new ObjectSet<>();
|
||||
/** Block containing these items as requirements are hidden. */
|
||||
public ObjectSet<Item> hiddenBuildItems = Items.erekirOnlyItems.asSet();
|
||||
public ObjectSet<UnlockableContent> researched = new ObjectSet<>();
|
||||
/** In-map objective executor. */
|
||||
public MapObjectives objectives = new MapObjectives();
|
||||
/** Flags set by objectives. Used in world processors. */
|
||||
|
||||
@@ -111,7 +111,7 @@ public class Saves{
|
||||
|
||||
if(state.isGame() && !state.gameOver && current != null && current.isAutosave()){
|
||||
time += Time.delta;
|
||||
if(time > Core.settings.getInt("saveinterval") * 60){
|
||||
if(time > Core.settings.getInt("saveinterval") * 60 && !Vars.disableSave){
|
||||
saving = true;
|
||||
|
||||
try{
|
||||
|
||||
@@ -200,8 +200,7 @@ public class Schematics implements Loadable{
|
||||
Seq<Schematic> keys = previews.orderedKeys().copy();
|
||||
for(int i = 0; i < previews.size - maxPreviewsMobile; i++){
|
||||
//dispose and remove unneeded previews
|
||||
previews.get(keys.get(i)).dispose();
|
||||
previews.remove(keys.get(i));
|
||||
previews.remove(keys.get(i)).dispose();
|
||||
}
|
||||
//update last clear time
|
||||
lastClearTime = Time.millis();
|
||||
@@ -654,7 +653,7 @@ public class Schematics implements Loadable{
|
||||
|
||||
private static Schematic rotated(Schematic input, boolean counter){
|
||||
int direction = Mathf.sign(counter);
|
||||
Schematic schem = input == tmpSchem ? tmpSchem2 : tmpSchem2;
|
||||
Schematic schem = input == tmpSchem ? tmpSchem2 : tmpSchem;
|
||||
schem.width = input.width;
|
||||
schem.height = input.height;
|
||||
Pools.freeAll(schem.tiles);
|
||||
|
||||
@@ -38,6 +38,8 @@ public class SectorInfo{
|
||||
public int storageCapacity = 0;
|
||||
/** Whether a core is available here. */
|
||||
public boolean hasCore = true;
|
||||
/** Whether a world processor is on this map - implies that the map will get cleared. */
|
||||
public boolean hasWorldProcessor;
|
||||
/** Whether this sector was ever fully captured. */
|
||||
public boolean wasCaptured = false;
|
||||
/** Sector that was launched from. */
|
||||
@@ -82,7 +84,7 @@ public class SectorInfo{
|
||||
public transient ItemSeq lastImported = new ItemSeq();
|
||||
|
||||
/** Special variables for simulation. */
|
||||
public float sumHealth, sumRps, sumDps, waveHealthBase, waveHealthSlope, waveDpsBase, waveDpsSlope, bossHealth, bossDps, curEnemyHealth, curEnemyDps;
|
||||
public float sumHealth, sumRps, sumDps, bossHealth, bossDps, curEnemyHealth, curEnemyDps;
|
||||
/** Wave where first boss shows up. */
|
||||
public int bossWave = -1;
|
||||
|
||||
@@ -175,6 +177,7 @@ public class SectorInfo{
|
||||
spawnPosition = entity.pos();
|
||||
}
|
||||
|
||||
hasWorldProcessor = state.teams.present.contains(t -> t.getBuildings(Blocks.worldProcessor).any());
|
||||
waveSpacing = state.rules.waveSpacing;
|
||||
wave = state.wave;
|
||||
winWave = state.rules.winWave;
|
||||
|
||||
@@ -8,6 +8,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
@@ -239,6 +240,8 @@ public class Teams{
|
||||
}
|
||||
|
||||
public static class TeamData{
|
||||
private static final IntSeq derelictBuffer = new IntSeq();
|
||||
|
||||
public final Team team;
|
||||
|
||||
/** Handles building ""bases"". */
|
||||
@@ -316,6 +319,8 @@ public class Teams{
|
||||
}
|
||||
}
|
||||
|
||||
finishScheduleDerelict();
|
||||
|
||||
//kill all units randomly
|
||||
units.each(u -> Time.run(Mathf.random(0f, 60f * 5f), () -> {
|
||||
//ensure unit hasn't switched teams for whatever reason
|
||||
@@ -325,21 +330,7 @@ public class Teams{
|
||||
}));
|
||||
}
|
||||
|
||||
/** Make all buildings within this range derelict / explode. */
|
||||
public void makeDerelict(float x, float y, float range){
|
||||
var builds = new Seq<Building>();
|
||||
if(buildingTree != null){
|
||||
buildingTree.intersect(x - range, y - range, range * 2f, range * 2f, builds);
|
||||
}
|
||||
|
||||
for(var build : builds){
|
||||
if(build.within(x, y, range) && !build.block.privileged){
|
||||
scheduleDerelict(build);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Make all buildings within this range explode. */
|
||||
/** Make all buildings within this range derelict/explode. */
|
||||
public void timeDestroy(float x, float y, float range){
|
||||
var builds = new Seq<Building>();
|
||||
if(buildingTree != null){
|
||||
@@ -347,23 +338,31 @@ public class Teams{
|
||||
}
|
||||
|
||||
for(var build : builds){
|
||||
if(build.within(x, y, range) && !cores.contains(c -> c.within(build, range))){
|
||||
//TODO GPU driver bugs?
|
||||
build.kill();
|
||||
//Time.run(Mathf.random(0f, 60f * 6f), build::kill);
|
||||
if(!build.block.privileged && build.within(x, y, range) && !cores.contains(c -> c.within(build, range))){
|
||||
scheduleDerelict(build);
|
||||
}
|
||||
}
|
||||
finishScheduleDerelict();
|
||||
}
|
||||
|
||||
private void scheduleDerelict(Building build){
|
||||
//TODO this may cause a lot of packet spam, optimize?
|
||||
Call.setTeam(build, Team.derelict);
|
||||
//queue block to be handled later, avoid packet spam
|
||||
derelictBuffer.add(build.pos());
|
||||
|
||||
if(Mathf.chance(0.25)){
|
||||
if(build.getPayload() instanceof UnitPayload){
|
||||
Call.destroyPayload(build);
|
||||
}
|
||||
|
||||
if(Mathf.chance(0.2)){
|
||||
Time.run(Mathf.random(0f, 60f * 6f), build::kill);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishScheduleDerelict(){
|
||||
derelictBuffer.chunked(1000, values -> Call.setTeams(values, Team.derelict));
|
||||
derelictBuffer.clear();
|
||||
}
|
||||
|
||||
//this is just an alias for consistency
|
||||
@Nullable
|
||||
public Seq<Unit> getUnits(UnitType type){
|
||||
@@ -425,14 +424,23 @@ public class Teams{
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
public static void destroyPayload(Building build){
|
||||
if(build != null && build.getPayload() instanceof UnitPayload && build.takePayload() instanceof UnitPayload unit){
|
||||
unit.dump();
|
||||
unit.unit.killed();
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents a block made by this team that was destroyed somewhere on the map.
|
||||
* This does not include deconstructed blocks.*/
|
||||
public static class BlockPlan{
|
||||
public final short x, y, rotation, block;
|
||||
public final short x, y, rotation;
|
||||
public final Block block;
|
||||
public final Object config;
|
||||
public boolean removed;
|
||||
|
||||
public BlockPlan(int x, int y, short rotation, short block, Object config){
|
||||
public BlockPlan(int x, int y, short rotation, Block block, Object config){
|
||||
this.x = (short)x;
|
||||
this.y = (short)y;
|
||||
this.rotation = rotation;
|
||||
|
||||
@@ -252,7 +252,7 @@ public class Universe{
|
||||
}
|
||||
|
||||
//queue random invasions
|
||||
if(!sector.isAttacked() && sector.planet.allowSectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
|
||||
if(!sector.isAttacked() && sector.planet.campaignRules.sectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
|
||||
int count = sector.near().count(s -> s.hasEnemyBase() && !s.hasBase());
|
||||
|
||||
//invasion chance depends on # of nearby bases
|
||||
|
||||
@@ -274,7 +274,7 @@ public class BlockRenderer{
|
||||
|
||||
if(brokenFade > 0.001f){
|
||||
for(BlockPlan block : player.team().data().plans){
|
||||
Block b = content.block(block.block);
|
||||
Block b = block.block;
|
||||
if(!camera.bounds(Tmp.r1).grow(tilesize * 2f).overlaps(Tmp.r2.setSize(b.size * tilesize).setCenter(block.x * tilesize + b.offset, block.y * tilesize + b.offset))) continue;
|
||||
|
||||
Draw.alpha(0.33f * brokenFade);
|
||||
|
||||
@@ -141,11 +141,18 @@ public class Drawf{
|
||||
Draw.z(pz);
|
||||
}
|
||||
|
||||
public static void limitLine(Position start, Position dest, float len1, float len2){
|
||||
public static void limitLine(Position start, Position dest, float len1, float len2, Color color){
|
||||
if(start.within(dest, len1 + len2)){
|
||||
return;
|
||||
}
|
||||
Tmp.v1.set(dest).sub(start).setLength(len1);
|
||||
Tmp.v2.set(Tmp.v1).scl(-1f).setLength(len2);
|
||||
|
||||
Drawf.line(Pal.accent, start.getX() + Tmp.v1.x, start.getY() + Tmp.v1.y, dest.getX() + Tmp.v2.x, dest.getY() + Tmp.v2.y);
|
||||
Drawf.line(color, start.getX() + Tmp.v1.x, start.getY() + Tmp.v1.y, dest.getX() + Tmp.v2.x, dest.getY() + Tmp.v2.y);
|
||||
}
|
||||
|
||||
public static void limitLine(Position start, Position dest, float len1, float len2){
|
||||
limitLine(start, dest, len1, len2, Pal.accent);
|
||||
}
|
||||
|
||||
public static void dashLineDst(Color color, float x, float y, float x2, float y2){
|
||||
@@ -306,7 +313,7 @@ public class Drawf{
|
||||
Draw.rect(region, x, y);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
|
||||
public static void shadow(TextureRegion region, float x, float y, float width, float height, float rotation){
|
||||
Draw.color(Pal.shadow);
|
||||
Draw.rect(region, x, y, width, height, rotation);
|
||||
@@ -354,13 +361,21 @@ public class Drawf{
|
||||
}
|
||||
|
||||
public static void square(float x, float y, float radius, float rotation, Color color){
|
||||
Lines.stroke(3f, Pal.gray);
|
||||
Lines.stroke(3f, Pal.gray.write(Tmp.c3).a(color.a));
|
||||
Lines.square(x, y, radius + 1f, rotation);
|
||||
Lines.stroke(1f, color);
|
||||
Lines.square(x, y, radius + 1f, rotation);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public static void poly(float x, float y, int sides, float radius, float rotation, Color color){
|
||||
Lines.stroke(3f, Pal.gray);
|
||||
Lines.poly(x, y, sides, radius + 1f, rotation);
|
||||
Lines.stroke(1f, color);
|
||||
Lines.poly(x, y, sides, radius + 1f, rotation);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public static void square(float x, float y, float radius, float rotation){
|
||||
square(x, y, radius, rotation, Pal.accent);
|
||||
}
|
||||
@@ -436,7 +451,7 @@ public class Drawf{
|
||||
public static void construct(float x, float y, TextureRegion region, float rotation, float progress, float alpha, float time){
|
||||
construct(x, y, region, Pal.accent, rotation, progress, alpha, time);
|
||||
}
|
||||
|
||||
|
||||
public static void construct(float x, float y, TextureRegion region, Color color, float rotation, float progress, float alpha, float time){
|
||||
Shaders.build.region = region;
|
||||
Shaders.build.progress = progress;
|
||||
@@ -458,7 +473,7 @@ public class Drawf{
|
||||
public static void construct(Building t, TextureRegion region, Color color, float rotation, float progress, float alpha, float time){
|
||||
construct(t, region, color, rotation, progress, alpha, time, t.block.size * tilesize - 4f);
|
||||
}
|
||||
|
||||
|
||||
public static void construct(Building t, TextureRegion region, Color color, float rotation, float progress, float alpha, float time, float size){
|
||||
Shaders.build.region = region;
|
||||
Shaders.build.progress = progress;
|
||||
@@ -477,7 +492,7 @@ public class Drawf{
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
||||
/** Draws a sprite that should be light-wise correct, when rotated. Provided sprite must be symmetrical in shape. */
|
||||
public static void spinSprite(TextureRegion region, float x, float y, float r){
|
||||
float a = Draw.getColorAlpha();
|
||||
|
||||
@@ -16,7 +16,6 @@ import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Highly experimental fog-of-war renderer. */
|
||||
public final class FogRenderer{
|
||||
private FrameBuffer staticFog = new FrameBuffer(), dynamicFog = new FrameBuffer();
|
||||
private LongSeq events = new LongSeq();
|
||||
|
||||
@@ -116,8 +116,7 @@ public class MultiPacker implements Disposable{
|
||||
//main page can be massive, but 8192 throws GL_OUT_OF_MEMORY on some GPUs and I can't deal with it yet.
|
||||
main(4096),
|
||||
|
||||
//TODO stuff like this throws OOM on some devices
|
||||
environment(4096, 2048),
|
||||
environment(4096),
|
||||
ui(4096),
|
||||
rubble(4096, 2048),
|
||||
editor(4096, 2048);
|
||||
|
||||
28
core/src/mindustry/graphics/NvGpuInfo.java
Normal file
28
core/src/mindustry/graphics/NvGpuInfo.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package mindustry.graphics;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
|
||||
/** Nvidia-specific utility class for querying GPU VRAM information. */
|
||||
public class NvGpuInfo{
|
||||
private static final int GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX = 0x9048;
|
||||
private static final int GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX = 0x9049;
|
||||
|
||||
private static boolean supported, initialized;
|
||||
|
||||
public static int getMaxMemoryKB(){
|
||||
return hasMemoryInfo() ? Gl.getInt(GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX) : 0;
|
||||
}
|
||||
|
||||
public static int getAvailableMemoryKB(){
|
||||
return hasMemoryInfo() ? Gl.getInt(GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX) : 0;
|
||||
}
|
||||
|
||||
public static boolean hasMemoryInfo(){
|
||||
if(!initialized){
|
||||
supported = Core.graphics.supportsExtension("GL_NVX_gpu_memory_info");
|
||||
initialized = true;
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,7 @@ public class OverlayRenderer{
|
||||
}
|
||||
|
||||
input.drawTop();
|
||||
input.drawUnitSelection();
|
||||
|
||||
buildFade = Mathf.lerpDelta(buildFade, input.isPlacing() || input.isUsingSchematic() ? 1f : 0f, 0.06f);
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ public enum Binding implements KeyBind{
|
||||
unit_command_load_units(KeyCode.unset),
|
||||
unit_command_load_blocks(KeyCode.unset),
|
||||
unit_command_unload_payload(KeyCode.unset),
|
||||
unit_command_loop_payload(KeyCode.unset),
|
||||
|
||||
category_prev(KeyCode.comma, "blocks"),
|
||||
category_next(KeyCode.period),
|
||||
@@ -80,6 +81,7 @@ public enum Binding implements KeyBind{
|
||||
block_select_10(KeyCode.num0),
|
||||
|
||||
zoom(new Axis(KeyCode.scroll), "view"),
|
||||
detach_camera(KeyCode.unset),
|
||||
menu(Vars.android ? KeyCode.back : KeyCode.escape),
|
||||
fullscreen(KeyCode.f11),
|
||||
pause(KeyCode.space),
|
||||
|
||||
@@ -56,6 +56,8 @@ public class DesktopInput extends InputHandler{
|
||||
/** Time of most recent control group selection */
|
||||
public long lastCtrlGroupSelectMillis;
|
||||
|
||||
private float buildPlanMouseOffsetX, buildPlanMouseOffsetY;
|
||||
|
||||
boolean showHint(){
|
||||
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectPlans.isEmpty() && !player.dead() &&
|
||||
(!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore());
|
||||
@@ -108,6 +110,10 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
@Override
|
||||
public void drawTop(){
|
||||
if(cursorType != SystemCursor.arrow && scene.hasMouse()){
|
||||
graphics.cursor(cursorType = SystemCursor.arrow);
|
||||
}
|
||||
|
||||
Lines.stroke(1f);
|
||||
int cursorX = tileX(Core.input.mouseX());
|
||||
int cursorY = tileY(Core.input.mouseY());
|
||||
@@ -126,9 +132,6 @@ public class DesktopInput extends InputHandler{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
drawCommanded();
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@@ -222,28 +225,46 @@ public class DesktopInput extends InputHandler{
|
||||
boolean locked = locked();
|
||||
boolean panCam = false;
|
||||
float camSpeed = (!Core.input.keyDown(Binding.boost) ? panSpeed : panBoostSpeed) * Time.delta;
|
||||
boolean detached = settings.getBool("detach-camera", false);
|
||||
|
||||
if(input.keyDown(Binding.pan) && !scene.hasField() && !scene.hasDialog()){
|
||||
panCam = true;
|
||||
panning = true;
|
||||
if(!scene.hasField() && !scene.hasDialog()){
|
||||
if(input.keyTap(Binding.detach_camera)){
|
||||
settings.put("detach-camera", detached = !detached);
|
||||
if(!detached){
|
||||
panning = false;
|
||||
}
|
||||
spectating = null;
|
||||
}
|
||||
|
||||
if(input.keyDown(Binding.pan)){
|
||||
panCam = true;
|
||||
panning = true;
|
||||
spectating = null;
|
||||
}
|
||||
|
||||
if((Math.abs(Core.input.axis(Binding.move_x)) > 0 || Math.abs(Core.input.axis(Binding.move_y)) > 0 || input.keyDown(Binding.mouse_move))){
|
||||
panning = false;
|
||||
spectating = null;
|
||||
}
|
||||
}
|
||||
|
||||
if((Math.abs(Core.input.axis(Binding.move_x)) > 0 || Math.abs(Core.input.axis(Binding.move_y)) > 0 || input.keyDown(Binding.mouse_move)) && (!scene.hasField())){
|
||||
panning = false;
|
||||
}
|
||||
panning |= detached;
|
||||
|
||||
|
||||
if(!locked){
|
||||
if(((player.dead() || state.isPaused()) && !ui.chatfrag.shown()) && !scene.hasField() && !scene.hasDialog()){
|
||||
if(((player.dead() || state.isPaused() || detached) && !ui.chatfrag.shown()) && !scene.hasField() && !scene.hasDialog()){
|
||||
if(input.keyDown(Binding.mouse_move)){
|
||||
panCam = true;
|
||||
}
|
||||
|
||||
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(camSpeed));
|
||||
}else if(!player.dead() && !panning){
|
||||
}else if((!player.dead() || spectating != null) && !panning){
|
||||
//TODO do not pan
|
||||
Team corePanTeam = state.won ? state.rules.waveTeam : player.team();
|
||||
Position coreTarget = state.gameOver && !state.rules.pvp && corePanTeam.data().lastCore != null ? corePanTeam.data().lastCore : null;
|
||||
Core.camera.position.lerpDelta(coreTarget != null ? coreTarget : player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f);
|
||||
Position panTarget = coreTarget != null ? coreTarget : spectating != null ? spectating : player;
|
||||
|
||||
Core.camera.position.lerpDelta(panTarget, Core.settings.getBool("smoothcamera") ? 0.08f : 1f);
|
||||
}
|
||||
|
||||
if(panCam){
|
||||
@@ -446,12 +467,14 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY());
|
||||
|
||||
cursorType = SystemCursor.arrow;
|
||||
|
||||
if(cursor != null){
|
||||
if(cursor.build != null && cursor.build.interactable(player.team())){
|
||||
cursorType = cursor.build.getCursor();
|
||||
}
|
||||
|
||||
if(cursor.build != null && player.team() != Team.derelict && cursor.build.team == Team.derelict && Build.validPlace(cursor.block(), player.team(), cursor.build.tileX(), cursor.build.tileY(), cursor.build.rotation)){
|
||||
if(canRepairDerelict(cursor)){
|
||||
cursorType = ui.repairCursor;
|
||||
}
|
||||
|
||||
@@ -498,9 +521,9 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
if(!Core.scene.hasMouse()){
|
||||
Core.graphics.cursor(cursorType);
|
||||
}else{
|
||||
cursorType = SystemCursor.arrow;
|
||||
}
|
||||
|
||||
cursorType = SystemCursor.arrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -521,8 +544,6 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
@Override
|
||||
public void buildPlacementUI(Table table){
|
||||
table.image().color(Pal.gray).height(4f).colspan(4).growX();
|
||||
table.row();
|
||||
table.left().margin(0f).defaults().size(48f).left();
|
||||
|
||||
table.button(Icon.paste, Styles.clearNonei, () -> {
|
||||
@@ -622,11 +643,10 @@ public class DesktopInput extends InputHandler{
|
||||
}
|
||||
|
||||
if(splan != null){
|
||||
float offset = ((splan.block.size + 2) % 2) * tilesize / 2f;
|
||||
float x = Core.input.mouseWorld().x + offset;
|
||||
float y = Core.input.mouseWorld().y + offset;
|
||||
splan.x = (int)(x / tilesize);
|
||||
splan.y = (int)(y / tilesize);
|
||||
float x = Core.input.mouseWorld().x + buildPlanMouseOffsetX;
|
||||
float y = Core.input.mouseWorld().y + buildPlanMouseOffsetY;
|
||||
splan.x = Math.round(x / tilesize);
|
||||
splan.y = Math.round(y / tilesize);
|
||||
}
|
||||
|
||||
if(block == null || mode != placing){
|
||||
@@ -657,6 +677,15 @@ public class DesktopInput extends InputHandler{
|
||||
tappedOne = false;
|
||||
BuildPlan plan = getPlan(cursorX, cursorY);
|
||||
|
||||
if(plan != null){
|
||||
//move selected to front
|
||||
int index = player.unit().plans.indexOf(plan, true);
|
||||
if(index != -1){
|
||||
player.unit().plans.removeIndex(index);
|
||||
player.unit().plans.addFirst(plan);
|
||||
}
|
||||
}
|
||||
|
||||
if(Core.input.keyDown(Binding.break_block)){
|
||||
mode = none;
|
||||
}else if(!selectPlans.isEmpty()){
|
||||
@@ -668,8 +697,10 @@ public class DesktopInput extends InputHandler{
|
||||
lastLineY = cursorY;
|
||||
mode = placing;
|
||||
updateLine(selectX, selectY);
|
||||
}else if(plan != null && !plan.breaking && mode == none && !plan.initialized){
|
||||
}else if(plan != null && !plan.breaking && mode == none && !plan.initialized && plan.progress <= 0f){
|
||||
splan = plan;
|
||||
buildPlanMouseOffsetX = splan.x * tilesize - Core.input.mouseWorld().x;
|
||||
buildPlanMouseOffsetY = splan.y * tilesize - Core.input.mouseWorld().y;
|
||||
}else if(plan != null && plan.breaking){
|
||||
deleting = true;
|
||||
}else if(commandMode){
|
||||
@@ -754,6 +785,15 @@ public class DesktopInput extends InputHandler{
|
||||
if(getPlan(splan.x, splan.y, splan.block.size, splan) != null){
|
||||
player.unit().plans().remove(splan, true);
|
||||
}
|
||||
|
||||
if(input.ctrl()){
|
||||
inv.hide();
|
||||
config.hideConfig();
|
||||
planConfig.showConfig(splan);
|
||||
}else{
|
||||
planConfig.hide();
|
||||
}
|
||||
|
||||
splan = null;
|
||||
}
|
||||
|
||||
@@ -853,9 +893,20 @@ public class DesktopInput extends InputHandler{
|
||||
float ya = Core.input.axis(Binding.move_y);
|
||||
boolean boosted = (unit instanceof Mechc && unit.isFlying());
|
||||
|
||||
movement.set(xa, ya).nor().scl(speed);
|
||||
if(Core.input.keyDown(Binding.mouse_move)){
|
||||
movement.add(input.mouseWorld().sub(player).scl(1f / 25f * speed)).limit(speed);
|
||||
if(settings.getBool("detach-camera")){
|
||||
Vec2 targetPos = camera.position;
|
||||
|
||||
movement.set(targetPos).sub(player).limit(speed);
|
||||
|
||||
if(player.within(targetPos, 15f)){
|
||||
movement.setZero();
|
||||
unit.vel.approachDelta(Vec2.ZERO, unit.speed() * unit.type().accel / 2f);
|
||||
}
|
||||
}else{
|
||||
movement.set(xa, ya).nor().scl(speed);
|
||||
if(Core.input.keyDown(Binding.mouse_move)){
|
||||
movement.add(input.mouseWorld().sub(player).scl(1f / 25f * speed)).limit(speed);
|
||||
}
|
||||
}
|
||||
|
||||
float mouseAngle = Angles.mouseAngle(unit.x, unit.y);
|
||||
|
||||
@@ -12,6 +12,7 @@ import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.struct.Queue;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.*;
|
||||
@@ -52,7 +53,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
/** Used for dropping items. */
|
||||
final static float playerSelectRange = mobile ? 17f : 11f;
|
||||
final static float unitSelectRadScl = 1f;
|
||||
final static IntSeq removed = new IntSeq();
|
||||
final static IntSet intSet = new IntSet();
|
||||
/** Maximum line length. */
|
||||
final static int maxLength = 100;
|
||||
final static Rect r1 = new Rect(), r2 = new Rect();
|
||||
@@ -96,6 +99,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
public BuildPlan bplan = new BuildPlan();
|
||||
public Seq<BuildPlan> linePlans = new Seq<>();
|
||||
public Seq<BuildPlan> selectPlans = new Seq<>(BuildPlan.class);
|
||||
public Queue<BuildPlan> lastPlans = new Queue<>();
|
||||
public @Nullable Unit lastUnit;
|
||||
public @Nullable Unit spectating;
|
||||
|
||||
//for RTS controls
|
||||
public Seq<Unit> selectedUnits = new Seq<>();
|
||||
@@ -112,6 +118,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
public final BlockInventoryFragment inv;
|
||||
public final BlockConfigFragment config;
|
||||
public final PlanConfigFragment planConfig;
|
||||
|
||||
private WidgetGroup group = new WidgetGroup();
|
||||
|
||||
@@ -132,6 +139,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
group.touchable = Touchable.childrenOnly;
|
||||
inv = new BlockInventoryFragment();
|
||||
config = new BlockConfigFragment();
|
||||
planConfig = new PlanConfigFragment();
|
||||
|
||||
Events.on(UnitDestroyEvent.class, e -> {
|
||||
if(e.unit != null && e.unit.isPlayer() && e.unit.getPlayer().isLocal() && e.unit.type.weapons.contains(w -> w.bullet.killShooter)){
|
||||
@@ -147,6 +155,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
logicCutscene = false;
|
||||
itemDepositCooldown = 0f;
|
||||
Arrays.fill(controlGroups, null);
|
||||
lastUnit = null;
|
||||
lastPlans.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -793,7 +803,16 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
return !selectPlans.isEmpty();
|
||||
}
|
||||
|
||||
public void spectate(Unit unit){
|
||||
spectating = unit;
|
||||
camera.position.set(unit);
|
||||
}
|
||||
|
||||
public void update(){
|
||||
if(spectating != null && (!spectating.isValid() || spectating.team != player.team())){
|
||||
spectating = null;
|
||||
}
|
||||
|
||||
if(logicCutscene && !renderer.isCutscene()){
|
||||
Core.camera.position.lerpDelta(logicCamPan, logicCamSpeed);
|
||||
}else{
|
||||
@@ -808,6 +827,24 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
commandRect = false;
|
||||
}
|
||||
|
||||
if(player.isBuilder()){
|
||||
var playerPlans = player.unit().plans;
|
||||
if(player.unit() != lastUnit && playerPlans.size <= 1){
|
||||
playerPlans.ensureCapacity(lastPlans.size);
|
||||
for(var plan : lastPlans){
|
||||
playerPlans.addLast(plan);
|
||||
}
|
||||
}
|
||||
if(lastPlans.size != playerPlans.size || (lastPlans.size > 0 && playerPlans.size > 0 && lastPlans.first() != playerPlans.first())){
|
||||
lastPlans.clear();
|
||||
for(var plan : playerPlans){
|
||||
lastPlans.addLast(plan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastUnit = player.unit();
|
||||
|
||||
playerPlanTree.clear();
|
||||
if(!player.dead()){
|
||||
player.unit().plans.each(playerPlanTree::insert);
|
||||
@@ -830,7 +867,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
//you don't want selected blocks while locked, looks weird
|
||||
if(locked()){
|
||||
block = null;
|
||||
|
||||
}
|
||||
|
||||
wasShooting = player.shooting;
|
||||
@@ -1035,30 +1071,69 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
public void drawCommand(Unit sel){
|
||||
Drawf.square(sel.x, sel.y, sel.hitSize / 1.4f + Mathf.absin(4f, 1f), selectedUnits.contains(sel) ? Pal.remove : Pal.accent);
|
||||
Drawf.poly(sel.x, sel.y, 6, sel.hitSize / unitSelectRadScl + Mathf.absin(4f, 1f), 0f, selectedUnits.contains(sel) ? Pal.remove : Pal.accent);
|
||||
}
|
||||
|
||||
public void drawCommanded(){
|
||||
Draw.draw(Layer.plans, () -> {
|
||||
drawCommanded(true);
|
||||
});
|
||||
|
||||
Draw.draw(Layer.groundUnit - 1, () -> {
|
||||
drawCommanded(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void drawCommanded(boolean flying){
|
||||
float lineLimit = 6.5f;
|
||||
Color color = Pal.accent;
|
||||
int sides = 6;
|
||||
float alpha = 0.5f;
|
||||
|
||||
if(commandMode){
|
||||
//happens sometimes
|
||||
selectedUnits.removeAll(u -> !u.isCommandable());
|
||||
|
||||
//draw command overlay UI
|
||||
for(Unit unit : selectedUnits){
|
||||
if(unit.isFlying() != flying) continue;
|
||||
CommandAI ai = unit.command();
|
||||
Position lastPos = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||
|
||||
//draw target line
|
||||
if(ai.targetPos != null && ai.currentCommand().drawTarget){
|
||||
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||
Drawf.limitLine(unit, lineDest, unit.hitSize / 2f, 3.5f);
|
||||
Drawf.limitLine(unit, lineDest, unit.hitSize / unitSelectRadScl + 1f, lineLimit, color.write(Tmp.c1).a(alpha));
|
||||
|
||||
if(ai.attackTarget == null){
|
||||
Drawf.square(lineDest.getX(), lineDest.getY(), 3.5f);
|
||||
Drawf.square(lineDest.getX(), lineDest.getY(), 3.5f, color.write(Tmp.c1).a(alpha));
|
||||
|
||||
if(ai.currentCommand() == UnitCommand.enterPayloadCommand){
|
||||
var build = world.buildWorld(lineDest.getX(), lineDest.getY());
|
||||
if(build != null && build.block.acceptsUnitPayloads && build.team == unit.team){
|
||||
Drawf.selected(build, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Drawf.square(unit.x, unit.y, unit.hitSize / 1.4f + 1f);
|
||||
float rad = unit.hitSize / unitSelectRadScl + 1f;
|
||||
|
||||
Fill.lightInner(unit.x, unit.y, sides,
|
||||
Math.max(0f, rad * 0.8f),
|
||||
rad,
|
||||
0f,
|
||||
Tmp.c3.set(color).a(0f),
|
||||
Tmp.c2.set(color).a(0.7f)
|
||||
);
|
||||
|
||||
Lines.stroke(1f);
|
||||
Draw.color(color);
|
||||
Lines.poly(unit.x, unit.y, sides, rad + 0.5f);
|
||||
//uncomment for a dark border
|
||||
//Draw.color(Pal.gray);
|
||||
//Lines.poly(unit.x, unit.y, sides, rad + 1.5f);
|
||||
Draw.reset();
|
||||
|
||||
if(ai.attackTarget != null && ai.currentCommand().drawTarget){
|
||||
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
||||
@@ -1071,54 +1146,72 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
//draw command queue
|
||||
if(ai.currentCommand().drawTarget && ai.commandQueue.size > 0){
|
||||
for(var next : ai.commandQueue){
|
||||
Drawf.limitLine(lastPos, next, 3.5f, 3.5f);
|
||||
Drawf.limitLine(lastPos, next, lineLimit, lineLimit, color.write(Tmp.c1).a(alpha));
|
||||
lastPos = next;
|
||||
|
||||
if(next instanceof Vec2 vec){
|
||||
Drawf.square(vec.x, vec.y, 3.5f);
|
||||
Drawf.square(vec.x, vec.y, 3.5f, color.write(Tmp.c1).a(alpha));
|
||||
}else{
|
||||
Drawf.target(next.getX(), next.getY(), 6f, Pal.remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ai.targetPos != null && ai.currentCommand() == UnitCommand.loopPayloadCommand && unit instanceof Payloadc pay){
|
||||
Draw.color(color, 0.4f + Mathf.absin(5f, 0.5f));
|
||||
TextureRegion region = pay.hasPayload() ? Icon.download.getRegion() : Icon.upload.getRegion();
|
||||
float offset = 11f;
|
||||
float size = 8f;
|
||||
Draw.rect(region, ai.targetPos.x, ai.targetPos.y + offset, size, size / region.ratio());
|
||||
|
||||
if(ai.commandQueue.size > 0){
|
||||
region = !pay.hasPayload() ? Icon.download.getRegion() : Icon.upload.getRegion();
|
||||
Draw.rect(region, ai.commandQueue.first().getX(), ai.commandQueue.first().getY() + offset, size, size / region.ratio());
|
||||
}
|
||||
Draw.color();
|
||||
}
|
||||
}
|
||||
|
||||
for(var commandBuild : commandBuildings){
|
||||
if(commandBuild != null){
|
||||
Drawf.square(commandBuild.x, commandBuild.y, commandBuild.hitSize() / 1.4f + 1f);
|
||||
var cpos = commandBuild.getCommandPosition();
|
||||
if(flying){
|
||||
for(var commandBuild : commandBuildings){
|
||||
if(commandBuild != null){
|
||||
Drawf.square(commandBuild.x, commandBuild.y, commandBuild.hitSize() / 1.4f + 1f);
|
||||
var cpos = commandBuild.getCommandPosition();
|
||||
|
||||
if(cpos != null){
|
||||
Drawf.limitLine(commandBuild, cpos, commandBuild.hitSize() / 2f, 3.5f);
|
||||
Drawf.square(cpos.x, cpos.y, 3.5f);
|
||||
if(cpos != null){
|
||||
Drawf.limitLine(commandBuild, cpos, commandBuild.hitSize() / 2f, lineLimit, color.write(Tmp.c1).a(alpha));
|
||||
Drawf.square(cpos.x, cpos.y, 3.5f, color.write(Tmp.c1).a(alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(commandMode && !commandRect){
|
||||
Unit sel = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||
|
||||
if(sel != null && !(!multiUnitSelect() && selectedUnits.size == 1 && selectedUnits.contains(sel))){
|
||||
drawCommand(sel);
|
||||
}
|
||||
}
|
||||
|
||||
if(commandRect){
|
||||
float x2 = input.mouseWorldX(), y2 = input.mouseWorldY();
|
||||
var units = selectedCommandUnits(commandRectX, commandRectY, x2 - commandRectX, y2 - commandRectY);
|
||||
for(var unit : units){
|
||||
drawCommand(unit);
|
||||
}
|
||||
|
||||
Draw.color(Pal.accent, 0.3f);
|
||||
Fill.crect(commandRectX, commandRectY, x2 - commandRectX, y2 - commandRectY);
|
||||
}
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
|
||||
}
|
||||
|
||||
public void drawUnitSelection(){
|
||||
if(commandRect && commandMode){
|
||||
float x2 = input.mouseWorldX(), y2 = input.mouseWorldY();
|
||||
var units = selectedCommandUnits(commandRectX, commandRectY, x2 - commandRectX, y2 - commandRectY);
|
||||
for(var unit : units){
|
||||
drawCommand(unit);
|
||||
}
|
||||
|
||||
Draw.color(Pal.accent, 0.3f);
|
||||
Fill.crect(commandRectX, commandRectY, x2 - commandRectX, y2 - commandRectY);
|
||||
}
|
||||
|
||||
if(commandMode && !commandRect){
|
||||
Unit sel = selectedCommandUnit(input.mouseWorldX(), input.mouseWorldY());
|
||||
|
||||
if(sel != null && !(!multiUnitSelect() && selectedUnits.size == 1 && selectedUnits.contains(sel))){
|
||||
drawCommand(sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawBottom(){
|
||||
|
||||
}
|
||||
@@ -1348,9 +1441,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
for(BlockPlan plan : player.team().data().plans){
|
||||
Block block = content.block(plan.block);
|
||||
Block block = plan.block;
|
||||
if(block.bounds(plan.x, plan.y, Tmp.r2).overlaps(Tmp.r1)){
|
||||
drawSelected(plan.x, plan.y, content.block(plan.block), Pal.remove);
|
||||
drawSelected(plan.x, plan.y, plan.block, Pal.remove);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1362,17 +1455,31 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
|
||||
}
|
||||
|
||||
protected void drawRebuildSelection(int x, int y, int x2, int y2){
|
||||
drawSelection(x, y, x2, y2, 0, Pal.sapBulletBack, Pal.sapBullet);
|
||||
protected void drawRebuildSelection(int x1, int y1, int x2, int y2){
|
||||
drawSelection(x1, y1, x2, y2, 0, Pal.sapBulletBack, Pal.sapBullet);
|
||||
|
||||
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x, y, x2, y2, false, 0, 1f);
|
||||
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, 0, 1f);
|
||||
|
||||
Tmp.r1.set(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
|
||||
|
||||
for(BlockPlan plan : player.team().data().plans){
|
||||
Block block = content.block(plan.block);
|
||||
Block block = plan.block;
|
||||
if(block.bounds(plan.x, plan.y, Tmp.r2).overlaps(Tmp.r1)){
|
||||
drawSelected(plan.x, plan.y, content.block(plan.block), Pal.sapBullet);
|
||||
drawSelected(plan.x, plan.y, plan.block, Pal.sapBullet);
|
||||
}
|
||||
}
|
||||
|
||||
NormalizeResult dresult = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, 999999999);
|
||||
|
||||
intSet.clear();
|
||||
for(int x = dresult.x; x <= dresult.x2; x++){
|
||||
for(int y = dresult.y; y <= dresult.y2; y++){
|
||||
|
||||
Tile tile = world.tileBuilding(x, y);
|
||||
|
||||
if(tile != null && intSet.add(tile.pos()) && canRepairDerelict(tile)){
|
||||
drawSelected(tile.x, tile.y, tile.block(), Pal.sapBullet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1398,7 +1505,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
protected void flushSelectPlans(Seq<BuildPlan> plans){
|
||||
for(BuildPlan plan : plans){
|
||||
if(plan.block != null && validPlace(plan.x, plan.y, plan.block, plan.rotation)){
|
||||
if(plan.block != null && validPlace(plan.x, plan.y, plan.block, plan.rotation, null, true)){
|
||||
BuildPlan other = getPlan(plan.x, plan.y, plan.block.size, null);
|
||||
if(other == null){
|
||||
selectPlans.add(plan.copy());
|
||||
@@ -1414,7 +1521,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
//reversed iteration.
|
||||
for(int i = plans.size - 1; i >= 0; i--){
|
||||
var plan = plans.get(i);
|
||||
if(plan.block != null && validPlace(plan.x, plan.y, plan.block, plan.rotation)){
|
||||
if(plan.block != null && validPlace(plan.x, plan.y, plan.block, plan.rotation, null, true)){
|
||||
BuildPlan copy = plan.copy();
|
||||
plan.block.onNewPlan(copy);
|
||||
player.unit().addBuild(copy, false);
|
||||
@@ -1424,7 +1531,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
protected void flushPlans(Seq<BuildPlan> plans){
|
||||
for(var plan : plans){
|
||||
if(plan.block != null && validPlace(plan.x, plan.y, plan.block, plan.rotation)){
|
||||
if(plan.block != null && validPlace(plan.x, plan.y, plan.block, plan.rotation, null, true)){
|
||||
BuildPlan copy = plan.copy();
|
||||
plan.block.onNewPlan(copy);
|
||||
player.unit().addBuild(copy);
|
||||
@@ -1522,7 +1629,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
Iterator<BlockPlan> broken = player.team().data().plans.iterator();
|
||||
while(broken.hasNext()){
|
||||
BlockPlan plan = broken.next();
|
||||
Block block = content.block(plan.block);
|
||||
Block block = plan.block;
|
||||
if(block.bounds(plan.x, plan.y, Tmp.r2).overlaps(Tmp.r1)){
|
||||
removed.add(Point2.pack(plan.x, plan.y));
|
||||
plan.removed = true;
|
||||
@@ -1567,6 +1674,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
/** Handles tile tap events that are not platform specific. */
|
||||
boolean tileTapped(@Nullable Building build){
|
||||
planConfig.hide();
|
||||
if(build == null){
|
||||
inv.hide();
|
||||
config.hideConfig();
|
||||
@@ -1638,7 +1746,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
/** Tries to begin mining a tile, returns true if successful. */
|
||||
boolean tryBeginMine(Tile tile){
|
||||
if(canMine(tile)){
|
||||
if(!player.dead() && canMine(tile)){
|
||||
player.unit().mineTile = tile;
|
||||
return true;
|
||||
}
|
||||
@@ -1647,7 +1755,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
/** Tries to stop mining, returns true if mining was stopped. */
|
||||
boolean tryStopMine(){
|
||||
if(player.unit().mining()){
|
||||
if(!player.dead() && player.unit().mining()){
|
||||
player.unit().mineTile = null;
|
||||
return true;
|
||||
}
|
||||
@@ -1655,7 +1763,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
boolean tryStopMine(Tile tile){
|
||||
if(player.unit().mineTile == tile){
|
||||
if(!player.dead() && player.unit().mineTile == tile){
|
||||
player.unit().mineTile = null;
|
||||
return true;
|
||||
}
|
||||
@@ -1663,13 +1771,20 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
boolean tryRepairDerelict(Tile selected){
|
||||
if(selected != null && player.team() != Team.derelict && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict && Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){
|
||||
if(selected != null && !state.rules.editor && player.team() != Team.derelict && selected.build != null && selected.build.block.unlockedNow() && selected.build.team == Team.derelict &&
|
||||
Build.validPlace(selected.block(), player.team(), selected.build.tileX(), selected.build.tileY(), selected.build.rotation)){
|
||||
|
||||
player.unit().addBuild(new BuildPlan(selected.build.tileX(), selected.build.tileY(), selected.build.rotation, selected.block(), selected.build.config()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean canRepairDerelict(Tile tile){
|
||||
return tile != null && tile.build != null && !state.rules.editor && player.team() != Team.derelict && tile.build.team == Team.derelict && tile.build.block.unlockedNowHost() &&
|
||||
Build.validPlace(tile.block(), player.team(), tile.build.tileX(), tile.build.tileY(), tile.build.rotation);
|
||||
}
|
||||
|
||||
boolean canMine(Tile tile){
|
||||
return !Core.scene.hasMouse()
|
||||
&& player.unit().validMine(tile)
|
||||
@@ -1835,6 +1950,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
inv.build(group);
|
||||
config.build(group);
|
||||
planConfig.build(group);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1877,16 +1993,28 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
public void rebuildArea(int x, int y, int x2, int y2){
|
||||
NormalizeResult result = Placement.normalizeArea(x, y, x2, y2, rotation, false, 999999999);
|
||||
public void rebuildArea(int x1, int y1, int x2, int y2){
|
||||
NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, 999999999);
|
||||
Tmp.r1.set(result.x * tilesize, result.y * tilesize, (result.x2 - result.x) * tilesize, (result.y2 - result.y) * tilesize);
|
||||
|
||||
Iterator<BlockPlan> broken = player.team().data().plans.iterator();
|
||||
while(broken.hasNext()){
|
||||
BlockPlan plan = broken.next();
|
||||
Block block = content.block(plan.block);
|
||||
Block block = plan.block;
|
||||
if(block.bounds(plan.x, plan.y, Tmp.r2).overlaps(Tmp.r1)){
|
||||
player.unit().addBuild(new BuildPlan(plan.x, plan.y, plan.rotation, content.block(plan.block), plan.config));
|
||||
player.unit().addBuild(new BuildPlan(plan.x, plan.y, plan.rotation, plan.block, plan.config));
|
||||
}
|
||||
}
|
||||
|
||||
intSet.clear();
|
||||
for(int x = result.x; x <= result.x2; x++){
|
||||
for(int y = result.y; y <= result.y2; y++){
|
||||
|
||||
Tile tile = world.tileBuilding(x, y);
|
||||
|
||||
if(tile != null && tile.build != null && intSet.add(tile.pos())){
|
||||
tryRepairDerelict(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1900,9 +2028,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
public boolean validPlace(int x, int y, Block type, int rotation){
|
||||
return validPlace(x, y, type, rotation, null);
|
||||
}
|
||||
public boolean validPlace(int x, int y, Block type, int rotation, @Nullable BuildPlan ignore){
|
||||
return validPlace(x, y, type, rotation, ignore, false);
|
||||
}
|
||||
|
||||
public boolean validPlace(int x, int y, Block type, int rotation, BuildPlan ignore){
|
||||
if(player.unit().plans.size > 0){
|
||||
public boolean validPlace(int x, int y, Block type, int rotation, @Nullable BuildPlan ignore, boolean ignoreUnits){
|
||||
if(player.isBuilder() && player.unit().plans.size > 0){
|
||||
Tmp.r1.setCentered(x * tilesize + type.offset, y * tilesize + type.offset, type.size * tilesize);
|
||||
plansOut.clear();
|
||||
playerPlanTree.intersect(Tmp.r1, plansOut);
|
||||
@@ -1918,7 +2049,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
return Build.validPlace(type, player.team(), x, y, rotation);
|
||||
return ignoreUnits ? Build.validPlaceIgnoreUnits(type, player.team(), x, y, rotation, true) : Build.validPlace(type, player.team(), x, y, rotation);
|
||||
}
|
||||
|
||||
public boolean validBreak(int x, int y){
|
||||
@@ -1926,6 +2057,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
public void breakBlock(int x, int y){
|
||||
if(!player.isBuilder()) return;
|
||||
|
||||
Tile tile = world.tile(x, y);
|
||||
if(tile != null && tile.build != null) tile = tile.build.tile;
|
||||
player.unit().addBuild(new BuildPlan(tile.x, tile.y));
|
||||
|
||||
@@ -88,9 +88,11 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
/** Check and assign targets for a specific position. */
|
||||
void checkTargets(float x, float y){
|
||||
if(player.dead()) return;
|
||||
|
||||
Unit unit = Units.closestEnemy(player.team(), x, y, 20f, u -> !u.dead);
|
||||
|
||||
if(unit != null && !player.dead() && player.unit().type.canAttack){
|
||||
if(unit != null && player.unit().type.canAttack){
|
||||
player.unit().mineTile = null;
|
||||
target = unit;
|
||||
}else{
|
||||
@@ -188,8 +190,6 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
@Override
|
||||
public void buildPlacementUI(Table table){
|
||||
table.image().color(Pal.gray).height(4f).colspan(4).growX();
|
||||
table.row();
|
||||
table.left().margin(0f).defaults().size(48f);
|
||||
|
||||
table.button(Icon.hammer, Styles.clearNoneTogglei, () -> {
|
||||
@@ -234,7 +234,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
//actually place/break all selected blocks
|
||||
if(tile != null){
|
||||
if(!plan.breaking){
|
||||
if(validPlace(plan.x, plan.y, plan.block, plan.rotation)){
|
||||
if(validPlace(plan.x, plan.y, plan.block, plan.rotation, null, true)){
|
||||
BuildPlan other = getPlan(plan.x, plan.y, plan.block.size, null);
|
||||
BuildPlan copy = plan.copy();
|
||||
|
||||
@@ -265,11 +265,11 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}).name("confirmplace");
|
||||
}
|
||||
|
||||
boolean showCancel(){
|
||||
return !player.dead() && (player.unit().isBuilding() || block != null || mode == breaking || !selectPlans.isEmpty()) && !hasSchem();
|
||||
public boolean showCancel(){
|
||||
return !player.dead() && (player.unit().isBuilding() || block != null || mode == breaking || !selectPlans.isEmpty()) && !hasSchematic();
|
||||
}
|
||||
|
||||
boolean hasSchem(){
|
||||
public boolean hasSchematic(){
|
||||
return lastSchematic != null && !selectPlans.isEmpty();
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
});
|
||||
|
||||
group.fill(t -> {
|
||||
t.visible(() -> !showCancel() && block == null && !hasSchem());
|
||||
t.visible(() -> !showCancel() && block == null && !hasSchematic() && !state.rules.editor);
|
||||
t.bottom().left();
|
||||
|
||||
t.button("@command.queue", Icon.rightOpen, Styles.clearTogglet, () -> {
|
||||
@@ -310,7 +310,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
});
|
||||
|
||||
group.fill(t -> {
|
||||
t.visible(this::hasSchem);
|
||||
t.visible(this::hasSchematic);
|
||||
t.bottom().left();
|
||||
t.table(Tex.pane, b -> {
|
||||
b.defaults().size(50f);
|
||||
@@ -391,8 +391,6 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}else if(mode == rebuildSelect){
|
||||
drawRebuildSelection(lineStartX, lineStartY, lastLineX, lastLineY);
|
||||
}
|
||||
|
||||
drawCommanded();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -759,7 +757,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
payloadTarget = null;
|
||||
}
|
||||
|
||||
if(locked || block != null || scene.hasField() || hasSchem() || selectPlans.size > 0){
|
||||
if(locked || block != null || scene.hasField() || hasSchematic() || selectPlans.size > 0){
|
||||
commandMode = false;
|
||||
}
|
||||
|
||||
@@ -772,14 +770,18 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}
|
||||
|
||||
//zoom camera
|
||||
if(!locked && Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!player.isBuilder() || !isPlacing() || !block.rotate) && selectPlans.isEmpty()))){
|
||||
if(!locked && !scene.hasKeyboard() && !scene.hasScroll() && Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!player.isBuilder() || !isPlacing() || !block.rotate) && selectPlans.isEmpty()))){
|
||||
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
|
||||
}
|
||||
|
||||
if(!Core.settings.getBool("keyboard") && !locked && !scene.hasKeyboard()){
|
||||
//move camera around
|
||||
float camSpeed = 6f;
|
||||
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta * camSpeed));
|
||||
Vec2 delta = Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta * camSpeed);
|
||||
Core.camera.position.add(delta);
|
||||
if(!delta.isZero()){
|
||||
spectating = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(Core.settings.getBool("keyboard")){
|
||||
@@ -940,6 +942,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
//pan player
|
||||
Core.camera.position.x -= deltaX;
|
||||
Core.camera.position.y -= deltaY;
|
||||
spectating = null;
|
||||
}
|
||||
|
||||
camera.position.clamp(-camera.width/4f, -camera.height/4f, world.unitWidth() + camera.width/4f, world.unitHeight() + camera.height/4f);
|
||||
|
||||
@@ -142,7 +142,7 @@ public class Placement{
|
||||
|
||||
Boolf<BuildPlan> placeable = plan ->
|
||||
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
|
||||
!(plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
!(plan != plans.first() && plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
|
||||
var result = plans1.clear();
|
||||
var rotated = plans.first().tile() != null && plans.first().tile().absoluteRelativeTo(plans.peek().x, plans.peek().y) == Mathf.mod(plans.first().rotation + 2, 4);
|
||||
@@ -217,7 +217,7 @@ public class Placement{
|
||||
|
||||
Boolf<BuildPlan> placeable = plan ->
|
||||
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
|
||||
!(plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
!(plan != plans.first() && plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
|
||||
var result = plans1.clear();
|
||||
|
||||
|
||||
@@ -261,15 +261,8 @@ public class JsonIO{
|
||||
public UnlockableContent read(Json json, JsonValue jsonData, Class type){
|
||||
if(jsonData.isNull()) return null;
|
||||
String str = jsonData.asString();
|
||||
Item item = Vars.content.item(str);
|
||||
Liquid liquid = Vars.content.liquid(str);
|
||||
Block block = Vars.content.block(str);
|
||||
UnitType unit = Vars.content.unit(str);
|
||||
return
|
||||
item != null ? item :
|
||||
liquid != null ? liquid :
|
||||
block != null ? block :
|
||||
unit;
|
||||
var map = Vars.content.byName(str);
|
||||
return map instanceof UnlockableContent u ? u : null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -232,7 +232,8 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
Tile tile = world.rawTile(i % world.width(), i / world.width());
|
||||
stream.writeShort(tile.blockID());
|
||||
|
||||
boolean savedata = tile.block().saveData;
|
||||
boolean savedata = tile.floor().saveData || tile.overlay().saveData || tile.block().saveData;
|
||||
|
||||
byte packed = (byte)((tile.build != null ? 1 : 0) | (savedata ? 2 : 0));
|
||||
|
||||
//make note of whether there was an entity/rotation here
|
||||
@@ -367,7 +368,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
stream.writeShort(block.x);
|
||||
stream.writeShort(block.y);
|
||||
stream.writeShort(block.rotation);
|
||||
stream.writeShort(block.block);
|
||||
stream.writeShort(block.block.id);
|
||||
TypeIO.writeObject(Writes.get(stream), block.config);
|
||||
}
|
||||
}
|
||||
@@ -425,7 +426,7 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
var obj = TypeIO.readObject(reads);
|
||||
//cannot have two in the same position
|
||||
if(set.add(Point2.pack(x, y))){
|
||||
data.plans.addLast(new BlockPlan(x, y, rot, content.block(bid).id, obj));
|
||||
data.plans.addLast(new BlockPlan(x, y, rot, content.block(bid), obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public class TypeIO{
|
||||
|
||||
//this is irrelevant.
|
||||
static final WeaponMount[] noMounts = {};
|
||||
|
||||
|
||||
public static WeaponMount[] readMounts(Reads read){
|
||||
read.skip(read.b() * (1 + 4 + 4));
|
||||
|
||||
@@ -581,7 +581,7 @@ public class TypeIO{
|
||||
if(ai.command == null) ai.command = UnitCommand.moveCommand;
|
||||
}
|
||||
|
||||
//command queue only in type 7
|
||||
//command queue only in type 7/8
|
||||
if(type == 7 || type == 8){
|
||||
ai.commandQueue.clear();
|
||||
int length = read.ub();
|
||||
|
||||
@@ -21,7 +21,7 @@ public class Save3 extends LegacySaveVersion{
|
||||
TeamData data = team.data();
|
||||
int blocks = stream.readInt();
|
||||
for(int j = 0; j < blocks; j++){
|
||||
data.plans.addLast(new BlockPlan(stream.readShort(), stream.readShort(), stream.readShort(), content.block(stream.readShort()).id, stream.readInt()));
|
||||
data.plans.addLast(new BlockPlan(stream.readShort(), stream.readShort(), stream.readShort(), content.block(stream.readShort()), stream.readInt()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.*;
|
||||
import arc.audio.*;
|
||||
import arc.files.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
@@ -8,6 +9,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
@@ -25,7 +27,7 @@ public class GlobalVars{
|
||||
public static final Rand rand = new Rand();
|
||||
|
||||
//non-constants that depend on state
|
||||
private static LVar varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
|
||||
private static LVar varTime, varTick, varSecond, varMinute, varWave, varWaveTime, varMapW, varMapH, varWait, varServer, varClient, varClientLocale, varClientUnit, varClientName, varClientTeam, varClientMobile;
|
||||
|
||||
private ObjectMap<String, LVar> vars = new ObjectMap<>();
|
||||
private Seq<VarEntry> varEntries = new Seq<>();
|
||||
@@ -33,6 +35,8 @@ public class GlobalVars{
|
||||
private UnlockableContent[][] logicIdToContent;
|
||||
private int[][] contentIdToLogicId;
|
||||
|
||||
public static final Seq<String> soundNames = new Seq<>();
|
||||
|
||||
public void init(){
|
||||
putEntryOnly("sectionProcessor");
|
||||
|
||||
@@ -69,6 +73,7 @@ public class GlobalVars{
|
||||
|
||||
varMapW = putEntry("@mapw", 0);
|
||||
varMapH = putEntry("@maph", 0);
|
||||
varWait = putEntry("@wait", null);
|
||||
|
||||
putEntryOnly("sectionNetwork");
|
||||
|
||||
@@ -87,6 +92,17 @@ public class GlobalVars{
|
||||
put("@ctrlPlayer", ctrlPlayer);
|
||||
put("@ctrlCommand", ctrlCommand);
|
||||
|
||||
//sounds
|
||||
if(Core.assets != null){
|
||||
for(Sound sound : Core.assets.getAll(Sound.class, new Seq<>(Sound.class))){
|
||||
if(sound != Sounds.none && sound != Sounds.swish && sound.file != null){
|
||||
String name = sound.file.nameWithoutExtension();
|
||||
soundNames.add(name);
|
||||
put("@sfx-" + name, Sounds.getSoundId(sound));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//store base content
|
||||
|
||||
for(Team team : Team.baseTeams){
|
||||
@@ -116,7 +132,9 @@ public class GlobalVars{
|
||||
}
|
||||
|
||||
for(UnitType type : Vars.content.units()){
|
||||
put("@" + type.name, type);
|
||||
if(!type.internal){
|
||||
put("@" + type.name, type);
|
||||
}
|
||||
}
|
||||
|
||||
for(Weather weather : Vars.content.weathers()){
|
||||
@@ -185,7 +203,7 @@ public class GlobalVars{
|
||||
varClient.numval = net.client() ? 1 : 0;
|
||||
|
||||
//client
|
||||
if(!net.server() && player != null){
|
||||
if(player != null){
|
||||
varClientLocale.objval = player.locale();
|
||||
varClientUnit.objval = player.unit();
|
||||
varClientName.objval = player.name();
|
||||
@@ -194,6 +212,10 @@ public class GlobalVars{
|
||||
}
|
||||
}
|
||||
|
||||
public LVar waitVar(){
|
||||
return varWait;
|
||||
}
|
||||
|
||||
public Seq<VarEntry> getEntries(){
|
||||
return varEntries;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user