Merge branch 'master' into port-field
This commit is contained in:
@@ -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(){
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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)){
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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){}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -24,6 +24,7 @@ public class LiquidBulletType extends BulletType{
|
||||
this.status = liquid.effect;
|
||||
}
|
||||
|
||||
ammoMultiplier = 1f;
|
||||
lifetime = 74f;
|
||||
statusDuration = 60f * 2f;
|
||||
despawnEffect = Fx.none;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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){}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}}
|
||||
);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ public class Planet extends UnlockableContent{
|
||||
sum += 2f;
|
||||
}
|
||||
|
||||
sector.baseCoverage = sum;
|
||||
sector.baseCoverage = Mathf.clamp(sum / 5f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user