Merge branch 'master' into port-field

This commit is contained in:
Antsiferov Andrew
2020-09-12 23:25:36 +03:00
committed by GitHub
393 changed files with 16934 additions and 14818 deletions

View File

@@ -37,7 +37,7 @@ public class Vars implements Loadable{
/** Whether the logger is loaded. */
public static boolean loadedLogger = false, loadedFileLogger = false;
/** Maximum extra padding around deployment schematics. */
public static final int maxLoadoutSchematicPad = 4;
public static final int maxLoadoutSchematicPad = 5;
/** Maximum schematic size.*/
public static final int maxSchematicSize = 32;
/** All schematic base64 starts with this string.*/
@@ -78,6 +78,8 @@ public class Vars implements Loadable{
public static final float miningRange = 70f;
/** range for building */
public static final float buildingRange = 220f;
/** range for moving items */
public static final float itemTransferRange = 220f;
/** duration of time between turns in ticks */
public static final float turnDuration = 20 * Time.toMinutes;
/** turns needed to destroy a sector completely */
@@ -199,8 +201,7 @@ public class Vars implements Loadable{
public static NetServer netServer;
public static NetClient netClient;
public static
Player player;
public static Player player;
@Override
public void loadAsync(){

View File

@@ -92,7 +92,7 @@ public class BaseRegistry{
}
}
cores.sort(Structs.comps(Structs.comparingFloat(b -> b.core.health), Structs.comparingFloat(b -> b.tier)));
cores.sort(b -> b.tier);
parts.sort();
reqParts.each((key, arr) -> arr.sort());
}

View File

@@ -37,7 +37,7 @@ public class BlockIndexer{
/** All ores available on this map. */
private ObjectSet<Item> allOres = new ObjectSet<>();
/** Stores teams that are present here as tiles. */
private Seq<Team> activeTeams = new Seq<>();
private Seq<Team> activeTeams = new Seq<>(Team.class);
/** Maps teams to a map of flagged tiles by flag. */
private TileArray[][] flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
/** Max units by team. */
@@ -52,7 +52,7 @@ public class BlockIndexer{
private Seq<Building> breturnArray = new Seq<>();
public BlockIndexer(){
Events.on(BuildinghangeEvent.class, event -> {
Events.on(TileChangeEvent.class, event -> {
if(typeMap.get(event.tile.pos()) != null){
TileIndex index = typeMap.get(event.tile.pos());
for(BlockFlag flag : index.flags){
@@ -73,6 +73,7 @@ public class BlockIndexer{
damagedTiles = new BuildingArray[Team.all.length];
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
unitCaps = new int[Team.all.length];
activeTeams = new Seq<>(Team.class);
for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
@@ -147,7 +148,7 @@ public class BlockIndexer{
BuildingArray set = damagedTiles[team.id];
for(Building build : set){
if((!build.isValid() || build.team != team || !build.damaged()) || build.block instanceof BuildBlock){
if((!build.isValid() || build.team != team || !build.damaged()) || build.block instanceof ConstructBlock){
breturnArray.add(build);
}
}
@@ -220,7 +221,10 @@ public class BlockIndexer{
}
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
for(Team enemy : team.enemies()){
for(int i = 0; i < activeTeams.size; i++){
Team enemy = activeTeams.items[i];
if(enemy == team) continue;
Building entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
@@ -251,15 +255,15 @@ public class BlockIndexer{
if(e == null) continue;
if(e.team != team || !pred.get(e) || !e.block().targetable)
if(e.team != team || !pred.get(e) || !e.block.targetable)
continue;
float ndst = e.dst2(x, y);
if(ndst < range2 && (closest == null ||
//this one is closer, and it is at least of equal priority
(ndst < dst && (!usePriority || closest.block().priority.ordinal() <= e.block().priority.ordinal())) ||
(ndst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
//priority is used, and new block has higher priority regardless of range
(usePriority && closest.block().priority.ordinal() < e.block().priority.ordinal()))){
(usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
dst = ndst;
closest = e;
}

View File

@@ -23,52 +23,107 @@ public class Pathfinder implements Runnable{
private static final int impassable = -1;
private static final int fieldTimeout = 1000 * 60 * 2;
public static final int
fieldCore = 0,
fieldRally = 1;
public static final Seq<Prov<Flowfield>> fieldTypes = Seq.with(
EnemyCoreField::new,
RallyField::new
);
public static final int
costGround = 0,
costLegs = 1,
costWater = 2;
public static final Seq<PathCost> costTypes = Seq.with(
//ground
(team, tile) -> (PathTile.team(tile) == team.id || PathTile.team(tile) == 0) && PathTile.solid(tile) ? impassable : 1 +
PathTile.health(tile) * 5 +
(PathTile.nearSolid(tile) ? 2 : 0) +
(PathTile.nearLiquid(tile) ? 6 : 0) +
(PathTile.deep(tile) ? 70 : 0),
//legs
(team, tile) -> PathTile.legSolid(tile) ? impassable : 1 +
(PathTile.solid(tile) ? 5 : 0),
//water
(team, tile) -> PathTile.solid(tile) || !PathTile.liquid(tile) ? 200 : 2 + //TODO cannot go through blocks - pathfinding isn't great
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
(PathTile.deep(tile) ? -1 : 0)
);
//maps team, cost, type to flow field
Flowfield[][][] cache;
/** tile data, see PathTileStruct */
private int[][] tiles;
int[][] tiles;
/** unordered array of path data for iteration only. DO NOT iterate or access this in the main thread. */
private Seq<Flowfield> threadList = new Seq<>(), mainList = new Seq<>();
/** Maps team ID and target to to a flowfield.*/
private ObjectMap<PathTarget, Flowfield>[] fieldMap = new ObjectMap[Team.all.length];
/** Used field maps. */
private ObjectSet<PathTarget>[] fieldMapUsed = new ObjectSet[Team.all.length];
Seq<Flowfield> threadList = new Seq<>(), mainList = new Seq<>();
/** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue();
/** Stores path target for a position. Main thread only.*/
private ObjectMap<Position, PathTarget> targetCache = new ObjectMap<>();
TaskQueue queue = new TaskQueue();
/** Current pathfinding thread */
private @Nullable Thread thread;
private IntSeq tmpArray = new IntSeq();
@Nullable Thread thread;
IntSeq tmpArray = new IntSeq();
public Pathfinder(){
clearCache();
Events.on(WorldLoadEvent.class, event -> {
stop();
//reset and update internal tile array
tiles = new int[world.width()][world.height()];
fieldMap = new ObjectMap[Team.all.length];
fieldMapUsed = new ObjectSet[Team.all.length];
targetCache = new ObjectMap<>();
threadList = new Seq<>();
mainList = new Seq<>();
clearCache();
for(Tile tile : world.tiles){
tiles[tile.x][tile.y] = packTile(tile);
}
//special preset which may help speed things up; this is optional
preloadPath(state.rules.waveTeam, FlagTarget.enemyCores);
preloadPath(getField(state.rules.waveTeam, costGround, fieldCore));
start();
});
Events.on(ResetEvent.class, event -> stop());
Events.on(BuildinghangeEvent.class, event -> updateTile(event.tile));
Events.on(TileChangeEvent.class, event -> updateTile(event.tile));
}
private void clearCache(){
cache = new Flowfield[256][5][5];
}
/** Packs a tile into its internal representation. */
private int packTile(Tile tile){
return PathTile.get(tile.cost, (byte)tile.getTeamID(), !tile.solid() && tile.floor().drownTime <= 0f, !tile.solid() && tile.floor().isLiquid);
//TODO nearGround is just the inverse of nearLiquid?
boolean nearLiquid = false, nearSolid = false, nearGround = false;
for(int i = 0; i < 4; i++){
Tile other = tile.getNearby(i);
if(other != null){
if(other.floor().isLiquid) nearLiquid = true;
if(other.solid()) nearSolid = true;
if(!other.floor().isLiquid) nearGround = true;
}
}
return PathTile.get(
tile.build == null ? 0 : Math.min((int)(tile.build.health / 40), 127),
tile.getTeamID(),
tile.solid(),
tile.floor().isLiquid,
tile.staticDarkness() >= 2,
nearLiquid,
nearGround,
nearSolid,
tile.floor().isDeep()
);
}
/** Starts or restarts the pathfinding thread. */
@@ -104,7 +159,7 @@ public class Pathfinder implements Runnable{
if(path != null){
synchronized(path.targets){
path.targets.clear();
path.target.getPositions(path.team, path.targets);
path.getPositions(path.targets);
}
}
}
@@ -131,18 +186,19 @@ public class Pathfinder implements Runnable{
updateFrontier(data, maxUpdate / threadList.size);
//remove flowfields that have 'timed out' so they can be garbage collected and no longer waste space
if(data.target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
if(data.refreshRate > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
//make sure it doesn't get removed twice
data.lastUpdateTime = Time.millis();
Team team = data.team;
Core.app.post(() -> {
//TODO ?????
//remove its used state
if(fieldMap[team.id] != null){
fieldMap[team.id].remove(data.target);
fieldMapUsed[team.id].remove(data.target);
}
//if(fieldMap[team.id] != null){
// fieldMap[team.id].remove(data.target);
// fieldMapUsed[team.id].remove(data.target);
//}
//remove from main thread list
mainList.remove(data);
});
@@ -167,51 +223,49 @@ public class Pathfinder implements Runnable{
}
}
public @Nullable Tile getTargetTile(Tile tile, Team team, Position target){
return getTargetTile(tile, team, getTarget(target));
public Flowfield getField(Team team, int costType, int fieldType){
if(cache[team.id][costType][fieldType] == null){
Flowfield field = fieldTypes.get(fieldType).get();
field.team = team;
field.cost = costTypes.get(costType);
field.targets.clear();
field.getPositions(field.targets);
cache[team.id][costType][fieldType] = field;
queue.post(() -> registerPath(field));
}
return cache[team.id][costType][fieldType];
}
/** Gets next tile to travel to. Main thread only. */
public @Nullable Tile getTargetTile(Tile tile, Team team, PathTarget target){
public @Nullable Tile getTargetTile(Tile tile, Flowfield path){
if(tile == null) return null;
if(fieldMap[team.id] == null){
fieldMap[team.id] = new ObjectMap<>();
fieldMapUsed[team.id] = new ObjectSet<>();
}
Flowfield data = fieldMap[team.id].get(target);
if(data == null){
//if this combination is not found, create it on request
if(fieldMapUsed[team.id].add(target)){
//grab targets since this is run on main thread
IntSeq targets = target.getPositions(team, new IntSeq());
queue.post(() -> createPath(team, target, targets));
}
//uninitialized flowfields are not applicable
if(!path.initialized){
return tile;
}
//if refresh rate is positive, queue a refresh
if(target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > target.refreshRate()){
data.lastUpdateTime = Time.millis();
if(path.refreshRate > 0 && Time.timeSinceMillis(path.lastUpdateTime) > path.refreshRate){
path.lastUpdateTime = Time.millis();
tmpArray.clear();
data.target.getPositions(data.team, tmpArray);
path.getPositions(tmpArray);
synchronized(data.targets){
synchronized(path.targets){
//make sure the position actually changed
if(!(data.targets.size == 1 && tmpArray.size == 1 && data.targets.first() == tmpArray.first())){
data.targets.clear();
data.target.getPositions(data.team, data.targets);
if(!(path.targets.size == 1 && tmpArray.size == 1 && path.targets.first() == tmpArray.first())){
path.targets.clear();
path.getPositions(path.targets);
//queue an update
queue.post(() -> updateTargets(data));
queue.post(() -> updateTargets(path));
}
}
}
int[][] values = data.weights;
int[][] values = path.weights;
int value = values[tile.x][tile.y];
Tile current = null;
@@ -222,8 +276,8 @@ public class Pathfinder implements Runnable{
Tile other = world.tile(dx, dy);
if(other == null) continue;
if(values[dx][dy] < value && (current == null || values[dx][dy] < tl) && !other.solid() && other.floor().drownTime <= 0 &&
!(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap
if(values[dx][dy] < value && (current == null || values[dx][dy] < tl) && path.passable(dx, dy) &&
!(point.x != 0 && point.y != 0 && (!path.passable(tile.x + point.x, tile.y) || !path.passable(tile.x, tile.y + point.y)))){ //diagonal corner trap
current = other;
tl = values[dx][dy];
}
@@ -234,16 +288,6 @@ public class Pathfinder implements Runnable{
return current;
}
private PathTarget getTarget(Position position){
return targetCache.get(position, () -> new PositionTarget(position));
}
/** @return whether a tile can be passed through by this team. Pathfinding thread only. */
private boolean passable(int x, int y, Team team){
int tile = tiles[x][y];
return PathTile.passable(tile) || (PathTile.team(tile) != team.id && PathTile.team(tile) != (int)Team.derelict.id);
}
/**
* Clears the frontier, increments the search and sets up all flow sources.
* This only occurs for active teams.
@@ -259,10 +303,8 @@ public class Pathfinder implements Runnable{
return;
}
//assign impassability to the tile
if(!passable(x, y, path.team)){
path.weights[x][y] = impassable;
}
//update cost of the tile TODO maybe only update the cost when it's not passable
path.weights[x][y] = path.cost.getCost(path.team, tiles[x][y]);
//clear frontier to prevent contamination
path.frontier.clear();
@@ -289,34 +331,33 @@ public class Pathfinder implements Runnable{
}
}
private void preloadPath(Team team, PathTarget target){
updateFrontier(createPath(team, target, target.getPositions(team, new IntSeq())), -1);
private void preloadPath(Flowfield path){
path.targets.clear();
path.getPositions(path.targets);
registerPath(path);
updateFrontier(path, -1);
}
/**
* TODO wrong docs
* Created a new flowfield that aims to get to a certain target for a certain team.
* Pathfinding thread only.
*/
private Flowfield createPath(Team team, PathTarget target, IntSeq targets){
Flowfield path = new Flowfield(team, target, world.width(), world.height());
private void registerPath(Flowfield path){
path.lastUpdateTime = Time.millis();
path.setup(tiles.length, tiles[0].length);
threadList.add(path);
//add to main thread's list of paths
Core.app.post(() -> {
mainList.add(path);
if(fieldMap[team.id] != null){
fieldMap[team.id].put(target, path);
}
//TODO
//if(fieldMap[team.id] != null){
// fieldMap[team.id].put(target, path);
//}
});
//grab targets from passed array
synchronized(path.targets){
path.targets.clear();
path.targets.addAll(targets);
}
//fill with impassables by default
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
@@ -330,8 +371,6 @@ public class Pathfinder implements Runnable{
path.weights[Point2.x(pos)][Point2.y(pos)] = 0;
path.frontier.addFirst(pos);
}
return path;
}
/** Update the frontier for a path. Pathfinding thread only. */
@@ -353,12 +392,14 @@ public class Pathfinder implements Runnable{
for(Point2 point : Geometry.d4){
int dx = tile.x + point.x, dy = tile.y + point.y;
Tile other = world.tile(dx, dy);
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search) && passable(dx, dy, path.team)){
if(other.cost < 0) throw new IllegalArgumentException("Tile cost cannot be negative! " + other);
if(dx < 0 || dy < 0 || dx >= tiles.length || dy >= tiles[0].length) continue;
int otherCost = path.cost.getCost(path.team, tiles[dx][dy]);
if((path.weights[dx][dy] > cost + otherCost || path.searches[dx][dy] < path.search) && otherCost != impassable){
path.frontier.addFirst(Point2.pack(dx, dy));
path.weights[dx][dy] = cost + other.cost;
path.weights[dx][dy] = cost + otherCost;
path.searches[dx][dy] = (short)path.search;
}
}
@@ -366,9 +407,9 @@ public class Pathfinder implements Runnable{
}
}
/** A path target defines a set of targets for a path. */
public enum FlagTarget implements PathTarget{
enemyCores((team, out) -> {
public static class EnemyCoreField extends Flowfield{
@Override
protected void getPositions(IntSeq out){
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
out.add(other.pos());
}
@@ -379,98 +420,99 @@ public class Pathfinder implements Runnable{
out.add(other.pos());
}
}
}),
rallyPoints((team, out) -> {
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
out.add(other.pos());
}
});
public static final FlagTarget[] all = values();
private final Cons2<Team, IntSeq> targeter;
FlagTarget(Cons2<Team, IntSeq> targeter){
this.targeter = targeter;
}
@Override
public IntSeq getPositions(Team team, IntSeq out){
targeter.get(team, out);
return out;
}
@Override
public int refreshRate(){
return 0;
}
}
public static class PositionTarget implements PathTarget{
public static class RallyField extends Flowfield{
@Override
protected void getPositions(IntSeq out){
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
out.add(other.pos());
}
}
}
public static class PositionTarget extends Flowfield{
public final Position position;
public PositionTarget(Position position){
this.position = position;
this.refreshRate = 900;
}
@Override
public IntSeq getPositions(Team team, IntSeq out){
public void getPositions(IntSeq out){
out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
return out;
}
@Override
public int refreshRate(){
return 900;
}
}
public interface PathTarget{
/** Gets targets to pathfind towards. This must run on the main thread. */
IntSeq getPositions(Team team, IntSeq out);
/**
* Data for a flow field to some set of destinations.
* Concrete subclasses must specify a way to fetch costs and destinations.
* */
static abstract class Flowfield{
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
int refreshRate();
}
protected int refreshRate;
/** Team this path is for. Set before using. */
protected Team team = Team.derelict;
/** Function for calculating path cost. Set before using. */
protected PathCost cost = costTypes.get(costGround);
/** Data for a specific flow field to some set of destinations. */
static class Flowfield{
/** Team this path is for. */
final Team team;
/** Flag that is being targeted. */
final PathTarget target;
/** costs of getting to a specific tile */
final int[][] weights;
int[][] weights;
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
final int[][] searches;
int[][] searches;
/** search frontier, these are Pos objects */
final IntQueue frontier = new IntQueue();
IntQueue frontier = new IntQueue();
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
final IntSeq targets = new IntSeq();
IntSeq targets = new IntSeq();
/** current search ID */
int search = 1;
/** last updated time */
long lastUpdateTime;
/** whether this flow field is ready to be used */
boolean initialized;
Flowfield(Team team, PathTarget target, int width, int height){
this.team = team;
this.target = target;
void setup(int width, int height){
this.weights = new int[width][height];
this.searches = new int[width][height];
this.frontier.ensureCapacity((width + height) * 3);
this.initialized = true;
}
protected boolean passable(int x, int y){
return cost.getCost(team, pathfinder.tiles[x][y]) != impassable;
}
/** Gets targets to pathfind towards. This must run on the main thread. */
protected abstract void getPositions(IntSeq out);
}
interface PathCost{
int getCost(Team traversing, int tile);
}
/** Holds a copy of tile data for a specific tile position. */
@Struct
class PathTileStruct{
//traversal cost
short cost;
//scaled block health
@StructField(8) int health;
//team of block, if applicable (0 by default)
byte team;
//whether it's viable to pass this block
boolean passable;
//whether it's viable to pass this block through water
boolean passableWater;
@StructField(8) int team;
//general solid state
boolean solid;
//whether this block is a liquid that boats can move on
boolean liquid;
//whether this block is solid for leg units that can move over some solid blocks
boolean legSolid;
//whether this block is near liquids
boolean nearLiquid;
//whether this block is near a solid floor tile
boolean nearGround;
//whether this block is near a solid object
boolean nearSolid;
//whether this block is deep / drownable
boolean deep;
}
}

View File

@@ -3,6 +3,7 @@ package mindustry.ai;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@@ -17,10 +18,11 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class WaveSpawner{
private static final float margin = 40f, coreMargin = tilesize * 3; //how far away from the edge flying units spawn
private static final float margin = 40f, coreMargin = tilesize * 2f, maxSteps = 30;
private Seq<Tile> spawns = new Seq<>();
private boolean spawning = false;
private boolean any = false;
public WaveSpawner(){
Events.on(WorldLoadEvent.class, e -> reset());
@@ -91,8 +93,34 @@ public class WaveSpawner{
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
Building firstCore = state.teams.playerCores().first();
for(Building core : state.rules.waveTeam.cores()){
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block().size * tilesize);
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block.size * tilesize /2f * Mathf.sqrt2);
boolean valid = false;
int steps = 0;
//keep moving forward until the max step amount is reached
while(steps++ < maxSteps){
int tx = world.toTile(core.x + Tmp.v1.x), ty = world.toTile(core.y + Tmp.v1.y);
any = false;
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
if(world.solid(x, y)){
any = true;
}
});
//nothing is in the way, spawn it
if(!any){
valid = true;
break;
}else{
//make the vector longer
Tmp.v1.setLength(Tmp.v1.len() + tilesize*1.1f);
}
}
if(valid){
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
}
}
}
}

View File

@@ -1,15 +1,20 @@
package mindustry.ai.types;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import static mindustry.Vars.*;
public class BuilderAI extends AIController{
float buildRadius = 700;
boolean found = false;
@Nullable Builderc following;
@Override
public void updateUnit(){
@@ -21,12 +26,27 @@ public class BuilderAI extends AIController{
builder.updateBuilding(true);
//approach request if building
if(following != null){
//try to follow and mimic someone
//validate follower
if(!following.isValid() || !following.activelyBuilding()){
following = null;
builder.plans().clear();
return;
}
//set to follower's first build plan, whatever that is
builder.plans().clear();
builder.plans().addFirst(following.buildPlan());
}
if(builder.buildPlan() != null){
//approach request if building
BuildPlan req = builder.buildPlan();
boolean valid =
(req.tile().build instanceof BuildEntity && req.tile().<BuildEntity>bc().cblock == req.block) ||
(req.tile().build instanceof ConstructBuild && req.tile().<ConstructBuild>bc().cblock == req.block) ||
(req.breaking ?
Build.validBreak(unit.team(), req.x, req.y) :
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation));
@@ -39,8 +59,35 @@ public class BuilderAI extends AIController{
builder.plans().removeFirst();
}
}else{
//follow someone and help them build
if(timer.get(timerTarget2, 60f)){
found = false;
Units.nearby(unit.team, unit.x, unit.y, buildRadius, u -> {
if(found) return;
if(u instanceof Builderc && u != unit && ((Builderc)u).activelyBuilding()){
Builderc b = (Builderc)u;
BuildPlan plan = b.buildPlan();
Building build = world.build(plan.x, plan.y);
if(build instanceof ConstructBuild){
ConstructBuild cons = (ConstructBuild)build;
float dist = Math.min(cons.dst(unit) - buildingRange, 0);
//make sure you can reach the request in time
if(dist / unit.type().speed < cons.buildCost * 0.9f){
following = b;
found = true;
}
}
}
});
}
//find new request
if(!unit.team().data().blocks.isEmpty()){
if(!unit.team().data().blocks.isEmpty() && following == null){
Queue<BlockPlan> blocks = unit.team().data().blocks;
BlockPlan block = blocks.first();
@@ -49,17 +96,14 @@ public class BuilderAI extends AIController{
blocks.removeFirst();
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
//add build request.
BuildPlan req = new BuildPlan(block.x, block.y, block.rotation, content.block(block.block));
if(block.config != null){
req.configure(block.config);
}
builder.addBuild(req);
builder.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
}else{
//shift head of queue to tail, try something else next time
blocks.removeFirst();
blocks.addLast(block);
}
}
}
}
}

View File

@@ -47,7 +47,8 @@ public class FormationAI extends AIController implements FormationMember{
Vec2 realtarget = vec.set(target);
if(unit.isGrounded() && Vars.world.raycast(unit.tileX(), unit.tileY(), leader.tileX(), leader.tileY(), Vars.world::solid)){
realtarget.set(Vars.pathfinder.getTargetTile(unit.tileOn(), unit.team, leader));
//TODO pathfind
//realtarget.set(Vars.pathfinder.getTargetTile(unit.tileOn(), unit.team, leader));
}
unit.moveAt(realtarget.sub(unit).limit(unit.type().speed));

View File

@@ -1,10 +1,9 @@
package mindustry.ai.types;
import arc.math.*;
import mindustry.ai.Pathfinder.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -35,23 +34,24 @@ public class GroundAI extends AIController{
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
if(move) moveToCore(FlagTarget.enemyCores);
if(move) moveTo(Pathfinder.fieldCore);
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
moveToCore(FlagTarget.rallyPoints);
moveTo(Pathfinder.fieldRally);
}
}
if(unit.type().canBoost){
if(unit.type().canBoost && !unit.onSolid()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
if(!Units.invalidateTarget(target, unit, unit.range())){
if(unit.type().hasWeapons()){
//TODO certain units should not look at the target, e.g. ships
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
}else if(unit.moving()){
@@ -74,40 +74,17 @@ public class GroundAI extends AIController{
}*/
}
protected void moveToCore(FlagTarget path){
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, unit.team, path);
if(tile == targetTile) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}
protected void moveAwayFromCore(){
Team enemy = null;
for(Team team : unit.team().enemies()){
if(team.active()){
enemy = team;
break;
}
}
if(enemy == null){
for(Team team : unit.team().enemies()){
enemy = team;
break;
}
}
if(enemy == null) return;
protected void moveTo(int pathType){
int costType =
unit instanceof Legsc ? Pathfinder.costLegs :
unit instanceof WaterMovec ? Pathfinder.costWater :
Pathfinder.costGround;
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, enemy, FlagTarget.enemyCores);
Building core = unit.closestCore();
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathType));
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}

View File

@@ -39,6 +39,7 @@ public class MinerAI extends AIController{
//core full of the target item, do nothing
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
unit.clearItem();
miner.mineTile(null);
return;
}
@@ -63,6 +64,8 @@ public class MinerAI extends AIController{
}
}
}else{
miner.mineTile(null);
if(unit.stack.amount == 0){
mining = true;
return;

View File

@@ -1,10 +1,12 @@
package mindustry.ai.types;
import mindustry.*;
import mindustry.ai.Pathfinder.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class SuicideAI extends GroundAI{
static boolean blockedByBlock;
@@ -27,7 +29,7 @@ public class SuicideAI extends GroundAI{
if(!Units.invalidateTarget(target, unit, unit.range())){
rotate = true;
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
(target instanceof Building ? ((Building)target).block().size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
@@ -58,8 +60,14 @@ public class SuicideAI extends GroundAI{
}
}else{
if(core != null){
moveToCore(FlagTarget.enemyCores);
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
moveTo(Pathfinder.fieldRally);
}
}else if(command() == UnitCommand.attack && core != null){
moveTo(Pathfinder.fieldCore);
}
if(unit.moving()) unit.lookAt(unit.vel().angle());

View File

@@ -43,6 +43,7 @@ public class PhysicsProcess implements AsyncProcess{
//find entities without bodies and assign them
for(Physicsc entity : group){
boolean grounded = entity.isGrounded();
int bits = grounded ? flying.maskBits : ground.maskBits;
if(entity.physref() == null){
//add bodies to entities that have none
@@ -66,12 +67,9 @@ public class PhysicsProcess implements AsyncProcess{
//save last position
PhysicRef ref = entity.physref();
if(ref.wasGround != grounded){
if(ref.body.getFixtureList().isEmpty()) continue;
if(ref.body.getFixtureList().any() && ref.body.getFixtureList().first().getFilterData().categoryBits != bits){
//set correct filter
ref.body.getFixtureList().first().setFilterData(grounded ? ground : flying);
ref.wasGround = grounded;
}
ref.velocity.set(entity.deltaX(), entity.deltaY());
@@ -143,10 +141,9 @@ public class PhysicsProcess implements AsyncProcess{
}
public static class PhysicRef{
Physicsc entity;
Body body;
boolean wasGround = true;
Vec2 lastPosition = new Vec2(), delta = new Vec2(), velocity = new Vec2(), lastVelocity = new Vec2(), position = new Vec2();
public Physicsc entity;
public Body body;
public Vec2 lastPosition = new Vec2(), delta = new Vec2(), velocity = new Vec2(), lastVelocity = new Vec2(), position = new Vec2();
public PhysicRef(Physicsc entity, Body body){
this.entity = entity;

View File

@@ -34,10 +34,10 @@ public class Blocks implements ContentList{
public static Block
//environment
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, sandBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, dirt, ice, snow, darksandTaintedWater,
dacite, stoneWall, dirtWall, sporeWall, iceWall, daciteWall, cliffs, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, darksandWater, duneWall, sandWall, moss, sporeMoss, shale, shaleWall, shaleBoulder, sandBoulder, daciteBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, basalt, magmarock, hotrock, snowWall, boulder, snowBoulder, saltWall,
darkPanel1, darkPanel2, darkPanel3, darkPanel4, darkPanel5, darkPanel6, darkMetal,
pebbles, tendrils,
@@ -64,7 +64,7 @@ public class Blocks implements ContentList{
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
//power
combustionGenerator, thermalGenerator, turbineGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
combustionGenerator, thermalGenerator, steamGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
impactReactor, battery, batteryLarge, powerNode, powerNodeLarge, surgeTower, diode,
//production
@@ -101,13 +101,12 @@ public class Blocks implements ContentList{
hasShadow = false;
}
public void drawBase(Tile tile){}
public void load(){}
public void init(){}
public boolean isHidden(){
return true;
}
@Override public void drawBase(Tile tile){}
@Override public void load(){}
@Override public void init(){}
@Override public boolean isHidden(){ return true; }
@Override
public TextureRegion[] variantRegions(){
if(variantRegions == null){
variantRegions = new TextureRegion[]{Core.atlas.find("clear")};
@@ -131,8 +130,8 @@ public class Blocks implements ContentList{
//Registers build blocks
//no reference is needed here since they can be looked up by name later
for(int i = 1; i <= BuildBlock.maxSize; i++){
new BuildBlock(i);
for(int i = 1; i <= ConstructBlock.maxSize; i++){
new ConstructBlock(i);
}
deepwater = new Floor("deepwater"){{
@@ -211,9 +210,7 @@ public class Blocks implements ContentList{
cacheLayer = CacheLayer.slag;
}};
stone = new Floor("stone"){{
}};
stone = new Floor("stone");
craters = new Floor("craters"){{
variants = 3;
@@ -224,14 +221,14 @@ public class Blocks implements ContentList{
blendGroup = stone;
}};
ignarock = new Floor("ignarock"){{
attributes.set(Attribute.water, -0.1f);
basalt = new Floor("basalt"){{
attributes.set(Attribute.water, -0.25f);
}};
hotrock = new Floor("hotrock"){{
attributes.set(Attribute.heat, 0.5f);
attributes.set(Attribute.water, -0.2f);
blendGroup = ignarock;
attributes.set(Attribute.water, -0.5f);
blendGroup = basalt;
emitLight = true;
lightRadius = 30f;
@@ -240,9 +237,9 @@ public class Blocks implements ContentList{
magmarock = new Floor("magmarock"){{
attributes.set(Attribute.heat, 0.75f);
attributes.set(Attribute.water, -0.5f);
attributes.set(Attribute.water, -0.75f);
updateEffect = Fx.magmasmoke;
blendGroup = ignarock;
blendGroup = basalt;
emitLight = true;
lightRadius = 60f;
@@ -261,13 +258,13 @@ public class Blocks implements ContentList{
attributes.set(Attribute.oil, 1.5f);
}};
dirt = new Floor("dirt");
((ShallowLiquid)darksandTaintedWater).set(Blocks.taintedWater, Blocks.darksand);
((ShallowLiquid)sandWater).set(Blocks.water, Blocks.sand);
((ShallowLiquid)darksandWater).set(Blocks.water, Blocks.darksand);
holostone = new Floor("holostone"){{
}};
dacite = new Floor("dacite");
grass = new Floor("grass"){{
attributes.set(Attribute.water, 0.1f);
@@ -300,41 +297,49 @@ public class Blocks implements ContentList{
fillsTile = false;
}};
rocks = new StaticWall("rocks"){{
stoneWall = new StaticWall("stone-wall"){{
variants = 2;
}};
sporerocks = new StaticWall("sporerocks"){{
sporeWall = new StaticWall("spore-wall"){{
variants = 2;
}};
rock = new Rock("rock"){{
boulder = new Boulder("boulder"){{
variants = 2;
}};
snowrock = new Rock("snowrock"){{
snowBoulder = new Boulder("snow-boulder"){{
variants = 2;
}};
icerocks = new StaticWall("icerocks"){{
dirtWall = new StaticWall("dirt-wall"){{
variants = 2;
}};
daciteWall = new StaticWall("dacite-wall"){{
variants = 2;
}};
iceWall = new StaticWall("ice-wall"){{
variants = 2;
iceSnow.asFloor().wall = this;
}};
snowrocks = new StaticWall("snowrocks"){{
snowWall = new StaticWall("snow-wall"){{
variants = 2;
}};
duneRocks = new StaticWall("dunerocks"){{
duneWall = new StaticWall("dune-wall"){{
variants = 2;
basalt.asFloor().wall = this;
}};
sandWall = new StaticWall("sand-wall"){{
variants = 2;
}};
sandRocks = new StaticWall("sandrocks"){{
variants = 2;
}};
saltRocks = new StaticWall("saltrocks"){{
}};
saltWall = new StaticWall("salt-wall");
sporePine = new StaticTree("spore-pine"){{
variants = 0;
@@ -348,17 +353,13 @@ public class Blocks implements ContentList{
variants = 0;
}};
shrubs = new StaticWall("shrubs"){{
shrubs = new StaticWall("shrubs");
}};
whiteTreeDead = new TreeBlock("white-tree-dead");
whiteTreeDead = new TreeBlock("white-tree-dead"){{
}};
whiteTree = new TreeBlock("white-tree");
whiteTree = new TreeBlock("white-tree"){{
}};
sporeCluster = new Rock("spore-cluster"){{
sporeCluster = new Boulder("spore-cluster"){{
variants = 3;
}};
@@ -367,15 +368,19 @@ public class Blocks implements ContentList{
attributes.set(Attribute.oil, 1f);
}};
shaleRocks = new StaticWall("shalerocks"){{
shaleWall = new StaticWall("shale-wall"){{
variants = 2;
}};
shaleBoulder = new Rock("shale-boulder"){{
shaleBoulder = new Boulder("shale-boulder"){{
variants = 2;
}};
sandBoulder = new Rock("sand-boulder"){{
sandBoulder = new Boulder("sand-boulder"){{
variants = 2;
}};
daciteBoulder = new Boulder("dacite-boulder"){{
variants = 2;
}};
@@ -388,7 +393,7 @@ public class Blocks implements ContentList{
sporeMoss = new Floor("spore-moss"){{
variants = 3;
attributes.set(Attribute.spores, 0.3f);
wall = sporerocks;
wall = sporeWall;
}};
metalFloor = new Floor("metal-floor"){{
@@ -507,7 +512,7 @@ public class Blocks implements ContentList{
siliconCrucible = new AttributeSmelter("silicon-crucible"){{
requirements(Category.crafting, with(Items.titanium, 120, Items.metaglass, 80, Items.plastanium, 35, Items.silicon, 60));
craftEffect = Fx.smeltsmoke;
outputItem = new ItemStack(Items.silicon, 6);
outputItem = new ItemStack(Items.silicon, 8);
craftTime = 90f;
size = 3;
hasPower = true;
@@ -516,7 +521,7 @@ public class Blocks implements ContentList{
itemCapacity = 30;
boostScale = 0.15f;
consumes.items(new ItemStack(Items.coal, 3), new ItemStack(Items.sand, 6), new ItemStack(Items.pyratite, 1));
consumes.items(new ItemStack(Items.coal, 4), new ItemStack(Items.sand, 6), new ItemStack(Items.pyratite, 1));
consumes.power(4f);
}};
@@ -767,14 +772,14 @@ public class Blocks implements ContentList{
phaseWall = new Wall("phase-wall"){{
requirements(Category.defense, with(Items.phasefabric, 6));
health = 150 * wallHealthMultiplier;
flashWhite = deflect = true;
flashHit = deflect = true;
}};
phaseWallLarge = new Wall("phase-wall-large"){{
requirements(Category.defense, ItemStack.mult(phaseWall.requirements, 4));
health = 150 * 4 * wallHealthMultiplier;
size = 2;
flashWhite = deflect = true;
flashHit = deflect = true;
}};
surgeWall = new Wall("surge-wall"){{
@@ -1086,14 +1091,14 @@ public class Blocks implements ContentList{
powerNode = new PowerNode("power-node"){{
requirements(Category.power, with(Items.copper, 1, Items.lead, 3));
maxNodes = 20;
maxNodes = 10;
laserRange = 6;
}};
powerNodeLarge = new PowerNode("power-node-large"){{
requirements(Category.power, with(Items.titanium, 5, Items.lead, 10, Items.silicon, 3));
size = 2;
maxNodes = 30;
maxNodes = 15;
laserRange = 9.5f;
}};
@@ -1132,9 +1137,9 @@ public class Blocks implements ContentList{
size = 2;
}};
turbineGenerator = new BurnerGenerator("turbine-generator"){{
steamGenerator = new BurnerGenerator("steam-generator"){{
requirements(Category.power, with(Items.copper, 35, Items.graphite, 25, Items.lead, 40, Items.silicon, 30));
powerProduction = 6f;
powerProduction = 5.5f;
itemDuration = 90f;
consumes.liquid(Liquids.water, 0.05f);
hasLiquids = true;
@@ -1143,21 +1148,21 @@ public class Blocks implements ContentList{
differentialGenerator = new SingleTypeGenerator("differential-generator"){{
requirements(Category.power, with(Items.copper, 70, Items.titanium, 50, Items.lead, 100, Items.silicon, 65, Items.metaglass, 50));
powerProduction = 17f;
itemDuration = 200f;
powerProduction = 18f;
itemDuration = 220f;
hasLiquids = true;
hasItems = true;
size = 3;
consumes.item(Items.pyratite).optional(true, false);
consumes.liquid(Liquids.cryofluid, 0.14f);
consumes.liquid(Liquids.cryofluid, 0.1f);
}};
rtgGenerator = new DecayGenerator("rtg-generator"){{
requirements(Category.power, with(Items.lead, 100, Items.silicon, 75, Items.phasefabric, 25, Items.plastanium, 75, Items.thorium, 50));
size = 2;
powerProduction = 4f;
itemDuration = 500f;
powerProduction = 4.5f;
itemDuration = 60 * 15f;
}};
solarPanel = new SolarGenerator("solar-panel"){{
@@ -1176,7 +1181,7 @@ public class Blocks implements ContentList{
size = 3;
health = 700;
itemDuration = 360f;
powerProduction = 14f;
powerProduction = 15f;
consumes.item(Items.thorium);
heating = 0.02f;
consumes.liquid(Liquids.cryofluid, heating / coolantPower).update(false);
@@ -1504,14 +1509,14 @@ public class Blocks implements ContentList{
hasPower = true;
size = 2;
force = 3f;
force = 4.5f;
scaledForce = 5.5f;
range = 170f;
damage = 0.08f;
range = 110f;
damage = 0.1f;
health = 160 * size * size;
rotateSpeed = 10;
consumes.powerCond(3f, (TractorBeamEntity e) -> e.target != null);
consumes.powerCond(3f, (TractorBeamBuild e) -> e.target != null);
}};
swarmer = new ItemTurret("swarmer"){{
@@ -1558,14 +1563,15 @@ public class Blocks implements ContentList{
}};
segment = new PointDefenseTurret("segment"){{
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 25));
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 40));
range = 140f;
hasPower = true;
consumes.power(3f);
size = 2;
shootLength = 5f;
bulletDamage = 12f;
reloadTime = 20f;
bulletDamage = 25f;
reloadTime = 10f;
health = 190 * size * size;
}};
@@ -1609,6 +1615,7 @@ public class Blocks implements ContentList{
reloadTime = 60f;
ammoEjectBack = 5f;
ammoUseEffect = Fx.shellEjectBig;
ammoPerShot = 2;
cooldown = 0.03f;
velocityInaccuracy = 0.2f;
restitution = 0.02f;
@@ -1685,7 +1692,7 @@ public class Blocks implements ContentList{
activeSoundVolume = 2f;
shootType = new ContinuousLaserBulletType(70){{
length = 220f;
length = 200f;
hitEffect = Fx.hitMeltdown;
drawSize = 420f;
@@ -1731,7 +1738,7 @@ public class Blocks implements ContentList{
navalFactory = new UnitFactory("naval-factory"){{
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.risso, 60f * 30f, with(Items.silicon, 20, Items.metaglass, 25)),
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35)),
};
size = 3;
requiresWater = true;
@@ -1758,11 +1765,11 @@ public class Blocks implements ContentList{
}};
multiplicativeReconstructor = new Reconstructor("multiplicative-reconstructor"){{
requirements(Category.units, with(Items.lead, 650, Items.silicon, 350, Items.titanium, 350, Items.thorium, 650));
requirements(Category.units, with(Items.lead, 650, Items.silicon, 450, Items.titanium, 350, Items.thorium, 650));
size = 5;
consumes.power(6f);
consumes.items(with(Items.silicon, 130, Items.titanium, 80, Items.metaglass, 30));
consumes.items(with(Items.silicon, 130, Items.titanium, 80, Items.metaglass, 40));
constructTime = 60f * 30f;
@@ -1777,11 +1784,11 @@ public class Blocks implements ContentList{
}};
exponentialReconstructor = new Reconstructor("exponential-reconstructor"){{
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 750, Items.titanium, 950, Items.thorium, 450, Items.plastanium, 350, Items.phasefabric, 450));
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 1000, Items.titanium, 2000, Items.thorium, 750, Items.plastanium, 450, Items.phasefabric, 600));
size = 7;
consumes.power(13f);
consumes.items(with(Items.silicon, 450, Items.titanium, 550, Items.plastanium, 550));
consumes.items(with(Items.silicon, 850, Items.titanium, 750, Items.plastanium, 650));
consumes.liquid(Liquids.cryofluid, 1f);
constructTime = 60f * 60f * 1.5f;
@@ -1790,15 +1797,16 @@ public class Blocks implements ContentList{
upgrades = new UnitType[][]{
{UnitTypes.zenith, UnitTypes.antumbra},
{UnitTypes.spiroct, UnitTypes.arkyid},
{UnitTypes.fortress, UnitTypes.scepter},
};
}};
tetrativeReconstructor = new Reconstructor("tetrative-reconstructor"){{
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 1500, Items.thorium, 500, Items.plastanium, 450, Items.phasefabric, 600, Items.surgealloy, 500));
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 3000, Items.thorium, 1000, Items.plastanium, 600, Items.phasefabric, 600, Items.surgealloy, 800));
size = 9;
consumes.power(25f);
consumes.items(with(Items.silicon, 350, Items.plastanium, 550, Items.surgealloy, 350, Items.phasefabric, 150));
consumes.items(with(Items.silicon, 1000, Items.plastanium, 600, Items.surgealloy, 500, Items.phasefabric, 350));
consumes.liquid(Liquids.cryofluid, 3f);
constructTime = 60f * 60f * 4;
@@ -1806,6 +1814,8 @@ public class Blocks implements ContentList{
upgrades = new UnitType[][]{
{UnitTypes.antumbra, UnitTypes.eclipse},
{UnitTypes.arkyid, UnitTypes.toxopid},
{UnitTypes.scepter, UnitTypes.reign},
};
}};
@@ -1916,9 +1926,9 @@ public class Blocks implements ContentList{
logicProcessor = new LogicBlock("logic-processor"){{
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 100, Items.graphite, 60, Items.thorium, 50));
instructionsPerTick = 5;
instructionsPerTick = 8;
range = 8 * 20;
range = 8 * 22;
size = 2;
}};
@@ -1931,7 +1941,7 @@ public class Blocks implements ContentList{
instructionsPerTick = 25;
range = 8 * 40;
range = 8 * 42;
size = 3;
}};

View File

@@ -199,7 +199,7 @@ public class Bullets implements ContentList{
hitEffect = Fx.flakExplosion;
splashDamage = 18f;
splashDamageRadius = 16f;
fragBullet = flakGlassFrag;
fragBullet = fragGlassFrag;
fragBullets = 3;
explodeRange = 20f;
collidesGround = true;
@@ -275,7 +275,7 @@ public class Bullets implements ContentList{
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 28f;
splashDamage = 40f;
splashDamage = 35f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
lightning = 2;

View File

@@ -52,10 +52,11 @@ public class Fx{
if(!(e.data instanceof Unit)) return;
Unit select = e.data();
boolean block = select instanceof BlockUnitc;
mixcol(Pal.accent, 1f);
alpha(e.fout());
rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type().icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
alpha(1f);
Lines.stroke(e.fslope() * 1f);
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
@@ -900,7 +901,7 @@ public class Fx{
}),
shootSmallFlame = new Effect(32f, e -> {
shootSmallFlame = new Effect(32f, 80f, e -> {
color(Pal.lightFlame, Pal.darkFlame, Color.gray, e.fin());
randLenVectors(e.id, 8, e.finpow() * 60f, e.rotation, 10f, (x, y) -> {
@@ -909,7 +910,7 @@ public class Fx{
}),
shootPyraFlame = new Effect(33f, e -> {
shootPyraFlame = new Effect(33f, 80f, e -> {
color(Pal.lightPyraFlame, Pal.darkPyraFlame, Color.gray, e.fin());
randLenVectors(e.id, 10, e.finpow() * 70f, e.rotation, 10f, (x, y) -> {
@@ -918,7 +919,7 @@ public class Fx{
}),
shootLiquid = new Effect(40f, e -> {
shootLiquid = new Effect(40f, 80f, e -> {
color(e.color, Color.white, e.fout() / 6f + Mathf.randomSeedRange(e.id, 0.1f));
randLenVectors(e.id, 6, e.finpow() * 60f, e.rotation, 11f, (x, y) -> {

View File

@@ -84,7 +84,7 @@ public class Items implements ContentList{
}};
pyratite = new Item("pyratite", Color.valueOf("ffaa5f")){{
flammability = 1.5f;
flammability = 1.4f;
explosiveness = 0.4f;
}};
}

View File

@@ -121,13 +121,14 @@ public class StatusEffects implements ContentList{
boss = new StatusEffect("boss"){{
permanent = true;
damageMultiplier = 1.5f;
armorMultiplier = 1.5f;
}};
shocked = new StatusEffect("shocked");
blasted = new StatusEffect("blasted");
//no effects, just small amounts of damage.
corroded = new StatusEffect("corroded"){{
damage = 0.1f;
}};

View File

@@ -261,7 +261,7 @@ public class TechTree implements ContentList{
});
});
node(turbineGenerator, () -> {
node(steamGenerator, () -> {
node(thermalGenerator, () -> {
node(differentialGenerator, () -> {
node(thoriumReactor, () -> {
@@ -366,7 +366,11 @@ public class TechTree implements ContentList{
node(dagger, () -> {
node(mace, () -> {
node(fortress, () -> {
node(scepter, () -> {
node(reign, () -> {
});
});
});
});
@@ -381,7 +385,11 @@ public class TechTree implements ContentList{
node(crawler, () -> {
node(atrax, () -> {
node(spiroct, () -> {
node(arkyid, () -> {
node(toxopid, () -> {
});
});
});
});
});
@@ -490,7 +498,7 @@ public class TechTree implements ContentList{
new SectorComplete(frozenForest),
new Research(pneumaticDrill),
new Research(powerNode),
new Research(turbineGenerator)
new Research(steamGenerator)
), () -> {
node(fungalPass, Seq.with(
new SectorComplete(stainedMountains),

View File

@@ -15,13 +15,10 @@ public class UnitTypes implements ContentList{
//region definitions
//ground
public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, vestige, cataclyst;
//ground + builder
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class}) UnitType nova;
public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, scepter, reign;
//ground + builder + miner + commander
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class, Minerc.class, Commanderc.class}) UnitType pulsar, quasar;
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class, Minerc.class, Commanderc.class}) UnitType nova, pulsar, quasar;
//legs
public static @EntityDef({Unitc.class, Legsc.class}) UnitType atrax;
@@ -130,6 +127,116 @@ public class UnitTypes implements ContentList{
}});
}};
scepter = new UnitType("scepter"){{
speed = 0.35f;
hitsize = 20f;
rotateSpeed = 2.1f;
targetAir = false;
health = 9000;
armor = 11f;
mechLegMoveScl = 1.3f;
canDrown = false;
weapons.add(
new Weapon("scepter-weapon"){{
y = 1f;
x = 16f;
shootY = 8f;
reload = 50f;
recoil = 5f;
shake = 2f;
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.artillery;
shots = 3;
inaccuracy = 3f;
shotDelay = 4f;
bullet = new BasicBulletType(7f, 45){{
width = 11f;
height = 20f;
lifetime = 25f;
shootEffect = Fx.shootBig;
lightning = 2;
lightningLength = 6;
lightningColor = Pal.surge;
//standard bullet damage is far too much for lightning
lightningDamage = 25;
}};
}},
new Weapon("mount-weapon"){{
reload = 13f;
x = 8.5f;
y = 6f;
rotate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}},
new Weapon("mount-weapon"){{
reload = 16f;
x = 8.5f;
y = -7f;
rotate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}}
);
}};
reign = new UnitType("reign"){{
speed = 0.35f;
hitsize = 26f;
rotateSpeed = 1.65f;
targetAir = false;
health = 24000;
armor = 14f;
mechLegMoveScl = 1.75f;
canDrown = false;
weapons.add(
new Weapon("reign-weapon"){{
y = 1f;
x = 21.5f;
shootY = 11f;
reload = 9f;
recoil = 5f;
shake = 4f;
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.artillery;
bullet = new BasicBulletType(13f, 55){{
pierce = true;
width = 14f;
height = 33f;
lifetime = 15f;
shootEffect = Fx.shootBig;
fragVelocityMin = 0.4f;
hitEffect = Fx.blastExplosion;
splashDamage = 18f;
splashDamageRadius = 30f;
fragBullets = 2;
fragLifeMin = 0f;
fragCone = 30f;
fragBullet = new BasicBulletType(9f, 15){{
width = 10f;
height = 10f;
pierce = true;
lifetime = 20f;
hitEffect = Fx.flakExplosion;
splashDamage = 15f;
splashDamageRadius = 15f;
}};
}};
}}
);
}};
//endregion
//region ground support
@@ -142,6 +249,7 @@ public class UnitTypes implements ContentList{
health = 110f;
buildSpeed = 0.8f;
armor = 1f;
commandLimit = 8;
abilities.add(new HealFieldAbility(10f, 60f * 4, 60f));
@@ -169,7 +277,7 @@ public class UnitTypes implements ContentList{
mineTier = 2;
mineSpeed = 5f;
commandLimit = 8;
commandLimit = 15;
abilities.add(new ShieldFieldAbility(20f, 40f, 60f * 5, 60f));
@@ -210,6 +318,8 @@ public class UnitTypes implements ContentList{
armor = 9f;
landShake = 2f;
commandLimit = 24;
speed = 0.4f;
hitsize = 10f;
@@ -217,7 +327,7 @@ public class UnitTypes implements ContentList{
mineSpeed = 7f;
drawShields = false;
abilities.add(new ForceFieldAbility(60f, 0.2f, 300f, 60f * 7));
abilities.add(new ForceFieldAbility(60f, 0.3f, 400f, 60f * 6));
weapons.add(new Weapon("beam-weapon"){{
shake = 2f;
@@ -228,7 +338,7 @@ public class UnitTypes implements ContentList{
shootSound = Sounds.laser;
bullet = new LaserBulletType(){{
damage = 30f;
damage = 40f;
recoil = 1f;
sideAngle = 45f;
sideWidth = 1f;
@@ -309,7 +419,6 @@ public class UnitTypes implements ContentList{
}};
spiroct = new UnitType("spiroct"){{
itemCapacity = 200;
speed = 0.4f;
drag = 0.4f;
hitsize = 12f;
@@ -381,6 +490,8 @@ public class UnitTypes implements ContentList{
health = 8000;
armor = 6f;
rotateSpeed = 2.7f;
legCount = 6;
legMoveSpace = 1f;
legPairOffset = 3;
@@ -389,7 +500,7 @@ public class UnitTypes implements ContentList{
legBaseOffset = 10f;
landShake = 1f;
legSpeed = 0.1f;
legLengthScl = 1f;
legLengthScl = 0.96f;
rippleScale = 2f;
legSpeed = 0.2f;
@@ -398,11 +509,11 @@ public class UnitTypes implements ContentList{
hovering = true;
allowLegStep = true;
visualElevation = 0.4f;
visualElevation = 0.65f;
groundLayer = Layer.legUnit;
BulletType sapper = new SapBulletType(){{
sapStrength = 0.8f;
sapStrength = 0.83f;
length = 55f;
damage = 34;
shootEffect = Fx.shootSmall;
@@ -453,10 +564,10 @@ public class UnitTypes implements ContentList{
knockback = 0.8f;
lifetime = 70f;
width = height = 19f;
collidesTiles = false;
collidesTiles = true;
ammoMultiplier = 4f;
splashDamageRadius = 95f;
splashDamage = 55f;
splashDamage = 65f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 3;
@@ -470,6 +581,124 @@ public class UnitTypes implements ContentList{
}});
}};
toxopid = new UnitType("toxopid"){{
drag = 0.1f;
speed = 0.5f;
hitsize = 21f;
health = 23000;
armor = 14f;
rotateSpeed = 1.9f;
legCount = 8;
legMoveSpace = 0.8f;
legPairOffset = 3;
legLength = 75f;
legExtension = -20;
legBaseOffset = 8f;
landShake = 1f;
legSpeed = 0.1f;
legLengthScl = 0.93f;
rippleScale = 3f;
legSpeed = 0.19f;
legSplashDamage = 80;
legSplashRange = 60;
hovering = true;
allowLegStep = true;
visualElevation = 0.95f;
groundLayer = Layer.legUnit;
weapons.add(
new Weapon("large-purple-mount"){{
y = -5f;
x = 11f;
shootY = 7f;
reload = 30;
shake = 4f;
rotateSpeed = 2f;
ejectEffect = Fx.shellEjectSmall;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 12f;
recoil = 3f;
shots = 2;
spacing = 17f;
bullet = new ShrapnelBulletType(){{
length = 90f;
damage = 110f;
width = 25f;
serrationLenScl = 7f;
serrationSpaceOffset = 60f;
serrationFadeOffset = 0f;
serrations = 10;
serrationWidth = 6f;
fromColor = Pal.sapBullet;
toColor = Pal.sapBulletBack;
shootEffect = smokeEffect = Fx.sparkShoot;
}};
}});
weapons.add(new Weapon("toxopid-cannon"){{
y = -14f;
x = 0f;
shootY = 22f;
mirror = false;
reload = 210;
shake = 10f;
recoil = 10f;
rotateSpeed = 1f;
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 30f;
bullet = new ArtilleryBulletType(3f, 50){{
hitEffect = Fx.sapExplosion;
knockback = 0.8f;
lifetime = 80f;
width = height = 25f;
collidesTiles = collides = true;
ammoMultiplier = 4f;
splashDamageRadius = 95f;
splashDamage = 90f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 5;
lightningLength = 20;
smokeEffect = Fx.shootBigSmoke2;
hitShake = 10f;
status = StatusEffects.sapped;
statusDuration = 60f * 10;
fragLifeMin = 0.3f;
fragBullets = 9;
fragBullet = new ArtilleryBulletType(2.3f, 30){{
hitEffect = Fx.sapExplosion;
knockback = 0.8f;
lifetime = 90f;
width = height = 20f;
collidesTiles = false;
splashDamageRadius = 80f;
splashDamage = 45f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 2;
lightningLength = 5;
smokeEffect = Fx.shootBigSmoke2;
hitShake = 5f;
status = StatusEffects.sapped;
statusDuration = 60f * 10;
}};
}};
}});
}};
//endregion
//region air attack
@@ -482,6 +711,8 @@ public class UnitTypes implements ContentList{
faceTarget = false;
engineOffset = 5.5f;
range = 140f;
crashDamageMultiplier = 4f;
weapons.add(new Weapon(){{
y = 0f;
x = 2f;
@@ -651,7 +882,7 @@ public class UnitTypes implements ContentList{
rotateSpeed = 1f;
flying = true;
lowAltitude = true;
health = 18000;
health = 20000;
engineOffset = 38;
engineSize = 7.3f;
hitsize = 58f;
@@ -735,6 +966,7 @@ public class UnitTypes implements ContentList{
engineOffset = 5.7f;
itemCapacity = 30;
range = 50f;
isCounted = false;
mineTier = 1;
mineSpeed = 2.5f;
@@ -755,6 +987,7 @@ public class UnitTypes implements ContentList{
engineOffset = 6.5f;
hitsize = 8f;
lowAltitude = true;
isCounted = false;
mineTier = 2;
mineSpeed = 3.5f;
@@ -801,7 +1034,8 @@ public class UnitTypes implements ContentList{
rotateShooting = false;
hitsize = 15f;
engineSize = 3f;
payloadCapacity = 4;
payloadCapacity = 4 * (8 * 8);
buildSpeed = 2.5f;
weapons.add(
new Weapon("heal-weapon-mount"){{
@@ -832,6 +1066,7 @@ public class UnitTypes implements ContentList{
rotateSpeed = 3.3f;
immunities = ObjectSet.with(StatusEffects.wet);
trailLength = 20;
rotateShooting = false;
armor = 2f;
@@ -887,6 +1122,7 @@ public class UnitTypes implements ContentList{
trailX = 5.5f;
trailY = -4f;
trailScl = 1.9f;
rotateShooting = false;
abilities.add(new StatusFieldAbility(StatusEffects.overclock, 60f * 6, 60f * 6f, 60f));
@@ -923,6 +1159,7 @@ public class UnitTypes implements ContentList{
hitsize = 14f;
armor = 6f;
immunities = ObjectSet.with(StatusEffects.wet);
rotateShooting = false;
trailLength = 22;
trailX = 7f;

View File

@@ -182,7 +182,7 @@ public class Control implements ApplicationListener, Loadable{
app.post(() -> ui.hudfrag.showLand());
renderer.zoomIn(Fx.coreLand.lifetime);
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block()));
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
Time.run(Fx.coreLand.lifetime, () -> {
Fx.launch.at(core);
Effect.shake(5f, 5f, core);
@@ -428,19 +428,12 @@ public class Control implements ApplicationListener, Loadable{
//just a regular reminder
if(!OS.prop("user.name").equals("anuke") && !OS.hasEnv("iknowwhatimdoing")){
app.post(() -> app.post(() -> {
ui.showStartupInfo("[accent]v6[] is currently in [accent]pre-alpha[].\n" +
"[lightgray]This means:[]\n" +
"- Content is missing\n" +
"- [scarlet]Mobile[] is not supported.\n" +
"- Most [scarlet]Unit AI[] does not work\n" +
"- Many units are [scarlet]missing[] or unfinished\n" +
"- The campaign is completely unfinished\n" +
"- Everything you see is subject to change or removal." +
"\n\nReport bugs or crashes on [accent]Github[].");
ui.showStartupInfo("@indevpopup");
}));
}
//play tutorial on stop
//play tutorial on start
//TODO no tutorial right now
if(!settings.getBool("playedtutorial", false)){
//Core.app.post(() -> Core.app.post(this::playTutorial));
}

View File

@@ -15,7 +15,7 @@ import mindustry.type.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
@@ -41,9 +41,9 @@ public class Logic implements ApplicationListener{
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
if(block instanceof BuildBlock){
if(block instanceof ConstructBlock){
BuildEntity entity = tile.bc();
ConstructBuild entity = tile.bc();
//update block to reflect the fact that something was being constructed
if(entity.cblock != null && entity.cblock.synthetic()){
@@ -187,7 +187,7 @@ public class Logic implements ApplicationListener{
//campaign maps do not have a 'win' state!
if(state.isCampaign()){
//gameover only when cores are dead
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
if(state.teams.playerCores().size == 0 && !state.gameOver){
state.gameOver = true;
Events.fire(new GameOverEvent(state.rules.waveTeam));
}

View File

@@ -585,8 +585,11 @@ public class NetClient implements ApplicationListener{
}
Unit unit = player.dead() ? Nulls.unit : player.unit();
int uid = player.dead() ? -1 : unit.id;
Call.clientSnapshot(lastSent++,
Call.clientSnapshot(
lastSent++,
uid,
player.dead(),
unit.x, unit.y,
player.unit().aimX(), player.unit().aimY(),
@@ -597,7 +600,8 @@ public class NetClient implements ApplicationListener{
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);
Core.camera.width * viewScale, Core.camera.height * viewScale
);
}
if(timer.get(1, 60)){

View File

@@ -174,7 +174,7 @@ public class NetServer implements ApplicationListener{
return;
}
boolean preventDuplicates = headless && netServer.admins.getStrict();
boolean preventDuplicates = headless && netServer.admins.isStrict();
if(preventDuplicates){
if(Groups.player.contains(p -> p.name.trim().equalsIgnoreCase(packet.name.trim()))){
@@ -485,7 +485,7 @@ public class NetServer implements ApplicationListener{
data.stream = new ByteArrayInputStream(stream.toByteArray());
player.con.sendStream(data);
Log.debug("Packed @ compressed bytes of world data.", stream.size());
Log.debug("Packed @ bytes of world data.", stream.size());
}
public void addPacketHandler(String type, Cons2<Player, String> handler){
@@ -539,6 +539,7 @@ public class NetServer implements ApplicationListener{
public static void clientSnapshot(
Player player,
int snapshotID,
int unitID,
boolean dead,
float x, float y,
float pointerX, float pointerY,
@@ -562,7 +563,7 @@ public class NetServer implements ApplicationListener{
if(invalid(rotation)) rotation = 0f;
if(invalid(baseRotation)) baseRotation = 0f;
boolean verifyPosition = !player.dead() && netServer.admins.getStrict() && headless;
boolean verifyPosition = netServer.admins.isStrict() && headless;
if(con.lastReceivedClientTime == 0) con.lastReceivedClientTime = Time.millis() - 16;
@@ -580,7 +581,6 @@ public class NetServer implements ApplicationListener{
boosting = false;
}
//TODO these need to be assigned elsewhere
player.mouseX = pointerX;
player.mouseY = pointerY;
player.typing = chatting;
@@ -635,41 +635,37 @@ public class NetServer implements ApplicationListener{
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
unit.vel.set(xVelocity, yVelocity).limit(maxSpeed);
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
if(con.lastUnit != unit){
con.lastUnit = unit;
con.lastPosition.set(unit);
}
//if the player think they're dead their position should be ignored
if(dead){
x = unit.x;
y = unit.y;
}
vector.set(x, y).sub(con.lastPosition);
vector.limit(maxMove);
float prevx = unit.x, prevy = unit.y;
unit.set(con.lastPosition);
if(!unit.isFlying()){
unit.move(vector.x, vector.y);
}else{
unit.trns(vector.x, vector.y);
}
//set last position after movement
con.lastPosition.set(unit);
//ignore the position if the player thinks they're dead, or the unit is wrong
boolean ignorePosition = dead || unit.id != unitID;
float newx = unit.x, newy = unit.y;
if(!verifyPosition){
unit.set(prevx, prevy);
newx = x;
newy = y;
}else if(!Mathf.within(x, y, newx, newy, correctDist) && !dead){
Call.setPosition(player.con, newx, newy); //teleport and correct position when necessary
if(!ignorePosition){
unit.vel.set(xVelocity, yVelocity).limit(maxSpeed);
vector.set(x, y).sub(unit);
vector.limit(maxMove);
float prevx = unit.x, prevy = unit.y;
//unit.set(con.lastPosition);
if(!unit.isFlying()){
unit.move(vector.x, vector.y);
}else{
unit.trns(vector.x, vector.y);
}
newx = unit.x;
newy = unit.y;
if(!verifyPosition){
unit.set(prevx, prevy);
newx = x;
newy = y;
}else if(!Mathf.within(x, y, newx, newy, correctDist)){
Call.setPosition(player.con, newx, newy); //teleport and correct position when necessary
}
}
//write sync data to the buffer
@@ -813,7 +809,7 @@ public class NetServer implements ApplicationListener{
short sent = 0;
for(Building entity : Groups.build){
if(!entity.block().sync) continue;
if(!entity.block.sync) continue;
sent ++;
dataStream.writeInt(entity.pos());
@@ -842,7 +838,7 @@ public class NetServer implements ApplicationListener{
dataStream.writeByte(cores.size);
for(CoreBuild entity : cores){
dataStream.writeInt(entity.tile().pos());
dataStream.writeInt(entity.tile.pos());
entity.items.write(Writes.get(dataStream));
}

View File

@@ -271,7 +271,7 @@ public class Renderer implements ApplicationListener{
float fract = landTime / Fx.coreLand.lifetime;
Building entity = player.closestCore();
TextureRegion reg = entity.block().icon(Cicon.full);
TextureRegion reg = entity.block.icon(Cicon.full);
float scl = Scl.scl(4f) / camerascale;
float s = reg.getWidth() * Draw.scl * scl * 4f * fract;

View File

@@ -105,7 +105,9 @@ public class World{
public Tile tileBuilding(int x, int y){
Tile tile = tiles.get(x, y);
if(tile == null) return null;
if(tile.build != null) return tile.build.tile();
if(tile.build != null){
return tile.build.tile();
}
return tile;
}
@@ -185,16 +187,12 @@ public class World{
continue;
}
tile.updateOcclusion();
if(tile.build != null){
tile.build.updateProximity();
}
}
if(!headless){
addDarkness(tiles);
}
addDarkness(tiles);
Groups.resize(-finalWorldBounds, -finalWorldBounds, tiles.width * tilesize + finalWorldBounds * 2, tiles.height * tilesize + finalWorldBounds * 2);
@@ -317,7 +315,7 @@ public class World{
public void notifyChanged(Tile tile){
if(!generating){
Core.app.post(() -> Events.fire(new BuildinghangeEvent(tile)));
Core.app.post(() -> Events.fire(new TileChangeEvent(tile)));
}
}

View File

@@ -39,13 +39,6 @@ public class EditorTile extends Tile{
super.setFloor(type);
}
@Override
public void updateOcclusion(){
super.updateOcclusion();
ui.editor.editor.renderer().updatePoint(x, y);
}
@Override
public void setBlock(Block type, Team team, int rotation){
if(state.isGame()){
@@ -85,8 +78,12 @@ public class EditorTile extends Tile{
}
@Override
protected void preChanged(){
super.preChanged();
protected void fireChanged(){
if(state.isGame()){
super.fireChanged();
}else{
ui.editor.editor.renderer().updatePoint(x, y);
}
}
@Override

View File

@@ -1,6 +1,7 @@
package mindustry.editor;
import arc.func.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
@@ -10,8 +11,8 @@ import mindustry.game.*;
import mindustry.world.*;
public enum EditorTool{
zoom,
pick{
zoom(KeyCode.v),
pick(KeyCode.i){
public void touched(MapEditor editor, int x, int y){
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
@@ -19,7 +20,7 @@ public enum EditorTool{
editor.drawBlock = tile.block() == Blocks.air ? tile.overlay() == Blocks.air ? tile.floor() : tile.overlay() : tile.block();
}
},
line("replace", "orthogonal"){
line(KeyCode.l, "replace", "orthogonal"){
@Override
public void touchedLine(MapEditor editor, int x1, int y1, int x2, int y2){
@@ -43,7 +44,7 @@ public enum EditorTool{
});
}
},
pencil("replace", "square", "drawteams"){
pencil(KeyCode.b, "replace", "square", "drawteams"){
{
edit = true;
draggable = true;
@@ -67,7 +68,7 @@ public enum EditorTool{
}
},
eraser("eraseores"){
eraser(KeyCode.e, "eraseores"){
{
edit = true;
draggable = true;
@@ -86,7 +87,7 @@ public enum EditorTool{
});
}
},
fill("replaceall", "fillteams"){
fill(KeyCode.g, "replaceall", "fillteams"){
{
edit = true;
}
@@ -205,7 +206,7 @@ public enum EditorTool{
}
}
},
spray("replace"){
spray(KeyCode.r, "replace"){
final double chance = 0.012;
{
@@ -231,8 +232,12 @@ public enum EditorTool{
}
};
public static final EditorTool[] all = values();
/** All the internal alternate placement modes of this tool. */
public final String[] altModes;
/** Key to activate this tool. */
public KeyCode key = KeyCode.unset;
/** The current alternate placement mode. -1 is the standard mode, no changes.*/
public int mode = -1;
/** Whether this tool causes canvas changes when touched.*/
@@ -244,10 +249,20 @@ public enum EditorTool{
this(new String[]{});
}
EditorTool(KeyCode code){
this(new String[]{});
this.key = code;
}
EditorTool(String... altModes){
this.altModes = altModes;
}
EditorTool(KeyCode code, String... altModes){
this.altModes = altModes;
this.key = code;
}
public void touched(MapEditor editor, int x, int y){}
public void touchedLine(MapEditor editor, int x1, int y1, int x2, int y2){}

View File

@@ -126,7 +126,7 @@ public class MapEditor{
x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1);
y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1);
if(!hasOverlap(x, y)){
tile(x, y).setBlock(drawBlock, drawTeam, 0);
tile(x, y).setBlock(drawBlock, drawTeam, rotation);
}
}else{
boolean isFloor = drawBlock.isFloor() && drawBlock != Blocks.air;

View File

@@ -576,22 +576,20 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(Core.input.ctrl()){
//alt mode select
for(int i = 0; i < view.getTool().altModes.length + 1; i++){
if(Core.input.keyTap(KeyCode.valueOf("num" + (i + 1)))){
view.getTool().mode = i - 1;
for(int i = 0; i < view.getTool().altModes.length; i++){
if(i + 1 < KeyCode.numbers.length && Core.input.keyTap(KeyCode.numbers[i + 1])){
view.getTool().mode = i;
}
}
}else{
//tool select
for(int i = 0; i < EditorTool.values().length; i++){
if(Core.input.keyTap(KeyCode.valueOf("num" + (i + 1)))){
view.setTool(EditorTool.values()[i]);
for(EditorTool tool : EditorTool.all){
if(Core.input.keyTap(tool.key)){
view.setTool(tool);
break;
}
}
}
if(Core.input.keyTap(KeyCode.escape)){
if(!menu.isShown()){
menu.show();
@@ -619,7 +617,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
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 Rock){
if(tile.block().breakable && tile.block() instanceof Boulder){
tile.setBlock(Blocks.air);
editor.renderer().updatePoint(x, y);
}

View File

@@ -348,6 +348,7 @@ public class MapGenerateDialog extends BaseDialog{
result = executor.submit(() -> {
try{
world.setGenerating(true);
generating = true;
if(!filters.isEmpty()){
@@ -400,7 +401,7 @@ public class MapGenerateDialog extends BaseDialog{
generating = false;
e.printStackTrace();
}
return null;
world.setGenerating(false);
});
}

View File

@@ -110,18 +110,13 @@ public class MapRenderer implements Disposable{
if(wall != Blocks.air && wall.synthetic()){
region = !Core.atlas.isFound(wall.editorIcon()) || !center ? Core.atlas.find("clear-editor") : wall.editorIcon();
if(wall.rotate){
mesh.draw(idxWall, region,
wx * tilesize + wall.offset, wy * tilesize + wall.offset,
region.getWidth() * Draw.scl, region.getHeight() * Draw.scl, tile.build == null ? 0 : tile.build.rotdeg() - 90);
}else{
float width = region.getWidth() * Draw.scl, height = region.getHeight() * Draw.scl;
float width = region.getWidth() * Draw.scl, height = region.getHeight() * Draw.scl;
mesh.draw(idxWall, region,
wx * tilesize + wall.offset + (tilesize - width) / 2f,
wy * tilesize + wall.offset + (tilesize - height) / 2f,
width, height);
}
mesh.draw(idxWall, region,
wx * tilesize + wall.offset + (tilesize - width) / 2f,
wy * tilesize + wall.offset + (tilesize - height) / 2f,
width, height,
tile.build == null || !wall.rotate ? 0 : tile.build.rotdeg() - 90);
}else{
region = floor.editorVariantRegions()[Mathf.randomSeed(idxWall, 0, floor.editorVariantRegions().length - 1)];

View File

@@ -20,6 +20,7 @@ import static mindustry.Vars.*;
/** Utility class for damaging in an area. */
public class Damage{
private static Tile furthest;
private static Rect rect = new Rect();
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2();
@@ -76,6 +77,23 @@ public class Damage{
}
}
/** Collides a bullet with blocks in a laser, taking into account absorption blocks. Resulting length is stored in the bullet's fdata. */
public static float collideLaser(Bullet b, float length){
Tmp.v1.trns(b.rotation(), length);
furthest = null;
world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
float resultLength = furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
Damage.collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), resultLength);
b.fdata = furthest != null ? resultLength : length;
return resultLength;
}
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
collideLine(hitter, team, effect, x, y, angle, length, false);
}

View File

@@ -40,7 +40,7 @@ public class Effect{
}
public Effect(float life, Cons<EffectContainer> renderer){
this(life, 28f, renderer);
this(life, 32f, renderer);
}
public Effect ground(){
@@ -89,6 +89,7 @@ public class Effect{
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
container.set(id, color, life, lifetime, rotation, x, y, data);
Draw.z(ground ? Layer.debris : Layer.effect);
Draw.reset();
renderer.get(container);
Draw.reset();
}

View File

@@ -99,7 +99,7 @@ public class Units{
nearby(x, y, width, height, unit -> {
if(boolResult) return;
if(unit.isGrounded() == ground){
if((unit.isGrounded() && !unit.type().hovering) == ground){
unit.hitbox(hitrect);
if(hitrect.overlaps(x, y, width, height)){
@@ -251,9 +251,20 @@ public class Units{
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
for(Team enemy : state.teams.enemiesOf(team)){
nearby(enemy, x, y, width, height, cons);
if(team.active()){
for(Team enemy : state.teams.enemiesOf(team)){
nearby(enemy, x, y, width, height, cons);
}
}else{
//inactive teams have no cache, check everything
//TODO cache all teams with units OR blocks
for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){
nearby(other, x, y, width, height, cons);
}
}
}
}
/** Iterates over all units that are enemies of this team. */

View File

@@ -74,7 +74,7 @@ public abstract class BulletType extends Content{
public float fragCone = 360f;
public int fragBullets = 9;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f, fragLifeMin = 1f, fragLifeMax = 1f;
public BulletType fragBullet = null;
public Color hitColor = Color.white;
@@ -149,7 +149,7 @@ public abstract class BulletType extends Content{
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(1f, 7f);
float a = b.rotation() + Mathf.range(fragCone/2);
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
}
}
@@ -247,6 +247,10 @@ public abstract class BulletType extends Content{
return create(parent.owner(), parent.team, x, y, angle);
}
public 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);
}
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl){
return create(parent.owner(), parent.team, x, y, angle, velocityScl);
}

View File

@@ -46,8 +46,8 @@ public class HealBulletType extends BulletType{
public void hitTile(Bullet b, Building tile){
super.hit(b);
if(tile.team == b.team && !(tile.block() instanceof BuildBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block().size, Pal.heal);
if(tile.team == b.team && !(tile.block instanceof ConstructBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block.size, Pal.heal);
tile.heal(healPercent / 100f * tile.maxHealth());
}
}

View File

@@ -10,8 +10,6 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.world;
public class LaserBulletType extends BulletType{
protected static Tile furthest;
@@ -49,24 +47,13 @@ public class LaserBulletType extends BulletType{
@Override
public void init(Bullet b){
Tmp.v1.trns(b.rotation(), length);
furthest = null;
world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
float resultLength = furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), resultLength);
if(furthest != null) b.data(resultLength);
float resultLength = Damage.collideLaser(b, length);
laserEffect.at(b.x, b.y, b.rotation(), resultLength * 0.75f);
}
@Override
public void draw(Bullet b){
float realLength = b.data() == null ? length : (Float)b.data();
float realLength = b.fdata;
float f = Mathf.curve(b.fin(), 0f, 0.2f);
float baseLen = realLength * f;

View File

@@ -24,6 +24,7 @@ public class LiquidBulletType extends BulletType{
this.status = liquid.effect;
}
ammoMultiplier = 1f;
lifetime = 74f;
statusDuration = 60f * 2f;
despawnEffect = Fx.none;

View File

@@ -15,7 +15,7 @@ public class ShrapnelBulletType extends BulletType{
public Color fromColor = Color.white, toColor = Pal.lancerLaser;
public int serrations = 7;
public float serrationLenScl = 10f, serrationWidth = 4f, serrationSpacing = 8f, serrationSpaceOffset = 80f;
public float serrationLenScl = 10f, serrationWidth = 4f, serrationSpacing = 8f, serrationSpaceOffset = 80f, serrationFadeOffset = 0.5f;
public ShrapnelBulletType(){
speed = 0.01f;
@@ -24,23 +24,31 @@ public class ShrapnelBulletType extends BulletType{
lifetime = 10f;
despawnEffect = Fx.none;
pierce = true;
keepVelocity = false;
}
@Override
public void init(Bullet b){
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length);
Damage.collideLaser(b, length);
}
@Override
public float range(){
return length;
}
@Override
public void draw(Bullet b){
float realLength = b.fdata;
Draw.color(fromColor, toColor, b.fin());
for(int i = 0; i < serrations; i++){
for(int i = 0; i < (int)(serrations * realLength / length); i++){
Tmp.v1.trns(b.rotation(), i * serrationSpacing);
float sl = Mathf.clamp(b.fout() - 0.5f) * (serrationSpaceOffset - i * serrationLenScl);
float sl = Mathf.clamp(b.fout() - serrationFadeOffset) * (serrationSpaceOffset - i * serrationLenScl);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() + 90);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() - 90);
}
Drawf.tri(b.x, b.y, width * b.fout(), (length + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), (realLength + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), 10f, b.rotation() + 180f);
Draw.reset();
}

View File

@@ -18,9 +18,9 @@ abstract class BlockUnitComp implements Unitc{
this.tile = tile;
//sets up block stats
maxHealth(tile.block().health);
maxHealth(tile.block.health);
health(tile.health());
hitSize(tile.block().size * tilesize * 0.7f);
hitSize(tile.block.size * tilesize * 0.7f);
set(tile);
}

View File

@@ -16,7 +16,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import java.util.*;
@@ -74,7 +74,7 @@ abstract class BuilderComp implements Unitc{
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
}
if(!(tile.block() instanceof BuildBlock)){
if(!(tile.block() instanceof ConstructBlock)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team(), current.x, current.y, current.rotation)){
boolean hasAll = infinite || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
@@ -94,27 +94,23 @@ abstract class BuilderComp implements Unitc{
return;
}
if(tile.build instanceof BuildEntity && !current.initialized){
if(tile.build instanceof ConstructBuild && !current.initialized){
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team(), (Builderc)this, current.breaking)));
current.initialized = true;
}
//if there is no core to build with or no build entity, stop building!
if((core == null && !infinite) || !(tile.build instanceof BuildEntity)){
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild)){
return;
}
//otherwise, update it.
BuildEntity entity = tile.bc();
ConstructBuild entity = tile.bc();
if(current.breaking){
entity.deconstruct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.tileConfig(null, tile.build, current.config);
}
}
entity.construct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.config);
}
current.stuck = Mathf.equal(current.progress, entity.progress);
@@ -183,8 +179,8 @@ abstract class BuilderComp implements Unitc{
plans.remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.build instanceof BuildEntity){
place.progress = tile.<BuildEntity>bc().progress;
if(tile != null && tile.build instanceof ConstructBuild){
place.progress = tile.<ConstructBuild>bc().progress;
}
if(tail){
plans.addLast(place);

View File

@@ -76,6 +76,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public Building init(Tile tile, Team team, boolean shouldAdd, int rotation){
if(!initialized){
create(tile.block(), team);
}else{
if(block.hasPower){
//reinit power graph
power.graph = new PowerGraph();
power.graph.add(base());
}
}
this.rotation = rotation;
this.tile = tile;
@@ -697,6 +703,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
other.build.power.links.removeValue(pos());
}
}
power.links.clear();
}
public Seq<Building> getPowerConnections(Seq<Building> out){
@@ -812,10 +819,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Called after the block is placed by this client. */
@CallSuper
public void playerPlaced(){
if(block.saveConfig && block.lastConfig != null){
configure(block.lastConfig);
}
public void playerPlaced(Object config){
}
/** Called after the block is placed by anyone. */
@@ -831,7 +836,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, base()) && !PowerNode.insulated(other, base())
&& !other.proximity().contains(this.<Building>base()) &&
!(block.outputsPower && proximity.contains(p -> p.power != null && p.power.graph == other.power.graph))){
tempTiles.add(other.tile());
tempTiles.add(other.tile);
}
});
tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile)));
@@ -856,7 +861,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** Called when arbitrary configuration is applied to a tile. */
public void configured(@Nullable Player player, @Nullable Object value){
public void configured(@Nullable Unit builder, @Nullable Object value){
//null is of type void.class; anonymous classes use their superclass.
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass();
@@ -967,7 +972,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
table.row();
table.table(this::displayConsumption).growX();
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow");
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow") && block.displayFlow;
if(displayFlow){
String ps = " " + StatUnit.perSecond.localized();
@@ -1005,9 +1010,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(liquids != null){
table.row();
table.table(l -> {
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
boolean[] had = {false};
Runnable rebuild = () -> {
l.clearChildren();
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
};
l.update(() -> {
if(!had[0] && liquids.hadFlow()){
had[0] = true;
rebuild.run();
}
});
}).left();
}
}
@@ -1200,6 +1217,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(sensor == LAccess.y) return y;
if(sensor == LAccess.team) return team.id;
if(sensor == LAccess.health) return health;
if(sensor == LAccess.maxHealth) return maxHealth();
if(sensor == LAccess.efficiency) return efficiency();
if(sensor == LAccess.rotation) return rotation;
if(sensor == LAccess.totalItems && items != null) return items.total();
@@ -1208,13 +1226,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(sensor == LAccess.itemCapacity) return block.itemCapacity;
if(sensor == LAccess.liquidCapacity) return block.liquidCapacity;
if(sensor == LAccess.powerCapacity) return block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getPowerProduced();
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getPowerNeeded();
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getLastScaledPowerIn() * 60;
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getLastScaledPowerOut() * 60;
if(sensor == LAccess.powerNetStored && power != null) return power.graph.getLastPowerStored();
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getBatteryCapacity();
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getLastCapacity();
return 0;
}
@Override
public Object senseObject(LAccess sensor){
if(sensor == LAccess.type) return block;
return noSensed;
}
@Override
public double sense(Content content){
if(content instanceof Item && items != null) return items.get((Item)content);

View File

@@ -25,11 +25,20 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
Object data;
BulletType type;
float damage;
float fdata;
@Override
public void getCollisions(Cons<QuadTree> consumer){
for(Team team : team.enemies()){
consumer.get(teamIndex.tree(team));
if(team.active()){
for(Team team : team.enemies()){
consumer.get(teamIndex.tree(team));
}
}else{
for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){
consumer.get(teamIndex.tree(other));
}
}
}
}

View File

@@ -31,29 +31,41 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
}
@Override
public void update(){
if(Mathf.dst(deltaX(), deltaY()) > 0.001f){
baseRotation = Mathf.slerpDelta(baseRotation, Mathf.angle(deltaX(), deltaY()), 0.1f);
}
public void add(){
resetLegs();
}
public void resetLegs(){
float rot = baseRotation;
int count = type.legCount;
float legLength = type.legLength;
this.legs = new Leg[count];
float spacing = 360f / count;
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
l.joint.trns(i * spacing + rot, legLength/2f + type.legBaseOffset).add(x, y);
l.base.trns(i * spacing + rot, legLength + type.legBaseOffset).add(x, y);
legs[i] = l;
}
}
@Override
public void update(){
if(Mathf.dst(deltaX(), deltaY()) > 0.001f){
baseRotation = Angles.moveToward(baseRotation, Mathf.angle(deltaX(), deltaY()), type.rotateSpeed);
}
float rot = baseRotation;
float legLength = type.legLength;
//set up initial leg positions
if(legs.length != type.legCount){
this.legs = new Leg[count];
float spacing = 360f / count;
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
l.joint.trns(i * spacing + rot, legLength/2f + type.legBaseOffset).add(x, y);
l.base.trns(i * spacing + rot, legLength + type.legBaseOffset).add(x, y);
legs[i] = l;
}
resetLegs();
}
float moveSpeed = type.legSpeed;

View File

@@ -8,16 +8,34 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
/** An entity that holds a payload. */
@Component
abstract class PayloadComp implements Posc, Rotc, Hitboxc{
abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
@Import float x, y, rotation;
@Import UnitType type;
Seq<Payload> payloads = new Seq<>();
float payloadUsed(){
return payloads.sumf(p -> p.size() * p.size());
}
boolean canPickup(Unit unit){
return payloadUsed() + unit.hitSize * unit.hitSize <= type.payloadCapacity;
}
boolean canPickup(Building build){
return payloadUsed() + build.block.size * build.block.size * Vars.tilesize * Vars.tilesize <= type.payloadCapacity;
}
boolean canPickupPayload(Payload pay){
return payloadUsed() + pay.size()*pay.size() <= type.payloadCapacity;
}
boolean hasPayload(){
return payloads.size > 0;
}
@@ -33,7 +51,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
}
void pickup(Building tile){
tile.tile().remove();
tile.tile.remove();
payloads.add(new BlockPayload(tile));
Fx.unitPickup.at(tile);
}
@@ -70,13 +88,14 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
boolean dropUnit(UnitPayload payload){
Unit u = payload.unit;
Fx.unitDrop.at(this);
//can't drop ground units
if(((tileOn() == null || tileOn().solid()) && u.elevation < 0.1f) || (!floorOn().isLiquid && u instanceof WaterMovec)){
return false;
}
Fx.unitDrop.at(this);
//clients do not drop payloads
if(Vars.net.client()) return true;
@@ -93,9 +112,9 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
/** @return whether the tile has been successfully placed. */
boolean dropBlock(BlockPayload payload){
Building tile = payload.entity;
int tx = Vars.world.toTile(x - tile.block().offset), ty = Vars.world.toTile(y - tile.block().offset);
int tx = Vars.world.toTile(x - tile.block.offset), ty = Vars.world.toTile(y - tile.block.offset);
Tile on = Vars.world.tile(tx, ty);
if(on != null && Build.validPlace(tile.block(), tile.team, tx, ty, tile.rotation)){
if(on != null && Build.validPlace(tile.block, tile.team, tx, ty, tile.rotation)){
int rot = (int)((rotation + 45f) / 90f) % 4;
payload.place(on, rot);

View File

@@ -34,10 +34,12 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Import float x, y;
@NonNull @ReadOnly Unit unit = Nulls.unit;
transient private Unit lastReadUnit = Nulls.unit;
transient @Nullable NetConnection con;
@ReadOnly Team team = Team.sharded;
@SyncLocal boolean admin, typing, shooting, boosting;
@SyncLocal boolean typing, shooting, boosting;
boolean admin;
@SyncLocal float mouseX, mouseY;
String name = "noname";
Color color = new Color();
@@ -54,13 +56,11 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return unit instanceof Minerc;
}
public @Nullable
CoreBuild closestCore(){
public @Nullable CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable
CoreBuild core(){
public @Nullable CoreBuild core(){
return team.core();
}
@@ -93,6 +93,12 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Override
public void afterSync(){
//simulate a unit change after sync
Unit set = unit;
unit = lastReadUnit;
unit(set);
lastReadUnit = unit;
unit.aim(mouseX, mouseY);
//this is only necessary when the thing being controlled isn't synced
unit.controlWeapons(shooting, shooting);
@@ -165,6 +171,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
public void unit(Unit unit){
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit == unit) return;
if(this.unit != Nulls.unit){
//un-control the old unit
this.unit.controller(this.unit.type().createController());
@@ -173,6 +180,11 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
if(unit != Nulls.unit){
unit.team(team);
unit.controller(this);
//this player just became remote, snap the interpolation so it doesn't go wild
if(unit.isRemote()){
unit.snapInterpolation();
}
}
Events.fire(new UnitChangeEvent(base(), unit));

View File

@@ -45,11 +45,16 @@ abstract class PosComp implements Position{
return tile == null || tile.block() != Blocks.air ? (Floor)Blocks.air : tile.floor();
}
Block blockOn(){
Block blockOn(){
Tile tile = tileOn();
return tile == null ? Blocks.air : tile.block();
}
boolean onSolid(){
Tile tile = tileOn();
return tile != null && tile.solid();
}
@Nullable Tile tileOn(){
return world.tileWorld(x, y);
}

View File

@@ -22,6 +22,8 @@ abstract class StatusComp implements Posc, Flyingc{
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, armorMultiplier = 1, reloadMultiplier = 1;
@Import UnitType type;
/** @return damage taken based on status armor multipliers */
float getShieldDamage(float amount){
return amount * Mathf.clamp(1f - armorMultiplier / 100f);
@@ -102,7 +104,7 @@ abstract class StatusComp implements Posc, Flyingc{
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded()){
if(isGrounded() && !type.hovering){
//apply effect
apply(floor.status, floor.statusDuration);
}

View File

@@ -13,6 +13,7 @@ abstract class SyncComp implements Entityc{
//all these method bodies are internally generated
void snapSync(){}
void snapInterpolation(){}
void readSync(Reads read){}
void writeSync(Writes write){}
void readSyncManual(FloatBuffer buffer){}

View File

@@ -75,6 +75,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(sensor == LAccess.totalItems) return stack().amount;
if(sensor == LAccess.rotation) return rotation;
if(sensor == LAccess.health) return health;
if(sensor == LAccess.maxHealth) return maxHealth;
if(sensor == LAccess.x) return x;
if(sensor == LAccess.y) return y;
if(sensor == LAccess.team) return team.id;
@@ -84,12 +85,25 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return 0;
}
@Override
public Object senseObject(LAccess sensor){
if(sensor == LAccess.type) return type;
return noSensed;
}
@Override
public double sense(Content content){
if(content == stack().item) return stack().amount;
return 0;
}
@Override
@Replace
public boolean canDrown(){
return isGrounded() && !hovering && type.canDrown && !(this instanceof WaterMovec);
}
@Override
public int itemCapacity(){
return type.itemCapacity;
@@ -233,7 +247,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
//simulate falling down
if(dead){
if(dead || health <= 0){
//less drag when dead
drag = 0.01f;

View File

@@ -83,12 +83,6 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
}
}
@Replace
@Override
public boolean canDrown(){
return false;
}
@Replace
public float floorSpeedMultiplier(){
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();

View File

@@ -16,6 +16,7 @@ import static mindustry.Vars.*;
public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0;
protected static final int timerTarget2 = 1;
protected Unit unit;
protected Interval timer = new Interval(4);
@@ -27,6 +28,7 @@ public class AIController implements UnitController{
{
timer.reset(0, Mathf.random(40f));
timer.reset(1, Mathf.random(60f));
}
@Override

View File

@@ -16,8 +16,6 @@ public class BuildPlan{
public @Nullable Block block;
/** Whether this is a break request.*/
public boolean breaking;
/** Whether this request comes with a config int. If yes, any blocks placed with this request will not call playerPlaced.*/
public boolean hasConfig;
/** Config int. Not used unless hasConfig is true.*/
public Object config;
/** Original position, only used in schematics.*/
@@ -40,6 +38,16 @@ public class BuildPlan{
this.breaking = false;
}
/** This creates a build request with a config. */
public BuildPlan(int x, int y, int rotation, Block block, Object config){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
this.config = config;
}
/** This creates a remove request. */
public BuildPlan(int x, int y){
this.x = x;
@@ -84,7 +92,6 @@ public class BuildPlan{
copy.rotation = rotation;
copy.block = block;
copy.breaking = breaking;
copy.hasConfig = hasConfig;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
@@ -127,12 +134,6 @@ public class BuildPlan{
return y*tilesize + block.offset;
}
public BuildPlan configure(Object config){
this.config = config;
this.hasConfig = true;
return this;
}
public @Nullable Tile tile(){
return world.tile(x, y);
}

View File

@@ -163,14 +163,14 @@ public class DefaultWaves{
spacing = 3;
}},
new SpawnGroup(UnitTypes.vestige){{
new SpawnGroup(UnitTypes.scepter){{
begin = 41;
unitAmount = 1;
unitScaling = 1;
spacing = 30;
}},
new SpawnGroup(UnitTypes.cataclyst){{
new SpawnGroup(UnitTypes.reign){{
begin = 81;
unitAmount = 1;
unitScaling = 1;

View File

@@ -170,11 +170,10 @@ public class EventType{
}
}
/** Called from the logic thread. Do not access graphics here! */
public static class BuildinghangeEvent{
public static class TileChangeEvent{
public final Tile tile;
public BuildinghangeEvent(Tile tile){
public TileChangeEvent(Tile tile){
this.tile = tile;
}
}
@@ -225,12 +224,14 @@ public class EventType{
public final Team team;
public final @Nullable Unit unit;
public final boolean breaking;
public final @Nullable Object config;
public BlockBuildEndEvent(Tile tile, @Nullable Unit unit, Team team, boolean breaking){
public BlockBuildEndEvent(Tile tile, @Nullable Unit unit, Team team, boolean breaking, @Nullable Object config){
this.tile = tile;
this.team = team;
this.unit = unit;
this.breaking = breaking;
this.config = config;
}
}

View File

@@ -89,9 +89,6 @@ public class Rules{
public boolean enemyLights = true;
/** Ambient light color, used when lighting is enabled. */
public Color ambientLight = new Color(0.01f, 0.01f, 0.04f, 0.99f);
/** Multiplier for solar panel power output.
negative = use ambient light if lighting is enabled. */
public float solarPowerMultiplier = -1f;
/** team of the player by default */
public Team defaultTeam = Team.sharded;
/** team of the enemy in waves/sectors */

View File

@@ -12,7 +12,6 @@ import mindustry.world.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -43,8 +42,6 @@ public class Schematic implements Publishable, Comparable<Schematic>{
IntIntMap amounts = new IntIntMap();
tiles.each(t -> {
if(t.block.buildVisibility == BuildVisibility.hidden) return;
for(ItemStack stack : t.block.requirements){
amounts.increment(stack.item.id, stack.amount);
}

View File

@@ -28,6 +28,7 @@ import mindustry.io.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.legacy.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
@@ -247,7 +248,7 @@ public class Schematics implements Loadable{
Draw.rect(Tmp.tr1, buffer.getWidth()/2f, buffer.getHeight()/2f, buffer.getWidth(), -buffer.getHeight());
Draw.color();
Seq<BuildPlan> requests = schematic.tiles.map(t -> new BuildPlan(t.x, t.y, t.rotation, t.block).configure(t.config));
Seq<BuildPlan> requests = schematic.tiles.map(t -> new BuildPlan(t.x, t.y, t.rotation, t.block, t.config));
Draw.flush();
//scale each request to fit schematic
@@ -278,7 +279,7 @@ public class Schematics implements Loadable{
/** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */
public Seq<BuildPlan> toRequests(Schematic schem, int x, int y){
return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block).original(t.x, t.y, schem.width, schem.height).configure(t.config))
return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block, t.config).original(t.x, t.y, schem.width, schem.height))
.removeAll(s -> !s.block.isVisible() || !s.block.unlockedNow());
}
@@ -346,9 +347,9 @@ public class Schematics implements Loadable{
for(int cy = y; cy <= y2; cy++){
Building linked = world.build(cx, cy);
if(linked != null &&linked.block().isVisible() && !(linked.block() instanceof BuildBlock)){
int top = linked.block().size/2;
int bot = linked.block().size % 2 == 1 ? -linked.block().size/2 : -(linked.block().size - 1)/2;
if(linked != null && linked.block.isVisible() && !(linked.block instanceof ConstructBlock)){
int top = linked.block.size/2;
int bot = linked.block.size % 2 == 1 ? -linked.block.size/2 : -(linked.block.size - 1)/2;
minx = Math.min(linked.tileX() + bot, minx);
miny = Math.min(linked.tileY() + bot, miny);
maxx = Math.max(linked.tileX() + top, maxx);
@@ -374,11 +375,11 @@ public class Schematics implements Loadable{
for(int cy = oy; cy <= oy2; cy++){
Building tile = world.build(cx, cy);
if(tile != null && !counted.contains(tile.pos()) && !(tile.block() instanceof BuildBlock)
&& (tile.block().isVisible() || (tile.block() instanceof CoreBlock))){
if(tile != null && !counted.contains(tile.pos()) && !(tile.block instanceof ConstructBlock)
&& (tile.block.isVisible() || (tile.block instanceof CoreBlock))){
Object config = tile.config();
tiles.add(new Stile(tile.block(), tile.tileX() + offsetX, tile.tileY() + offsetY, config, (byte)tile.rotation));
tiles.add(new Stile(tile.block, tile.tileX() + offsetX, tile.tileY() + offsetY, config, (byte)tile.rotation));
counted.add(tile.pos());
}
}
@@ -486,8 +487,9 @@ public class Schematics implements Loadable{
IntMap<Block> blocks = new IntMap<>();
byte length = stream.readByte();
for(int i = 0; i < length; i++){
Block block = Vars.content.getByName(ContentType.block, stream.readUTF());
blocks.put(i, block == null ? Blocks.air : block);
String name = stream.readUTF();
Block block = Vars.content.getByName(ContentType.block, SaveFileReader.fallback.get(name, name));
blocks.put(i, block == null || block instanceof LegacyBlock ? Blocks.air : block);
}
int total = stream.readInt();

View File

@@ -250,7 +250,7 @@ public class Tutorial{
Building core = state.teams.playerCores().first();
for(int i = 0; i < blocksToBreak; i++){
if(world.tile(core.tile().x + blockOffset, core.tile().y + i).block() == Blocks.scrapWall){
if(world.tile(core.tile.x + blockOffset, core.tile.y + i).block() == Blocks.scrapWall){
return false;
}
}

View File

@@ -82,7 +82,7 @@ public class BlockRenderer implements Disposable{
dark.end();
});
Events.on(BuildinghangeEvent.class, event -> {
Events.on(TileChangeEvent.class, event -> {
shadowEvents.add(event.tile);
int avgx = (int)(camera.position.x / tilesize);
@@ -192,7 +192,9 @@ public class BlockRenderer implements Disposable{
Tile tile = world.rawTile(x, y);
Block block = tile.block();
//link to center
if(tile.build != null) tile = tile.build.tile();
if(tile.build != null){
tile = tile.build.tile;
}
if(block != Blocks.air && block.cacheLayer == CacheLayer.normal && (tile.build == null || !processedEntities.contains(tile.build.id()))){
if(block.expanded || !expanded){
@@ -207,8 +209,8 @@ public class BlockRenderer implements Disposable{
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
for(Building other : tile.build.getPowerConnections(outArray2)){
if(other.block() instanceof PowerNode){ //TODO need a generic way to render connections!
tileview.add(other.tile());
if(other.block instanceof PowerNode){ //TODO need a generic way to render connections!
tileview.add(other.tile);
}
}
}

View File

@@ -187,7 +187,7 @@ public class Drawf{
Draw.color(Pal.accent);
Draw.alpha(speed);
Lines.lineAngleCenter(t.x + Mathf.sin(time, 20f, Vars.tilesize / 2f * t.block().size - 2f), t.y, 90, t.block().size * Vars.tilesize - 4f);
Lines.lineAngleCenter(t.x + Mathf.sin(time, 20f, Vars.tilesize / 2f * t.block.size - 2f), t.y, 90, t.block.size * Vars.tilesize - 4f);
Draw.reset();
}

View File

@@ -228,7 +228,7 @@ public class FloorRenderer implements Disposable{
int chunksx = Mathf.ceil((float)(world.width()) / chunksize),
chunksy = Mathf.ceil((float)(world.height()) / chunksize);
cache = new Chunk[chunksx][chunksy];
cbatch = new MultiCacheBatch(chunksize * chunksize * 5);
cbatch = new MultiCacheBatch(chunksize * chunksize * 6);
Time.mark();

View File

@@ -50,21 +50,21 @@ public class MenuRenderer implements Disposable{
Simplex s3 = new Simplex(offset + 2);
RidgedPerlin rid = new RidgedPerlin(1 + offset, 1);
Block[] selected = Structs.select(
new Block[]{Blocks.sand, Blocks.sandRocks},
new Block[]{Blocks.shale, Blocks.shaleRocks},
new Block[]{Blocks.ice, Blocks.icerocks},
new Block[]{Blocks.sand, Blocks.sandRocks},
new Block[]{Blocks.shale, Blocks.shaleRocks},
new Block[]{Blocks.ice, Blocks.icerocks},
new Block[]{Blocks.sand, Blocks.sandWall},
new Block[]{Blocks.shale, Blocks.shaleWall},
new Block[]{Blocks.ice, Blocks.iceWall},
new Block[]{Blocks.sand, Blocks.sandWall},
new Block[]{Blocks.shale, Blocks.shaleWall},
new Block[]{Blocks.ice, Blocks.iceWall},
new Block[]{Blocks.moss, Blocks.sporePine}
);
Block[] selected2 = Structs.select(
new Block[]{Blocks.ignarock, Blocks.duneRocks},
new Block[]{Blocks.ignarock, Blocks.duneRocks},
new Block[]{Blocks.stone, Blocks.rocks},
new Block[]{Blocks.stone, Blocks.rocks},
new Block[]{Blocks.moss, Blocks.sporerocks},
new Block[]{Blocks.salt, Blocks.saltRocks}
new Block[]{Blocks.basalt, Blocks.duneWall},
new Block[]{Blocks.basalt, Blocks.duneWall},
new Block[]{Blocks.stone, Blocks.stoneWall},
new Block[]{Blocks.stone, Blocks.stoneWall},
new Block[]{Blocks.moss, Blocks.sporeWall},
new Block[]{Blocks.salt, Blocks.saltWall}
);
Block ore1 = ores.random();
@@ -113,7 +113,7 @@ public class MenuRenderer implements Disposable{
if(heat > base){
ore = Blocks.air;
wall = Blocks.air;
floor = Blocks.ignarock;
floor = Blocks.basalt;
if(heat > base + 0.1){
floor = Blocks.hotrock;
@@ -146,7 +146,7 @@ public class MenuRenderer implements Disposable{
floor = Mathf.chance(0.2) ? Blocks.sporeMoss : Blocks.moss;
if(wall != Blocks.air){
wall = Blocks.sporerocks;
wall = Blocks.sporeWall;
}
}
}

View File

@@ -36,7 +36,11 @@ public class MinimapRenderer implements Disposable{
});
//make sure to call on the graphics thread
Events.on(BuildinghangeEvent.class, event -> Core.app.post(() -> update(event.tile)));
Events.on(TileChangeEvent.class, event -> {
if(!ui.editor.isShown()){
update(event.tile);
}
});
}
public Pixmap getPixmap(){

View File

@@ -82,7 +82,7 @@ public class OverlayRenderer{
if(select instanceof BlockUnitc){
//special selection for block "units"
Fill.square(select.x, select.y, ((BlockUnitc)select).tile().block().size * tilesize/2f);
Fill.square(select.x, select.y, ((BlockUnitc)select).tile().block.size * tilesize/2f);
}else{
Draw.rect(select.type().icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
}
@@ -132,23 +132,25 @@ public class OverlayRenderer{
//draw selected block
if(input.block == null && !Core.scene.hasMouse()){
Vec2 vec = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());
Building tile = world.buildWorld(vec.x, vec.y);
Building build = world.buildWorld(vec.x, vec.y);
if(tile != null && tile.team == player.team()){
tile.drawSelect();
if(!tile.enabled && tile.block.drawDisabled){
tile.drawDisabled();
if(build != null && build.team == player.team()){
build.drawSelect();
if(!build.enabled && build.block.drawDisabled){
build.drawDisabled();
}
if(Core.input.keyDown(Binding.rotateplaced) && tile.block().rotate && tile.interactable(player.team())){
control.input.drawArrow(tile.block(), tile.tileX(), tile.tileY(), tile.rotation, true);
if(Core.input.keyDown(Binding.rotateplaced) && build.block.rotate && build.block.quickRotate && build.interactable(player.team())){
control.input.drawArrow(build.block, build.tileX(), build.tileY(), build.rotation, true);
Draw.color(Pal.accent, 0.3f + Mathf.absin(4f, 0.2f));
Fill.square(tile.x, tile.y, tile.block().size * tilesize/2f);
Fill.square(build.x, build.y, build.block.size * tilesize/2f);
Draw.color();
}
}
}
input.drawOverSelect();
//draw selection overlay when dropping item
if(input.isDroppingItem()){
Vec2 v = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());
@@ -159,11 +161,11 @@ public class OverlayRenderer{
Draw.reset();
Building tile = world.buildWorld(v.x, v.y);
if(tile != null && tile.interactable(player.team()) && tile.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0){
if(tile != null && tile.interactable(player.team()) && tile.acceptStack(player.unit().item(), player.unit().stack.amount, player.unit()) > 0 && player.within(tile, itemTransferRange)){
Lines.stroke(3f, Pal.gray);
Lines.square(tile.x, tile.y, tile.block().size * tilesize / 2f + 3 + Mathf.absin(Time.time(), 5f, 1f));
Lines.square(tile.x, tile.y, tile.block.size * tilesize / 2f + 3 + Mathf.absin(Time.time(), 5f, 1f));
Lines.stroke(1f, Pal.place);
Lines.square(tile.x, tile.y, tile.block().size * tilesize / 2f + 2 + Mathf.absin(Time.time(), 5f, 1f));
Lines.square(tile.x, tile.y, tile.block.size * tilesize / 2f + 2 + Mathf.absin(Time.time(), 5f, 1f));
Draw.reset();
}

View File

@@ -37,7 +37,7 @@ public class PlanetRenderer implements Disposable{
/** Camera used for rendering. */
public Camera3D cam = new Camera3D();
/** Raw vertex batch. */
public final VertexBatch3D batch = new VertexBatch3D(10000, false, true, 0);
public final VertexBatch3D batch = new VertexBatch3D(20000, false, true, 0);
public float zoom = 1f;

View File

@@ -31,7 +31,7 @@ public class DesktopInput extends InputHandler{
/** Current cursor type. */
public Cursor cursorType = SystemCursor.arrow;
/** Position where the player started dragging a line. */
public int selectX, selectY, schemX, schemY;
public int selectX = -1, selectY = -1, schemX = -1, schemY = -1;
/** Last known line positions.*/
public int lastLineX, lastLineY, schematicX, schematicY;
/** Whether selecting mode is active. */
@@ -48,7 +48,7 @@ public class DesktopInput extends InputHandler{
@Override
public void buildUI(Group group){
group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty()));
t.visible(() -> Core.settings.getBool("hints") && ui.hudfrag.shown() && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty()));
t.bottom();
t.table(Styles.black6, b -> {
b.defaults().left();
@@ -75,13 +75,13 @@ public class DesktopInput extends InputHandler{
});
group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty());
t.visible(() -> lastSchematic != null && !selectRequests.isEmpty());
t.bottom();
t.table(Styles.black6, b -> {
b.defaults().left();
b.label( () -> Core.bundle.format("schematic.flip",
Core.keybinds.get(Binding.schematic_flip_x).key.toString(),
Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel);
b.label(() -> Core.bundle.format("schematic.flip",
Core.keybinds.get(Binding.schematic_flip_x).key.toString(),
Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel).visible(() -> Core.settings.getBool("hints"));
b.row();
b.table(a -> {
a.button("@schematic.add", Icon.save, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
@@ -98,10 +98,10 @@ public class DesktopInput extends InputHandler{
//draw break selection
if(mode == breaking){
drawBreakSelection(selectX, selectY, cursorX, cursorY);
drawBreakSelection(selectX, selectY, cursorX, cursorY, !Core.input.keyDown(Binding.schematic_select) ? maxLength : Vars.maxSchematicSize);
}
if(Core.input.keyDown(Binding.schematic_select) && !Core.scene.hasKeyboard()){
if(Core.input.keyDown(Binding.schematic_select) && !Core.scene.hasKeyboard() && mode != breaking){
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
}
@@ -306,7 +306,7 @@ public class DesktopInput extends InputHandler{
cursorType = ui.unloadCursor;
}
if(cursor.build != null && cursor.interactable(player.team()) && !isPlacing() && Math.abs(Core.input.axisTap(Binding.rotate)) > 0 && Core.input.keyDown(Binding.rotateplaced) && cursor.block().rotate){
if(cursor.build != null && cursor.interactable(player.team()) && !isPlacing() && Math.abs(Core.input.axisTap(Binding.rotate)) > 0 && Core.input.keyDown(Binding.rotateplaced) && cursor.block().rotate && cursor.block().quickRotate){
Call.rotateBlock(player, cursor.build, Core.input.axisTap(Binding.rotate) > 0);
}
}
@@ -340,8 +340,6 @@ public class DesktopInput extends InputHandler{
table.row();
table.left().margin(0f).defaults().size(48f).left();
//TODO localize these
table.button(Icon.paste, Styles.clearPartiali, () -> {
ui.schematics.show();
}).tooltip("@schematics");
@@ -356,8 +354,7 @@ public class DesktopInput extends InputHandler{
table.button(Icon.up, Styles.clearPartiali, () -> {
ui.planet.show(state.getSector(), player.team().core());
}).visible(() -> state.isCampaign())
.disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements)).tooltip("@launchcore");
}).visible(() -> state.isCampaign()).tooltip("@launchcore").disabled(b -> player.team().core() == null);
}
void pollInput(){
@@ -394,7 +391,7 @@ public class DesktopInput extends InputHandler{
player.builder().clearBuilding();
}
if(Core.input.keyTap(Binding.schematic_select) && !Core.scene.hasKeyboard()){
if(Core.input.keyTap(Binding.schematic_select) && !Core.scene.hasKeyboard() && mode != breaking){
schemX = rawCursorX;
schemY = rawCursorY;
}
@@ -413,12 +410,14 @@ public class DesktopInput extends InputHandler{
selectRequests.clear();
}
if(Core.input.keyRelease(Binding.schematic_select) && !Core.scene.hasKeyboard()){
if(Core.input.keyRelease(Binding.schematic_select) && !Core.scene.hasKeyboard() && selectX == -1 && selectY == -1 && schemX != -1 && schemY != -1){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
useSchematic(lastSchematic);
if(selectRequests.isEmpty()){
lastSchematic = null;
}
schemX = -1;
schemY = -1;
}
if(!selectRequests.isEmpty()){
@@ -497,6 +496,8 @@ public class DesktopInput extends InputHandler{
mode = breaking;
selectX = tileX(Core.input.mouseX());
selectY = tileY(Core.input.mouseY());
schemX = rawCursorX;
schemY = rawCursorY;
}
if(Core.input.keyDown(Binding.select) && mode == none && !isPlacing() && deleting){
@@ -517,6 +518,12 @@ public class DesktopInput extends InputHandler{
overrideLineRotation = false;
}
if(Core.input.keyRelease(Binding.break_block) && Core.input.keyDown(Binding.schematic_select) && mode == breaking){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
schemX = -1;
schemY = -1;
}
if(Core.input.keyRelease(Binding.break_block) || Core.input.keyRelease(Binding.select)){
if(mode == placing && block != null){ //touch up while placing, place everything in selection
@@ -524,8 +531,14 @@ public class DesktopInput extends InputHandler{
lineRequests.clear();
Events.fire(new LineConfirmEvent());
}else if(mode == breaking){ //touch up while breaking, break everything in selection
removeSelection(selectX, selectY, cursorX, cursorY);
removeSelection(selectX, selectY, cursorX, cursorY, !Core.input.keyDown(Binding.schematic_select) ? maxLength : Vars.maxSchematicSize);
if(lastSchematic != null){
useSchematic(lastSchematic);
lastSchematic = null;
}
}
selectX = -1;
selectY = -1;
tryDropItems(selected == null ? null : selected.build, Core.input.mouseWorld().x, Core.input.mouseWorld().y);

View File

@@ -31,7 +31,7 @@ import mindustry.type.*;
import mindustry.ui.fragments.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.storage.CoreBlock.*;
@@ -105,58 +105,107 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.builder().removeBuild(x, y, breaking);
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void pickupUnitPayload(Player player, Unit target){
@Remote(targets = Loc.both, called = Loc.server)
public static void requestUnitPayload(Player player, Unit target){
if(player == null) return;
Unit unit = player.unit();
Payloadc pay = (Payloadc)unit;
if(target.isAI() && target.isGrounded() && pay.payloads().size < unit.type().payloadCapacity
&& target.mass() < unit.mass()
&& target.within(unit, unit.type().hitsize * 1.5f + target.type().hitsize)){
pay.pickup(target);
if(target.isAI() && target.isGrounded() && pay.canPickup(target)
&& target.within(unit, unit.type().hitsize * 2f + target.type().hitsize * 2f)){
Call.pickedUnitPayload(player, target);
}
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void pickupBlockPayload(Player player, Building tile){
@Remote(targets = Loc.both, called = Loc.server)
public static void requestBlockPayload(Player player, Building tile){
if(player == null) return;
Unit unit = player.unit();
Payloadc pay = (Payloadc)unit;
if(tile != null && tile.team == unit.team && pay.payloads().size < unit.type().payloadCapacity
&& unit.within(tile, tilesize * tile.block.size * 1.2f)){
if(tile != null && tile.team == unit.team
&& unit.within(tile, tilesize * tile.block.size * 1.2f + tilesize * 5f)){
//pick up block directly
if(tile.block().buildVisibility != BuildVisibility.hidden && tile.block().size <= 2 && tile.canPickup()){
pay.pickup(tile);
if(tile.block.buildVisibility != BuildVisibility.hidden && tile.canPickup() && pay.canPickup(tile)){
Call.pickedBlockPayload(player, tile, true);
}else{ //pick up block payload
Payload current = tile.getPayload();
if(current != null && current.canBeTaken(pay)){
Payload taken = tile.takePayload();
if(taken != null){
pay.addPayload(taken);
Fx.unitPickup.at(tile);
}
if(current != null && pay.canPickupPayload(current)){
Call.pickedBlockPayload(player, tile, false);
}
}
}
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void dropPayload(Player player, float x, float y){
@Remote(targets = Loc.server, called = Loc.server)
public static void pickedUnitPayload(Player player, Unit target){
if(player == null || target == null || !(player.unit() instanceof Payloadc)){
if(target != null){
target.remove();
}
return;
}
((Payloadc)player.unit()).pickup(target);
}
@Remote(targets = Loc.server, called = Loc.server)
public static void pickedBlockPayload(Player player, Building tile, boolean onGround){
if(player == null || tile == null || !(player.unit() instanceof Payloadc)){
if(tile != null && onGround){
Fx.unitPickup.at(tile);
tile.tile.remove();
}
return;
}
Unit unit = player.unit();
Payloadc pay = (Payloadc)unit;
if(onGround){
if(tile.block.buildVisibility != BuildVisibility.hidden && tile.canPickup() && pay.canPickup(tile)){
pay.pickup(tile);
}else{
Fx.unitPickup.at(tile);
tile.tile.remove();
}
}else{
Payload current = tile.getPayload();
if(current != null && pay.canPickupPayload(current)){
Payload taken = tile.takePayload();
if(taken != null){
pay.addPayload(taken);
Fx.unitPickup.at(tile);
}
}
}
}
@Remote(targets = Loc.both, called = Loc.server)
public static void requestDropPayload(Player player, float x, float y){
if(player == null || net.client()) return;
Payloadc pay = (Payloadc)player.unit();
//apply margin of error
Tmp.v1.set(x, y).sub(pay).limit(tilesize * 4f).add(pay);
float cx = Tmp.v1.x, cy = Tmp.v1.y;
Call.payloadDropped(player, cx, cy);
}
@Remote(called = Loc.server, targets = Loc.server)
public static void payloadDropped(Player player, float x, float y){
if(player == null) return;
Payloadc pay = (Payloadc)player.unit();
//allow a slight margin of error
if(pay.within(x, y, tilesize * 2f)){
float prevx = pay.x(), prevy = pay.y();
pay.set(x, y);
pay.dropLastPayload();
pay.set(prevx, prevy);
}
float prevx = pay.x(), prevy = pay.y();
pay.set(x, y);
pay.dropLastPayload();
pay.set(prevx, prevy);
}
@Remote(targets = Loc.client, called = Loc.server)
@@ -173,6 +222,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true)
public static void rotateBlock(Player player, Building tile, boolean direction){
if(tile == null) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.rotate, tile.tile(), action -> action.rotation = Mathf.mod(tile.rotation + Mathf.sign(direction), 4)))){
throw new ValidateException(player, "Player cannot rotate a block.");
@@ -185,10 +236,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Building tile){
if(player == null || tile == null) return;
if(player == null || tile == null || !player.within(tile, buildingRange)) return;
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.depositItem, tile.tile(), action -> {
!netServer.admins.allowAction(player, ActionType.depositItem, tile.tile, action -> {
action.itemAmount = player.unit().stack.amount;
action.item = player.unit().item();
}))){
@@ -215,11 +266,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void tileConfig(Player player, Building tile, @Nullable Object value){
public static void tileConfig(@Nullable Player player, Building tile, @Nullable Object value){
if(tile == null) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.configure, tile.tile(), action -> action.config = value))) throw new ValidateException(player, "Player cannot configure a tile.");
tile.configured(player, value);
!netServer.admins.allowAction(player, ActionType.configure, tile.tile, action -> action.config = value))) throw new ValidateException(player, "Player cannot configure a tile.");
tile.configured(player == null || player.dead() ? null : player.unit(), value);
Core.app.post(() -> Events.fire(new ConfigEvent(tile, player, value)));
}
@@ -242,7 +293,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.clearUnit();
//make sure it's AI controlled, so players can't overwrite each other
}else if(unit.isAI() && unit.team == player.team() && !unit.deactivated() && !unit.dead){
player.unit(unit);
if(!net.client()){
player.unit(unit);
}
Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
if(!player.dead()){
Fx.unitSpirit.at(player.x, player.y, 0f, unit);
@@ -334,16 +388,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(!(unit instanceof Payloadc)) return;
Payloadc pay = (Payloadc)unit;
if(pay.payloads().size >= unit.type().payloadCapacity) return;
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitsize * 2.5f, u -> u.isAI() && u.isGrounded() && u.mass() < unit.mass() && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitsize * 2.5f, u -> u.isAI() && u.isGrounded() && pay.canPickup(u) && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
if(target != null){
Call.pickupUnitPayload(player, target);
}else if(!pay.hasPayload()){
Call.requestUnitPayload(player, target);
}else{
Building tile = world.buildWorld(pay.x(), pay.y());
if(tile != null && tile.team == unit.team){
Call.pickupBlockPayload(player, tile);
Call.requestBlockPayload(player, tile);
}
}
}
@@ -351,10 +403,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public void tryDropPayload(){
Unit unit = player.unit();
if(!(unit instanceof Payloadc)) return;
Payloadc pay = (Payloadc)unit;
Call.dropPayload(player, player.x, player.y);
pay.dropLastPayload();
Call.requestDropPayload(player, player.x, player.y);
}
public float getMouseX(){
@@ -387,6 +437,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public void drawOverSelect(){
}
public void drawSelected(int x, int y, Block block, Color color){
Drawf.selected(x, y, block, color);
}
@@ -401,7 +455,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean requestMatches(BuildPlan request){
Tile tile = world.tile(request.x, request.y);
return tile != null && tile.block() instanceof BuildBlock && tile.<BuildEntity>bc().cblock == request.block;
return tile != null && tile.block() instanceof ConstructBlock && tile.<ConstructBuild>bc().cblock == request.block;
}
public void drawBreaking(int x, int y){
@@ -545,7 +599,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
return selectRequests.find(test);
}
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
protected void drawBreakSelection(int x1, int y1, int x2, int y2, int maxLength){
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
NormalizeResult dresult = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
@@ -592,6 +646,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
drawBreakSelection(x1, y1, x2, y2, maxLength);
}
protected void drawSelection(int x1, int y1, int x2, int y2, int maxLength){
NormalizeDrawResult result = Placement.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
@@ -638,13 +696,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
protected void drawRequest(BuildPlan request){
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation));
if(request.block.saveConfig && request.block.lastConfig != null && !request.hasConfig){
Object conf = request.config;
request.config = request.block.lastConfig;
request.block.drawRequestConfig(request, allRequests());
request.config = conf;
}
}
/** Draws a placement icon for a specific block. */
@@ -659,8 +710,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
removeSelection(x1, y1, x2, y2, false);
}
/** Remove everything from the queue in a selection. */
protected void removeSelection(int x1, int y1, int x2, int y2, int maxLength){
removeSelection(x1, y1, x2, y2, false, maxLength);
}
/** Remove everything from the queue in a selection. */
protected void removeSelection(int x1, int y1, int x2, int y2, boolean flush){
removeSelection(x1, y1, x2, y2, false, maxLength);
}
/** Remove everything from the queue in a selection. */
protected void removeSelection(int x1, int y1, int x2, int y2, boolean flush, int maxLength){
NormalizeResult result = Placement.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){
for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){
@@ -713,7 +774,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
lineRequests.clear();
iterateLine(x1, y1, x2, y2, l -> {
rotation = l.rotation;
BuildPlan req = new BuildPlan(l.x, l.y, l.rotation, block);
BuildPlan req = new BuildPlan(l.x, l.y, l.rotation, block, block.nextConfig());
req.animScale = 1f;
lineRequests.add(req);
});
@@ -742,7 +803,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
boolean consumed = false, showedInventory = false;
//check if tapped block is configurable
if(tile.block().configurable && tile.interactable(player.team())){
if(tile.block.configurable && tile.interactable(player.team())){
consumed = true;
if(((!frag.config.isShown() && tile.shouldShowConfigure(player)) //if the config fragment is hidden, show
//alternatively, the current selected block can 'agree' to switch config tiles
@@ -769,10 +830,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
//consume tap event if necessary
if(tile.interactable(player.team()) && tile.block().consumesTap){
if(tile.interactable(player.team()) && tile.block.consumesTap){
consumed = true;
}else if(tile.interactable(player.team()) && tile.block().synthetic() && !consumed){
if(tile.block().hasItems && tile.items.total() > 0){
}else if(tile.interactable(player.team()) && tile.block.synthetic() && !consumed){
if(tile.block.hasItems && tile.items.total() > 0){
frag.inv.showFor(tile);
consumed = true;
showedInventory = true;
@@ -942,7 +1003,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
ItemStack stack = player.unit().stack;
if(tile != null && tile.acceptStack(stack.item, stack.amount, player.unit()) > 0 && tile.interactable(player.team()) && tile.block().hasItems && player.unit().stack().amount > 0 && tile.interactable(player.team())){
if(tile != null && tile.acceptStack(stack.item, stack.amount, player.unit()) > 0 && tile.interactable(player.team()) && tile.block.hasItems && player.unit().stack().amount > 0 && tile.interactable(player.team())){
Call.transferInventory(player, tile);
}else{
Call.dropItem(player.angleTo(x, y));
@@ -986,13 +1047,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(req != null){
player.builder().plans().remove(req);
}
player.builder().addBuild(new BuildPlan(x, y, rotation, block));
player.builder().addBuild(new BuildPlan(x, y, rotation, block, block.nextConfig()));
}
public void breakBlock(int x, int y){
Tile tile = world.tile(x, y);
//TODO hacky
if(tile != null && tile.build != null) tile = tile.build.tile();
if(tile != null && tile.build != null) tile = tile.build.tile;
player.builder().addBuild(new BuildPlan(tile.x, tile.y));
}

View File

@@ -277,7 +277,7 @@ public class MobileInput extends InputHandler implements GestureListener{
public void drawBottom(){
Lines.stroke(1f);
//draw removals
//draw requests about to be removed
for(BuildPlan request : removals){
Tile tile = request.tile();
@@ -292,6 +292,43 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
Draw.mixcol();
Draw.color(Pal.accent);
//Draw lines
if(lineMode){
int tileX = tileX(Core.input.mouseX());
int tileY = tileY(Core.input.mouseY());
if(mode == placing && block != null){
//draw placing
for(int i = 0; i < lineRequests.size; i++){
BuildPlan request = lineRequests.get(i);
if(i == lineRequests.size - 1 && request.block.rotate){
drawArrow(block, request.x, request.y, request.rotation);
}
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation) && getRequest(request.x, request.y, request.block.size, null) == null);
drawSelected(request.x, request.y, request.block, Pal.accent);
}
}else if(mode == breaking){
drawBreakSelection(lineStartX, lineStartY, tileX, tileY);
}
}
Draw.reset();
}
@Override
public void drawTop(){
//draw schematic selection
if(mode == schematicSelect){
drawSelection(lineStartX, lineStartY, lastLineX, lastLineY, Vars.maxSchematicSize);
}
}
@Override
public void drawOverSelect(){
//draw list of requests
for(BuildPlan request : selectRequests){
Tile tile = request.tile();
@@ -322,29 +359,6 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
Draw.mixcol();
Draw.color(Pal.accent);
//Draw lines
if(lineMode){
int tileX = tileX(Core.input.mouseX());
int tileY = tileY(Core.input.mouseY());
if(mode == placing && block != null){
//draw placing
for(int i = 0; i < lineRequests.size; i++){
BuildPlan request = lineRequests.get(i);
if(i == lineRequests.size - 1 && request.block.rotate){
drawArrow(block, request.x, request.y, request.rotation);
}
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation) && getRequest(request.x, request.y, request.block.size, null) == null);
drawSelected(request.x, request.y, request.block, Pal.accent);
}
}else if(mode == breaking){
drawBreakSelection(lineStartX, lineStartY, tileX, tileY);
}
}
//draw targeting crosshair
if(target != null && !state.isEditor()){
if(target != lastTarget){
@@ -366,15 +380,6 @@ public class MobileInput extends InputHandler implements GestureListener{
Draw.reset();
}
@Override
public void drawTop(){
//draw schematic selection
if(mode == schematicSelect){
drawSelection(lineStartX, lineStartY, lastLineX, lastLineY, Vars.maxSchematicSize);
}
}
@Override
protected void drawRequest(BuildPlan request){
if(request.tile() == null) return;
@@ -553,7 +558,7 @@ public class MobileInput extends InputHandler implements GestureListener{
//ignore off-screen taps
if(cursor == null || Core.scene.hasMouse(x, y)) return false;
Tile linked = cursor.build == null ? cursor : cursor.build.tile();
Tile linked = cursor.build == null ? cursor : cursor.build.tile;
if(!player.dead()){
checkTargets(worldx, worldy);
@@ -564,7 +569,7 @@ public class MobileInput extends InputHandler implements GestureListener{
removeRequest(getRequest(cursor));
}else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, block, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, block)){
//add to selection queue if it's a valid place position
selectRequests.add(lastPlaced = new BuildPlan(cursor.x, cursor.y, rotation, block));
selectRequests.add(lastPlaced = new BuildPlan(cursor.x, cursor.y, rotation, block, block.nextConfig()));
}else if(mode == breaking && validBreak(linked.x,linked.y) && !hasRequest(linked)){
//add to selection queue if it's a valid BREAK position
selectRequests.add(new BuildPlan(linked.x, linked.y));

View File

@@ -8,11 +8,7 @@ import mindustry.world.*;
import java.io.*;
public abstract class SaveFileReader{
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();
protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall);
protected final ObjectMap<String, String> fallback = ObjectMap.of(
public static final ObjectMap<String, String> fallback = ObjectMap.of(
"dart-mech-pad", "legacy-mech-pad",
"dart-ship-pad", "legacy-mech-pad",
"javelin-ship-pad", "legacy-mech-pad",
@@ -34,9 +30,32 @@ public abstract class SaveFileReader{
"titan-factory", "legacy-unit-factory",
"fortress-factory", "legacy-unit-factory",
"mass-conveyor", "payload-conveyor"
"mass-conveyor", "payload-conveyor",
"vestige", "scepter",
"turbine-generator", "steam-generator",
"rocks", "stone-wall",
"sporerocks", "spore-wall",
"icerocks", "ice-wall",
"dunerocks", "dune-wall",
"sandrocks", "sand-wall",
"shalerocks", "shale-wall",
"snowrocks", "snow-wall",
"saltrocks", "salt-wall",
"dirtwall", "dirt-wall",
"ignarock", "basalt",
"holostone", "dacite",
"holostone-wall", "dacite-wall",
"rock", "boulder",
"snowrock", "snow-boulder"
);
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();
protected final DataOutputStream dataBytesSmall = new DataOutputStream(byteOutputSmall);
protected int lastRegionLength;
protected void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{

View File

@@ -231,7 +231,7 @@ public class TypeIO{
if(!request.breaking){
write.s(request.block.id);
write.b((byte)request.rotation);
write.b(request.hasConfig ? (byte)1 : 0);
write.b(1); //always has config
writeObject(write, request.config);
}
}
@@ -254,8 +254,9 @@ public class TypeIO{
boolean hasConfig = read.b() == 1;
Object config = readObject(read);
currentRequest = new BuildPlan(Point2.x(position), Point2.y(position), rotation, content.block(block));
//should always happen, but is kept for legacy reasons just in case
if(hasConfig){
currentRequest.configure(config);
currentRequest.config = config;
}
}

View File

@@ -10,8 +10,8 @@ public class LegacyIO{
/** Maps old unit names to new ones. */
public static final StringMap unitMap = StringMap.of(
"titan", "mace",
"chaos-array", "vestige",
"eradicator", "cataclyst",
"chaos-array", "scepter",
"eradicator", "reign",
"eruptor", "atrax",
"wraith", "flare",
"ghoul", "horizon",

View File

@@ -54,7 +54,7 @@ public abstract class LegacySaveVersion extends SaveVersion{
if(block == null) block = Blocks.air;
//occupied by multiblock part
boolean occupied = tile.build != null && !tile.isCenter() && (tile.build.block() == block || block == Blocks.air);
boolean occupied = tile.build != null && !tile.isCenter() && (tile.build.block == block || block == Blocks.air);
//do not override occupied cells
if(!occupied){

View File

@@ -1,8 +1,8 @@
package mindustry.logic;
public enum ConditionOp{
equal("==", (a, b) -> Math.abs(a - b) < 0.000001),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001, (a, b) -> a == b),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001, (a, b) -> a != b),
lessThan("<", (a, b) -> a < b),
lessThanEq("<=", (a, b) -> a <= b),
greaterThan(">", (a, b) -> a > b),
@@ -10,12 +10,18 @@ public enum ConditionOp{
public static final ConditionOp[] all = values();
public final CondObjOpLambda objFunction;
public final CondOpLambda function;
public final String symbol;
ConditionOp(String symbol, CondOpLambda function){
this(symbol, function, null);
}
ConditionOp(String symbol, CondOpLambda function, CondObjOpLambda objFunction){
this.symbol = symbol;
this.function = function;
this.objFunction = objFunction;
}
@Override
@@ -23,6 +29,10 @@ public enum ConditionOp{
return symbol;
}
interface CondObjOpLambda{
boolean get(Object a, Object b);
}
interface CondOpLambda{
boolean get(double a, double b);
}

View File

@@ -15,6 +15,7 @@ public enum LAccess{
powerNetIn,
powerNetOut,
health,
maxHealth,
heat,
efficiency,
rotation,
@@ -24,6 +25,7 @@ public enum LAccess{
shootY,
shooting,
team,
type,
//values with parameters are considered controllable
enabled("to"), //"to" is standard for single parameter access

View File

@@ -8,7 +8,7 @@ import mindustry.gen.*;
import mindustry.logic.LExecutor.*;
import mindustry.logic.LStatements.*;
import mindustry.type.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.*;
/** "Compiles" a sequence of statements into instructions. */
public class LAssembler{
@@ -39,6 +39,12 @@ public class LAssembler{
putConst("@" + liquid.name, liquid);
}
for(Block block : Vars.content.blocks()){
if(block.synthetic()){
putConst("@" + block.name, block);
}
}
//store sensor constants
for(LAccess sensor : LAccess.all){
@@ -66,7 +72,7 @@ public class LAssembler{
}
public static Seq<LStatement> read(String data){
return read(data, LogicBlock.maxInstructions);
return read(data, LExecutor.maxInstructions);
}
public static Seq<LStatement> read(String data, int max){
@@ -82,6 +88,8 @@ public class LAssembler{
if(index++ > max) break;
line = line.replace("\t", "").trim();
try{
String[] arr;

View File

@@ -17,18 +17,20 @@ import arc.util.ArcAnnotate.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.blocks.logic.*;
public class LCanvas extends Table{
static Seq<Runnable> postDraw = new Seq<>();
static Seq<Runnable> postDrawPriority = new Seq<>();
//ew static variables
static LCanvas canvas;
DragLayout statements;
StatementElem dragging;
ScrollPane pane;
Group jumps;
float targetWidth;
public LCanvas(){
canvas = this;
rebuild();
}
@@ -45,12 +47,16 @@ public class LCanvas extends Table{
clear();
statements = new DragLayout();
jumps = new WidgetGroup();
pane = pane(t -> {
t.center();
t.add(statements).pad(2f).center().width(targetWidth);
t.addChild(jumps);
jumps.cullable = false;
}).grow().get();
pane.setClip(false);
//pane.setClip(false);
pane.setFlickScroll(false);
//load old scroll percent
@@ -76,8 +82,10 @@ public class LCanvas extends Table{
}
void load(String asm){
jumps.clear();
Seq<LStatement> statements = LAssembler.read(asm);
statements.truncate(LogicBlock.maxInstructions);
statements.truncate(LExecutor.maxInstructions);
this.statements.clearChildren();
for(LStatement st : statements){
add(st);
@@ -104,19 +112,11 @@ public class LCanvas extends Table{
}
}
@Override
public void draw(){
postDraw.clear();
postDrawPriority.clear();
super.draw();
postDraw.each(Runnable::run);
postDrawPriority.each(Runnable::run);
}
public class DragLayout extends WidgetGroup{
float space = Scl.scl(10f), prefWidth, prefHeight;
Seq<Element> seq = new Seq<>();
int insertPosition = 0;
boolean invalidated;
{
setTransform(true);
@@ -124,6 +124,7 @@ public class LCanvas extends Table{
@Override
public void layout(){
invalidated = true;
float cy = 0;
seq.clear();
@@ -171,6 +172,10 @@ public class LCanvas extends Table{
}
invalidateHierarchy();
if(parent != null && parent instanceof Table){
setCullingArea(parent.getCullingArea());
}
}
@Override
@@ -196,7 +201,16 @@ public class LCanvas extends Table{
Tex.pane.draw(lastX, lastY - shiftAmount, width, dragging.getHeight());
}
if(invalidated){
children.each(c -> c.cullable = false);
}
super.draw();
if(invalidated){
children.each(c -> c.cullable = true);
invalidated = false;
}
}
void finishLayout(){
@@ -338,6 +352,9 @@ public class LCanvas extends Table{
boolean selecting;
float mx, my;
ClickListener listener;
StatementElem hovered;
JumpCurve curve;
public JumpButton(@NonNull Prov<StatementElem> getter, Cons<StatementElem> setter){
super(Tex.logicNode, Styles.colori);
@@ -383,53 +400,26 @@ public class LCanvas extends Table{
setColor(listener.isOver() ? hoverColor : defaultColor);
getStyle().imageUpColor = this.color;
});
curve = new JumpCurve(this);
}
@Override
public void draw(){
super.draw();
public void act(float delta){
super.act(delta);
(listener.isOver() ? postDrawPriority : postDraw).add(() -> {
Element hover = to.get() == null && selecting ? hovered() : to.get();
float tx = 0, ty = 0;
boolean draw = false;
//capture coordinates for use in lambda
float rx = x + translation.x, ry = y + translation.y;
hovered = hovered();
}
Element p = parent;
while(p != null){
rx += p.x + p.translation.x;
ry += p.y + p.translation.y;
p = p.parent;
}
@Override
protected void setScene(Scene stage){
super.setScene(stage);
if(hover != null){
tx = hover.getX(Align.right) + hover.translation.x;
ty = hover.getY(Align.right) + hover.translation.y;
Element op = hover.parent;
while(op != null){
tx += op.x + op.translation.x;
ty += op.y + op.translation.y;
op = op.parent;
}
draw = true;
}else if(selecting){
tx = rx + mx;
ty = ry + my;
draw = true;
}
if(draw){
drawCurve(rx + width/2f, ry + height/2f, tx, ty);
float s = width;
Draw.color(color);
Tex.logicNode.draw(tx + s*0.75f, ty - s/2f, -s, s);
Draw.reset();
}
});
if(stage == null){
curve.remove();
}else{
canvas.jumps.addChild(curve);
}
}
StatementElem hovered(){
@@ -442,9 +432,60 @@ public class LCanvas extends Table{
if(e == null || isDescendantOf(e)) return null;
return (StatementElem)e;
}
}
public static class JumpCurve extends Element{
JumpButton button;
public JumpCurve(JumpButton button){
this.button = button;
}
@Override
public void act(float delta){
super.act(delta);
if(button.listener.isOver()){
toFront();
}
}
@Override
public void draw(){
Element hover = button.to.get() == null && button.selecting ? button.hovered : button.to.get();
boolean draw = false;
Vec2 t = Tmp.v1, r = Tmp.v2;
Group desc = canvas.pane;
button.localToAscendantCoordinates(desc, r.set(0, 0));
if(hover != null){
hover.localToAscendantCoordinates(desc, t.set(hover.getWidth(), hover.getHeight()/2f));
draw = true;
}else if(button.selecting){
t.set(r).add(button.mx, button.my);
draw = true;
}
float offset = canvas.pane.getVisualScrollY() - canvas.pane.getMaxY();
t.y += offset;
r.y += offset;
if(draw){
drawCurve(r.x + button.getWidth()/2f, r.y + button.getHeight()/2f, t.x, t.y);
float s = button.getWidth();
Draw.color(button.color);
Tex.logicNode.draw(t.x + s*0.75f, t.y - s/2f, -s, s);
Draw.reset();
}
}
void drawCurve(float x, float y, float x2, float y2){
Lines.stroke(4f, color);
Lines.stroke(4f, button.color);
Draw.alpha(parentAlpha);
float dist = 100f;
@@ -454,7 +495,7 @@ public class LCanvas extends Table{
x + dist, y,
x2 + dist, y2,
x2, y2,
Math.max(20, (int)(Mathf.dst(x, y, x2, y2) / 5))
Math.max(20, (int)(Mathf.dst(x, y, x2, y2) / 6))
);
}
}

View File

@@ -3,6 +3,7 @@ package mindustry.logic;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.*;
import mindustry.ctype.*;
import mindustry.entities.*;
@@ -15,6 +16,11 @@ import mindustry.world.blocks.logic.MessageBlock.*;
import static mindustry.Vars.*;
public class LExecutor{
public static final int maxInstructions = 1000;
//for noise operations
public static final Simplex noise = new Simplex();
//special variables
public static final int
varCounter = 0,
@@ -42,7 +48,8 @@ public class LExecutor{
vars[varTime].numval = Time.millis();
//reset to start
if(vars[varCounter].numval >= instructions.length) vars[varCounter].numval = 0;
if(vars[varCounter].numval >= instructions.length
|| vars[varCounter].numval < 0) vars[varCounter].numval = 0;
if(vars[varCounter].numval < instructions.length){
instructions[(int)(vars[varCounter].numval++)].run(this);
@@ -252,18 +259,24 @@ public class LExecutor{
Object target = exec.obj(from);
Object sense = exec.obj(type);
double output = 0;
if(target instanceof Senseable){
Senseable se = (Senseable)target;
if(sense instanceof Content){
output = ((Senseable)target).sense(((Content)sense));
exec.setnum(to, se.sense(((Content)sense)));
}else if(sense instanceof LAccess){
output = ((Senseable)target).sense(((LAccess)sense));
Object objOut = se.senseObject((LAccess)sense);
if(objOut == Senseable.noSensed){
//numeric output
exec.setnum(to, se.sense((LAccess)sense));
}else{
//object output
exec.setobj(to, objOut);
}
}
}else{
exec.setnum(to, 0);
}
exec.setnum(to, output);
}
}
@@ -396,7 +409,17 @@ public class LExecutor{
if(op.unary){
exec.setnum(dest, op.function1.get(exec.num(a)));
}else{
exec.setnum(dest, op.function2.get(exec.num(a), exec.num(b)));
Var va = exec.vars[a];
Var vb = exec.vars[b];
if(op.objFunction2 != null && (va.isobj || vb.isobj)){
//use object function if provided, and one of the variables is an object
exec.setnum(dest, op.objFunction2.get(exec.obj(a), exec.obj(b)));
}else{
//otherwise use the numeric function
exec.setnum(dest, op.function2.get(exec.num(a), exec.num(b)));
}
}
}
}
@@ -490,7 +513,9 @@ public class LExecutor{
//this should avoid any garbage allocation
Var v = exec.vars[value];
if(v.isobj && value != 0){
String strValue = v.objval instanceof String ? (String)v.objval : v.objval == null ? "null" :
String strValue =
v.objval == null ? "null" :
v.objval instanceof String ? (String)v.objval :
v.objval instanceof Content ? "[content]" :
v.objval instanceof Building ? "[building]" :
v.objval instanceof Unit ? "[unit]" :
@@ -549,8 +574,21 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
if(address != -1 && op.function.get(exec.num(value), exec.num(compare))){
exec.vars[varCounter].numval = address;
if(address != -1){
Var va = exec.vars[value];
Var vb = exec.vars[compare];
boolean cmp = false;
if(op.objFunction != null && (va.isobj || vb.isobj)){
//use object function if provided, and one of the variables is an object
cmp = op.objFunction.get(exec.obj(value), exec.obj(compare));
}else{
cmp = op.function.get(exec.num(value), exec.num(compare));
}
if(cmp){
exec.vars[varCounter].numval = address;
}
}
}
}

View File

@@ -93,7 +93,13 @@ public abstract class LStatement{
Core.scene.add(t);
t.update(() -> {
if(b.parent == null) return;
if(b.parent == null || !b.isDescendantOf(Core.scene.root)){
Core.app.post(() -> {
hitter.remove();
t.remove();
});
return;
}
b.localToStageCoordinates(Tmp.v1.set(b.getWidth()/2f, b.getHeight()/2f));
t.setPosition(Tmp.v1.x, Tmp.v1.y, Align.center);

View File

@@ -7,7 +7,6 @@ import mindustry.gen.*;
import mindustry.logic.LStatements.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.blocks.logic.*;
import static mindustry.Vars.*;
@@ -21,6 +20,7 @@ public class LogicDialog extends BaseDialog{
clearChildren();
canvas = new LCanvas();
shouldPause = true;
addCloseButton();
buttons.getCells().first().width(170f);
@@ -75,7 +75,7 @@ public class LogicDialog extends BaseDialog{
});
dialog.addCloseButton();
dialog.show();
}).width(170f).disabled(t -> canvas.statements.getChildren().size >= LogicBlock.maxInstructions);
}).width(170f).disabled(t -> canvas.statements.getChildren().size >= LExecutor.maxInstructions);
add(canvas).grow();

View File

@@ -8,24 +8,25 @@ public enum LogicOp{
mul("*", (a, b) -> a * b),
div("/", (a, b) -> a / b),
mod("%", (a, b) -> a % b),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0),
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> a == b ? 1 : 0),
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1, (a, b) -> a != b ? 1 : 0),
lessThan("<", (a, b) -> a < b ? 1 : 0),
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
greaterThan(">", (a, b) -> a > b ? 1 : 0),
greaterThanEq(">=", (a, b) -> a >= b ? 1 : 0),
pow("^", Math::pow),
shl(">>", (a, b) -> (int)a >> (int)b),
shr("<<", (a, b) -> (int)a << (int)b),
or("or", (a, b) -> (int)a | (int)b),
and("and", (a, b) -> (int)a & (int)b),
xor("xor", (a, b) -> (int)a ^ (int)b),
shl(">>", (a, b) -> (long)a >> (long)b),
shr("<<", (a, b) -> (long)a << (long)b),
or("or", (a, b) -> (long)a | (long)b),
and("and", (a, b) -> (long)a & (long)b),
xor("xor", (a, b) -> (long)a ^ (long)b),
max("max", Math::max),
min("min", Math::min),
atan2("atan2", (x, y) -> Mathf.atan2((float)x, (float)y) * Mathf.radDeg),
dst("dst", (x, y) -> Mathf.dst((float)x, (float)y)),
noise("noise", LExecutor.noise::rawNoise2D),
not("not", a -> ~(int)(a)),
not("not", a -> ~(long)(a)),
abs("abs", a -> Math.abs(a)),
log("log", Math::log),
log10("log10", Math::log10),
@@ -41,16 +42,22 @@ public enum LogicOp{
public static final LogicOp[] all = values();
public final OpObjLambda2 objFunction2;
public final OpLambda2 function2;
public final OpLambda1 function1;
public final boolean unary;
public final String symbol;
LogicOp(String symbol, OpLambda2 function){
this(symbol, function, null);
}
LogicOp(String symbol, OpLambda2 function, OpObjLambda2 objFunction){
this.symbol = symbol;
this.function2 = function;
this.function1 = null;
this.unary = false;
this.objFunction2 = objFunction;
}
LogicOp(String symbol, OpLambda1 function){
@@ -58,6 +65,7 @@ public enum LogicOp{
this.function1 = function;
this.function2 = null;
this.unary = true;
this.objFunction2 = null;
}
@Override
@@ -65,6 +73,10 @@ public enum LogicOp{
return symbol;
}
interface OpObjLambda2{
double get(Object a, Object b);
}
interface OpLambda2{
double get(double a, double b);
}

View File

@@ -3,6 +3,12 @@ package mindustry.logic;
import mindustry.ctype.*;
public interface Senseable{
Object noSensed = new Object();
double sense(LAccess sensor);
double sense(Content content);
default Object senseObject(LAccess sensor){
return noSensed;
}
}

View File

@@ -292,25 +292,29 @@ public class Maps{
if(str == null || str.isEmpty()){
//create default filters list
Seq<GenerateFilter> filters = Seq.with(
new ScatterFilter(){{
flooronto = Blocks.stone;
block = Blocks.rock;
}},
new ScatterFilter(){{
flooronto = Blocks.shale;
block = Blocks.shaleBoulder;
}},
new ScatterFilter(){{
flooronto = Blocks.snow;
block = Blocks.snowrock;
block = Blocks.snowBoulder;
}},
new ScatterFilter(){{
flooronto = Blocks.ice;
block = Blocks.snowrock;
block = Blocks.snowBoulder;
}},
new ScatterFilter(){{
flooronto = Blocks.sand;
block = Blocks.sandBoulder;
}},
new ScatterFilter(){{
flooronto = Blocks.dacite;
block = Blocks.daciteBoulder;
}},
new ScatterFilter(){{
flooronto = Blocks.stone;
block = Blocks.boulder;
}},
new ScatterFilter(){{
flooronto = Blocks.shale;
block = Blocks.shaleBoulder;
}}
);

View File

@@ -35,7 +35,8 @@ public class SectorDamage{
if(core != null && !frontier.isEmpty()){
for(Tile spawner : frontier){
//find path from spawn to core
Seq<Tile> path = Astar.pathfind(spawner, core.tile, t -> t.cost, t -> !(t.block().isStatic() && t.solid()));
//TODO this is broken
Seq<Tile> path = Astar.pathfind(spawner, core.tile, SectorDamage::cost, t -> !(t.block().isStatic() && t.solid()));
int amount = (int)(path.size * fraction);
for(int i = 0; i < amount; i++){
Tile t = path.get(i);
@@ -104,8 +105,12 @@ public class SectorDamage{
}
}
}
}
static float cost(Tile tile){
return 1f +
(tile.block().isStatic() && tile.solid() ? 200f : 0f) +
(tile.build != null ? tile.build.health / 40f : 0f) +
(tile.floor().isLiquid ? 10f : 0f);
}
}

View File

@@ -11,7 +11,7 @@ import static mindustry.maps.filters.FilterOption.wallsOnly;
public class NoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0.5f, octaves = 3f, falloff = 0.5f;
Block floor = Blocks.stone, block = Blocks.rocks;
Block floor = Blocks.stone, block = Blocks.stoneWall;
@Override
public FilterOption[] options(){

View File

@@ -11,7 +11,7 @@ import static mindustry.maps.filters.FilterOption.wallsOnly;
public class RiverNoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0f, threshold2 = 0.1f;
Block floor = Blocks.water, floor2 = Blocks.deepwater, block = Blocks.sandRocks;
Block floor = Blocks.water, floor2 = Blocks.deepwater, block = Blocks.sandWall;
@Override
public FilterOption[] options(){

View File

@@ -12,7 +12,7 @@ import static mindustry.maps.filters.FilterOption.wallsOnly;
public class TerrainFilter extends GenerateFilter{
float scl = 40, threshold = 0.9f, octaves = 3f, falloff = 0.5f, magnitude = 1f, circleScl = 2.1f;
Block floor = Blocks.stone, block = Blocks.rocks;
Block floor = Blocks.stone, block = Blocks.stoneWall;
@Override
public FilterOption[] options(){

View File

@@ -15,6 +15,7 @@ import mindustry.world.blocks.defense.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.production.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -26,9 +27,10 @@ public class BaseGenerator{
private Tiles tiles;
private Team team;
private ObjectMap<Item, OreBlock> ores = new ObjectMap<>();
private ObjectMap<Item, Floor> oreFloors = new ObjectMap<>();
private Seq<Tile> cores;
public void generate(Tiles tiles, Seq<Tile> cores, Tile spawn, Team team, Sector sector){
public void generate(Tiles tiles, Seq<Tile> cores, Tile spawn, Team team, Sector sector, float difficulty){
this.tiles = tiles;
this.team = team;
this.cores = cores;
@@ -41,16 +43,24 @@ public class BaseGenerator{
for(Block block : content.blocks()){
if(block instanceof OreBlock && block.asFloor().itemDrop != null){
ores.put(block.asFloor().itemDrop, (OreBlock)block);
}else if(block.isFloor() && block.asFloor().itemDrop != null && !oreFloors.containsKey(block.asFloor().itemDrop)){
oreFloors.put(block.asFloor().itemDrop, block.asFloor());
}
}
//TODO limit base size
float costBudget = 1000;
Seq<Block> wallsSmall = content.blocks().select(b -> b instanceof Wall && b.size == 1);
Seq<Block> wallsLarge = content.blocks().select(b -> b instanceof Wall && b.size == 2);
Seq<Block> wallsSmall = content.blocks().select(b -> b instanceof Wall && b.size == 1 && b.buildVisibility == BuildVisibility.shown && !(b instanceof Door));
Seq<Block> wallsLarge = content.blocks().select(b -> b instanceof Wall && b.size == 2 && b.buildVisibility == BuildVisibility.shown && !(b instanceof Door));
float bracket = 0.1f;
//sort by cost for correct fraction
wallsSmall.sort(b -> b.buildCost);
wallsLarge.sort(b -> b.buildCost);
//TODO proper difficulty selection
float bracket = difficulty;
float bracketRange = 0.2f;
int wallAngle = 70; //180 for full coverage
double resourceChance = 0.5;
double nonResourceChance = 0.0005;
@@ -65,7 +75,7 @@ public class BaseGenerator{
//fill core with every type of item (even non-material)
Building entity = tile.build;
for(Item item : content.items()){
entity.items.add(item, entity.block().itemCapacity);
entity.items.add(item, entity.block.itemCapacity);
}
}
@@ -73,13 +83,22 @@ public class BaseGenerator{
pass(tile -> {
if(!tile.block().alwaysReplace) return;
if((tile.drop() != null || (tile.floor().liquidDrop != null && Mathf.chance(nonResourceChance * 2))) && Mathf.chance(resourceChance)){
if(((tile.overlay().asFloor().itemDrop != null || (tile.drop() != null && Mathf.chance(nonResourceChance)))
|| (tile.floor().liquidDrop != null && Mathf.chance(nonResourceChance * 2))) && Mathf.chance(resourceChance)){
Seq<BasePart> parts = bases.forResource(tile.drop() != null ? tile.drop() : tile.floor().liquidDrop);
if(!parts.isEmpty()){
tryPlace(parts.random(), tile.x, tile.y);
tryPlace(parts.getFrac(bracket + Mathf.range(bracketRange)), tile.x, tile.y);
}
}else if(Mathf.chance(nonResourceChance)){
tryPlace(bases.parts.random(), tile.x, tile.y);
tryPlace(bases.parts.getFrac(bracket + Mathf.range(bracketRange)), tile.x, tile.y);
}
});
//replace walls with the correct type (disabled)
if(false)
pass(tile -> {
if(tile.block() instanceof Wall && tile.team() == team && tile.block() != wall && tile.block() != wallLarge){
tile.setBlock(tile.block().size == 2 ? wallLarge : wall, team);
}
});
@@ -87,6 +106,7 @@ public class BaseGenerator{
//small walls
pass(tile -> {
if(tile.block().alwaysReplace){
boolean any = false;
@@ -163,19 +183,20 @@ public class BaseGenerator{
}
if(part.required instanceof Item){
Item item = (Item)part.required;
for(Stile tile : result.tiles){
if(tile.block instanceof Drill){
tile.block.iterateTaken(tile.x + cx, tile.y + cy, (ex, ey) -> {
if(!tiles.getn(ex, ey).floor().isLiquid){
tiles.getn(ex, ey).setOverlay(ores.get((Item)part.required));
set(tiles.getn(ex, ey), item);
}
Tile rand = tiles.getc(ex + Mathf.range(1), ey + Mathf.range(1));
if(!rand.floor().isLiquid){
//random ores nearby to make it look more natural
rand.setOverlay(ores.get((Item)part.required));
set(rand, item);
}
});
}
@@ -184,24 +205,43 @@ public class BaseGenerator{
Schematics.place(result, cx + result.width/2, cy + result.height/2, team);
return true;
}
//fill drills with items after placing
if(part.required instanceof Item){
Item item = (Item)part.required;
for(Stile tile : result.tiles){
if(tile.block instanceof Drill){
boolean isTaken(Block block, int x, int y){
if(block.isMultiblock()){
int offsetx = -(block.size - 1) / 2;
int offsety = -(block.size - 1) / 2;
Building build = world.tile(tile.x + cx, tile.y + cy).build;
for(int dx = 0; dx < block.size; dx++){
for(int dy = 0; dy < block.size; dy++){
if(overlaps(dx + offsetx + x, dy + offsety + y)){
return true;
if(build != null){
build.items.add(item, build.block.itemCapacity);
}
}
}
}
}else{
return overlaps(x, y);
return true;
}
void set(Tile tile, Item item){
if(ores.containsKey(item)){
tile.setOverlay(ores.get(item));
}else if(oreFloors.containsKey(item)){
tile.setFloor(oreFloors.get(item));
}
}
boolean isTaken(Block block, int x, int y){
int offsetx = -(block.size - 1) / 2;
int offsety = -(block.size - 1) / 2;
int pad = 1;
for(int dx = -pad; dx < block.size + pad; dx++){
for(int dy = -pad; dy < block.size + pad; dy++){
if(overlaps(dx + offsetx + x, dy + offsety + y)){
return true;
}
}
}
return false;

View File

@@ -39,63 +39,6 @@ public abstract class BasicGenerator implements WorldGenerator{
}
//for visual testing only
public void cliffs2(){
for(Tile tile : tiles){
tile.setBlock(Blocks.air);
tile.cost = tile.floor().isLiquid ? 0 : (byte)(noise(tile.x, tile.y, 4, 0.5f, 90f, 1) * 5);
}
for(Tile tile : tiles){
if(tile.floor().isLiquid) continue;
int rotation = 0;
for(int i = 0; i < 8; i++){
Tile other = tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
if(other != null && other.cost < tile.cost){ //down slope
rotation |= (1 << i);
}
}
tile.data = (byte)rotation;
}
for(Tile tile : tiles){
if(tile.data != 0){
int rotation = tile.data;
tile.setBlock(Blocks.cliff);
tile.setOverlay(Blocks.air);
tile.data = (byte)rotation;
}
}
}
public void cliffs(){
for(Tile tile : tiles){
if(!tile.block().isStatic()) continue;
int rotation = 0;
for(int i = 0; i < 8; i++){
Tile other = tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
if(other != null && !other.block().isStatic()){
rotation |= (1 << i);
}
}
if(rotation != 0){
tile.setBlock(Blocks.cliff);
}
tile.data = (byte)rotation;
}
for(Tile tile : tiles){
if(tile.block() != Blocks.cliff && tile.block().isStatic()){
tile.setBlock(Blocks.air);
}
}
}
public void median(int radius){
median(radius, 0.5);
}
@@ -331,18 +274,20 @@ public abstract class BasicGenerator implements WorldGenerator{
}
public void inverseFloodFill(Tile start){
GridBits used = new GridBits(tiles.width, tiles.height);
IntSeq arr = new IntSeq();
arr.add(start.pos());
while(!arr.isEmpty()){
int i = arr.pop();
int x = Point2.x(i), y = Point2.y(i);
tiles.getn(x, y).cost = 2;
used.set(x, y);
for(Point2 point : Geometry.d4){
int newx = x + point.x, newy = y + point.y;
if(tiles.in(newx, newy)){
Tile child = tiles.getn(newx, newy);
if(child.block() == Blocks.air && child.cost != 2){
child.cost = 2;
if(child.block() == Blocks.air && !used.get(child.x, child.y)){
used.set(child.x, child.y);
arr.add(child.pos());
}
}
@@ -350,7 +295,7 @@ public abstract class BasicGenerator implements WorldGenerator{
}
for(Tile tile : tiles){
if(tile.cost != 2 && tile.block() == Blocks.air){
if(!used.get(tile.x, tile.y) && tile.block() == Blocks.air){
tile.setBlock(tile.floor().wall);
}
}

View File

@@ -27,10 +27,10 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
{Blocks.water, Blocks.darksandWater, Blocks.darksand, Blocks.darksand, Blocks.sand, Blocks.sand, Blocks.sand, Blocks.sand, Blocks.sand, Blocks.darksandTaintedWater, Blocks.stone, Blocks.stone, Blocks.stone},
{Blocks.water, Blocks.darksandWater, Blocks.darksand, Blocks.sand, Blocks.salt, Blocks.sand, Blocks.sand, Blocks.sand, Blocks.sand, Blocks.darksandTaintedWater, Blocks.stone, Blocks.stone, Blocks.stone},
{Blocks.water, Blocks.sandWater, Blocks.sand, Blocks.salt, Blocks.salt, Blocks.salt, Blocks.sand, Blocks.stone, Blocks.stone, Blocks.stone, Blocks.snow, Blocks.iceSnow, Blocks.ice},
{Blocks.deepwater, Blocks.water, Blocks.sandWater, Blocks.sand, Blocks.salt, Blocks.sand, Blocks.sand, Blocks.ignarock, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice},
{Blocks.deepwater, Blocks.water, Blocks.sandWater, Blocks.sand, Blocks.salt, Blocks.sand, Blocks.sand, Blocks.basalt, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice},
{Blocks.deepwater, Blocks.water, Blocks.sandWater, Blocks.sand, Blocks.sand, Blocks.sand, Blocks.moss, Blocks.iceSnow, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.snow, Blocks.ice},
{Blocks.deepwater, Blocks.sandWater, Blocks.sand, Blocks.sand, Blocks.moss, Blocks.moss, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.ice, Blocks.snow, Blocks.ice},
{Blocks.taintedWater, Blocks.darksandTaintedWater, Blocks.darksand, Blocks.darksand, Blocks.ignarock, Blocks.moss, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.snow, Blocks.ice, Blocks.ice},
{Blocks.taintedWater, Blocks.darksandTaintedWater, Blocks.darksand, Blocks.darksand, Blocks.basalt, Blocks.moss, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.snow, Blocks.ice, Blocks.ice},
{Blocks.darksandWater, Blocks.darksand, Blocks.darksand, Blocks.darksand, Blocks.moss, Blocks.sporeMoss, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.ice},
{Blocks.darksandWater, Blocks.darksand, Blocks.darksand, Blocks.sporeMoss, Blocks.ice, Blocks.ice, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.ice, Blocks.ice},
{Blocks.taintedWater, Blocks.darksandTaintedWater, Blocks.darksand, Blocks.sporeMoss, Blocks.sporeMoss, Blocks.ice, Blocks.ice, Blocks.snow, Blocks.snow, Blocks.ice, Blocks.ice, Blocks.ice, Blocks.ice},
@@ -284,20 +284,25 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
float difficulty = sector.baseCoverage;
if(sector.hasEnemyBase()){
basegen.generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector);
basegen.generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector, difficulty);
state.rules.attackMode = true;
}else{
state.rules.winWave = 15 * (int)Math.max(difficulty, 1);
state.rules.winWave = 15 * (int)Math.max(difficulty * 5, 1);
}
state.rules.waves = true;
//TODO better waves
state.rules.spawns = defaultWaves.get();
float waveScaling = 1f + difficulty*2;
//scale up the spawning base on difficulty (this is just for testing)
for(SpawnGroup group : state.rules.spawns){
group.unitAmount *= difficulty;
group.unitAmount *= waveScaling;
if(group.unitScaling != SpawnGroup.never){
group.unitScaling *= difficulty;
group.unitScaling /= waveScaling;
}
}
}

View File

@@ -241,8 +241,8 @@ public class ContentParser{
readFields(block, value, true);
if(block.size > BuildBlock.maxSize){
throw new IllegalArgumentException("Blocks cannot be larger than " + BuildBlock.maxSize);
if(block.size > ConstructBlock.maxSize){
throw new IllegalArgumentException("Blocks cannot be larger than " + ConstructBlock.maxSize);
}
//add research tech node

View File

@@ -425,6 +425,7 @@ public class Mods implements Loadable{
/** This must be run on the main thread! */
public void loadScripts(){
Time.mark();
boolean[] any = {false};
try{
eachEnabled(mod -> {
@@ -438,6 +439,7 @@ public class Mods implements Loadable{
if(scripts == null){
scripts = platform.createScripts();
}
any[0] = true;
scripts.run(mod, main);
}catch(Throwable e){
Core.app.post(() -> {
@@ -454,7 +456,9 @@ public class Mods implements Loadable{
content.setCurrentMod(null);
}
Log.info("Time to initialize modded scripts: @", Time.elapsed());
if(any[0]){
Log.info("Time to initialize modded scripts: @", Time.elapsed());
}
}
/** Creates all the content found in mod files. */
@@ -703,14 +707,49 @@ public class Mods implements Loadable{
/** @return whether this mod is supported by the game verison */
public boolean isSupported(){
if(Version.build <= 0 || meta.minGameVersion == null) return true;
if(meta.minGameVersion.contains(".")){
String[] split = meta.minGameVersion.split("\\.");
if(isOutdated()) return false;
int major = getMinMajor(), minor = getMinMinor();
if(Version.build <= 0) return true;
return Version.build >= major && Version.revision >= minor;
}
/** @return whether this mod is outdated, e.g. not compatible with v6. */
public boolean isOutdated(){
//must be at least 105 to indicate v6 compat
return getMinMajor() < 105;
}
public int getMinMajor(){
int major = 0;
String ver = meta.minGameVersion == null ? "0" : meta.minGameVersion;
if(ver.contains(".")){
String[] split = ver.split("\\.");
if(split.length == 2){
return Version.build >= Strings.parseInt(split[0], 0) && Version.revision >= Strings.parseInt(split[1], 0);
major = Strings.parseInt(split[0], 0);
}
}else{
major = Strings.parseInt(ver, 0);
}
return major;
}
public int getMinMinor(){
String ver = meta.minGameVersion == null ? "0" : meta.minGameVersion;
if(ver.contains(".")){
String[] split = ver.split("\\.");
if(split.length == 2){
return Strings.parseInt(split[1], 0);
}
}
return Version.build >= Strings.parseInt(meta.minGameVersion, 0);
return 0;
}
@Override
@@ -788,7 +827,7 @@ public class Mods implements Loadable{
/** Mod metadata information.*/
public static class ModMeta{
public String name, displayName, author, description, version, main, minGameVersion;
public String name, displayName, author, description, version, main, minGameVersion = "0";
public Seq<String> dependencies = Seq.with();
/** Hidden mods are only server-side or client-side, and do not support adding new content. */
public boolean hidden;

View File

@@ -22,7 +22,7 @@ public class Scripts implements Disposable{
private final Seq<String> blacklist = Seq.with(".net.", "java.net", "files", "reflect", "javax", "rhino", "file", "channels", "jdk",
"runtime", "util.os", "rmi", "security", "org.", "sun.", "beans", "sql", "http", "exec", "compiler", "process", "system",
".awt", "socket", "classloader", "oracle", "invoke", "java.util.function", "java.util.stream", "org.");
private final Seq<String> whitelist = Seq.with("mindustry.net", "netserver", "netclient", "com.sun.proxy.$proxy", "mindustry.gen.", "mindustry.logic.");
private final Seq<String> whitelist = Seq.with("mindustry.net", "netserver", "netclient", "com.sun.proxy.$proxy", "mindustry.gen.", "mindustry.logic.", "mindustry.async.");
private final Context context;
private final Scriptable scope;
private boolean errored;

View File

@@ -27,21 +27,10 @@ public class Administration{
/** All player info. Maps UUIDs to info. This persists throughout restarts. Do not access directly. */
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
private IntIntMap lastPlaced = new IntIntMap();
public Administration(){
load();
Events.on(ResetEvent.class, e -> lastPlaced = new IntIntMap());
//keep track of who placed what on the server
Events.on(BlockBuildEndEvent.class, e -> {
//players should be able to configure their own tiles
if(net.server() && e.unit != null && e.unit.isPlayer()){
lastPlaced.put(e.tile.pos(), e.unit.getPlayer().id());
}
});
//anti-spam
addChatFilter((player, message) -> {
long resetTime = Config.messageRateLimit.num() * 1000;
@@ -80,19 +69,13 @@ public class Administration{
action.type != ActionType.placeBlock &&
Config.antiSpam.bool()){
//make sure players can configure their own stuff, e.g. in schematics - but only once.
if(lastPlaced.get(action.tile.pos(), -1) == action.player.id()){
lastPlaced.remove(action.tile.pos());
return true;
}
Ratekeeper rate = action.player.getInfo().rate;
if(rate.allow(Config.interactRateWindow.num() * 1000, Config.interactRateLimit.num())){
return true;
}else{
if(rate.occurences > Config.interactRateKick.num()){
action.player.kick("You are interacting with too many blocks.", 1000 * 30);
}else{
}else if(action.player.getInfo().messageTimer.get(60f * 2f)){
action.player.sendMessage("[scarlet]You are interacting with blocks too quickly.");
}
@@ -168,7 +151,7 @@ public class Administration{
Core.settings.put("playerlimit", limit);
}
public boolean getStrict(){
public boolean isStrict(){
return Config.strict.bool();
}
@@ -660,6 +643,7 @@ public class Administration{
public transient String lastSentMessage;
public transient int messageInfractions;
public transient Ratekeeper rate = new Ratekeeper();
public transient Interval messageTimer = new Interval();
PlayerInfo(String id){
this.id = id;

View File

@@ -44,6 +44,17 @@ public class BeControl{
}
}, updateInterval, updateInterval);
}
if(System.getProperties().contains("becopy")){
try{
Fi dest = Fi.get(System.getProperty("becopy"));
Fi self = Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
self.copyTo(dest);
}catch(Throwable e){
e.printStackTrace();
}
}
}
/** asynchronously checks for updates. */
@@ -68,13 +79,7 @@ public class BeControl{
}else{
Core.app.post(() -> done.get(false));
}
}, error -> Core.app.post(() -> {
if(!headless){
ui.showException(error);
}else{
error.printStackTrace();
}
}));
}, error -> {}); //ignore errors
}
/** @return whether a new update is available */
@@ -93,14 +98,17 @@ public class BeControl{
boolean[] cancel = {false};
float[] progress = {0};
int[] length = {0};
Fi file = Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
Fi file = bebuildDirectory.child("client-be-" + updateBuild + ".jar");
Fi fileDest = System.getProperties().contains("becopy") ?
Fi.get(System.getProperty("becopy")) :
Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
BaseDialog dialog = new BaseDialog("@be.updating");
download(updateUrl, file, i -> length[0] = i, v -> progress[0] = v, () -> cancel[0], () -> {
try{
Runtime.getRuntime().exec(OS.isMac ?
new String[]{"java", "-XstartOnFirstThread", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()} :
new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()}
new String[]{"java", "-XstartOnFirstThread", "-DlastBuild=" + Version.build, "-Dberestart", "-Dbecopy=" + fileDest.absolutePath(), "-jar", file.absolutePath()} :
new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-Dbecopy=" + fileDest.absolutePath(), "-jar", file.absolutePath()}
);
System.exit(0);
}catch(IOException e){

View File

@@ -152,7 +152,9 @@ public class Net{
}
public void disconnect(){
Log.info("Disconnecting.");
if(active && !server){
Log.info("Disconnecting.");
}
provider.disconnectClient();
server = false;
active = false;

View File

@@ -1,6 +1,5 @@
package mindustry.net;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
@@ -12,15 +11,13 @@ import mindustry.net.Packets.*;
import java.io.*;
import static mindustry.Vars.netServer;
import static mindustry.Vars.*;
public abstract class NetConnection{
public final String address;
public String uuid = "AAAAAAAA", usid = uuid;
public boolean mobile, modclient;
public @Nullable Player player;
public @Nullable Unitc lastUnit;
public Vec2 lastPosition = new Vec2();
public boolean kicked = false;
/** ID of last received client snapshot. */

View File

@@ -11,8 +11,6 @@ import mindustry.world.modules.ItemModule.*;
import java.util.*;
public class ItemSeq implements Iterable<ItemStack>, Serializable{
private final static ItemStack tmp = new ItemStack();
protected final int[] values;
public int total;

View File

@@ -205,7 +205,7 @@ public class Planet extends UnlockableContent{
sum += 2f;
}
sector.baseCoverage = sum;
sector.baseCoverage = Mathf.clamp(sum / 5f);
}
}

View File

@@ -32,6 +32,7 @@ public class Sector{
public @Nullable SaveSlot save;
public @Nullable SectorPreset preset;
/** Number 0-1 indicating the difficulty based on nearby bases. */
public float baseCoverage;
//TODO implement a dynamic launch period
@@ -390,6 +391,8 @@ public class Sector{
/** Has an enemy base. */
base,
/** Has spore weather. */
spores
spores,
/** Path from core to spawns requires traversing water. */
navalPath
}
}

View File

@@ -49,7 +49,7 @@ public class UnitType extends UnlockableContent{
public boolean destructibleWreck = true;
public float groundLayer = Layer.groundUnit;
public float sway = 1f;
public int payloadCapacity = 1;
public float payloadCapacity = 8;
public int commandLimit = 24;
public float visualElevation = -1f;
public boolean allowLegStep = false;
@@ -62,12 +62,14 @@ public class UnitType extends UnlockableContent{
public float legLength = 10f, legSpeed = 0.1f, legTrns = 1f, legBaseOffset = 0f, legMoveSpace = 1f, legExtension = 0, legPairOffset = 0, legLengthScl = 1f, kinematicScl = 1f, maxStretch = 1.75f;
public float legSplashDamage = 0f, legSplashRange = 5;
public boolean flipBackLegs = true;
public float mechLegMoveScl = 1f;
public int itemCapacity = 30;
public int itemCapacity = -1;
public int ammoCapacity = 220;
public int mineTier = -1;
public float buildSpeed = 1f, mineSpeed = 1f;
public boolean canDrown = true;
public float engineOffset = 5f, engineSize = 2.5f;
public float strafePenalty = 0.5f;
public float hitsize = 6f;
@@ -110,6 +112,17 @@ public class UnitType extends UnlockableContent{
return unit;
}
public Unit spawn(Team team, float x, float y){
Unit out = create(team);
out.set(x, y);
out.add();
return out;
}
public Unit spawn(float x, float y){
return spawn(state.rules.defaultTeam, x, y);
}
public boolean hasWeapons(){
return weapons.size > 0;
}
@@ -183,6 +196,10 @@ public class UnitType extends UnlockableContent{
singleTarget = weapons.size <= 1;
if(itemCapacity < 0){
itemCapacity = Math.max(Mathf.round(hitsize * 7, 20), 20);
}
//set up default range
if(range < 0){
range = Float.MAX_VALUE;
@@ -254,25 +271,25 @@ public class UnitType extends UnlockableContent{
//region drawing
public void draw(Unit unit){
Mechc legs = unit instanceof Mechc ? (Mechc)unit : null;
float z = unit.elevation > 0.5f ? (lowAltitude ? Layer.flyingUnitLow : Layer.flyingUnit) : groundLayer;
Mechc mech = unit instanceof Mechc ? (Mechc)unit : null;
float z = unit.elevation > 0.5f ? (lowAltitude ? Layer.flyingUnitLow : Layer.flyingUnit) : groundLayer + Mathf.clamp(hitsize/4000f, 0, 0.01f);
if(unit.controller().isBeingControlled(player.unit())){
drawControl(unit);
}
if(unit.isFlying()){
if(unit.isFlying() || visualElevation > 0){
Draw.z(Math.min(Layer.darkness, z - 1f));
drawShadow(unit);
}
Draw.z(z - 0.02f);
if(legs != null){
drawMech((Unit & Mechc)legs);
if(mech != null){
drawMech((Unit & Mechc)mech);
float ft = Mathf.sin(legs.walkTime(), 3f, 3f);
legOffset.trns(legs.baseRotation(), 0f, Mathf.lerp(ft * 0.18f * sway, 0f, unit.elevation));
float ft = Mathf.sin(mech.walkTime(), 3f * mechLegMoveScl, 3f);
legOffset.trns(mech.baseRotation(), 0f, Mathf.lerp(ft * 0.18f * sway, 0f, unit.elevation));
unit.trns(legOffset.x, legOffset.y);
}
@@ -300,7 +317,7 @@ public class UnitType extends UnlockableContent{
drawShield(unit);
}
if(legs != null){
if(mech != null){
unit.trns(-legOffset.x, -legOffset.y);
}
@@ -478,7 +495,7 @@ public class UnitType extends UnlockableContent{
}
public <T extends Unit & Legsc> void drawLegs(T unit){
//Draw.z(Layer.groundUnit - 0.02f);
applyColor(unit);
Leg[] legs = unit.legs();
@@ -494,8 +511,9 @@ public class UnitType extends UnlockableContent{
Draw.rect(baseRegion, unit.x, unit.y, rotation);
}
//TODO figure out layering
for(int i = 0; i < legs.length; i++){
//legs are drawn front first
for(int j = legs.length - 1; j >= 0; j--){
int i = (j % 2 == 0 ? j/2 : legs.length - 1 - j/2);
Leg leg = legs[i];
float angle = unit.legAngle(rotation, i);
boolean flip = i >= legs.length/2f;
@@ -539,7 +557,7 @@ public class UnitType extends UnlockableContent{
Draw.mixcol(Color.white, unit.hitTime);
float e = unit.elevation;
float sin = Mathf.lerp(Mathf.sin(unit.walkTime(), 3f, 1f), 0f, e);
float sin = Mathf.lerp(Mathf.sin(unit.walkTime(), 3f * mechLegMoveScl, 1f), 0f, e);
float ft = sin*(2.5f + (unit.hitSize-8f)/2f);
float boostTrns = e * 2f;
@@ -570,6 +588,7 @@ public class UnitType extends UnlockableContent{
}
public void applyColor(Unit unit){
Draw.color();
Draw.mixcol(Color.white, unit.hitTime);
if(unit.drownTime > 0 && unit.floorOn().isDeep()){
Draw.mixcol(unit.floorOn().mapColor, unit.drownTime * 0.8f);

Some files were not shown because too many files have changed in this diff Show More