Merge remote-tracking branch 'upstream/master' into burning-affects-tiles

This commit is contained in:
Leonwang4234
2020-10-02 17:47:11 -07:00
895 changed files with 41849 additions and 24819 deletions

View File

@@ -33,6 +33,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void setup(){
loadLogger();
loader = new LoadRenderer();
Events.fire(new ClientCreateEvent());

View File

@@ -37,11 +37,11 @@ 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.*/
public static final String schematicBaseStart ="bXNjaAB";
public static final String schematicBaseStart ="bXNjaA";
/** IO buffer size. */
public static final int bufferSize = 8192;
/** global charset, since Android doesn't support the Charsets class */
@@ -60,8 +60,10 @@ public class Vars implements Loadable{
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers.json";
/** URL to the JSON file containing all the BE servers. Only queried in BE. */
public static final String serverJsonBeURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_be.json";
/** URL to the JSON file containing all the BE servers. Only queried in the V6 alpha (will be removed once it's out). */
public static final String serverJsonV6URL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_v6.json";
/** URL of the github issue report template.*/
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?template=bug_report.md";
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?labels=bug&template=bug_report.md";
/** list of built-in servers.*/
public static final Seq<String> defaultServers = Seq.with();
/** maximum distance between mine and core that supports automatic transferring */
@@ -78,20 +80,24 @@ 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 */
public static final float sectorDestructionTurns = 3f;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.05f;
public static final float minArmorDamage = 0.1f;
/** launch animation duration */
public static final float launchDuration = 140f;
/** size of tiles in units */
public static final int tilesize = 8;
/** size of one tile payload (^2) */
public static final float tilePayload = tilesize * tilesize;
/** tile used in certain situations, instead of null */
public static Tile emptyTile;
/** for map generator dialog */
public static boolean updateEditorOnChange = false;
/** size of tiles in units */
public static final int tilesize = 8;
/** all choosable player colors in join/host dialog */
public static final Color[] playerColors = {
Color.valueOf("82759a"),
@@ -199,8 +205,7 @@ public class Vars implements Loadable{
public static NetServer netServer;
public static NetClient netClient;
public static
Player player;
public static Player player;
@Override
public void loadAsync(){

View File

@@ -6,7 +6,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.world.*;
import static mindustry.Vars.world;
import static mindustry.Vars.*;
public class Astar{
public static final DistanceHeuristic manhattan = (x1, y1, x2, y2) -> Math.abs(x1 - x2) + Math.abs(y1 - y2);

View File

@@ -63,6 +63,7 @@ public class BaseAI{
int range = 150;
Position pos = randomPosition();
//when there are no random positions, do nothing.
if(pos == null) return;
@@ -159,7 +160,10 @@ public class BaseAI{
private void tryWalls(){
Block wall = Blocks.copperWall;
Tile spawn = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core().tile : data.team.core().tile;
Building spawnt = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core() : data.team.core();
Tile spawn = spawnt == null ? null : spawnt.tile;
if(spawn == null) return;
for(int wx = lastX; wx <= lastX + lastW; wx++){
for(int wy = lastY; wy <= lastY + lastH; wy++){

View File

@@ -1,6 +1,7 @@
package mindustry.ai;
import arc.*;
import arc.math.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
@@ -16,7 +17,7 @@ import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.*;
public class BaseRegistry{
public Seq<BasePart> cores = new Seq<>();
@@ -68,7 +69,7 @@ public class BaseRegistry{
}
schem.tiles.removeAll(s -> s.block.buildVisibility == BuildVisibility.sandboxOnly);
part.tier = schem.tiles.sumf(s -> s.block.buildCost / s.block.buildCostMultiplier);
part.tier = schem.tiles.sumf(s -> Mathf.pow(s.block.buildCost / s.block.buildCostMultiplier, 1.2f));
if(part.core != null){
cores.add(part);
@@ -92,7 +93,7 @@ public class BaseRegistry{
}
}
cores.sort(Structs.comps(Structs.comparingFloat(b -> b.core.health), Structs.comparingFloat(b -> b.tier)));
cores.sort(b -> b.tier);
parts.sort();
reqParts.each((key, arr) -> arr.sort());
}

View File

@@ -6,6 +6,7 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.EnumSet;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -33,11 +34,11 @@ public class BlockIndexer{
/** Maps each team ID to a quarant. A quadrant is a grid of bits, where each bit is set if and only if there is a block of that team in that quadrant. */
private GridBits[] structQuadrants;
/** Stores all damaged tile entities by team. */
private BuildingArray[] damagedTiles = new BuildingArray[Team.all.length];
private ObjectSet<Building>[] damagedTiles = new ObjectSet[Team.all.length];
/** 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 +53,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){
@@ -70,9 +71,10 @@ public class BlockIndexer{
Events.on(WorldLoadEvent.class, event -> {
scanOres.clear();
scanOres.addAll(Item.getAllOres());
damagedTiles = new BuildingArray[Team.all.length];
damagedTiles = new ObjectSet[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++){
@@ -138,16 +140,16 @@ public class BlockIndexer{
}
/** Returns all damaged tiles by team. */
public BuildingArray getDamaged(Team team){
returnArray.clear();
public ObjectSet<Building> getDamaged(Team team){
breturnArray.clear();
if(damagedTiles[team.id] == null){
damagedTiles[team.id] = new BuildingArray();
damagedTiles[team.id] = new ObjectSet<>();
}
BuildingArray set = damagedTiles[team.id];
ObjectSet<Building> 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);
}
}
@@ -164,6 +166,11 @@ public class BlockIndexer{
return flagMap[team.id][type.ordinal()];
}
@Nullable
public Tile findClosestFlag(float x, float y, Team team, BlockFlag flag){
return Geometry.findClosest(x, y, getAllied(team, flag));
}
public boolean eachBlock(Teamc team, float range, Boolf<Building> pred, Cons<Building> cons){
return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons);
}
@@ -185,7 +192,7 @@ public class BlockIndexer{
if(other == null) continue;
if((other.team() == team || team == null) && pred.get(other) && intSet.add(other.pos())){
if(other.team == team && pred.get(other) && intSet.add(other.pos())){
cons.get(other);
any = true;
}
@@ -212,15 +219,18 @@ public class BlockIndexer{
}
public void notifyTileDamaged(Building entity){
if(damagedTiles[entity.team().id] == null){
damagedTiles[entity.team().id] = new BuildingArray();
if(damagedTiles[entity.team.id] == null){
damagedTiles[entity.team.id] = new ObjectSet<>();
}
damagedTiles[entity.team().id].add(entity);
damagedTiles[entity.team.id].add(entity);
}
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 +261,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;
}
@@ -390,7 +400,7 @@ public class BlockIndexer{
for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){
Building result = world.build(x, y);
//when a targetable block is found, mark this quadrant as occupied and stop searching
if(result != null && result.team() == team){
if(result != null && result.team == team){
bits.set(quadrantX, quadrantY);
break outer;
}
@@ -472,35 +482,4 @@ public class BlockIndexer{
return tiles.iterator();
}
}
//TODO copy-pasted code, generics would be nice here
public static class BuildingArray implements Iterable<Building>{
private Seq<Building> tiles = new Seq<>(false, 16);
private IntSet contained = new IntSet();
public void add(Building tile){
if(contained.add(tile.pos())){
tiles.add(tile);
}
}
public void remove(Building tile){
if(contained.remove(tile.pos())){
tiles.remove(tile);
}
}
public int size(){
return tiles.size;
}
public Building first(){
return tiles.first();
}
@Override
public Iterator<Building> iterator(){
return tiles.iterator();
}
}
}

View File

@@ -23,52 +23,109 @@ 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) ? 6000 : 0) +
(PathTile.damages(tile) ? 30 : 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 +
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
(PathTile.deep(tile) ? -1 : 0) +
(PathTile.damages(tile) ? 35 : 0)
);
//maps team, cost, type to flow field
Flowfield[][][] cache;
/** tile data, see PathTileStruct */
private int[][] tiles;
int[][] tiles = new int[0][0];
/** 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);
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), 80),
tile.getTeamID(),
tile.solid(),
tile.floor().isLiquid,
tile.staticDarkness() >= 2,
nearLiquid,
nearGround,
nearSolid,
tile.floor().isDeep(),
tile.floor().damageTaken > 0.00001f
);
}
/** Starts or restarts the pathfinding thread. */
@@ -104,7 +161,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 +188,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 +225,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 +278,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 +290,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 +305,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 +333,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 +373,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 +394,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 +409,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 +422,101 @@ 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();
/** 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;
//whether the floor damages
boolean damages;
}
}

View File

@@ -3,22 +3,26 @@ 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.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
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());
@@ -41,6 +45,8 @@ public class WaveSpawner{
spawning = true;
for(SpawnGroup group : state.rules.spawns){
if(group.type == null) continue;
int spawned = group.getUnitsSpawned(state.wave - 1);
if(group.type.flying){
@@ -87,15 +93,41 @@ 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);
}
}
}
}
private void eachFlyerSpawn(Floatc2 cons){
for(Tile tile : spawns){
float angle = Angles.angle(tile.x, tile.y, world.width() / 2, world.height() / 2);
float angle = Angles.angle(world.width() / 2, world.height() / 2, tile.x, tile.y);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
@@ -125,14 +157,18 @@ public class WaveSpawner{
}
private void spawnEffect(Unit unit){
Fx.unitSpawn.at(unit.x(), unit.y(), 0f, unit);
Time.run(30f, () -> {
unit.add();
Fx.spawn.at(unit);
});
Call.spawnEffect(unit.x, unit.y, unit.type());
Time.run(30f, unit::add);
}
private interface SpawnConsumer{
void accept(float x, float y, boolean shockwave);
}
@Remote(called = Loc.server, unreliable = true)
public static void spawnEffect(float x, float y, UnitType type){
Fx.unitSpawn.at(x, y, 0f, type);
Time.run(30f, () -> Fx.spawn.at(x, y));
}
}

View File

@@ -11,4 +11,6 @@ import arc.math.geom.*;
public interface FormationMember{
/** Returns the target location of this formation member. */
Vec3 formationPos();
float formationSize();
}

View File

@@ -12,6 +12,8 @@ import arc.math.geom.*;
*/
public abstract class FormationPattern{
public int slots;
/** Spacing between members. */
public float spacing = 20f;
/** Returns the location of the given slot index. */
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);

View File

@@ -5,20 +5,14 @@ import arc.math.geom.*;
import mindustry.ai.formations.*;
public class CircleFormation extends FormationPattern{
/** The radius of one member. This is needed to determine how close we can pack a given number of members around circle. */
public float memberRadius;
/** Angle offset. */
public float angleOffset = 0;
public CircleFormation(float memberRadius){
this.memberRadius = memberRadius;
}
@Override
public Vec3 calculateSlotLocation(Vec3 outLocation, int slotNumber){
if(slots > 1){
float angle = (360f * slotNumber) / slots;
float radius = memberRadius / (float)Math.sin(180f / slots * Mathf.degRad);
float radius = spacing / (float)Math.sin(180f / slots * Mathf.degRad);
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
}else{
outLocation.set(0, 0, 360f * slotNumber);

View File

@@ -5,7 +5,6 @@ import arc.math.geom.*;
import mindustry.ai.formations.*;
public class SquareFormation extends FormationPattern{
public float spacing = 20;
@Override
public Vec3 calculateSlotLocation(Vec3 out, int slot){

View File

@@ -1,18 +1,20 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
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 = 1500;
boolean found = false;
@Nullable Builderc following;
@Override
public void updateUnit(){
@@ -22,12 +24,29 @@ public class BuilderAI extends AIController{
builder.lookAt(builder.vel().angle());
}
//approach request if building
builder.updateBuilding(true);
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));
@@ -40,9 +59,36 @@ 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()){
Queue<BlockPlan> blocks = unit.team().data().blocks;
if(!unit.team.data().blocks.isEmpty() && following == null && timer.get(timerTarget3, 60 * 2f)){
Queue<BlockPlan> blocks = unit.team.data().blocks;
BlockPlan block = blocks.first();
//check if it's already been placed
@@ -50,32 +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);
}
}
}
}
protected void moveTo(Position target, float circleLength){
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
}

View File

@@ -1,93 +1,50 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class FlyingAI extends AIController{
@Override
public void updateUnit(){
if(unit.moving()){
unit.rotation(unit.vel().angle());
}
if(unit.isFlying()){
unit.wobble();
}
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y())){
target = null;
}
if(retarget()){
targetClosest();
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
}
boolean shoot = false;
if(target != null && unit.hasWeapons()){
public void updateMovement(){
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
if(unit.type().weapons.first().rotate){
moveTo(unit.range() * 0.85f);
moveTo(target, unit.range() * 0.8f);
unit.lookAt(target);
}else{
attack(80f);
}
shoot = unit.inRange(target);
if(shoot && unit.type().hasWeapons()){
Vec2 to = Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed);
unit.aim(to);
attack(100f);
}
}
unit.controlWeapons(shoot, shoot);
if(target == null && command() == UnitCommand.attack && state.rules.waves && unit.team == state.rules.defaultTeam){
moveTo(getClosestSpawner(), state.rules.dropZoneRadius + 120f);
}
if(command() == UnitCommand.rally){
moveTo(targetFlag(unit.x, unit.y, BlockFlag.rally, false), 60f);
}
}
@Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
Teamc result = target(x, y, range, air, ground);
if(result != null) return result;
if(ground) result = targetFlag(x, y, BlockFlag.producer, true);
if(result != null) return result;
if(ground) result = targetFlag(x, y, BlockFlag.turret, true);
if(result != null) return result;
return null;
}
//TODO clean up
protected void circle(float circleLength){
circle(circleLength, unit.type().speed);
}
protected void circle(float circleLength, float speed){
if(target == null) return;
vec.set(target).sub(unit);
if(vec.len() < circleLength){
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Time.delta);
unit.moveAt(vec);
}
protected void moveTo(float circleLength){
if(target == null) return;
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
protected void attack(float circleLength){
vec.set(target).sub(unit);
@@ -100,7 +57,7 @@ public class FlyingAI extends AIController{
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
}
vec.setLength(unit.type().speed * Time.delta);
vec.setLength(unit.type().speed);
unit.moveAt(vec);
}

View File

@@ -1,11 +1,12 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.ai.formations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
public class FormationAI extends AIController implements FormationMember{
public Unit leader;
@@ -25,41 +26,56 @@ public class FormationAI extends AIController implements FormationMember{
@Override
public void updateUnit(){
UnitType type = unit.type();
if(leader.dead){
unit.resetController();
return;
}
unit.controlWeapons(leader.isRotate(), leader.isShooting);
if(unit.type().canBoost && unit.canPassOn()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
unit.controlWeapons(true, leader.isShooting);
// unit.moveAt(Tmp.v1.set(deltaX, deltaY).limit(unit.type().speed));
if(leader.isShooting){
unit.aimLook(leader.aimX(), leader.aimY());
}else{
if(!leader.moving() || !unit.type().rotateShooting){
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
}else{
unit.lookAt(leader.rotation);
}
unit.aim(leader.aimX(), leader.aimY());
if(unit.type().rotateShooting){
unit.lookAt(leader.aimX(), leader.aimY());
}else if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
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));
}
float margin = 3f;
unit.moveAt(realtarget.sub(unit).limit(unit.type().speed));
if(unit.dst(realtarget) <= margin){
unit.vel.approachDelta(Vec2.ZERO, type.speed * type.accel / 2f);
}else{
unit.moveAt(realtarget.sub(unit).limit(type.speed));
}
}
@Override
public void removed(Unit unit){
if(formation != null){
formation.removeMember(this);
unit.resetController();
}
}
@Override
public float formationSize(){
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
//TODO return formation size
//eturn ((Commanderc)unit).formation().
}
return unit.hitSize * 1f;
}
@Override
public boolean isBeingControlled(Unit player){
return leader == player;

View File

@@ -1,89 +1,86 @@
package mindustry.ai.types;
import mindustry.ai.Pathfinder.*;
import arc.math.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.pathfinder;
import java.util.*;
import static mindustry.Vars.*;
public class GroundAI extends AIController{
//static final float commandCooldown = 60f * 10;
//float commandTimer = 60*3;
@Override
public void updateUnit(){
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
target = null;
}
if(retarget()){
targetClosest();
}
public void updateMovement(){
Building core = unit.closestEnemyCore();
if(core != null){
if(unit.within(core,unit.range() / 1.1f)){
target = core;
if(core != null && unit.within(core, unit.range() / 1.1f + core.block.size * tilesize / 2f)){
target = core;
Arrays.fill(targets, core);
}
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
if(!unit.within(core, unit.range() * 0.5f)){
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)){
moveTo(Pathfinder.fieldRally);
}
}
boolean rotate = false, shoot = false;
if(!Units.invalidateTarget(target, unit, unit.range())){
rotate = true;
shoot = unit.within(target, unit.range());
if(unit.type().canBoost && unit.tileOn() != null && !unit.tileOn().solid()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type().rotateShooting){
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
unit.lookAt(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
unit.controlWeapons(rotate, shoot);
//auto-command works but it's very buggy
/*
if(unit instanceof Commanderc){
Commanderc c = (Commanderc)unit;
//try to command when missing members
if(c.controlling().size <= unit.type().commandLimit/2){
commandTimer -= Time.delta;
if(commandTimer <= 0){
c.commandNearby(new SquareFormation(), u -> !(u.controller() instanceof FormationAI) && !(u instanceof Commanderc));
commandTimer = commandCooldown;
}
}
}*/
}
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 pathTarget){
int costType = unit.pathType();
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, pathTarget));
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}

View File

@@ -0,0 +1,83 @@
package mindustry.ai.types;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class MinerAI extends AIController{
boolean mining = true;
Item targetItem;
Tile ore;
@Override
protected void updateMovement(){
Building core = unit.closestCore();
if(!(unit instanceof Minerc) || core == null) return;
Minerc miner = (Minerc)unit;
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type().range)){
miner.mineTile(null);
}
if(mining){
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
//core full of the target item, do nothing
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
unit.clearItem();
miner.mineTile(null);
return;
}
//if inventory is full, drop it off.
if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
mining = false;
}else{
if(retarget() && targetItem != null){
ore = indexer.findClosestOre(unit.x, unit.y, targetItem);
}
if(ore != null){
moveTo(ore, unit.type().range / 2f);
if(unit.within(ore, unit.type().range)){
miner.mineTile(ore);
}
if(ore.block() != Blocks.air){
mining = false;
}
}
}
}else{
miner.mineTile(null);
if(unit.stack.amount == 0){
mining = true;
return;
}
if(unit.within(core, unit.type().range)){
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
}
unit.clearItem();
mining = true;
}
circle(core, unit.type().range / 1.8f);
}
}
@Override
protected void updateTargeting(){
}
}

View File

@@ -0,0 +1,34 @@
package mindustry.ai.types;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.world.blocks.ConstructBlock.*;
//note that repair AI doesn't attack anything even if it theoretically can
public class RepairAI extends AIController{
@Override
protected void updateMovement(){
boolean shoot = false;
if(target != null){
if(!target.within(unit, unit.type().range * 0.8f)){
moveTo(target, unit.type().range * 0.8f);
}
if(target.within(unit, unit.type().range)){
unit.aim(target);
shoot = true;
}
}
unit.controlWeapons(shoot);
}
@Override
protected void updateTargeting(){
target = Units.findDamagedTile(unit.team, unit.x, unit.y);
if(target instanceof ConstructBuild) target = null;
}
}

View File

@@ -1,10 +1,12 @@
package mindustry.ai.types;
import mindustry.*;
import mindustry.ai.Pathfinder.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class SuicideAI extends GroundAI{
static boolean blockedByBlock;
@@ -17,17 +19,17 @@ public class SuicideAI extends GroundAI{
}
if(retarget()){
targetClosest();
target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
}
Building core = unit.closestEnemyCore();
boolean rotate = false, shoot = false;
boolean rotate = false, shoot = false, moveToTarget = false;
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));
@@ -39,7 +41,7 @@ public class SuicideAI extends GroundAI{
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
Tile tile = Vars.world.tile(x, y);
if(tile != null && tile.build == target) return false;
if(tile != null && tile.build != null && tile.build.team() != unit.team()){
if(tile != null && tile.build != null && tile.build.team != unit.team()){
blockedByBlock = true;
return true;
}else{
@@ -53,13 +55,22 @@ public class SuicideAI extends GroundAI{
}
if(!blocked){
moveToTarget = true;
//move towards target directly
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
}
}else{
if(core != null){
moveToCore(FlagTarget.enemyCores);
}
if(!moveToTarget){
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
moveTo(Pathfinder.fieldRally);
}
}else if(command() == UnitCommand.attack && core != null){
moveTo(Pathfinder.fieldCore);
}
if(unit.moving()) unit.lookAt(unit.vel().angle());

View File

@@ -7,7 +7,7 @@ import mindustry.game.EventType.*;
import java.util.concurrent.*;
import static mindustry.Vars.state;
import static mindustry.Vars.*;
public class AsyncCore{
//all processes to be executed each frame

View File

@@ -1,32 +1,25 @@
package mindustry.async;
import arc.*;
import arc.box2d.*;
import arc.box2d.BodyDef.*;
import arc.math.*;
import arc.math.geom.*;
import arc.math.geom.QuadTree.*;
import arc.struct.*;
import mindustry.*;
import mindustry.async.PhysicsProcess.PhysicsWorld.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class PhysicsProcess implements AsyncProcess{
private Physics physics;
private static final int
layers = 3,
layerGround = 0,
layerLegs = 1,
layerFlying = 2;
private PhysicsWorld physics;
private Seq<PhysicRef> refs = new Seq<>(false);
private BodyDef def;
private EntityGroup<? extends Physicsc> group;
private Filter flying = new Filter(){{
maskBits = categoryBits = 2;
}}, ground = new Filter(){{
maskBits = categoryBits = 1;
}};
public PhysicsProcess(){
def = new BodyDef();
def.type = BodyType.dynamicBody;
//currently only enabled for units
group = Groups.unit;
}
//currently only enabled for units
private EntityGroup<Unit> group = Groups.unit;
@Override
public void begin(){
@@ -35,46 +28,39 @@ public class PhysicsProcess implements AsyncProcess{
//remove stale entities
refs.removeAll(ref -> {
if(!ref.entity.isAdded()){
physics.destroyBody(ref.body);
physics.remove(ref.body);
ref.entity.physref(null);
return true;
}
return false;
});
//find entities without bodies and assign them
for(Physicsc entity : group){
boolean grounded = entity.isGrounded();
//find Unit without bodies and assign them
for(Unit entity : group){
if(entity.physref() == null){
//add bodies to entities that have none
FixtureDef fd = new FixtureDef();
fd.shape = new CircleShape(entity.hitSize() / 2f);
fd.density = 5f;
fd.restitution = 0.0f;
fd.filter.maskBits = fd.filter.categoryBits = (grounded ? ground : flying).maskBits;
def.position.set(entity);
Body body = physics.createBody(def);
body.createFixture(fd);
PhysicsBody body = new PhysicsBody();
body.x = entity.x();
body.y = entity.y();
body.mass = entity.mass();
body.radius = entity.hitSize() / 2f;
PhysicRef ref = new PhysicRef(entity, body);
refs.add(ref);
entity.physref(ref);
physics.add(body);
}
//save last position
PhysicRef ref = entity.physref();
if(ref.wasGround != grounded){
//set correct filter
ref.body.getFixtureList().first().setFilterData(grounded ? ground : flying);
ref.wasGround = grounded;
}
ref.velocity.set(entity.deltaX(), entity.deltaY());
ref.position.set(entity);
ref.body.layer =
entity.type().allowLegStep ? layerLegs :
entity.isGrounded() ? layerGround : layerFlying;
ref.x = entity.x();
ref.y = entity.y();
}
}
@@ -85,26 +71,11 @@ public class PhysicsProcess implements AsyncProcess{
//get last position vectors before step
for(PhysicRef ref : refs){
//force set target position
ref.body.setPosition(ref.position.x, ref.position.y);
//save last position for delta
ref.lastPosition.set(ref.body.getPosition());
//write velocity
ref.body.setLinearVelocity(ref.velocity);
ref.lastVelocity.set(ref.velocity);
ref.body.x = ref.x;
ref.body.y = ref.y;
}
physics.step(Core.graphics.getDeltaTime(), 5, 8);
//get delta vectors
for(PhysicRef ref : refs){
//get delta vector
ref.delta.set(ref.body.getPosition()).sub(ref.lastPosition);
ref.velocity.set(ref.body.getLinearVelocity());
}
physics.update();
}
@Override
@@ -115,13 +86,8 @@ public class PhysicsProcess implements AsyncProcess{
for(PhysicRef ref : refs){
Physicsc entity = ref.entity;
entity.move(ref.delta.x, ref.delta.y);
//save last position
ref.position.set(entity);
//add delta velocity - this doesn't work very well yet
//entity.vel().add(ref.velocity).sub(ref.lastVelocity);
//move by delta
entity.move(ref.body.x - ref.x, ref.body.y - ref.y);
}
}
@@ -129,7 +95,6 @@ public class PhysicsProcess implements AsyncProcess{
public void reset(){
if(physics != null){
refs.clear();
physics.dispose();
physics = null;
}
}
@@ -138,18 +103,95 @@ public class PhysicsProcess implements AsyncProcess{
public void init(){
reset();
physics = new Physics(new Vec2(), true);
physics = new PhysicsWorld(Vars.world.getQuadBounds(new Rect()));
}
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 PhysicsBody body;
public float x, y;
public PhysicRef(Physicsc entity, Body body){
public PhysicRef(Physicsc entity, PhysicsBody body){
this.entity = entity;
this.body = body;
}
}
//world for simulating physics in a different thread
public static class PhysicsWorld{
//how much to soften movement by
private static final float scl = 1.25f;
private final QuadTree<PhysicsBody>[] trees = new QuadTree[layers];
private final Seq<PhysicsBody> bodies = new Seq<>(false, 16, PhysicsBody.class);
private final Seq<PhysicsBody> seq = new Seq<>(PhysicsBody.class);
private final Rect rect = new Rect();
private final Vec2 vec = new Vec2();
public PhysicsWorld(Rect bounds){
for(int i = 0; i < layers; i++){
trees[i] = new QuadTree<>(new Rect(bounds));
}
}
public void add(PhysicsBody body){
bodies.add(body);
}
public void remove(PhysicsBody body){
bodies.remove(body);
}
public void update(){
for(int i = 0; i < layers; i++){
trees[i].clear();
}
for(int i = 0; i < bodies.size; i++){
PhysicsBody body = bodies.items[i];
body.collided = false;
trees[body.layer].insert(body);
}
for(int i = 0; i < bodies.size; i++){
PhysicsBody body = bodies.items[i];
body.hitbox(rect);
seq.size = 0;
trees[body.layer].intersect(rect, seq);
for(int j = 0; j < seq.size; j++){
PhysicsBody other = seq.items[j];
if(other == body || other.collided) continue;
float rs = body.radius + other.radius;
float dst = Mathf.dst(body.x, body.y, other.x, other.y);
if(dst < rs){
vec.set(body.x - other.x, body.y - other.y).setLength(rs - dst);
float ms = body.mass + other.mass;
float m1 = other.mass / ms, m2 = body.mass / ms;
body.x += vec.x * m1 / scl;
body.y += vec.y * m1 / scl;
other.x -= vec.x * m2 / scl;
other.y -= vec.y * m2 / scl;
}
}
body.collided = true;
}
}
public static class PhysicsBody implements QuadTreeObject{
public float x, y, radius, mass;
public int layer = 0;
public boolean collided = false;
@Override
public void hitbox(Rect out){
out.setCentered(x, y, radius * 2, radius * 2);
}
}
}
}

View File

@@ -5,6 +5,7 @@ import mindustry.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.payloads.*;
import java.util.*;
@@ -13,7 +14,6 @@ public class TeamIndexProcess implements AsyncProcess{
private QuadTree<Unit>[] trees = new QuadTree[Team.all.length];
private int[] counts = new int[Team.all.length];
private int[][] typeCounts = new int[Team.all.length][0];
private int[][] activeCounts = new int[Team.all.length][0];
public QuadTree<Unit> tree(Team team){
if(trees[team.id] == null) trees[team.id] = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
@@ -29,10 +29,6 @@ public class TeamIndexProcess implements AsyncProcess{
return typeCounts[team.id].length <= type.id ? 0 : typeCounts[team.id][type.id];
}
public int countActive(Team team, UnitType type){
return activeCounts[team.id].length <= type.id ? 0 : activeCounts[team.id][type.id];
}
public void updateCount(Team team, UnitType type, int amount){
counts[team.id] += amount;
if(typeCounts[team.id].length <= type.id){
@@ -41,11 +37,16 @@ public class TeamIndexProcess implements AsyncProcess{
typeCounts[team.id][type.id] += amount;
}
public void updateActiveCount(Team team, UnitType type, int amount){
if(activeCounts[team.id].length <= type.id){
activeCounts[team.id] = new int[Vars.content.units().size];
private void count(Unit unit){
updateCount(unit.team, unit.type(), 1);
if(unit instanceof Payloadc){
((Payloadc)unit).payloads().each(p -> {
if(p instanceof UnitPayload){
count(((UnitPayload)p).unit);
}
});
}
activeCounts[team.id][type.id] += amount;
}
@Override
@@ -63,7 +64,6 @@ public class TeamIndexProcess implements AsyncProcess{
}
Arrays.fill(typeCounts[team.id], 0);
Arrays.fill(activeCounts[team.id], 0);
}
Arrays.fill(counts, 0);
@@ -71,8 +71,7 @@ public class TeamIndexProcess implements AsyncProcess{
for(Unit unit : Groups.unit){
tree(unit.team).insert(unit);
updateCount(unit.team, unit.type(), 1);
if(!unit.deactivated) updateActiveCount(unit.team, unit.type(), 1);
count(unit);
}
}

View File

@@ -2,9 +2,9 @@ package mindustry.audio;
import arc.*;
import arc.audio.*;
import arc.struct.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.*;
public class LoopControl{

View File

@@ -19,6 +19,7 @@ public class MusicControl{
public Seq<Music> ambientMusic = Seq.with();
/** darker music, used in times of conflict */
public Seq<Music> darkMusic = Seq.with();
protected Music lastRandomPlayed;
protected Interval timer = new Interval();
protected @Nullable Music current;

View File

@@ -18,6 +18,7 @@ import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.experimental.*;
import mindustry.world.blocks.legacy.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
@@ -33,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, mud, ice, snow, darksandTaintedWater,
dacite, stoneWall, dirtWall, sporeWall, iceWall, daciteWall, 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,
@@ -48,7 +49,7 @@ public class Blocks implements ContentList{
melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge,
//sandbox
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, message, illuminator,
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, illuminator,
//defense
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
@@ -63,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
@@ -73,15 +74,19 @@ public class Blocks implements ContentList{
coreShard, coreFoundation, coreNucleus, vault, container, unloader,
//turrets
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown, segment, parallax,
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, foreshadow, spectre, meltdown, segment, parallax, tsunami,
//units
commandCenter,
groundFactory, airFactory, navalFactory,
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
repairPoint, resupplyPoint,
//logic
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, largeLogicDisplay, logicDisplay, memoryCell, memoryBank,
//campaign
launchPad, launchPadLarge, dataProcessor,
launchPad, launchPadLarge,
//misc experimental
blockForge, blockLoader, blockUnloader;
@@ -96,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")};
@@ -126,14 +130,15 @@ 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"){{
speedMultiplier = 0.2f;
variants = 0;
liquidDrop = Liquids.water;
liquidMultiplier = 1.5f;
isLiquid = true;
status = StatusEffects.wet;
statusDuration = 120f;
@@ -203,11 +208,14 @@ public class Blocks implements ContentList{
liquidDrop = Liquids.slag;
isLiquid = true;
cacheLayer = CacheLayer.slag;
attributes.set(Attribute.heat, 0.85f);
emitLight = true;
lightRadius = 40f;
lightColor = Color.orange.cpy().a(0.38f);
}};
stone = new Floor("stone"){{
}};
stone = new Floor("stone");
craters = new Floor("craters"){{
variants = 3;
@@ -218,14 +226,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;
@@ -234,40 +242,53 @@ 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;
lightRadius = 50f;
lightColor = Color.orange.cpy().a(0.3f);
}};
sand = new Floor("sand"){{
itemDrop = Items.sand;
playerUnmineable = true;
attributes.set(Attribute.oil, 0.7f);
}};
darksand = new Floor("darksand"){{
itemDrop = Items.sand;
playerUnmineable = true;
attributes.set(Attribute.oil, 1.5f);
}};
dirt = new Floor("dirt");
mud = new Floor("mud"){{
speedMultiplier = 0.6f;
variants = 3;
status = StatusEffects.muddy;
statusDuration = 30f;
attributes.set(Attribute.water, 1f);
cacheLayer = CacheLayer.mud;
albedo = 0.35f;
}};
((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);
}};
salt = new Floor("salt"){{
variants = 0;
attributes.set(Attribute.water, -0.2f);
attributes.set(Attribute.water, -0.25f);
attributes.set(Attribute.oil, 0.3f);
}};
snow = new Floor("snow"){{
@@ -286,46 +307,51 @@ public class Blocks implements ContentList{
attributes.set(Attribute.water, 0.3f);
}};
cliffs = new StaticWall("cliffs"){{
variants = 1;
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;
snow.asFloor().decoration = ice.asFloor().decoration = iceSnow.asFloor().decoration = this;
}};
dirtWall = new StaticWall("dirt-wall"){{
variants = 2;
}};
icerocks = new StaticWall("icerocks"){{
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 = darksandWater.asFloor().wall = darksandTaintedWater.asFloor().wall = this;
}};
sandRocks = new StaticWall("sandrocks"){{
sandWall = new StaticWall("sand-wall"){{
variants = 2;
sandWater.asFloor().wall = this;
}};
saltRocks = new StaticWall("saltrocks"){{
}};
saltWall = new StaticWall("salt-wall");
sporePine = new StaticTree("spore-pine"){{
variants = 0;
@@ -339,34 +365,34 @@ 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;
}};
shale = new Floor("shale"){{
variants = 3;
attributes.set(Attribute.oil, 0.15f);
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;
}};
@@ -379,7 +405,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"){{
@@ -498,8 +524,8 @@ 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, 5);
craftTime = 140f;
outputItem = new ItemStack(Items.silicon, 8);
craftTime = 90f;
size = 3;
hasPower = true;
hasLiquids = false;
@@ -507,7 +533,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);
}};
@@ -568,7 +594,7 @@ public class Blocks implements ContentList{
consumes.items(new ItemStack(Items.copper, 3), new ItemStack(Items.lead, 4), new ItemStack(Items.titanium, 2), new ItemStack(Items.silicon, 3));
}};
cryofluidMixer = new LiquidConverter("cryofluidmixer"){{
cryofluidMixer = new LiquidConverter("cryofluid-mixer"){{
requirements(Category.crafting, with(Items.lead, 65, Items.silicon, 40, Items.titanium, 60));
outputLiquid = new LiquidStack(Liquids.cryofluid, 0.2f);
craftTime = 120f;
@@ -758,14 +784,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"){{
@@ -782,7 +808,7 @@ public class Blocks implements ContentList{
}};
door = new Door("door"){{
requirements(Category.defense, with(Items.graphite, 6, Items.silicon, 4));
requirements(Category.defense, with(Items.titanium, 6, Items.silicon, 4));
health = 100 * wallHealthMultiplier;
}};
@@ -844,7 +870,8 @@ public class Blocks implements ContentList{
size = 2;
reload = 250f;
range = 85f;
healPercent = 14f;
healPercent = 11f;
phaseBoost = 15f;
health = 80 * size * size;
consumes.item(Items.phasefabric).boost();
}};
@@ -885,10 +912,10 @@ public class Blocks implements ContentList{
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
hasShadow = false;
health = 40;
damage = 11;
damage = 23;
tileDamage = 7f;
length = 10;
tendrils = 5;
tendrils = 4;
}};
//endregion
@@ -899,6 +926,7 @@ public class Blocks implements ContentList{
health = 45;
speed = 0.03f;
displayedSpeed = 4.2f;
buildCostMultiplier = 2f;
}};
titaniumConveyor = new Conveyor("titanium-conveyor"){{
@@ -986,12 +1014,14 @@ public class Blocks implements ContentList{
consumes.power(1.75f);
}};
payloadConveyor = new PayloadConveyor("mass-conveyor"){{
requirements(Category.distribution, with(Items.copper, 1));
payloadConveyor = new PayloadConveyor("payload-conveyor"){{
requirements(Category.distribution, with(Items.graphite, 10, Items.copper, 20));
canOverdrive = false;
}};
payloadRouter = new PayloadRouter("payload-router"){{
requirements(Category.distribution, with(Items.copper, 1));
requirements(Category.distribution, with(Items.graphite, 15, Items.copper, 20));
canOverdrive = false;
}};
//endregion
@@ -999,7 +1029,7 @@ public class Blocks implements ContentList{
mechanicalPump = new Pump("mechanical-pump"){{
requirements(Category.liquid, with(Items.copper, 15, Items.metaglass, 10));
pumpAmount = 0.1f;
pumpAmount = 0.11f;
}};
rotaryPump = new Pump("rotary-pump"){{
@@ -1074,14 +1104,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;
}};
@@ -1118,34 +1148,35 @@ public class Blocks implements ContentList{
powerProduction = 1.8f;
generateEffect = Fx.redgeneratespark;
size = 2;
floating = true;
}};
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);
consumes.liquid(Liquids.water, 0.1f);
hasLiquids = true;
size = 2;
}};
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"){{
@@ -1164,7 +1195,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);
@@ -1244,7 +1275,7 @@ public class Blocks implements ContentList{
rotateSpeed = 1.4f;
attribute = Attribute.water;
consumes.power(1f);
consumes.power(1.5f);
}};
cultivator = new Cultivator("cultivator"){{
@@ -1257,7 +1288,7 @@ public class Blocks implements ContentList{
hasItems = true;
consumes.power(0.80f);
consumes.liquid(Liquids.water, 0.18f);
consumes.liquid(Liquids.water, 0.2f);
}};
oilExtractor = new Fracker("oil-extractor"){{
@@ -1270,6 +1301,8 @@ public class Blocks implements ContentList{
size = 3;
liquidCapacity = 30f;
attribute = Attribute.oil;
baseEfficiency = 0f;
itemUseTime = 60f;
consumes.item(Items.sand);
consumes.power(3f);
@@ -1295,39 +1328,42 @@ public class Blocks implements ContentList{
requirements(Category.effect, with(Items.copper, 3000, Items.lead, 3000, Items.silicon, 2000));
unitType = UnitTypes.beta;
health = 2000;
health = 3500;
itemCapacity = 9000;
size = 4;
unitCapModifier = 16;
unitCapModifier = 14;
}};
coreNucleus = new CoreBlock("core-nucleus"){{
requirements(Category.effect, with(Items.copper, 1000, Items.lead, 1000));
requirements(Category.effect, with(Items.copper, 8000, Items.lead, 8000, Items.silicon, 5000, Items.thorium, 4000));
unitType = UnitTypes.gamma;
health = 4000;
health = 6000;
itemCapacity = 13000;
size = 5;
unitCapModifier = 24;
unitCapModifier = 20;
}};
vault = new StorageBlock("vault"){{
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
group = BlockGroup.storage;
}};
container = new StorageBlock("container"){{
requirements(Category.effect, with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
group = BlockGroup.storage;
}};
unloader = new Unloader("unloader"){{
requirements(Category.effect, with(Items.titanium, 25, Items.silicon, 30));
speed = 7f;
speed = 6f;
group = BlockGroup.transportation;
}};
//endregion
@@ -1352,7 +1388,7 @@ public class Blocks implements ContentList{
ammoUseEffect = Fx.shellEjectSmall;
health = 250;
inaccuracy = 2f;
rotatespeed = 10f;
rotateSpeed = 10f;
}};
scatter = new ItemTurret("scatter"){{
@@ -1370,7 +1406,7 @@ public class Blocks implements ContentList{
targetGround = false;
recoilAmount = 2f;
rotatespeed = 15f;
rotateSpeed = 15f;
inaccuracy = 17f;
shootCone = 35f;
@@ -1441,7 +1477,7 @@ public class Blocks implements ContentList{
recoilAmount = 2f;
reloadTime = 90f;
cooldown = 0.03f;
powerUse = 2.5f;
powerUse = 6f;
shootShake = 2f;
shootEffect = Fx.lancerLaserShoot;
smokeEffect = Fx.none;
@@ -1460,20 +1496,21 @@ public class Blocks implements ContentList{
hitSize = 4;
lifetime = 16f;
drawSize = 400f;
collidesAir = false;
}};
}};
arc = new PowerTurret("arc"){{
requirements(Category.turret, with(Items.copper, 35, Items.lead, 50));
shootType = new LightningBulletType(){{
damage = 21;
damage = 20;
lightningLength = 25;
collidesAir = false;
}};
reloadTime = 35f;
shootCone = 40f;
rotatespeed = 8f;
powerUse = 1.5f;
rotateSpeed = 8f;
powerUse = 3f;
targetAir = false;
range = 90f;
shootEffect = Fx.lightningShoot;
@@ -1489,14 +1526,14 @@ public class Blocks implements ContentList{
hasPower = true;
size = 2;
force = 2.5f;
scaledForce = 5f;
range = 170f;
damage = 0.08f;
force = 4.5f;
scaledForce = 5.5f;
range = 110f;
damage = 0.4f;
health = 160 * size * size;
rotateSpeed = 10;
consumes.power(3f);
consumes.powerCond(3f, (TractorBeamBuild e) -> e.target != null);
}};
swarmer = new ItemTurret("swarmer"){{
@@ -1543,15 +1580,40 @@ public class Blocks implements ContentList{
}};
segment = new PointDefenseTurret("segment"){{
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 50));
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 40));
health = 250 * size * size;
range = 160f;
hasPower = true;
consumes.power(3f);
consumes.powerCond(8f, (PointDefenseBuild b) -> b.target != null);
size = 2;
shootLength = 5f;
bulletDamage = 12f;
reloadTime = 25f;
health = 190 * size * size;
bulletDamage = 25f;
reloadTime = 9f;
}};
tsunami = new LiquidTurret("tsunami"){{
requirements(Category.turret, with(Items.metaglass, 100, Items.lead, 400, Items.titanium, 250, Items.thorium, 100));
ammo(
Liquids.water, Bullets.heavyWaterShot,
Liquids.slag, Bullets.heavySlagShot,
Liquids.cryofluid, Bullets.heavyCryoShot,
Liquids.oil, Bullets.heavyOilShot
);
size = 3;
recoilAmount = 0f;
reloadTime = 2f;
shots = 2;
velocityInaccuracy = 0.1f;
inaccuracy = 4f;
recoilAmount = 1f;
restitution = 0.04f;
shootCone = 45f;
liquidCapacity = 40f;
shootEffect = Fx.shootLiquid;
range = 190f;
health = 250 * size * size;
shootSound = Sounds.splash;
}};
fuse = new ItemTurret("fuse"){{
@@ -1570,8 +1632,10 @@ public class Blocks implements ContentList{
health = 220 * size * size;
shootSound = Sounds.shotgun;
float brange = range + 10f;
ammo(Items.thorium, new ShrapnelBulletType(){{
length = range + 10f;
length = brange;
damage = 105f;
ammoMultiplier = 6f;
}});
@@ -1594,6 +1658,7 @@ public class Blocks implements ContentList{
reloadTime = 60f;
ammoEjectBack = 5f;
ammoUseEffect = Fx.shellEjectBig;
ammoPerShot = 2;
cooldown = 0.03f;
velocityInaccuracy = 0.2f;
restitution = 0.02f;
@@ -1619,7 +1684,7 @@ public class Blocks implements ContentList{
range = 200f;
size = 3;
recoilAmount = 3f;
rotatespeed = 10f;
rotateSpeed = 10f;
inaccuracy = 10f;
shootCone = 30f;
shootSound = Sounds.shootSnap;
@@ -1627,8 +1692,50 @@ public class Blocks implements ContentList{
health = 145 * size * size;
}};
foreshadow = new ItemTurret("foreshadow"){{
float brange = range = 500f;
requirements(Category.turret, with(Items.copper, 1000, Items.metaglass, 600, Items.surgealloy, 300, Items.plastanium, 200, Items.silicon, 600));
ammo(
Items.surgealloy, new PointBulletType(){{
shootEffect = Fx.instShoot;
hitEffect = Fx.instHit;
smokeEffect = Fx.smokeCloud;
trailEffect = Fx.instTrail;
despawnEffect = Fx.instBomb;
trailSpacing = 20f;
damage = 1350;
tileDamageMultiplier = 0.5f;
speed = brange;
hitShake = 6f;
ammoMultiplier = 1f;
}}
);
rotateSpeed = 2.5f;
reloadTime = 200f;
restitution = 0.2f;
ammoUseEffect = Fx.shellEjectBig;
recoilAmount = 5f;
restitution = 0.009f;
cooldown = 0.009f;
shootShake = 4f;
shots = 1;
size = 4;
shootCone = 2f;
shootSound = Sounds.shootBig;
unitSort = (u, x, y) -> -u.maxHealth;
coolantMultiplier = 0.09f;
health = 150 * size * size;
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 2f)).update(false).optional(true, true);
consumes.powerCond(10f, (TurretBuild entity) -> entity.target != null || (entity.logicControlled() && entity.logicShooting));
}};
spectre = new ItemTurret("spectre"){{
requirements(Category.turret, with(Items.copper, 350, Items.graphite, 300, Items.surgealloy, 250, Items.plastanium, 175, Items.thorium, 250));
requirements(Category.turret, with(Items.copper, 900, Items.graphite, 300, Items.surgealloy, 250, Items.plastanium, 175, Items.thorium, 250));
ammo(
Items.graphite, Bullets.standardDenseBig,
Items.pyratite, Bullets.standardIncendiaryBig,
@@ -1649,12 +1756,12 @@ public class Blocks implements ContentList{
shootCone = 24f;
shootSound = Sounds.shootBig;
health = 155 * size * size;
health = 160 * size * size;
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 2f)).update(false).optional(true, true);
}};
meltdown = new LaserTurret("meltdown"){{
requirements(Category.turret, with(Items.copper, 250, Items.lead, 350, Items.graphite, 300, Items.surgealloy, 325, Items.silicon, 325));
requirements(Category.turret, with(Items.copper, 1200, Items.lead, 350, Items.graphite, 300, Items.surgealloy, 325, Items.silicon, 325));
shootEffect = Fx.shootBigSmoke2;
shootCone = 40f;
recoilAmount = 4f;
@@ -1664,13 +1771,13 @@ public class Blocks implements ContentList{
reloadTime = 90f;
firingMoveFract = 0.5f;
shootDuration = 220f;
powerUse = 14f;
powerUse = 17f;
shootSound = Sounds.laserbig;
activeSound = Sounds.beam;
activeSoundVolume = 2f;
shootType = new ContinuousLaserBulletType(70){{
length = 220f;
length = 200f;
hitEffect = Fx.hitMeltdown;
drawSize = 420f;
@@ -1686,11 +1793,17 @@ public class Blocks implements ContentList{
//endregion
//region units
commandCenter = new CommandCenter("command-center"){{
requirements(Category.units, ItemStack.with(Items.copper, 200, Items.lead, 250, Items.silicon, 250, Items.graphite, 100));
size = 2;
health = size * size * 55;
}};
groundFactory = new UnitFactory("ground-factory"){{
requirements(Category.units, with(Items.copper, 50, Items.lead, 120, Items.silicon, 80));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.dagger, 60f * 20, with(Items.silicon, 10, Items.lead, 10)),
new UnitPlan(UnitTypes.crawler, 60f * 15, with(Items.silicon, 10, Items.blastCompound, 10)),
new UnitPlan(UnitTypes.dagger, 60f * 15, with(Items.silicon, 10, Items.lead, 10)),
new UnitPlan(UnitTypes.crawler, 60f * 12, with(Items.silicon, 10, Items.coal, 20)),
new UnitPlan(UnitTypes.nova, 60f * 40, with(Items.silicon, 30, Items.lead, 20, Items.titanium, 20)),
};
size = 3;
@@ -1698,9 +1811,9 @@ public class Blocks implements ContentList{
}};
airFactory = new UnitFactory("air-factory"){{
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
requirements(Category.units, with(Items.copper, 60, Items.lead, 70));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 10)),
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 15)),
new UnitPlan(UnitTypes.mono, 60f * 35, with(Items.silicon, 30, Items.lead, 15)),
};
size = 3;
@@ -1708,13 +1821,13 @@ public class Blocks implements ContentList{
}};
navalFactory = new UnitFactory("naval-factory"){{
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
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;
consumes.power(1.2f);
floating = true;
}};
additiveReconstructor = new Reconstructor("additive-reconstructor"){{
@@ -1737,11 +1850,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;
@@ -1756,11 +1869,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, 250));
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(12f);
consumes.items(with(Items.silicon, 250, Items.titanium, 500, Items.plastanium, 400));
consumes.power(13f);
consumes.items(with(Items.silicon, 850, Items.titanium, 750, Items.plastanium, 650));
consumes.liquid(Liquids.cryofluid, 1f);
constructTime = 60f * 60f * 1.5f;
@@ -1768,15 +1881,20 @@ public class Blocks implements ContentList{
upgrades = new UnitType[][]{
{UnitTypes.zenith, UnitTypes.antumbra},
{UnitTypes.spiroct, UnitTypes.arkyid},
{UnitTypes.fortress, UnitTypes.scepter},
{UnitTypes.bryde, UnitTypes.sei},
{UnitTypes.mega, UnitTypes.quad},
{UnitTypes.quasar, UnitTypes.vela},
};
}};
tetrativeReconstructor = new Reconstructor("tetrative-reconstructor"){{
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 1500, Items.thorium, 500, Items.plastanium, 50, 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, 450, Items.surgealloy, 400, 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;
@@ -1784,6 +1902,11 @@ public class Blocks implements ContentList{
upgrades = new UnitType[][]{
{UnitTypes.antumbra, UnitTypes.eclipse},
{UnitTypes.arkyid, UnitTypes.toxopid},
{UnitTypes.scepter, UnitTypes.reign},
{UnitTypes.sei, UnitTypes.omura},
{UnitTypes.quad, UnitTypes.oct},
{UnitTypes.vela, UnitTypes.corvus}
};
}};
@@ -1799,6 +1922,8 @@ public class Blocks implements ContentList{
size = 2;
range = 80f;
itemCapacity = 20;
ammoAmount = 5;
consumes.item(Items.copper, 1);
}};
@@ -1836,10 +1961,6 @@ public class Blocks implements ContentList{
alwaysUnlocked = true;
}};
message = new MessageBlock("message"){{
requirements(Category.effect, with(Items.graphite, 5));
}};
illuminator = new LightBlock("illuminator"){{
requirements(Category.effect, BuildVisibility.lightingOnly, with(Items.graphite, 12, Items.silicon, 8));
brightness = 0.67f;
@@ -1853,7 +1974,6 @@ public class Blocks implements ContentList{
//looked up by name, no ref needed
new LegacyMechPad("legacy-mech-pad");
new LegacyUnitFactory("legacy-unit-factory");
new LegacyCommandCenter("legacy-command-center");
//endregion
//region campaign
@@ -1877,14 +1997,78 @@ public class Blocks implements ContentList{
consumes.power(6f);
}};
dataProcessor = new ResearchBlock("data-processor"){{
//requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 200, Items.lead, 100));
//endregion campaign
//region logic
size = 3;
alwaysUnlocked = true;
message = new MessageBlock("message"){{
requirements(Category.logic, with(Items.graphite, 5));
}};
//endregion campaign
switchBlock = new SwitchBlock("switch"){{
requirements(Category.logic, with(Items.graphite, 5));
}};
microProcessor = new LogicBlock("micro-processor"){{
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 50));
instructionsPerTick = 2;
size = 1;
}};
logicProcessor = new LogicBlock("logic-processor"){{
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 100, Items.graphite, 60, Items.thorium, 50));
instructionsPerTick = 8;
range = 8 * 22;
size = 2;
}};
hyperProcessor = new LogicBlock("hyper-processor"){{
requirements(Category.logic, with(Items.lead, 450, Items.silicon, 150, Items.thorium, 75, Items.surgealloy, 50));
consumes.liquid(Liquids.cryofluid, 0.08f);
hasLiquids = true;
instructionsPerTick = 25;
range = 8 * 42;
size = 3;
}};
memoryCell = new MemoryBlock("memory-cell"){{
requirements(Category.logic, with(Items.graphite, 30, Items.silicon, 30));
memoryCapacity = 64;
}};
memoryBank = new MemoryBlock("memory-bank"){{
requirements(Category.logic, with(Items.graphite, 80, Items.silicon, 80, Items.phasefabric, 30));
memoryCapacity = 512;
size = 2;
}};
logicDisplay = new LogicDisplay("logic-display"){{
requirements(Category.logic, with(Items.lead, 100, Items.silicon, 50, Items.metaglass, 50));
displaySize = 80;
size = 3;
}};
largeLogicDisplay = new LogicDisplay("large-logic-display"){{
requirements(Category.logic, with(Items.lead, 200, Items.silicon, 150, Items.metaglass, 100, Items.phasefabric, 75));
displaySize = 176;
size = 6;
}};
//endregion
//region experimental
blockForge = new BlockForge("block-forge"){{

View File

@@ -34,7 +34,7 @@ public class Bullets implements ContentList{
standardGlaive, standardDenseBig, standardThoriumBig, standardIncendiaryBig,
//liquid
waterShot, cryoShot, slagShot, oilShot,
waterShot, cryoShot, slagShot, oilShot, heavyWaterShot, heavyCryoShot, heavySlagShot, heavyOilShot,
//environment, misc.
damageLightning, damageLightningGround, fireball, basicFlame, pyraFlame, driverBolt, healBullet, healBulletBig, frag;
@@ -42,7 +42,7 @@ public class Bullets implements ContentList{
@Override
public void load(){
artilleryDense = new ArtilleryBulletType(3f, 12, "shell"){{
artilleryDense = new ArtilleryBulletType(3f, 20, "shell"){{
hitEffect = Fx.flakExplosion;
knockback = 0.8f;
lifetime = 80f;
@@ -63,7 +63,7 @@ public class Bullets implements ContentList{
collidesAir = false;
}};
artilleryPlastic = new ArtilleryBulletType(3.4f, 12, "shell"){{
artilleryPlastic = new ArtilleryBulletType(3.4f, 20, "shell"){{
hitEffect = Fx.plasticExplosion;
knockback = 1f;
lifetime = 80f;
@@ -77,7 +77,7 @@ public class Bullets implements ContentList{
frontColor = Pal.plastaniumFront;
}};
artilleryHoming = new ArtilleryBulletType(3f, 12, "shell"){{
artilleryHoming = new ArtilleryBulletType(3f, 20, "shell"){{
hitEffect = Fx.flakExplosion;
knockback = 0.8f;
lifetime = 80f;
@@ -91,7 +91,7 @@ public class Bullets implements ContentList{
homingRange = 50f;
}};
artilleryIncendiary = new ArtilleryBulletType(3f, 12, "shell"){{
artilleryIncendiary = new ArtilleryBulletType(3f, 20, "shell"){{
hitEffect = Fx.blastExplosion;
knockback = 0.8f;
lifetime = 80f;
@@ -105,7 +105,7 @@ public class Bullets implements ContentList{
trailEffect = Fx.incendTrail;
}};
artilleryExplosive = new ArtilleryBulletType(2f, 12, "shell"){{
artilleryExplosive = new ArtilleryBulletType(2f, 20, "shell"){{
hitEffect = Fx.blastExplosion;
knockback = 0.8f;
lifetime = 80f;
@@ -163,7 +163,7 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 20f;
splashDamage = 22f;
splashDamageRadius = 20f;
fragBullet = flakGlassFrag;
fragBullets = 5;
@@ -191,7 +191,7 @@ public class Bullets implements ContentList{
fragGlass = new FlakBulletType(4f, 3){{
lifetime = 70f;
ammoMultiplier = 5f;
ammoMultiplier = 3f;
shootEffect = Fx.shootSmall;
reloadMultiplier = 0.8f;
width = 6f;
@@ -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;
@@ -221,8 +221,8 @@ public class Bullets implements ContentList{
fragExplosive = new FlakBulletType(4f, 5){{
shootEffect = Fx.shootBig;
ammoMultiplier = 4f;
splashDamage = 15f;
splashDamageRadius = 34f;
splashDamage = 18f;
splashDamageRadius = 55f;
collidesGround = true;
status = StatusEffects.blasted;
@@ -230,7 +230,8 @@ public class Bullets implements ContentList{
}};
fragSurge = new FlakBulletType(4.5f, 13){{
splashDamage = 45f;
ammoMultiplier = 4f;
splashDamage = 50f;
splashDamageRadius = 40f;
lightning = 2;
lightningLength = 7;
@@ -239,7 +240,7 @@ public class Bullets implements ContentList{
explodeRange = 20f;
}};
missileExplosive = new MissileBulletType(2.7f, 10){{
missileExplosive = new MissileBulletType(3.7f, 10){{
width = 8f;
height = 8f;
shrinkY = 0f;
@@ -247,7 +248,6 @@ public class Bullets implements ContentList{
splashDamageRadius = 30f;
splashDamage = 30f;
ammoMultiplier = 4f;
lifetime = 100f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
@@ -255,7 +255,7 @@ public class Bullets implements ContentList{
statusDuration = 60f;
}};
missileIncendiary = new MissileBulletType(2.9f, 12){{
missileIncendiary = new MissileBulletType(3.7f, 12){{
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
width = 7f;
@@ -265,23 +265,21 @@ public class Bullets implements ContentList{
homingPower = 0.08f;
splashDamageRadius = 20f;
splashDamage = 20f;
lifetime = 100f;
hitEffect = Fx.blastExplosion;
status = StatusEffects.burning;
}};
missileSurge = new MissileBulletType(4.4f, 20){{
missileSurge = new MissileBulletType(3.7f, 18){{
width = 8f;
height = 8f;
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 28f;
splashDamage = 40f;
lifetime = 100f;
splashDamageRadius = 25f;
splashDamage = 25f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
lightning = 2;
lightningLength = 14;
lightningLength = 10;
}};
standardCopper = new BasicBulletType(2.5f, 9){{
@@ -377,10 +375,9 @@ public class Bullets implements ContentList{
}};
//this is just a copy of the damage lightning bullet that doesn't damage air units
damageLightningGround = new BulletType(0.0001f, 0f){{
collidesAir = false;
}};
damageLightningGround = new BulletType(0.0001f, 0f){};
JsonIO.copy(damageLightning, damageLightningGround);
damageLightningGround.collidesAir = false;
healBullet = new HealBulletType(5.2f, 13){{
healPercent = 3f;
@@ -430,34 +427,27 @@ public class Bullets implements ContentList{
}
};
basicFlame = new BulletType(3f, 15f){
{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 42f;
pierce = true;
drag = 0.05f;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;
status = StatusEffects.burning;
keepVelocity = false;
hittable = false;
}
basicFlame = new BulletType(3.35f, 15f){{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 18f;
pierce = true;
collidesAir = false;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;
status = StatusEffects.burning;
keepVelocity = false;
hittable = false;
}};
@Override
public float range(){
return 50f;
}
};
pyraFlame = new BulletType(3.3f, 22f){{
pyraFlame = new BulletType(3.35f, 22f){{
ammoMultiplier = 4f;
hitSize = 7f;
lifetime = 42f;
lifetime = 18f;
pierce = true;
drag = 0.05f;
collidesAir = false;
statusDuration = 60f * 6;
shootEffect = Fx.shootPyraFlame;
hitEffect = Fx.hitFlameSmall;
@@ -483,6 +473,50 @@ public class Bullets implements ContentList{
drag = 0.03f;
}};
heavyWaterShot = new LiquidBulletType(Liquids.water){{
lifetime = 49f;
speed = 4f;
knockback = 1.7f;
puddleSize = 8f;
drag = 0.001f;
ammoMultiplier = 2f;
statusDuration = 60f * 4f;
damage = 0.1f;
}};
heavyCryoShot = new LiquidBulletType(Liquids.cryofluid){{
lifetime = 49f;
speed = 4f;
knockback = 1.3f;
puddleSize = 8f;
drag = 0.001f;
ammoMultiplier = 2f;
statusDuration = 60f * 4f;
damage = 0.1f;
}};
heavySlagShot = new LiquidBulletType(Liquids.slag){{
lifetime = 49f;
speed = 4f;
knockback = 1.3f;
puddleSize = 8f;
damage = 6f;
drag = 0.001f;
ammoMultiplier = 2f;
statusDuration = 60f * 4f;
}};
heavyOilShot = new LiquidBulletType(Liquids.oil){{
lifetime = 49f;
speed = 4f;
knockback = 1.3f;
puddleSize = 8f;
drag = 0.001f;
ammoMultiplier = 2f;
statusDuration = 60f * 4f;
damage = 0.1f;
}};
driverBolt = new MassDriverBolt();
frag = new BasicBulletType(5f, 8, "bullet"){{

View File

@@ -18,7 +18,7 @@ import static arc.graphics.g2d.Draw.rect;
import static arc.graphics.g2d.Draw.*;
import static arc.graphics.g2d.Lines.*;
import static arc.math.Angles.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.*;
public class Fx{
public static final Effect
@@ -26,15 +26,17 @@ public class Fx{
none = new Effect(0, 0f, e -> {}),
unitSpawn = new Effect(30f, e -> {
if(!(e.data instanceof Unit)) return;
if(!(e.data instanceof UnitType)) return;
alpha(e.fin());
float scl = 1f + e.fout() * 2f;
Unit unit = e.data();
rect(unit.type().region, e.x, e.y,
unit.type().region.getWidth() * Draw.scl * scl, unit.type().region.getHeight() * Draw.scl * scl, 180f);
UnitType unit = e.data();
TextureRegion region = unit.icon(Cicon.full);
rect(region, e.x, e.y,
region.width * Draw.scl * scl, region.height * Draw.scl * scl, 180f);
}),
@@ -50,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);
@@ -102,7 +105,7 @@ public class Fx{
Tmp.v1.set(e.x, e.y).interpolate(Tmp.v2.set(to), e.fin(), Interp.pow3)
.add(Tmp.v2.sub(e.x, e.y).nor().rotate90(1).scl(Mathf.randomSeedRange(e.id, 1f) * e.fslope() * 10f));
float x = Tmp.v1.x, y = Tmp.v1.y;
float size = Math.min(0.8f + e.rotation / 5f, 2);
float size = 1f;
stroke(e.fslope() * 2f * size, Pal.accent);
Lines.circle(x, y, e.fslope() * 2f * size);
@@ -135,14 +138,15 @@ public class Fx{
stroke(3f * e.fout());
color(e.color, Color.white, e.fin());
beginLine();
lines.each(Lines::linePoint);
linePoint(e.x, e.y);
endLine();
for(int i = 0; i < lines.size - 1; i++){
Vec2 cur = lines.get(i);
Vec2 next = lines.get(i + 1);
Lines.line(cur.x, cur.y, next.x, next.y, false);
}
int i = 0;
for(Vec2 p : lines){
Fill.square(p.x, p.y, (5f - (float)i++ / lines.size * 2f) * e.fout(), 45);
Fill.circle(p.x, p.y, Lines.getStroke() / 2f);
}
}),
@@ -287,6 +291,50 @@ public class Fx{
Lines.spikes(e.x, e.y, 1f + e.fin() * 6f, e.fout() * 4f, 6);
}),
greenBomb = new Effect(40f, 100f, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 4f + e.finpow() * 65f);
color(Pal.heal);
for(int i = 0; i < 4; i++){
Drawf.tri(e.x, e.y, 6f, 100f * e.fout(), i*90);
}
color();
for(int i = 0; i < 4; i++){
Drawf.tri(e.x, e.y, 3f, 35f * e.fout(), i*90);
}
}),
greenLaserCharge = new Effect(80f, 100f, e -> {
color(Pal.heal);
stroke(e.fin() * 2f);
Lines.circle(e.x, e.y, 4f + e.fout() * 100f);
Fill.circle(e.x, e.y, e.fin() * 20);
randLenVectors(e.id, 20, 40f * e.fout(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fin() * 5f);
});
color();
Fill.circle(e.x, e.y, e.fin() * 10);
}),
greenLaserChargeSmall = new Effect(40f, 100f, e -> {
color(Pal.heal);
stroke(e.fin() * 2f);
Lines.circle(e.x, e.y, e.fout() * 50f);
}),
healWaveDynamic = new Effect(22, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 4f + e.finpow() * e.rotation);
}),
healWave = new Effect(22, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
@@ -369,7 +417,7 @@ public class Fx{
hitLiquid = new Effect(16, e -> {
color(e.color);
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation + 180f, 60f, (x, y) -> {
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 60f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
});
@@ -397,6 +445,90 @@ public class Fx{
}),
hitMeltHeal = new Effect(12, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
randLenVectors(e.id, 6, e.finpow() * 18f, e.rotation, 360f, (x, y) -> {
float ang = Mathf.angle(x, y);
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
});
}),
instBomb = new Effect(15f, 100f, e -> {
color(Pal.bulletYellowBack);
stroke(e.fout() * 4f);
Lines.circle(e.x, e.y, 4f + e.finpow() * 20f);
for(int i = 0; i < 4; i++){
Drawf.tri(e.x, e.y, 6f, 80f * e.fout(), i*90 + 45);
}
color();
for(int i = 0; i < 4; i++){
Drawf.tri(e.x, e.y, 3f, 30f * e.fout(), i*90 + 45);
}
}),
instTrail = new Effect(30, e -> {
for(int i = 0; i < 2; i++){
color(i == 0 ? Pal.bulletYellowBack : Pal.bulletYellow);
float m = i == 0 ? 1f : 0.5f;
float rot = e.rotation + 180f;
float w = 15f * e.fout() * m;
Drawf.tri(e.x, e.y, w, (30f + Mathf.randomSeedRange(e.id, 15f)) * m, rot);
Drawf.tri(e.x, e.y, w, 10f * m, rot + 180f);
}
}),
instShoot = new Effect(24f, e -> {
e.scaled(10f, b -> {
color(Color.white, Pal.bulletYellowBack, b.fin());
stroke(b.fout() * 3f + 0.2f);
Lines.circle(b.x, b.y, b.fin() * 50f);
});
color(Pal.bulletYellowBack);
for(int i : Mathf.signs){
Drawf.tri(e.x, e.y, 13f * e.fout(), 85f, e.rotation + 90f * i);
Drawf.tri(e.x, e.y, 13f * e.fout(), 50f, e.rotation + 20f * i);
}
}),
instHit = new Effect(20f, 200f, e -> {
color(Pal.bulletYellowBack);
for(int i = 0; i < 2; i++){
color(i == 0 ? Pal.bulletYellowBack : Pal.bulletYellow);
float m = i == 0 ? 1f : 0.5f;
for(int j = 0; j < 5; j++){
float rot = e.rotation + Mathf.randomSeedRange(e.id + j, 50f);
float w = 23f * e.fout() * m;
Drawf.tri(e.x, e.y, w, (80f + Mathf.randomSeedRange(e.id + j, 40f)) * m, rot);
Drawf.tri(e.x, e.y, w, 20f * m, rot + 180f);
}
}
e.scaled(10f, c -> {
color(Pal.bulletYellow);
stroke(c.fout() * 2f + 0.2f);
Lines.circle(e.x, e.y, c.fin() * 30f);
});
e.scaled(12f, c -> {
color(Pal.bulletYellowBack);
randLenVectors(e.id, 25, 5f + e.fin() * 80f, e.rotation, 60f, (x, y) -> {
Fill.square(e.x + x, e.y + y, c.fout() * 3f, 45f);
});
});
}),
hitLaser = new Effect(8, e -> {
color(Color.white, Pal.heal, e.fin());
stroke(0.5f + e.fout());
@@ -512,6 +644,29 @@ public class Fx{
}),
sapExplosion = new Effect(25, e -> {
color(Pal.sapBullet);
e.scaled(6, i -> {
stroke(3f * i.fout());
Lines.circle(e.x, e.y, 3f + i.fin() * 80f);
});
color(Color.gray);
randLenVectors(e.id, 9, 2f + 70 * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
});
color(Pal.sapBulletBack);
stroke(1f * e.fout());
randLenVectors(e.id + 1, 8, 1f + 60f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
});
}),
massiveExplosion = new Effect(30, e -> {
color(Pal.missileYellow);
@@ -654,13 +809,18 @@ public class Fx{
}),
wet = new Effect(40f, e -> {
wet = new Effect(80f, e -> {
color(Liquids.water.color);
alpha(Mathf.clamp(e.fin() * 2f));
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
});
Fill.circle(e.x, e.y, e.fout() * 1f);
}),
muddy = new Effect(80f, e -> {
color(Color.valueOf("432722"));
alpha(Mathf.clamp(e.fin() * 2f));
Fill.circle(e.x, e.y, e.fout() * 1f);
}),
sapped = new Effect(40f, e -> {
@@ -672,6 +832,12 @@ public class Fx{
}),
sporeSlowed = new Effect(40f, e -> {
color(Pal.spore);
Fill.circle(e.x, e.y, e.fslope() * 1.1f);
}),
oily = new Effect(42f, e -> {
color(Liquids.oil.color);
@@ -871,7 +1037,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) -> {
@@ -880,7 +1046,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) -> {
@@ -889,7 +1055,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) -> {
@@ -959,6 +1125,36 @@ public class Fx{
}).ground(400f),
railShoot = new Effect(24f, e -> {
e.scaled(10f, b -> {
color(Color.white, Color.lightGray, b.fin());
stroke(b.fout() * 3f + 0.2f);
Lines.circle(b.x, b.y, b.fin() * 50f);
});
color(Pal.orangeSpark);
for(int i : Mathf.signs){
Drawf.tri(e.x, e.y, 13f * e.fout(), 85f, e.rotation + 90f * i);
}
}),
railTrail = new Effect(16f, e -> {
color(Pal.orangeSpark);
for(int i : Mathf.signs){
Drawf.tri(e.x, e.y, 10f * e.fout(), 24f, e.rotation + 90 + 90f * i);
}
}),
railHit = new Effect(18f, 200f, e -> {
color(Pal.orangeSpark);
for(int i : Mathf.signs){
Drawf.tri(e.x, e.y, 10f * e.fout(), 60f, e.rotation + 140f * i);
}
}),
lancerLaserShoot = new Effect(21f, e -> {
color(Pal.lancerLaser);
@@ -970,7 +1166,7 @@ public class Fx{
lancerLaserShootSmoke = new Effect(26f, e -> {
color(Color.white);
float length = e.data == null ? 70f : (Float)e.data;
float length = !(e.data instanceof Float) ? 70f : (Float)e.data;
randLenVectors(e.id, 7, length, e.rotation, 0f, (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fout() * 9f);
@@ -1040,6 +1236,15 @@ public class Fx{
});
}),
cloudsmoke = new Effect(70, e -> {
randLenVectors(e.id, 12, 15f + e.fin() * 45f, (x, y) -> {
float size = e.fslope() * 2f;
color(Color.gray);
alpha(e.fslope());
Fill.circle(e.x + x, e.y + y, size);
});
}),
nuclearcloud = new Effect(90, 200f, e -> {
randLenVectors(e.id, 10, e.finpow() * 90f, (x, y) -> {
float size = e.fout() * 14f;
@@ -1136,6 +1341,14 @@ public class Fx{
Fill.square(e.x + x, e.y + y, 1f + e.fout() * 3f, 45);
});
}),
smokeCloud = new Effect(70, e -> {
randLenVectors(e.id, e.fin(), 30, 30f, (x, y, fin, fout) -> {
color(Color.gray);
alpha((0.5f - Math.abs(fin - 0.5f)) * 2f);
Fill.circle(e.x + x, e.y + y, 0.5f + fout * 4f);
});
}),
smeltsmoke = new Effect(15, e -> {
randLenVectors(e.id, 6, 4f + e.fin() * 5f, (x, y) -> {
@@ -1265,10 +1478,11 @@ public class Fx{
}),
//TODO fix false in constructor
ripple = new Effect(30, e -> {
e.lifetime = 30f*e.rotation;
color(Tmp.c1.set(e.color).mul(1.5f));
stroke(e.fout() + 0.4f);
stroke(e.fout() * 1.4f);
Lines.circle(e.x, e.y, (2f + e.fin() * 4f) * e.rotation);
}).ground(),

View File

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

View File

@@ -1,8 +1,8 @@
package mindustry.content;
import arc.graphics.Color;
import mindustry.ctype.ContentList;
import mindustry.type.Liquid;
import arc.graphics.*;
import mindustry.ctype.*;
import mindustry.type.*;
public class Liquids implements ContentList{
public static Liquid water, slag, oil, cryofluid;

View File

@@ -2,14 +2,15 @@ package mindustry.content;
import arc.*;
import arc.graphics.*;
import arc.math.Mathf;
import mindustry.ctype.ContentList;
import arc.math.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.type.StatusEffect;
import mindustry.type.*;
import static mindustry.Vars.*;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, wet, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss;
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed;
@Override
public void load(){
@@ -17,7 +18,7 @@ public class StatusEffects implements ContentList{
none = new StatusEffect("none");
burning = new StatusEffect("burning"){{
damage = 0.08f; //over 10 seconds, this would be 48 damage
damage = 0.12f; //over 8 seconds, this would be 60 damage
effect = Fx.burning;
init(() -> {
@@ -45,14 +46,23 @@ public class StatusEffects implements ContentList{
});
}};
unmoving = new StatusEffect("unmoving"){{
speedMultiplier = 0.001f;
}};
slow = new StatusEffect("slow"){{
speedMultiplier = 0.4f;
}};
wet = new StatusEffect("wet"){{
color = Color.royal;
speedMultiplier = 0.9f;
speedMultiplier = 0.94f;
effect = Fx.wet;
effectChance = 0.09f;
init(() -> {
trans(shocked, ((unit, time, newTime, result) -> {
unit.damagePierce(20f);
unit.damagePierce(14f);
if(unit.team() == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
@@ -61,6 +71,13 @@ public class StatusEffects implements ContentList{
opposite(burning);
});
}};
muddy = new StatusEffect("muddy"){{
color = Color.valueOf("46382a");
speedMultiplier = 0.94f;
effect = Fx.muddy;
effectChance = 0.09f;
}};
melting = new StatusEffect("melting"){{
speedMultiplier = 0.8f;
@@ -81,6 +98,12 @@ public class StatusEffects implements ContentList{
effectChance = 0.1f;
}};
sporeSlowed = new StatusEffect("spore-slowed"){{
speedMultiplier = 0.8f;
effect = Fx.sapped;
effectChance = 0.04f;
}};
tarred = new StatusEffect("tarred"){{
speedMultiplier = 0.6f;
effect = Fx.oily;
@@ -114,13 +137,14 @@ public class StatusEffects implements ContentList{
boss = new StatusEffect("boss"){{
permanent = true;
damageMultiplier = 1.5f;
armorMultiplier = 1.5f;
}};
shocked = new StatusEffect("shocked");
blasted = new StatusEffect("blasted");
//no effects, just small amounts of damage.
corroded = new StatusEffect("corroded"){{
damage = 0.1f;
}};

View File

@@ -1,23 +1,20 @@
package mindustry.content;
import arc.*;
import arc.math.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.Objectives.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.content.Blocks.*;
import static mindustry.content.SectorPresets.*;
import static mindustry.content.SectorPresets.craters;
import static mindustry.content.SectorPresets.*;
import static mindustry.content.UnitTypes.*;
import static mindustry.type.ItemStack.*;
public class TechTree implements ContentList{
private static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
public static Seq<TechNode> all;
public static TechNode root;
@@ -38,7 +35,6 @@ public class TechTree implements ContentList{
node(distributor);
node(sorter, () -> {
node(invertedSorter);
node(message);
node(overflowGate, () -> {
node(underflowGate);
});
@@ -113,6 +109,9 @@ public class TechTree implements ContentList{
node(Items.coal, with(Items.lead, 3000), () -> {
node(Items.graphite, with(Items.coal, 1000), () -> {
node(illuminator, () -> {
});
node(graphitePress, () -> {
node(Items.titanium, with(Items.graphite, 6000, Items.copper, 10000, Items.lead, 10000), () -> {
node(pneumaticDrill, () -> {
@@ -204,6 +203,30 @@ public class TechTree implements ContentList{
});
});
});
node(microProcessor, () -> {
node(switchBlock, () -> {
node(message, () -> {
node(logicDisplay, () -> {
node(largeLogicDisplay, () -> {
});
});
node(memoryCell, () -> {
node(memoryBank, () -> {
});
});
});
node(logicProcessor, () -> {
node(hyperProcessor, () -> {
});
});
});
});
});
});
});
@@ -242,10 +265,10 @@ public class TechTree implements ContentList{
});
});
node(turbineGenerator, () -> {
node(steamGenerator, () -> {
node(thermalGenerator, () -> {
node(differentialGenerator, () -> {
node(thoriumReactor, () -> {
node(thoriumReactor, Seq.with(new Research(Liquids.cryofluid)), () -> {
node(impactReactor, () -> {
});
@@ -324,11 +347,17 @@ public class TechTree implements ContentList{
});
});
node(tsunami, () -> {
});
});
node(lancer, () -> {
node(meltdown, () -> {
node(foreshadow, () -> {
node(meltdown, () -> {
});
});
node(shockMine, () -> {
@@ -340,17 +369,29 @@ public class TechTree implements ContentList{
});
node(groundFactory, () -> {
node(commandCenter, () -> {
});
node(dagger, () -> {
node(mace, () -> {
node(fortress, () -> {
node(scepter, () -> {
node(reign, () -> {
});
});
});
});
node(nova, () -> {
node(pulsar, () -> {
node(quasar, () -> {
node(vela, () -> {
node(corvus, () -> {
});
});
});
});
});
@@ -358,7 +399,11 @@ public class TechTree implements ContentList{
node(crawler, () -> {
node(atrax, () -> {
node(spiroct, () -> {
node(arkyid, () -> {
node(toxopid, () -> {
});
});
});
});
});
@@ -379,7 +424,11 @@ public class TechTree implements ContentList{
node(mono, () -> {
node(poly, () -> {
node(mega, () -> {
node(quad, () -> {
node(oct, () -> {
});
});
});
});
});
@@ -389,7 +438,11 @@ public class TechTree implements ContentList{
node(risso, () -> {
node(minke, () -> {
node(bryde, () -> {
node(sei, () -> {
node(omura, () -> {
});
});
});
});
});
@@ -406,8 +459,6 @@ public class TechTree implements ContentList{
});
});
//TODO research sectors
node(groundZero, () -> {
node(frozenForest, Seq.with(
new SectorComplete(groundZero),
@@ -469,7 +520,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),
@@ -491,42 +542,27 @@ public class TechTree implements ContentList{
});
}
private static void setup(){
public static void setup(){
TechNode.context = null;
map = new ObjectMap<>();
all = new Seq<>();
}
private static TechNode node(UnlockableContent content, Runnable children){
ItemStack[] requirements;
if(content instanceof Block){
Block block = (Block)content;
requirements = new ItemStack[block.requirements.length];
for(int i = 0; i < requirements.length; i++){
int quantity = 40 + Mathf.round(Mathf.pow(block.requirements[i].amount, 1.25f) * 20, 10);
requirements[i] = new ItemStack(block.requirements[i].item, UI.roundAmount(quantity));
}
}else{
requirements = ItemStack.empty;
}
return node(content, requirements, children);
public static TechNode node(UnlockableContent content, Runnable children){
return node(content, content.researchRequirements(), children);
}
private static TechNode node(UnlockableContent content, ItemStack[] requirements, Runnable children){
public static TechNode node(UnlockableContent content, ItemStack[] requirements, Runnable children){
return new TechNode(content, requirements, children);
}
private static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
TechNode node = new TechNode(content, empty, children);
public static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
TechNode node = new TechNode(content, content.researchRequirements(), children);
node.objectives = objectives;
return node;
}
private static TechNode node(UnlockableContent block){
public static TechNode node(UnlockableContent block){
return node(block, () -> {});
}
@@ -544,7 +580,7 @@ public class TechTree implements ContentList{
}
public static class TechNode{
private static TechNode context;
static TechNode context;
/** Depth in tech tree. */
public int depth;
@@ -556,25 +592,20 @@ public class TechTree implements ContentList{
public ItemStack[] requirements;
/** Requirements that have been fulfilled. Always the same length as the requirement array. */
public final ItemStack[] finishedRequirements;
/** Extra objectives needed to research this. TODO implement */
/** Extra objectives needed to research this. */
public Seq<Objective> objectives = new Seq<>();
/** Time required to research this content, in seconds. */
public float time;
/** Nodes that depend on this node. */
public final Seq<TechNode> children = new Seq<>();
/** Research progress, in seconds. */
public float progress;
TechNode(@Nullable TechNode ccontext, UnlockableContent content, ItemStack[] requirements, Runnable children){
if(ccontext != null){
ccontext.children.add(this);
}
if(ccontext != null) ccontext.children.add(this);
this.parent = ccontext;
this.content = content;
this.requirements = requirements;
this.depth = parent == null ? 0 : parent.depth + 1;
this.progress = Core.settings == null ? 0 : Core.settings.getFloat("research-" + content.name, 0f);
this.time = Seq.with(requirements).mapFloat(i -> i.item.cost * i.amount).sum() * 10;
this.finishedRequirements = new ItemStack[requirements.length];
@@ -599,7 +630,6 @@ public class TechTree implements ContentList{
/** Flushes research progress to settings. */
public void save(){
Core.settings.put("research-" + content.name, progress);
//save finished requirements by item type
for(ItemStack stack : finishedRequirements){

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@ import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.gen.*;
@@ -70,7 +69,6 @@ public class Weathers implements ContentList{
}
};
//TODO should apply wet effect
rain = new Weather("rain"){
float yspeed = 5f, xspeed = 1.5f, padding = 16f, size = 40f, density = 1200f;
TextureRegion[] splashes = new TextureRegion[12];
@@ -78,6 +76,7 @@ public class Weathers implements ContentList{
{
attrs.set(Attribute.light, -0.2f);
attrs.set(Attribute.water, 0.2f);
status = StatusEffects.wet;
}
@Override
@@ -170,13 +169,14 @@ public class Weathers implements ContentList{
sandstorm = new Weather("sandstorm"){
TextureRegion region;
float yspeed = 0.3f, xspeed = 6f, size = 140f, padding = size, invDensity = 1500f;
Vec2 force = new Vec2(0.45f, 0.01f);
float size = 140f, padding = size, invDensity = 1500f, baseSpeed = 6.1f;
float force = 0.4f * 0;
Color color = Color.valueOf("f7cba4");
Texture noise;
{
attrs.set(Attribute.light, -0.1f);
opacityMultiplier = 0.8f;
}
@Override
@@ -194,22 +194,26 @@ public class Weathers implements ContentList{
@Override
public void update(WeatherState state){
float speed = force * state.intensity;
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
for(Unit unit : Groups.unit){
unit.impulse(force.x * state.intensity(), force.y * state.intensity());
unit.impulse(windx, windy);
}
}
@Override
public void drawOver(WeatherState state){
Draw.tint(color);
float speed = baseSpeed * state.intensity;
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
float scale = 1f / 2000f;
float scroll = Time.time() * scale;
Tmp.tr1.setTexture(noise);
Tmp.tr1.texture = noise;
Core.camera.bounds(Tmp.r1);
Tmp.tr1.set(Tmp.r1.x*scale, Tmp.r1.y*scale, (Tmp.r1.x + Tmp.r1.width)*scale, (Tmp.r1.y + Tmp.r1.height)*scale);
Tmp.tr1.scroll(-xspeed * scroll, -yspeed * scroll);
Tmp.tr1.scroll(-windx * scroll, windy * scroll);
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
rand.setSeed(0);
@@ -224,8 +228,8 @@ public class Weathers implements ContentList{
float scl = rand.random(0.5f, 1f);
float scl2 = rand.random(0.5f, 1f);
float sscl = rand.random(0.5f, 1f);
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
float x = (rand.random(0f, world.unitWidth()) + Time.time() * windx * scl2);
float y = (rand.random(0f, world.unitHeight()) + Time.time() * windy * scl);
float alpha = rand.random(0.2f);
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
@@ -247,14 +251,16 @@ public class Weathers implements ContentList{
sporestorm = new Weather("sporestorm"){
TextureRegion region;
float yspeed = 1f, xspeed = 4f, size = 5f, padding = size, invDensity = 2000f;
float size = 5f, padding = size, invDensity = 2000f, baseSpeed = 4.3f, force = 0.28f * 0;
Color color = Color.valueOf("7457ce");
Vec2 force = new Vec2(0.25f, 0.01f);
Texture noise;
{
attrs.set(Attribute.spores, 0.5f);
attrs.set(Attribute.light, -0.1f);
attrs.set(Attribute.spores, 1f);
attrs.set(Attribute.light, -0.15f);
status = StatusEffects.sporeSlowed;
statusGround = false;
opacityMultiplier = 0.85f;
}
@Override
@@ -267,9 +273,11 @@ public class Weathers implements ContentList{
@Override
public void update(WeatherState state){
float speed = force * state.intensity;
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
for(Unit unit : Groups.unit){
unit.impulse(force.x * state.intensity(), force.y * state.intensity());
unit.impulse(windx, windy);
}
}
@@ -283,12 +291,15 @@ public class Weathers implements ContentList{
Draw.alpha(state.opacity * 0.8f);
Draw.tint(color);
float speed = baseSpeed * state.intensity;
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
float scale = 1f / 2000f;
float scroll = Time.time() * scale;
Tmp.tr1.setTexture(noise);
Tmp.tr1.texture = noise;
Core.camera.bounds(Tmp.r1);
Tmp.tr1.set(Tmp.r1.x*scale, Tmp.r1.y*scale, (Tmp.r1.x + Tmp.r1.width)*scale, (Tmp.r1.y + Tmp.r1.height)*scale);
Tmp.tr1.scroll(-xspeed * scroll, -yspeed * scroll);
Tmp.tr1.scroll(-windx * scroll, windy * scroll);
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
rand.setSeed(0);
@@ -304,8 +315,8 @@ public class Weathers implements ContentList{
float scl = rand.random(0.5f, 1f);
float scl2 = rand.random(0.5f, 1f);
float sscl = rand.random(0.5f, 1f);
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
float x = (rand.random(0f, world.unitWidth()) + Time.time() * windx * scl2);
float y = (rand.random(0f, world.unitHeight()) + Time.time() * windy * scl);
float alpha = rand.random(0.1f, 0.8f);
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));

View File

@@ -1,9 +1,9 @@
package mindustry.core;
import arc.files.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.content.*;
@@ -13,8 +13,8 @@ import mindustry.mod.Mods.*;
import mindustry.type.*;
import mindustry.world.*;
import static arc.Core.files;
import static mindustry.Vars.mods;
import static arc.Core.*;
import static mindustry.Vars.*;
/**
* Loads all game content.
@@ -33,6 +33,7 @@ public class ContentLoader{
new StatusEffects(),
new Liquids(),
new Bullets(),
new AmmoTypes(),
new UnitTypes(),
new Blocks(),
new Loadouts(),
@@ -243,11 +244,11 @@ public class ContentLoader{
}
public Block block(int id){
return (Block)getByID(ContentType.block, id);
return getByID(ContentType.block, id);
}
public Block block(String name){
return (Block)getByName(ContentType.block, name);
return getByName(ContentType.block, name);
}
public Seq<Item> items(){
@@ -255,7 +256,7 @@ public class ContentLoader{
}
public Item item(int id){
return (Item)getByID(ContentType.item, id);
return getByID(ContentType.item, id);
}
public Seq<Liquid> liquids(){
@@ -263,7 +264,7 @@ public class ContentLoader{
}
public Liquid liquid(int id){
return (Liquid)getByID(ContentType.liquid, id);
return getByID(ContentType.liquid, id);
}
public Seq<BulletType> bullets(){
@@ -271,7 +272,7 @@ public class ContentLoader{
}
public BulletType bullet(int id){
return (BulletType)getByID(ContentType.bullet, id);
return getByID(ContentType.bullet, id);
}
public Seq<SectorPreset> sectors(){

View File

@@ -8,8 +8,8 @@ import arc.input.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.audio.*;
import mindustry.content.*;
@@ -94,7 +94,6 @@ public class Control implements ApplicationListener, Loadable{
tutorial.reset();
hiscore = false;
saves.resetSave();
});
@@ -109,20 +108,24 @@ public class Control implements ApplicationListener, Loadable{
Events.on(GameOverEvent.class, event -> {
state.stats.wavesLasted = state.wave;
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
Effect.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
//the restart dialog can show info for any number of scenarios
Call.gameOver(event.winner);
});
//add player when world loads regardless
Events.on(WorldLoadEvent.class, e -> {
player.add();
});
//autohost for pvp maps
Events.on(WorldLoadEvent.class, event -> app.post(() -> {
player.add();
if(state.rules.pvp && !net.active()){
try{
net.host(port);
player.admin(true);
}catch(IOException e){
ui.showException("$server.error", e);
ui.showException("@server.error", e);
state.set(State.menu);
}
}
@@ -152,12 +155,6 @@ public class Control implements ApplicationListener, Loadable{
}
});
Events.on(ZoneRequireCompleteEvent.class, e -> {
if(e.objective.display() != null){
ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", e.zoneForMet.localizedName, e.objective.display()));
}
});
//delete save on campaign game over
Events.on(GameOverEvent.class, e -> {
if(state.isCampaign() && !net.client() && !headless){
@@ -171,7 +168,7 @@ public class Control implements ApplicationListener, Loadable{
}
});
Events.on(Trigger.newGame, () -> {
Events.run(Trigger.newGame, () -> {
Building core = player.closestCore();
if(core == null) return;
@@ -185,10 +182,10 @@ 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);
Effects.shake(5f, 5f, core);
Effect.shake(5f, 5f, core);
});
});
@@ -196,7 +193,7 @@ public class Control implements ApplicationListener, Loadable{
@Override
public void loadAsync(){
Draw.scl = 1f / Core.atlas.find("scale_marker").getWidth();
Draw.scl = 1f / Core.atlas.find("scale_marker").width;
Core.input.setCatch(KeyCode.back, true);
@@ -257,7 +254,7 @@ public class Control implements ApplicationListener, Loadable{
}
//TODO move
public void handleLaunch(CoreEntity tile){
public void handleLaunch(CoreBuild tile){
LaunchCorec ent = LaunchCore.create();
ent.set(tile);
ent.block(Blocks.coreShard);
@@ -283,6 +280,7 @@ public class Control implements ApplicationListener, Loadable{
try{
net.reset();
slot.load();
slot.setAutosave(true);
state.rules.sector = sector;
//if there is no base, simulate a new game and place the right loadout at the spawn position
@@ -312,7 +310,7 @@ public class Control implements ApplicationListener, Loadable{
}catch(SaveException e){
Log.err(e);
sector.save = null;
Time.runTask(10f, () -> ui.showErrorMessage("$save.corrupted"));
Time.runTask(10f, () -> ui.showErrorMessage("@save.corrupted"));
slot.delete();
playSector(origin, sector);
}
@@ -324,6 +322,7 @@ public class Control implements ApplicationListener, Loadable{
state.rules.sector = sector;
//assign origin when launching
state.secinfo.origin = origin;
state.secinfo.destination = origin;
logic.play();
control.saves.saveSector(sector);
Events.fire(Trigger.newGame);
@@ -332,6 +331,7 @@ public class Control implements ApplicationListener, Loadable{
}
public void playTutorial(){
ui.showInfo("@indev.notready");
//TODO implement
//ui.showInfo("death");
/*
@@ -431,19 +431,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("@indev.popup");
}));
}
//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));
}
@@ -451,7 +444,7 @@ public class Control implements ApplicationListener, Loadable{
//display UI scale changed dialog
if(Core.settings.getBool("uiscalechanged", false)){
Core.app.post(() -> Core.app.post(() -> {
BaseDialog dialog = new BaseDialog("$confirm");
BaseDialog dialog = new BaseDialog("@confirm");
dialog.setFillParent(true);
float[] countdown = {60 * 11};
@@ -470,9 +463,9 @@ public class Control implements ApplicationListener, Loadable{
}).pad(10f).expand().center();
dialog.buttons.defaults().size(200f, 60f);
dialog.buttons.button("$uiscale.cancel", exit);
dialog.buttons.button("@uiscale.cancel", exit);
dialog.buttons.button("$ok", () -> {
dialog.buttons.button("@ok", () -> {
Core.settings.put("uiscalechanged", false);
dialog.hide();
});
@@ -488,7 +481,7 @@ public class Control implements ApplicationListener, Loadable{
@Override
public void update(){
//TODO find out why this happens on Android
//this happens on Android and nobody knows why
if(assets == null) return;
saves.update();
@@ -526,7 +519,7 @@ public class Control implements ApplicationListener, Loadable{
platform.updateRPC();
}
if(Core.input.keyTap(Binding.pause) && !state.isOutOfTime() && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
if(Core.input.keyTap(Binding.pause) && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
state.set(state.is(State.playing) ? State.paused : State.playing);
}

View File

@@ -2,8 +2,8 @@ package mindustry.core;
import arc.*;
import arc.assets.loaders.*;
import arc.struct.*;
import arc.files.*;
import arc.struct.*;
/** Handles files in a modded context. */
public class FileTree implements FileHandleResolver{

View File

@@ -17,9 +17,9 @@ public class GameState{
/** Wave countdown in ticks. */
public float wavetime;
/** Whether the game is in game over state. */
public boolean gameOver = false, launched = false, serverPaused = false;
public boolean gameOver = false, serverPaused = false, wasTimeout;
/** Map that is currently being played on. */
public @NonNull Map map = emptyMap;
public Map map = emptyMap;
/** The current game rules. */
public Rules rules = new Rules();
/** Statistics for this save/game. Displayed after game over. */
@@ -50,11 +50,6 @@ public class GameState{
return rules.sector != null;
}
/** @return whether the player is in a campaign and they are out of sector time */
public boolean isOutOfTime(){
return isCampaign() && isGame() && getSector().getTimeSpent() >= turnDuration;
}
public boolean hasSector(){
return rules.sector != null;
}

View File

@@ -4,7 +4,6 @@ import arc.*;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -15,7 +14,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 +40,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()){
@@ -89,7 +88,7 @@ public class Logic implements ApplicationListener{
Events.on(WorldLoadEvent.class, e -> {
if(state.isCampaign()){
long seconds = state.rules.sector.getSecondsPassed();
CoreEntity core = state.rules.defaultTeam.core();
CoreBuild core = state.rules.defaultTeam.core();
//apply fractional damage based on how many turns have passed for this sector
float turnsPassed = seconds / (turnDuration / 60f);
@@ -178,7 +177,7 @@ public class Logic implements ApplicationListener{
public void runWave(){
spawner.spawnEnemies();
state.wave++;
state.wavetime = state.hasSector() && state.getSector().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
state.wavetime = state.rules.waveSpacing;
Events.fire(new WaveEvent());
}
@@ -187,7 +186,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));
}
@@ -245,61 +244,15 @@ public class Logic implements ApplicationListener{
if(entry.cooldown < 0 && !entry.weather.isActive()){
float duration = Mathf.random(entry.minDuration, entry.maxDuration);
entry.cooldown = duration + Mathf.random(entry.minFrequency, entry.maxFrequency);
Call.createWeather(entry.weather, entry.intensity, duration);
Tmp.v1.setToRandomDirection();
Call.createWeather(entry.weather, entry.intensity, duration, Tmp.v1.x, Tmp.v1.y);
}
}
}
@Remote(called = Loc.both)
public static void launchZone(){
if(!state.isCampaign()) return;
if(!headless){
ui.hudfrag.showLaunch();
}
//TODO better core launch effect
for(Building tile : state.teams.playerCores()){
Fx.launch.at(tile);
}
Sector sector = state.rules.sector;
//TODO containers must be launched too
Time.runTask(30f, () -> {
Sector origin = sector.save.meta.secinfo.origin;
if(origin != null){
ItemSeq stacks = origin.getExtraItems();
//add up all items into list
for(Building entity : state.teams.playerCores()){
entity.items.each(stacks::add);
}
//save received items
origin.setExtraItems(stacks);
}
//remove all the cores
state.teams.playerCores().each(b -> b.tile.remove());
state.launched = true;
state.gameOver = true;
//save over the data w/o the cores
sector.save.save();
//run a turn, since launching takes up a turn
universe.runTurn();
//TODO apply extra damage to sector
//sector.setTurnsPassed(sector.getTurnsPassed() + 3);
//TODO load the sector that was launched from
Events.fire(new LaunchEvent());
//manually fire game over event now
Events.fire(new GameOverEvent(state.rules.defaultTeam));
});
public static void updateGameOver(Team winner){
state.gameOver = true;
}
@Remote(called = Loc.both)
@@ -320,18 +273,19 @@ public class Logic implements ApplicationListener{
Events.fire(Trigger.update);
universe.updateGlobal();
if(Core.settings.modified() && !state.isPlaying()){
Core.settings.forceSave();
}
if(state.isGame()){
if(!net.client()){
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted);
}
//force pausing when the player is out of sector time
if(state.isOutOfTime()){
state.set(State.paused);
}
if(!state.isPaused()){
state.secinfo.update();
if(state.isCampaign()){
state.secinfo.update();
}
if(state.isCampaign()){
universe.update();

View File

@@ -2,6 +2,7 @@ package mindustry.core;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
@@ -11,6 +12,7 @@ import arc.util.serialization.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -63,7 +65,7 @@ public class NetClient implements ApplicationListener{
reset();
ui.loadfrag.hide();
ui.loadfrag.show("$connecting.data");
ui.loadfrag.show("@connecting.data");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
@@ -80,7 +82,7 @@ public class NetClient implements ApplicationListener{
c.uuid = platform.getUUID();
if(c.uuid == null){
ui.showErrorMessage("$invalidid");
ui.showErrorMessage("@invalidid");
ui.loadfrag.hide();
disconnectQuietly();
return;
@@ -103,15 +105,13 @@ public class NetClient implements ApplicationListener{
Time.runTask(3f, ui.loadfrag::hide);
if(packet.reason != null){
if(packet.reason.equals("closed")){
ui.showSmall("$disconnect", "$disconnect.closed");
}else if(packet.reason.equals("timeout")){
ui.showSmall("$disconnect", "$disconnect.timeout");
}else if(packet.reason.equals("error")){
ui.showSmall("$disconnect", "$disconnect.error");
switch(packet.reason){
case "closed" -> ui.showSmall("@disconnect", "@disconnect.closed");
case "timeout" -> ui.showSmall("@disconnect", "@disconnect.timeout");
case "error" -> ui.showSmall("@disconnect", "@disconnect.error");
}
}else{
ui.showErrorMessage("$disconnect");
ui.showErrorMessage("@disconnect");
}
});
@@ -261,7 +261,7 @@ public class NetClient implements ApplicationListener{
if(reason.extraText() != null){
ui.showText(reason.toString(), reason.extraText());
}else{
ui.showText("$disconnect", reason.toString());
ui.showText("@disconnect", reason.toString());
}
}
ui.loadfrag.hide();
@@ -271,7 +271,7 @@ public class NetClient implements ApplicationListener{
public static void kick(String reason){
netClient.disconnectQuietly();
logic.reset();
ui.showText("$disconnect", reason, Align.left);
ui.showText("@disconnect", reason, Align.left);
ui.loadfrag.hide();
}
@@ -314,7 +314,6 @@ public class NetClient implements ApplicationListener{
ui.showLabel(message, duration, worldx, worldy);
}
/*
@Remote(variants = Variant.both, unreliable = true)
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
if(effect == null) return;
@@ -325,7 +324,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
onEffect(effect, x, y, rotation, color);
}*/
}
@Remote(variants = Variant.both)
public static void infoToast(String message, float duration){
@@ -344,10 +343,11 @@ public class NetClient implements ApplicationListener{
Groups.clear();
netClient.removed.clear();
logic.reset();
netClient.connecting = true;
net.setClientLoaded(false);
ui.loadfrag.show("$connecting.data");
ui.loadfrag.show("@connecting.data");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
@@ -364,6 +364,9 @@ public class NetClient implements ApplicationListener{
@Remote
public static void playerDisconnect(int playerid){
if(netClient != null){
netClient.addRemovedEntity(playerid);
}
Groups.player.removeByID(playerid);
}
@@ -435,18 +438,21 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, short coreDataLen, byte[] coreData){
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, boolean gameOver, int timeData, short coreDataLen, byte[] coreData){
try{
if(wave > state.wave){
state.wave = wave;
Events.fire(new WaveEvent());
}
state.gameOver = gameOver;
state.wavetime = waveTime;
state.wave = wave;
state.enemies = enemies;
state.serverPaused = paused;
universe.updateNetSeconds(timeData);
netClient.byteStream.setBytes(net.decompressSnapshot(coreData, coreDataLen));
DataInputStream input = netClient.dataStream;
@@ -481,7 +487,7 @@ public class NetClient implements ApplicationListener{
Log.err("Failed to load data!");
ui.loadfrag.hide();
quiet = true;
ui.showErrorMessage("$disconnect.data");
ui.showErrorMessage("@disconnect.data");
net.disconnect();
timeoutTime = 0f;
}
@@ -541,6 +547,10 @@ public class NetClient implements ApplicationListener{
quiet = true;
}
public void clearRemovedEntity(int id){
removed.remove(id);
}
public void addRemovedEntity(int id){
removed.add(id);
}
@@ -552,10 +562,26 @@ public class NetClient implements ApplicationListener{
void sync(){
if(timer.get(0, playerSyncTime)){
BuildPlan[] requests = null;
if(player.isBuilder() && control.input.isBuilding){
if(player.isBuilder()){
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.builder().plans().size, 10);
int totalLength = 0;
//prevent buffer overflow by checking config length
for(int i = 0; i < usedRequests; i++){
BuildPlan plan = player.builder().plans().get(i);
if(plan.config instanceof byte[]){
int length = ((byte[])plan.config).length;
totalLength += length;
}
if(totalLength > 2048){
usedRequests = i + 1;
break;
}
}
requests = new BuildPlan[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.builder().plans().get(i);
@@ -563,8 +589,11 @@ public class NetClient implements ApplicationListener{
}
Unit unit = player.dead() ? Nulls.unit : player.unit();
int uid = player.dead() ? -1 : unit.id;
Call.clientShapshot(lastSent++,
Call.clientSnapshot(
lastSent++,
uid,
player.dead(),
unit.x, unit.y,
player.unit().aimX(), player.unit().aimY(),
@@ -572,10 +601,11 @@ public class NetClient implements ApplicationListener{
unit instanceof Mechc ? ((Mechc)unit).baseRotation() : 0,
unit.vel.x, unit.vel.y,
player.miner().mineTile(),
player.boosting, player.shooting, ui.chatfrag.shown(),
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);
Core.camera.width * viewScale, Core.camera.height * viewScale
);
}
if(timer.get(1, 60)){

View File

@@ -34,8 +34,9 @@ import static arc.util.Log.*;
import static mindustry.Vars.*;
public class NetServer implements ApplicationListener{
private static final int maxSnapshotSize = 430, timerBlockSync = 0;
private static final float serverSyncTime = 12, blockSyncTime = 60 * 8;
/** note that snapshots are compressed, so the max snapshot size here is above the typical UDP safe limit */
private static final int maxSnapshotSize = 800, timerBlockSync = 0;
private static final float serverSyncTime = 12, blockSyncTime = 60 * 6;
private static final FloatBuffer fbuffer = FloatBuffer.allocate(20);
private static final Vec2 vector = new Vec2();
private static final Rect viewport = new Rect();
@@ -130,7 +131,7 @@ public class NetServer implements ApplicationListener{
return;
}
if(Time.millis() < info.lastKicked){
if(Time.millis() < admins.getKickTime(uuid, con.address)){
con.kick(KickReason.recentKick);
return;
}
@@ -173,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()))){
@@ -332,6 +333,8 @@ public class NetServer implements ApplicationListener{
Call.sendMessage(Strings.format("[lightgray]A player has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
target.name, votes, votesRequired()));
checkPass();
}
boolean checkPass(){
@@ -368,6 +371,11 @@ public class NetServer implements ApplicationListener{
return;
}
if(currentlyKicking[0] != null){
player.sendMessage("[scarlet]A vote is already in progress.");
return;
}
if(args.length == 0){
StringBuilder builder = new StringBuilder();
builder.append("[orange]Players to kick: \n");
@@ -382,9 +390,7 @@ public class NetServer implements ApplicationListener{
int id = Strings.parseInt(args[0].substring(1));
found = Groups.player.find(p -> p.id() == id);
}else{
found = Groups.player.find(p -> {
return p.name.equalsIgnoreCase(args[0]);
});
found = Groups.player.find(p -> p.name.equalsIgnoreCase(args[0]));
}
if(found != null){
@@ -479,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){
@@ -524,25 +530,40 @@ public class NetServer implements ApplicationListener{
public static void serverPacketUnreliable(Player player, String type, String contents){
serverPacketReliable(player, type, contents);
}
private static boolean invalid(float f){
return Float.isInfinite(f) || Float.isNaN(f);
}
@Remote(targets = Loc.client, unreliable = true)
public static void clientShapshot(
public static void clientSnapshot(
Player player,
int snapshotID,
int unitID,
boolean dead,
float x, float y,
float pointerX, float pointerY,
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean chatting,
boolean boosting, boolean shooting, boolean chatting, boolean building,
@Nullable BuildPlan[] requests,
float viewX, float viewY, float viewWidth, float viewHeight
){
NetConnection con = player.con;
if(con == null || snapshotID < con.lastReceivedClientSnapshot) return;
boolean verifyPosition = !player.dead() && netServer.admins.getStrict() && headless;
//validate coordinates just in case
if(invalid(x)) x = 0f;
if(invalid(y)) y = 0f;
if(invalid(xVelocity)) xVelocity = 0f;
if(invalid(yVelocity)) yVelocity = 0f;
if(invalid(pointerX)) pointerX = 0f;
if(invalid(pointerY)) pointerY = 0f;
if(invalid(rotation)) rotation = 0f;
if(invalid(baseRotation)) baseRotation = 0f;
boolean verifyPosition = netServer.admins.isStrict() && headless;
if(con.lastReceivedClientTime == 0) con.lastReceivedClientTime = Time.millis() - 16;
@@ -556,7 +577,10 @@ public class NetServer implements ApplicationListener{
shooting = false;
}
//TODO these need to be assigned elsewhere
if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
boosting = false;
}
player.mouseX = pointerX;
player.mouseY = pointerY;
player.typing = chatting;
@@ -568,80 +592,80 @@ public class NetServer implements ApplicationListener{
if(player.isBuilder()){
player.builder().clearBuilding();
player.builder().updateBuilding(building);
if(requests != null){
for(BuildPlan req : requests){
if(req == null) continue;
Tile tile = world.tile(req.x, req.y);
if(tile == null || (!req.breaking && req.block == null)) continue;
//auto-skip done requests
if(req.breaking && tile.block() == Blocks.air){
continue;
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || (tile.build != null && tile.build.rotation == req.rotation))){
continue;
}else if(con.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
continue;
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
action.block = req.block;
action.rotation = req.rotation;
action.config = req.config;
})){
//force the player to remove this request if that's not the case
Call.removeQueueBlock(player.con, req.x, req.y, req.breaking);
con.rejectedRequests.add(req);
continue;
}
player.builder().plans().addLast(req);
}
}
}
if(player.isMiner()){
player.miner().mineTile(mining);
}
if(requests != null){
for(BuildPlan req : requests){
if(req == null) continue;
Tile tile = world.tile(req.x, req.y);
if(tile == null || (!req.breaking && req.block == null)) continue;
//auto-skip done requests
if(req.breaking && tile.block() == Blocks.air){
continue;
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || (tile.build != null && tile.build.rotation == req.rotation))){
continue;
}else if(con.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
continue;
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
action.block = req.block;
action.rotation = req.rotation;
action.config = req.config;
})){
//force the player to remove this request if that's not the case
Call.removeQueueBlock(player.con, req.x, req.y, req.breaking);
con.rejectedRequests.add(req);
continue;
}
player.builder().plans().addLast(req);
}
}
con.rejectedRequests.clear();
if(!player.dead()){
Unit unit = player.unit();
unit.vel.set(xVelocity, yVelocity).limit(unit.type().speed);
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
float maxSpeed = player.unit().type().speed;
float maxSpeed = ((player.unit().type().canBoost && player.unit().isFlying()) ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
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
@@ -671,8 +695,8 @@ public class NetServer implements ApplicationListener{
public static void adminRequest(Player player, Player other, AdminAction action){
if(!player.admin){
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action without proper security access.",
player.name, player.con.address);
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action '@' on '@' without proper security access.",
player.name, player.con.address, action.name(), other == null ? null : other.name);
return;
}
@@ -706,13 +730,15 @@ public class NetServer implements ApplicationListener{
@Remote(targets = Loc.client)
public static void connectConfirm(Player player){
player.add();
if(player.con == null || player.con.hasConnected) return;
player.add();
player.con.hasConnected = true;
if(Config.showConnectMessages.bool()){
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
Log.info("&lm[@] &y@ has connected. ", player.uuid(), player.name);
Log.info("&lm[@] &y@ has connected.", player.uuid(), player.name);
}
if(!Config.motd.string().equalsIgnoreCase("off")){
@@ -723,7 +749,7 @@ public class NetServer implements ApplicationListener{
}
public boolean isWaitingForPlayers(){
if(state.rules.pvp){
if(state.rules.pvp && !state.gameOver){
int used = 0;
for(TeamData t : state.teams.getActive()){
if(Groups.player.count(p -> p.team() == t.team) > 0){
@@ -740,7 +766,7 @@ public class NetServer implements ApplicationListener{
if(!headless && !closing && net.server() && state.isMenu()){
closing = true;
ui.loadfrag.show("$server.closing");
ui.loadfrag.show("@server.closing");
Time.runTask(5f, () -> {
net.closeServer();
ui.loadfrag.hide();
@@ -749,6 +775,10 @@ public class NetServer implements ApplicationListener{
}
if(state.isGame() && net.server()){
if(state.rules.pvp){
state.serverPaused = isWaitingForPlayers();
}
sync();
}
}
@@ -778,11 +808,11 @@ public class NetServer implements ApplicationListener{
syncStream.reset();
short sent = 0;
for(Building entity : Groups.tile){
if(!entity.block().sync) continue;
for(Building entity : Groups.build){
if(!entity.block.sync) continue;
sent ++;
dataStream.writeInt(entity.tile().pos());
dataStream.writeInt(entity.pos());
entity.writeAll(Writes.get(dataStream));
if(syncStream.size() > maxSnapshotSize){
@@ -803,12 +833,12 @@ public class NetServer implements ApplicationListener{
public void writeEntitySnapshot(Player player) throws IOException{
syncStream.reset();
Seq<CoreEntity> cores = state.teams.cores(player.team());
Seq<CoreBuild> cores = state.teams.cores(player.team());
dataStream.writeByte(cores.size);
for(CoreEntity entity : cores){
dataStream.writeInt(entity.tile().pos());
for(CoreBuild entity : cores){
dataStream.writeInt(entity.tile.pos());
entity.items.write(Writes.get(dataStream));
}
@@ -816,7 +846,7 @@ public class NetServer implements ApplicationListener{
byte[] stateBytes = syncStream.toByteArray();
//write basic state data.
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, (short)stateBytes.length, net.compressSnapshot(stateBytes));
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, state.gameOver, universe.seconds(), (short)stateBytes.length, net.compressSnapshot(stateBytes));
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);

View File

@@ -14,10 +14,18 @@ import mindustry.type.*;
import mindustry.ui.dialogs.*;
import rhino.*;
import java.net.*;
import static mindustry.Vars.*;
public interface Platform{
/** Dynamically loads a jar file. */
default Class<?> loadJar(Fi jar, String mainClass) throws Exception{
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
return classLoader.loadClass(mainClass);
}
/** Steam: Update lobby visibility.*/
default void updateLobby(){}
@@ -111,7 +119,7 @@ public interface Platform{
* @param extension File extension to filter
*/
default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
new FileChooser(open ? "$open" : "$save", file -> file.extEquals(extension), open, file -> {
new FileChooser(open ? "@open" : "@save", file -> file.extEquals(extension), open, file -> {
if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
}else{
@@ -121,7 +129,7 @@ public interface Platform{
}
/**
* Show a file chooser for multiple file types. Only supported on desktop.
* Show a file chooser for multiple file types.
* @param cons Selection listener
* @param extensions File extensions to filter
*/
@@ -129,7 +137,7 @@ public interface Platform{
if(mobile){
showFileChooser(true, extensions[0], cons);
}else{
new FileChooser("$open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
}
}

View File

@@ -28,6 +28,8 @@ public class Renderer implements ApplicationListener{
public PlanetRenderer planets;
public FrameBuffer effectBuffer = new FrameBuffer();
public float laserOpacity = 1f;
private Bloom bloom;
private FxProcessor fx = new FxProcessor();
private Color clearColor = new Color(0f, 0f, 0f, 1f);
@@ -59,8 +61,10 @@ public class Renderer implements ApplicationListener{
@Override
public void update(){
Color.white.set(1f, 1f, 1f, 1f);
Gl.clear(Gl.stencilBufferBit);
camerascale = Mathf.lerpDelta(camerascale, targetscale, 0.1f);
laserOpacity = Core.settings.getInt("lasersopacity") / 100f;
if(landTime > 0){
landTime -= Time.delta;
@@ -138,7 +142,7 @@ public class Renderer implements ApplicationListener{
}catch(Throwable e){
e.printStackTrace();
settings.put("bloom", false);
ui.showErrorMessage("$error.bloom");
ui.showErrorMessage("@error.bloom");
}
}
@@ -185,6 +189,8 @@ public class Renderer implements ApplicationListener{
}
public void draw(){
Events.fire(Trigger.preDraw);
camera.update();
if(Float.isNaN(camera.position.x) || Float.isNaN(camera.position.y)){
@@ -205,12 +211,12 @@ public class Renderer implements ApplicationListener{
Draw.sort(true);
Events.fire(Trigger.draw);
if(pixelator.enabled()){
pixelator.register();
}
//TODO fx
Draw.draw(Layer.background, this::drawBackground);
Draw.draw(Layer.floor, blocks.floor::drawFloor);
Draw.draw(Layer.block - 1, blocks::drawShadows);
@@ -254,6 +260,8 @@ public class Renderer implements ApplicationListener{
Draw.reset();
Draw.flush();
Draw.sort(false);
Events.fire(Trigger.postDraw);
}
private void drawBackground(){
@@ -265,9 +273,9 @@ 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;
float s = reg.width * Draw.scl * scl * 4f * fract;
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.getX(), entity.getY(), s, s);
@@ -279,7 +287,7 @@ public class Renderer implements ApplicationListener{
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.getX(), entity.getY(), reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.rect(reg, entity.getX(), entity.getY(), reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fract * 135f);
Draw.reset();
}
@@ -318,7 +326,7 @@ public class Renderer implements ApplicationListener{
int memory = w * h * 4 / 1024 / 1024;
if(memory >= 65){
ui.showInfo("$screenshot.invalid");
ui.showInfo("@screenshot.invalid");
return;
}

View File

@@ -24,6 +24,7 @@ import mindustry.editor.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.ui.fragments.*;
@@ -67,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public SchematicsDialog schematics;
public ModsDialog mods;
public ColorPicker picker;
public LogicDialog logic;
public Cursor drillCursor, unloadCursor;
@@ -126,6 +128,8 @@ public class UI implements ApplicationListener, Loadable{
public void update(){
if(disableUI || Core.scene == null) return;
Events.fire(Trigger.uiDrawBegin);
Core.scene.act();
Core.scene.draw();
@@ -141,6 +145,8 @@ public class UI implements ApplicationListener, Loadable{
control.tutorial.draw();
Draw.flush();
}
Events.fire(Trigger.uiDrawEnd);
}
@Override
@@ -179,6 +185,7 @@ public class UI implements ApplicationListener, Loadable{
research = new ResearchDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
logic = new LogicDialog();
Group group = Core.scene.root;
@@ -218,14 +225,17 @@ public class UI implements ApplicationListener, Loadable{
}
public TextureRegionDrawable getIcon(String name){
if(Icon.icons.containsKey(name)){
return Icon.icons.get(name);
}
if(Icon.icons.containsKey(name)) return Icon.icons.get(name);
return Core.atlas.getDrawable("error");
}
public TextureRegionDrawable getIcon(String name, String def){
if(Icon.icons.containsKey(name)) return Icon.icons.get(name);
return getIcon(def);
}
public void loadAnd(Runnable call){
loadAnd("$loading", call);
loadAnd("@loading", call);
}
public void loadAnd(String text, Runnable call){
@@ -239,7 +249,7 @@ public class UI implements ApplicationListener, Loadable{
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Cons<String> confirmed){
if(mobile){
Core.input.getTextInput(new TextInput(){{
this.title = (titleText.startsWith("$") ? Core.bundle.get(titleText.substring(1)) : titleText);
this.title = (titleText.startsWith("@") ? Core.bundle.get(titleText.substring(1)) : titleText);
this.text = def;
this.numeric = inumeric;
this.maxLength = textLength;
@@ -252,11 +262,11 @@ public class UI implements ApplicationListener, Loadable{
TextField field = cont.field(def, t -> {}).size(330f, 50f).get();
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
buttons.defaults().size(120, 54).pad(4);
buttons.button("$ok", () -> {
buttons.button("@ok", () -> {
confirmed.get(field.getText());
hide();
}).disabled(b -> field.getText().isEmpty());
buttons.button("$cancel", this::hide);
buttons.button("@cancel", this::hide);
keyDown(KeyCode.enter, () -> {
String text = field.getText();
if(!text.isEmpty()){
@@ -283,6 +293,7 @@ public class UI implements ApplicationListener, Loadable{
public void showInfoFade(String info){
Table table = new Table();
table.touchable = Touchable.disabled;
table.setFillParent(true);
table.actions(Actions.fadeOut(7f, Interp.fade), Actions.remove());
table.top().add(info).style(Styles.outlineLabel).padTop(10);
@@ -341,7 +352,7 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
buttons.button("$ok", () -> {
buttons.button("@ok", () -> {
hide();
listener.run();
}).size(110, 50).pad(4);
@@ -352,7 +363,7 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.left);
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
@@ -360,13 +371,13 @@ public class UI implements ApplicationListener, Loadable{
new Dialog(""){{
setFillParent(true);
cont.margin(15f);
cont.add("$error.title");
cont.add("@error.title");
cont.row();
cont.image().width(300f).pad(2).height(4f).color(Color.scarlet);
cont.row();
cont.add(text).pad(2f).growX().wrap().get().setAlignment(Align.center);
cont.row();
cont.button("$ok", this::hide).size(120, 50).pad(4);
cont.button("@ok", this::hide).size(120, 50).pad(4);
}}.show();
}
@@ -381,17 +392,17 @@ public class UI implements ApplicationListener, Loadable{
setFillParent(true);
cont.margin(15);
cont.add("$error.title").colspan(2);
cont.add("@error.title").colspan(2);
cont.row();
cont.image().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add((text.startsWith("$") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.add((text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.row();
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(Strings.neatError(exc)).color(Color.lightGray).left()), true);
cont.button("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
cont.button("$ok", this::hide).size(110, 50).fillX().left();
cont.button("@details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
cont.button("@ok", this::hide).size(110, 50).fillX().left();
cont.row();
cont.add(col).colspan(2).pad(2);
}}.show();
@@ -408,14 +419,14 @@ public class UI implements ApplicationListener, Loadable{
cont.row();
cont.add(text).width(400f).wrap().get().setAlignment(align, align);
cont.row();
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
public void showInfoText(String titleText, String text){
new Dialog(titleText){{
cont.margin(15).add(text).width(400f).wrap().left().get().setAlignment(Align.left, Align.left);
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
@@ -424,7 +435,7 @@ public class UI implements ApplicationListener, Loadable{
cont.margin(10).add(text);
titleTable.row();
titleTable.image().color(Pal.accent).height(3f).growX().pad(2f);
buttons.button("$ok", this::hide).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
}}.show();
}
@@ -437,8 +448,8 @@ public class UI implements ApplicationListener, Loadable{
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.button("$cancel", dialog::hide);
dialog.buttons.button("$ok", () -> {
dialog.buttons.button("@cancel", dialog::hide);
dialog.buttons.button("@ok", () -> {
dialog.hide();
confirmed.run();
});
@@ -478,10 +489,11 @@ public class UI implements ApplicationListener, Loadable{
public void announce(String text){
Table t = new Table();
t.touchable = Touchable.disabled;
t.background(Styles.black3).margin(8f)
.add(text).style(Styles.outlineLabel);
.add(text).style(Styles.outlineLabel).labelAlign(Align.center);
t.update(() -> t.setPosition(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Align.center));
t.actions(Actions.fadeOut(3, Interp.pow4In));
t.actions(Actions.fadeOut(3, Interp.pow4In), Actions.remove());
Core.scene.add(t);
}
@@ -490,7 +502,7 @@ public class UI implements ApplicationListener, Loadable{
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.button("$ok", () -> {
dialog.buttons.button("@ok", () -> {
dialog.hide();
confirmed.run();
});

View File

@@ -2,8 +2,8 @@ package mindustry.core;
import arc.*;
import arc.Files.*;
import arc.struct.*;
import arc.files.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
@@ -45,4 +45,12 @@ public class Version{
build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1;
}
}
/** get menu version without colors */
public static String combined(){
if(build == -1){
return "custom build";
}
return (type.equals("official") ? modifier : type) + " build " + build + (revision == 0 ? "" : "." + revision);
}
}

View File

@@ -5,11 +5,13 @@ import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.struct.ObjectIntMap.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
@@ -19,7 +21,6 @@ import mindustry.maps.*;
import mindustry.maps.filters.*;
import mindustry.maps.filters.GenerateFilter.*;
import mindustry.type.*;
import mindustry.type.Sector.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
@@ -30,14 +31,20 @@ import static mindustry.Vars.*;
public class World{
public final Context context = new Context();
public @NonNull Tiles tiles = new Tiles(0, 0);
public Tiles tiles = new Tiles(0, 0);
private boolean generating, invalidMap;
private ObjectMap<Map, Runnable> customMapLoaders = new ObjectMap<>();
public World(){
}
/** Adds a custom handler function for loading a custom map - usually a generated one. */
public void addMapLoader(Map map, Runnable loader){
customMapLoaders.put(map, loader);
}
public boolean isInvalidMap(){
return invalidMap;
}
@@ -79,13 +86,11 @@ public class World{
return height()*tilesize;
}
@NonNull
public Floor floor(int x, int y){
Tile tile = tile(x, y);
return tile == null ? Blocks.air.asFloor() : tile.floor();
}
@NonNull
public Floor floorWorld(float x, float y){
Tile tile = tileWorld(x, y);
return tile == null ? Blocks.air.asFloor() : tile.floor();
@@ -105,7 +110,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;
}
@@ -123,7 +130,6 @@ public class World{
return tile.build;
}
@NonNull
public Tile rawTile(int x, int y){
return tiles.getn(x, y);
}
@@ -185,16 +191,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);
@@ -257,9 +259,71 @@ public class World{
state.rules.weather.clear();
if(sector.is(SectorAttribute.rainy)) state.rules.weather.add(new WeatherEntry(Weathers.rain));
if(sector.is(SectorAttribute.snowy)) state.rules.weather.add(new WeatherEntry(Weathers.snow));
if(sector.is(SectorAttribute.desert)) state.rules.weather.add(new WeatherEntry(Weathers.sandstorm));
//apply weather based on terrain
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
ObjectSet<UnlockableContent> content = new ObjectSet<>();
float waterFloors = 0, totalFloors = 0;
for(Tile tile : world.tiles){
if(world.getDarkness(tile.x, tile.y) >= 3){
continue;
}
Liquid liquid = tile.floor().liquidDrop;
if(tile.floor().itemDrop != null) content.add(tile.floor().itemDrop);
if(tile.overlay().itemDrop != null) content.add(tile.overlay().itemDrop);
if(liquid != null) content.add(liquid);
if(!tile.block().isStatic()){
totalFloors ++;
if(liquid == Liquids.water){
waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
}
floorc.increment(tile.floor());
if(tile.overlay() != Blocks.air){
floorc.increment(tile.overlay());
}
}
}
//sort counts in descending order
Seq<Entry<Block>> entries = floorc.entries().toArray();
entries.sort(e -> -e.value);
//remove all blocks occuring < 30 times - unimportant
entries.removeAll(e -> e.value < 30);
Block[] floors = new Block[entries.size];
int[] floorCounts = new int[entries.size];
for(int i = 0; i < entries.size; i++){
floorCounts[i] = entries.get(i).value;
floors[i] = entries.get(i).key;
}
//TODO bad code
boolean hasSnow = floors[0].name.contains("ice") || floors[0].name.contains("snow");
boolean hasRain = !hasSnow && floors[0].name.contains("water");
boolean hasDesert = !hasSnow && !hasRain && floors[0].name.contains("sand");
boolean hasSpores = floors[0].name.contains("spore") || floors[0].name.contains("moss") || floors[0].name.contains("tainted");
if(hasSnow){
state.rules.weather.add(new WeatherEntry(Weathers.snow));
}
if(hasRain){
state.rules.weather.add(new WeatherEntry(Weathers.rain));
}
if(hasDesert){
state.rules.weather.add(new WeatherEntry(Weathers.sandstorm));
}
if(hasSpores){
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
}
state.secinfo.resources = content.asArray();
state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
}
@@ -272,12 +336,18 @@ public class World{
}
public void loadMap(Map map, Rules checkRules){
//load using custom loader if possible
if(customMapLoaders.containsKey(map)){
customMapLoaders.get(map).run();
return;
}
try{
SaveIO.load(map.file, new FilterContext(map));
}catch(Throwable e){
Log.err(e);
if(!headless){
ui.showErrorMessage("$map.invalid");
ui.showErrorMessage("@map.invalid");
Core.app.post(() -> state.set(State.menu));
invalidMap = true;
}
@@ -291,17 +361,17 @@ public class World{
if(!headless){
if(state.teams.playerCores().size == 0 && !checkRules.pvp){
ui.showErrorMessage("$map.nospawn");
ui.showErrorMessage("@map.nospawn");
invalidMap = true;
}else if(checkRules.pvp){ //pvp maps need two cores to be valid
if(state.teams.getActive().count(TeamData::hasCore) < 2){
invalidMap = true;
ui.showErrorMessage("$map.nospawn.pvp");
ui.showErrorMessage("@map.nospawn.pvp");
}
}else if(checkRules.attackMode){ //attack maps need two cores to be valid
invalidMap = state.teams.get(state.rules.waveTeam).noCores();
if(invalidMap){
ui.showErrorMessage("$map.nospawn.attack");
ui.showErrorMessage("@map.nospawn.attack");
}
}
}else{
@@ -317,7 +387,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)));
}
}
@@ -446,7 +516,7 @@ public class World{
dark = Math.max((edgeBlend - edgeDst) * (4f / edgeBlend), dark);
}
if(state.hasSector()){
if(state.hasSector() && state.getSector().preset == null){
int circleBlend = 14;
//quantized angle
float offset = state.getSector().rect.rotation + 90;
@@ -484,6 +554,9 @@ public class World{
private class Context implements WorldContext{
Context(){
}
@Override
public Tile tile(int index){
return tiles.geti(index);

View File

@@ -1,8 +1,8 @@
package mindustry.ctype;
import arc.files.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.mod.Mods.*;
@@ -10,8 +10,7 @@ import mindustry.mod.Mods.*;
public abstract class Content implements Comparable<Content>, Disposable{
public final short id;
/** Info on which mod this content was loaded from. */
public @NonNull ModContentInfo minfo = new ModContentInfo();
public ModContentInfo minfo = new ModContentInfo();
public Content(){
this.id = (short)Vars.content.getBy(getContentType()).size;

View File

@@ -15,7 +15,8 @@ public enum ContentType{
loadout_UNUSED,
typeid_UNUSED,
error,
planet;
planet,
ammo;
public static final ContentType[] all = values();
}

View File

@@ -8,6 +8,7 @@ import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
@@ -30,7 +31,7 @@ public abstract class UnlockableContent extends MappableContent{
this.localizedName = Core.bundle.get(getContentType() + "." + this.name + ".name", this.name);
this.description = Core.bundle.getOrNull(getContentType() + "." + this.name + ".description");
this.unlocked = Core.settings != null && Core.settings.getBool(name + "-unlocked", false);
this.unlocked = Core.settings != null && Core.settings.getBool(this.name + "-unlocked", false);
}
public String displayDescription(){
@@ -43,6 +44,11 @@ public abstract class UnlockableContent extends MappableContent{
}
/** @return items needed to research this content */
public ItemStack[] researchRequirements(){
return ItemStack.empty;
}
public String emoji(){
return Fonts.getUnicodeStr(name);
}

View File

@@ -1,14 +1,13 @@
package mindustry.editor;
import arc.struct.*;
import mindustry.annotations.Annotations.*;
import arc.struct.LongSeq;
import mindustry.game.Team;
import mindustry.gen.TileOp;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.blocks.environment.Floor;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.content;
import static mindustry.Vars.*;
public class DrawOperation{
private MapEditor editor;
@@ -64,8 +63,12 @@ public class DrawOperation{
if(type == OpType.floor.ordinal()){
tile.setFloor((Floor)content.block(to));
}else if(type == OpType.block.ordinal()){
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
Block block = content.block(to);
tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation);
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
}else if(type == OpType.rotation.ordinal()){
if(tile.build != null) tile.build.rotation = to;
}else if(type == OpType.team.ordinal()){
@@ -74,7 +77,7 @@ public class DrawOperation{
tile.setOverlayID(to);
}
});
editor.renderer().updatePoint(tile.x, tile.y);
editor.renderer.updatePoint(tile.x, tile.y);
}
@Struct

View File

@@ -1,7 +1,6 @@
package mindustry.editor;
import arc.func.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.editor.DrawOperation.*;
import mindustry.game.*;
@@ -19,8 +18,8 @@ public class EditorTile extends Tile{
}
@Override
public void setFloor(@NonNull Floor type){
if(state.isGame()){
public void setFloor(Floor type){
if(skip()){
super.setFloor(type);
return;
}
@@ -39,29 +38,27 @@ 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()){
if(skip()){
super.setBlock(type, team, rotation);
return;
}
if(this.block == type && (build == null || build.rotation == rotation)){
update();
return;
}
op(OpType.block, block.id);
if(rotation != 0) op(OpType.rotation, (byte)rotation);
if(team() != Team.derelict) op(OpType.team, (byte)team().id);
if(team != Team.derelict) op(OpType.team, (byte)team.id);
super.setBlock(type, team, rotation);
}
@Override
public void setTeam(Team team){
if(state.isGame()){
if(skip()){
super.setTeam(team);
return;
}
@@ -73,7 +70,7 @@ public class EditorTile extends Tile{
@Override
public void setOverlay(Block overlay){
if(state.isGame()){
if(skip()){
super.setOverlay(overlay);
return;
}
@@ -85,20 +82,24 @@ public class EditorTile extends Tile{
}
@Override
protected void preChanged(){
super.preChanged();
protected void fireChanged(){
if(skip()){
super.fireChanged();
}else{
update();
}
}
@Override
public void recache(){
if(state.isGame()){
if(skip()){
super.recache();
}
}
@Override
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
if(state.isGame()){
if(skip()){
super.changeEntity(team, entityprov, rotation);
return;
}
@@ -110,15 +111,23 @@ public class EditorTile extends Tile{
Block block = block();
if(block.hasEntity()){
if(block.hasBuilding()){
build = entityprov.get().init(this, team, false, rotation);
build.cons(new ConsumeModule(build));
build.cons = new ConsumeModule(build);
if(block.hasItems) build.items = new ItemModule();
if(block.hasLiquids) build.liquids(new LiquidModule());
if(block.hasPower) build.power(new PowerModule());
}
}
private void update(){
ui.editor.editor.renderer.updatePoint(x, y);
}
private boolean skip(){
return state.isGame() || ui.editor.editor.isLoading();
}
private void op(OpType type, short value){
ui.editor.editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value));
}

View File

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

View File

@@ -18,10 +18,10 @@ import static mindustry.Vars.*;
public class MapEditor{
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20};
private final Context context = new Context();
private StringMap tags = new StringMap();
private MapRenderer renderer = new MapRenderer(this);
public StringMap tags = new StringMap();
public MapRenderer renderer = new MapRenderer(this);
private final Context context = new Context();
private OperationStack stack = new OperationStack();
private DrawOperation currentOp;
private boolean loading;
@@ -31,8 +31,8 @@ public class MapEditor{
public Block drawBlock = Blocks.stone;
public Team drawTeam = Team.sharded;
public StringMap getTags(){
return tags;
public boolean isLoading(){
return loading;
}
public void beginEdit(int width, int height){
@@ -52,7 +52,7 @@ public class MapEditor{
if(map.file.parent().parent().name().equals("1127400") && steam){
tags.put("steamid", map.file.parent().name());
}
MapIO.loadMap(map, context);
load(() -> MapIO.loadMap(map, context));
renderer.resize(width(), height());
loading = false;
}
@@ -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;
@@ -210,10 +210,6 @@ public class MapEditor{
}
}
public MapRenderer renderer(){
return renderer;
}
public void resize(int width, int height){
clearOp();
@@ -227,8 +223,14 @@ public class MapEditor{
int px = offsetX + x, py = offsetY + y;
if(previous.in(px, py)){
tiles.set(x, y, previous.getn(px, py));
tiles.getn(x, y).x = (short)x;
tiles.getn(x, y).y = (short)y;
Tile tile = tiles.getn(x, y);
tile.x = (short)x;
tile.y = (short)y;
if(tile.build != null && tile.isCenter()){
tile.build.x = x * tilesize + tile.block().offset;
tile.build.y = y * tilesize + tile.block().offset;
}
}else{
tiles.set(x, y, new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0));
}

View File

@@ -1,7 +1,6 @@
package mindustry.editor;
import arc.*;
import arc.struct.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
@@ -14,6 +13,7 @@ import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
@@ -58,7 +58,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
infoDialog = new MapInfoDialog(editor);
generateDialog = new MapGenerateDialog(editor, true);
menu = new BaseDialog("$menu");
menu = new BaseDialog("@menu");
menu.addCloseButton();
float swidth = 180f;
@@ -66,41 +66,41 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.cont.table(t -> {
t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5);
t.button("$editor.savemap", Icon.save, this::save);
t.button("@editor.savemap", Icon.save, this::save);
t.button("$editor.mapinfo", Icon.pencil, () -> {
t.button("@editor.mapinfo", Icon.pencil, () -> {
infoDialog.show();
menu.hide();
});
t.row();
t.button("$editor.generate", Icon.terrain, () -> {
t.button("@editor.generate", Icon.terrain, () -> {
generateDialog.show(generateDialog::applyToEditor);
menu.hide();
});
t.button("$editor.resize", Icon.resize, () -> {
t.button("@editor.resize", Icon.resize, () -> {
resizeDialog.show();
menu.hide();
});
t.row();
t.button("$editor.import", Icon.download, () -> createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
t.button("@editor.import", Icon.download, () -> createDialog("@editor.import",
"@editor.importmap", "@editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"@editor.importfile", "@editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
ui.showInfo("@editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
})),
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
"@editor.importimage", "@editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
@@ -108,17 +108,17 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
ui.showException("@editor.errorload", e);
Log.err(e);
}
})))
);
t.button("$editor.export", Icon.upload, () -> createDialog("$editor.export",
"$editor.exportfile", "$editor.exportfile.description", Icon.file,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
"$editor.exportimage", "$editor.exportimage.description", Icon.fileImage,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), "png", file -> {
t.button("@editor.export", Icon.upload, () -> createDialog("@editor.export",
"@editor.exportfile", "@editor.exportfile.description", Icon.file,
(Runnable)() -> platform.export(editor.tags.get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
"@editor.exportimage", "@editor.exportimage.description", Icon.fileImage,
(Runnable)() -> platform.export(editor.tags.get("name", "unknown"), "png", file -> {
Pixmap out = MapIO.writeImage(editor.tiles());
file.writePNG(out);
out.dispose();
@@ -128,17 +128,17 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.cont.row();
if(steam){
menu.cont.button("$editor.publish.workshop", Icon.link, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
menu.cont.button("@editor.publish.workshop", Icon.link, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.tags.get("name", "").trim()));
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
platform.viewListingID(editor.getTags().get("steamid"));
if(editor.tags.containsKey("steamid") && builtin != null && !builtin.custom){
platform.viewListingID(editor.tags.get("steamid"));
return;
}
Map map = save();
if(editor.getTags().containsKey("steamid") && map != null){
if(editor.tags.containsKey("steamid") && map != null){
platform.viewListing(map);
return;
}
@@ -146,26 +146,26 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(map == null) return;
if(map.tags.get("description", "").length() < 4){
ui.showErrorMessage("$editor.nodescription");
ui.showErrorMessage("@editor.nodescription");
return;
}
if(!Structs.contains(Gamemode.all, g -> g.valid(map))){
ui.showErrorMessage("$map.nospawn");
ui.showErrorMessage("@map.nospawn");
return;
}
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.tags.containsKey("steamid") ? editor.tags.get("author").equals(player.name) ? "@workshop.listing" : "@view.workshop" : "@editor.publish.workshop"));
menu.cont.row();
}
menu.cont.button("$editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.button("@editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.row();
menu.cont.button("$quit", Icon.exit, () -> {
menu.cont.button("@quit", Icon.exit, () -> {
tryExit();
menu.hide();
}).size(swidth * 2f + 10, 60f);
@@ -182,7 +182,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
try{
editor.beginEdit(map);
}catch(Exception e){
ui.showException("$editor.errorload", e);
ui.showException("@editor.errorload", e);
Log.err(e);
}
}));
@@ -235,7 +235,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
state.rules = (lastSavedRules == null ? new Rules() : lastSavedRules);
lastSavedRules = null;
saved = false;
editor.renderer().updateAll();
editor.renderer.updateAll();
}
private void playtest(){
@@ -254,14 +254,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
"height", editor.height()
));
world.endMapLoad();
//add entities so they update. is this really needed?
for(Tile tile : world.tiles){
if(tile.build != null){
tile.build.add();
}
}
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
player.clearUnit();
Groups.unit.clear();
Groups.build.clear();
logic.play();
});
}
@@ -269,10 +265,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
public @Nullable Map save(){
boolean isEditor = state.rules.editor;
state.rules.editor = false;
String name = editor.getTags().get("name", "").trim();
editor.getTags().put("rules", JsonIO.write(state.rules));
editor.getTags().remove("width");
editor.getTags().remove("height");
String name = editor.tags.get("name", "").trim();
editor.tags.put("rules", JsonIO.write(state.rules));
editor.tags.remove("width");
editor.tags.remove("height");
player.clearUnit();
@@ -280,14 +276,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(name.isEmpty()){
infoDialog.show();
Core.app.post(() -> ui.showErrorMessage("$editor.save.noname"));
Core.app.post(() -> ui.showErrorMessage("@editor.save.noname"));
}else{
Map map = maps.all().find(m -> m.name().equals(name));
if(map != null && !map.custom){
handleSaveBuiltin(map);
}else{
returned = maps.saveMap(editor.getTags());
ui.showInfoFade("$editor.saved");
returned = maps.saveMap(editor.tags);
ui.showInfoFade("@editor.saved");
}
}
@@ -299,7 +295,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
/** Called when a built-in map save is attempted.*/
protected void handleSaveBuiltin(Map map){
ui.showErrorMessage("$editor.save.overwrite");
ui.showErrorMessage("@editor.save.overwrite");
}
/**
@@ -357,7 +353,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
@Override
public void dispose(){
editor.renderer().dispose();
editor.renderer.dispose();
}
public void beginEditMap(Fi file){
@@ -368,7 +364,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
show();
}catch(Exception e){
Log.err(e);
ui.showException("$editor.errorload", e);
ui.showException("@editor.errorload", e);
}
});
}
@@ -521,7 +517,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
tools.row();
tools.table(Tex.underline, t -> t.add("$editor.teams"))
tools.table(Tex.underline, t -> t.add("@editor.teams"))
.colspan(3).height(40).width(size * 3f + 3f).padBottom(3);
tools.row();
@@ -557,7 +553,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
t.top();
t.add("$editor.brush");
t.add("@editor.brush");
t.row();
t.add(slider).width(size * 3f - 20).padTop(4f);
}).padTop(5).growX().top();
@@ -576,22 +572,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,14 +613,14 @@ 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);
editor.renderer.updatePoint(x, y);
}
if(tile.overlay() != Blocks.air && tile.overlay() != Blocks.spawn){
tile.setOverlay(Blocks.air);
editor.renderer().updatePoint(x, y);
editor.renderer.updatePoint(x, y);
}
}
}
@@ -649,11 +643,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
private void tryExit(){
if(!saved){
ui.showConfirm("$confirm", "$editor.unsaved", this::hide);
}else{
hide();
}
ui.showConfirm("@confirm", "@editor.unsaved", this::hide);
}
private void addBlockSelection(Table table){

View File

@@ -37,18 +37,18 @@ public class MapGenerateDialog extends BaseDialog{
private Pixmap pixmap;
private Texture texture;
private GenerateInput input = new GenerateInput();
private Seq<GenerateFilter> filters = new Seq<>();
Seq<GenerateFilter> filters = new Seq<>();
private int scaling = mobile ? 3 : 1;
private Table filterTable;
private AsyncExecutor executor = new AsyncExecutor(1);
private AsyncResult<Void> result;
private boolean generating;
boolean generating;
private GenTile returnTile = new GenTile();
private GenTile[][] buffer1, buffer2;
private Cons<Seq<GenerateFilter>> applier;
private CachedTile ctile = new CachedTile(){
CachedTile ctile = new CachedTile(){
//nothing.
@Override
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
@@ -58,34 +58,34 @@ public class MapGenerateDialog extends BaseDialog{
/** @param applied whether or not to use the applied in-game mode. */
public MapGenerateDialog(MapEditor editor, boolean applied){
super("$editor.generate");
super("@editor.generate");
this.editor = editor;
this.applied = applied;
shown(this::setup);
addCloseButton();
if(applied){
buttons.button("$editor.apply", () -> {
buttons.button("@editor.apply", () -> {
ui.loadAnd(() -> {
apply();
hide();
});
}).size(160f, 64f);
}else{
buttons.button("$settings.reset", () -> {
buttons.button("@settings.reset", () -> {
filters.set(maps.readFilters(""));
rebuildFilters();
update();
}).size(160f, 64f);
}
buttons.button("$editor.randomize", () -> {
buttons.button("@editor.randomize", () -> {
for(GenerateFilter filter : filters){
filter.randomize();
}
update();
}).size(160f, 64f);
buttons.button("$add", Icon.add, this::showAdd).height(64f).width(140f);
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(140f);
if(!applied){
hidden(this::apply);
@@ -144,7 +144,7 @@ public class MapGenerateDialog extends BaseDialog{
}
//reset undo stack as generation... messes things up
editor.renderer().updateAll();
editor.renderer.updateAll();
editor.clearOp();
}
@@ -178,7 +178,7 @@ public class MapGenerateDialog extends BaseDialog{
add(new Image(Styles.black8));
add(new Image(Icon.refresh, Scaling.none));
visible(() -> generating && !updateEditorOnChange);
}}).grow().padRight(10);
}}).uniformX().grow().padRight(10);
t.pane(p -> filterTable = p.marginRight(6)).update(pane -> {
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
return;
@@ -191,7 +191,7 @@ public class MapGenerateDialog extends BaseDialog{
}else{
Core.scene.setScrollFocus(null);
}
}).grow().get().setScrollingDisabled(true, false);
}).grow().uniformX().get().setScrollingDisabled(true, false);
}).grow();
buffer1 = create();
@@ -213,7 +213,7 @@ public class MapGenerateDialog extends BaseDialog{
}
void rebuildFilters(){
int cols = Math.max((int)(Math.max(filterTable.parent.getWidth(), Core.graphics.getWidth()/2f * 0.9f) / Scl.scl(290f)), 1);
int cols = Math.max((int)(Core.graphics.getWidth()/2f / Scl.scl(290f)), 1);
filterTable.clearChildren();
filterTable.top().left();
int i = 0;
@@ -221,41 +221,45 @@ public class MapGenerateDialog extends BaseDialog{
for(GenerateFilter filter : filters){
//main container
filterTable.table(Tex.button, c -> {
filterTable.table(Tex.pane, c -> {
c.margin(0);
//icons to perform actions
c.table(t -> {
t.top();
t.add(filter.name()).padTop(5).color(Pal.accent).growX().left();
c.table(Tex.whiteui, t -> {
t.setColor(Pal.gray);
t.row();
t.top().left();
t.add(filter.name()).left().padLeft(6).width(100f).wrap();
t.table(b -> {
ImageButtonStyle style = Styles.cleari;
b.defaults().size(50f);
b.button(Icon.refresh, style, () -> {
filter.randomize();
update();
});
t.add().growX();
b.button(Icon.upOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
b.button(Icon.downOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
rebuildFilters();
update();
});
b.button(Icon.trash, style, () -> {
filters.remove(filter);
rebuildFilters();
update();
});
ImageButtonStyle style = Styles.geni;
t.defaults().size(42f);
t.button(Icon.refresh, style, () -> {
filter.randomize();
update();
});
}).fillX();
t.button(Icon.upOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
t.button(Icon.downOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
rebuildFilters();
update();
});
t.button(Icon.cancel, style, () -> {
filters.remove(filter);
rebuildFilters();
update();
});
}).growX();
c.row();
//all the options
c.table(f -> {
@@ -269,20 +273,22 @@ public class MapGenerateDialog extends BaseDialog{
}).growX().left();
f.row();
}
}).grow().left().pad(2).top();
}).grow().left().pad(6).top();
}).width(280f).pad(3).top().left().fillY();
if(++i % cols == 0){
filterTable.row();
}
}
if(filters.isEmpty()){
filterTable.add("$filters.empty").wrap().width(200f);
filterTable.add("@filters.empty").wrap().width(200f);
}
}
void showAdd(){
BaseDialog selection = new BaseDialog("$add");
BaseDialog selection = new BaseDialog("@add");
selection.setFillParent(false);
selection.cont.defaults().size(210f, 60f);
int i = 0;
@@ -300,7 +306,7 @@ public class MapGenerateDialog extends BaseDialog{
if(++i % 2 == 0) selection.cont.row();
}
selection.cont.button("$filter.defaultores", () -> {
selection.cont.button("@filter.defaultores", () -> {
maps.addDefaultOres(filters);
rebuildFilters();
update();
@@ -344,6 +350,7 @@ public class MapGenerateDialog extends BaseDialog{
result = executor.submit(() -> {
try{
world.setGenerating(true);
generating = true;
if(!filters.isEmpty()){
@@ -396,7 +403,7 @@ public class MapGenerateDialog extends BaseDialog{
generating = false;
e.printStackTrace();
}
return null;
world.setGenerating(false);
});
}
@@ -404,10 +411,13 @@ public class MapGenerateDialog extends BaseDialog{
public byte team;
public short block, floor, ore;
GenTile(){
}
public void set(Block floor, Block wall, Block ore, Team team){
this.floor = floor.id;
this.block = wall.id;
this.ore = ore.id;
this.ore = floor.asFloor().isLiquid ? 0 : ore.id;
this.team = (byte)team.id;
}

View File

@@ -1,8 +1,8 @@
package mindustry.editor;
import arc.*;
import arc.struct.*;
import arc.scene.ui.*;
import arc.struct.*;
import mindustry.*;
import mindustry.game.*;
import mindustry.io.*;
@@ -16,7 +16,7 @@ public class MapInfoDialog extends BaseDialog{
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
public MapInfoDialog(MapEditor editor){
super("$editor.mapinfo");
super("@editor.mapinfo");
this.editor = editor;
this.waveInfo = new WaveInfoDialog(editor);
this.generate = new MapGenerateDialog(editor, false);
@@ -29,52 +29,52 @@ public class MapInfoDialog extends BaseDialog{
private void setup(){
cont.clear();
ObjectMap<String, String> tags = editor.getTags();
ObjectMap<String, String> tags = editor.tags;
cont.pane(t -> {
t.add("$editor.mapname").padRight(8).left();
t.add("@editor.mapname").padRight(8).left();
t.defaults().padTop(15);
TextField name = t.field(tags.get("name", ""), text -> {
tags.put("name", text);
}).size(400, 55f).addInputDialog(50).get();
name.setMessageText("$unknown");
name.setMessageText("@unknown");
t.row();
t.add("$editor.description").padRight(8).left();
t.add("@editor.description").padRight(8).left();
TextArea description = t.area(tags.get("description", ""), Styles.areaField, text -> {
tags.put("description", text);
}).size(400f, 140f).addInputDialog(1000).get();
t.row();
t.add("$editor.author").padRight(8).left();
t.add("@editor.author").padRight(8).left();
TextField author = t.field(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
tags.put("author", text);
Core.settings.put("mapAuthor", text);
}).size(400, 55f).addInputDialog(50).get();
author.setMessageText("$unknown");
author.setMessageText("@unknown");
t.row();
t.add("$editor.rules").padRight(8).left();
t.button("$edit", () -> {
t.add("@editor.rules").padRight(8).left();
t.button("@edit", () -> {
ruleInfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules());
hide();
}).left().width(200f);
t.row();
t.add("$editor.waves").padRight(8).left();
t.button("$edit", () -> {
t.add("@editor.waves").padRight(8).left();
t.button("@edit", () -> {
waveInfo.show();
hide();
}).left().width(200f);
t.row();
t.add("$editor.generation").padRight(8).left();
t.button("$edit", () -> {
generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)));
t.add("@editor.generation").padRight(8).left();
t.button("@edit", () -> {
generate.show(Vars.maps.readFilters(editor.tags.get("genfilters", "")),
filters -> editor.tags.put("genfilters", JsonIO.write(filters)));
hide();
}).left().width(200f);

View File

@@ -8,17 +8,17 @@ import mindustry.maps.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.maps;
import static mindustry.Vars.*;
public class MapLoadDialog extends BaseDialog{
private Map selected = null;
public MapLoadDialog(Cons<Map> loader){
super("$editor.loadmap");
super("@editor.loadmap");
shown(this::rebuild);
TextButton button = new TextButton("$load");
TextButton button = new TextButton("@load");
button.setDisabled(() -> selected == null);
button.clicked(() -> {
if(selected != null){
@@ -28,7 +28,7 @@ public class MapLoadDialog extends BaseDialog{
});
buttons.defaults().size(200f, 50f);
buttons.button("$cancel", this::hide);
buttons.button("@cancel", this::hide);
buttons.add(button);
}
@@ -64,9 +64,9 @@ public class MapLoadDialog extends BaseDialog{
}
if(maps.all().size == 0){
table.add("$maps.none").center();
table.add("@maps.none").center();
}else{
cont.add("$editor.loadmap");
cont.add("@editor.loadmap");
}
cont.row();

View File

@@ -24,7 +24,7 @@ public class MapRenderer implements Disposable{
public MapRenderer(MapEditor editor){
this.editor = editor;
this.texture = Core.atlas.find("clear-editor").getTexture();
this.texture = Core.atlas.find("clear-editor").texture;
}
public void resize(int width, int height){
@@ -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.width * Draw.scl, height = region.height * 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());
}else{
region = floor.editorVariantRegions()[Mathf.randomSeed(idxWall, 0, floor.editorVariantRegions().length - 1)];
@@ -135,15 +130,15 @@ public class MapRenderer implements Disposable{
region = Core.atlas.find("block-border-editor");
}else if(!wall.synthetic() && wall != Blocks.air && center){
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
offsetX = tilesize / 2f - region.getWidth() / 2f * Draw.scl;
offsetY = tilesize / 2f - region.getHeight() / 2f * Draw.scl;
offsetX = tilesize / 2f - region.width / 2f * Draw.scl;
offsetY = tilesize / 2f - region.height / 2f * Draw.scl;
}else if(wall == Blocks.air && !tile.overlay().isAir()){
region = tile.overlay().editorVariantRegions()[Mathf.randomSeed(idxWall, 0, tile.overlay().editorVariantRegions().length - 1)];
}else{
region = Core.atlas.find("clear-editor");
}
float width = region.getWidth() * Draw.scl, height = region.getHeight() * Draw.scl;
float width = region.width * Draw.scl, height = region.height * Draw.scl;
if(!wall.synthetic() && wall != Blocks.air && !wall.isMultiblock()){
offsetX = 0;
offsetY = 0;

View File

@@ -12,7 +12,7 @@ public class MapResizeDialog extends BaseDialog{
int width, height;
public MapResizeDialog(MapEditor editor, Intc2 cons){
super("$editor.resizemap");
super("@editor.resizemap");
shown(() -> {
cont.clear();
width = editor.width();
@@ -21,7 +21,7 @@ public class MapResizeDialog extends BaseDialog{
Table table = new Table();
for(boolean w : Mathf.booleans){
table.add(w ? "$width" : "$height").padRight(8f);
table.add(w ? "@width" : "@height").padRight(8f);
table.defaults().height(60f).padTop(8);
table.field((w ? width : height) + "", TextFieldFilter.digitsOnly, value -> {
@@ -37,8 +37,8 @@ public class MapResizeDialog extends BaseDialog{
});
buttons.defaults().size(200f, 50f);
buttons.button("$cancel", this::hide);
buttons.button("$ok", () -> {
buttons.button("@cancel", this::hide);
buttons.button("@ok", () -> {
cons.get(width, height);
hide();
});

View File

@@ -1,28 +1,26 @@
package mindustry.editor;
import arc.Core;
import arc.graphics.Color;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.GestureDetector;
import arc.input.GestureDetector.GestureListener;
import arc.input.KeyCode;
import arc.math.Mathf;
import arc.input.*;
import arc.input.GestureDetector.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.Element;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.TextField;
import arc.scene.ui.layout.Scl;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.graphics.Pal;
import mindustry.input.Binding;
import mindustry.ui.GridImage;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.ui.*;
import static mindustry.Vars.mobile;
import static mindustry.Vars.ui;
import static mindustry.Vars.*;
public class MapView extends Element implements GestureListener{
private MapEditor editor;
private EditorTool tool = EditorTool.pencil;
EditorTool tool = EditorTool.pencil;
private float offsetx, offsety;
private float zoom = 1f;
private boolean grid = false;
@@ -31,11 +29,11 @@ public class MapView extends Element implements GestureListener{
private Rect rect = new Rect();
private Vec2[][] brushPolygons = new Vec2[MapEditor.brushSizes.length][0];
private boolean drawing;
private int lastx, lasty;
private int startx, starty;
private float mousex, mousey;
private EditorTool lastTool;
boolean drawing;
int lastx, lasty;
int startx, starty;
float mousex, mousey;
EditorTool lastTool;
public MapView(MapEditor editor){
this.editor = editor;
@@ -204,7 +202,7 @@ public class MapView extends Element implements GestureListener{
zoom = Mathf.clamp(zoom, 0.2f, 20f);
}
private Point2 project(float x, float y){
Point2 project(float x, float y){
float ratio = 1f / ((float)editor.width() / editor.height());
float size = Math.min(width, height);
float sclwidth = size * zoom;
@@ -213,9 +211,9 @@ public class MapView extends Element implements GestureListener{
y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.height();
if(editor.drawBlock.size % 2 == 0 && tool != EditorTool.eraser){
return Tmp.g1.set((int)(x - 0.5f), (int)(y - 0.5f));
return Tmp.p1.set((int)(x - 0.5f), (int)(y - 0.5f));
}else{
return Tmp.g1.set((int)x, (int)y);
return Tmp.p1.set((int)x, (int)y);
}
}
@@ -248,14 +246,20 @@ public class MapView extends Element implements GestureListener{
Draw.color(Pal.remove);
Lines.stroke(2f);
Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2);
editor.renderer().draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
editor.renderer.draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
Draw.reset();
if(grid){
Draw.color(Color.gray);
image.setBounds(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
image.draw();
Draw.color();
Lines.stroke(3f);
Draw.color(Pal.accent);
Lines.line(centerx - sclwidth/2f, centery, centerx + sclwidth/2f, centery);
Lines.line(centerx, centery - sclheight/2f, centerx, centery + sclheight/2f);
Draw.reset();
}
int index = 0;

View File

@@ -1,6 +1,6 @@
package mindustry.editor;
import arc.struct.Seq;
import arc.struct.*;
public class OperationStack{
private static final int maxSize = 10;

View File

@@ -32,7 +32,6 @@ public class WaveGraph extends Table{
rect((x, y, width, height) -> {
Lines.stroke(Scl.scl(3f));
Lines.precise(true);
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
Font font = Fonts.outline;
@@ -122,7 +121,6 @@ public class WaveGraph extends Table{
Pools.free(lay);
Lines.precise(false);
Draw.reset();
}).pad(4).padBottom(10).grow();
@@ -137,7 +135,7 @@ public class WaveGraph extends Table{
ButtonGroup<Button> group = new ButtonGroup<>();
for(Mode m : Mode.all){
t.button("$wavemode." + m.name(), Styles.fullTogglet, () -> {
t.button("@wavemode." + m.name(), Styles.fullTogglet, () -> {
mode = m;
}).group(group).height(35f).update(b -> b.setChecked(m == mode)).width(130f);
}

View File

@@ -23,7 +23,7 @@ import static mindustry.game.SpawnGroup.*;
public class WaveInfoDialog extends BaseDialog{
private int displayed = 20;
private Seq<SpawnGroup> groups = new Seq<>();
Seq<SpawnGroup> groups = new Seq<>();
private Table table;
private int start = 0;
@@ -32,7 +32,7 @@ public class WaveInfoDialog extends BaseDialog{
private WaveGraph graph = new WaveGraph();
public WaveInfoDialog(MapEditor editor){
super("$waves.title");
super("@waves.title");
shown(this::setup);
hidden(() -> {
@@ -48,29 +48,29 @@ public class WaveInfoDialog extends BaseDialog{
onResize(this::setup);
addCloseButton();
buttons.button("$waves.edit", () -> {
BaseDialog dialog = new BaseDialog("$waves.edit");
buttons.button("@waves.edit", () -> {
BaseDialog dialog = new BaseDialog("@waves.edit");
dialog.addCloseButton();
dialog.setFillParent(false);
dialog.cont.defaults().size(210f, 64f);
dialog.cont.button("$waves.copy", () -> {
ui.showInfoFade("$waves.copied");
dialog.cont.button("@waves.copy", () -> {
ui.showInfoFade("@waves.copied");
Core.app.setClipboardText(maps.writeWaves(groups));
dialog.hide();
}).disabled(b -> groups == null);
dialog.cont.row();
dialog.cont.button("$waves.load", () -> {
dialog.cont.button("@waves.load", () -> {
try{
groups = maps.readWaves(Core.app.getClipboardText());
buildGroups();
}catch(Exception e){
e.printStackTrace();
ui.showErrorMessage("$waves.invalid");
ui.showErrorMessage("@waves.invalid");
}
dialog.hide();
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
dialog.cont.row();
dialog.cont.button("$settings.reset", () -> ui.showConfirm("$confirm", "$settings.clear.confirm", () -> {
dialog.cont.button("@settings.reset", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
groups = JsonIO.copy(defaultWaves.get());
buildGroups();
dialog.hide();
@@ -130,12 +130,12 @@ public class WaveInfoDialog extends BaseDialog{
cont.stack(new Table(Tex.clear, main -> {
main.pane(t -> table = t).growX().growY().padRight(8f).get().setScrollingDisabled(true, false);
main.row();
main.button("$add", () -> {
main.button("@add", () -> {
if(groups == null) groups = new Seq<>();
groups.add(new SpawnGroup(lastType));
buildGroups();
}).growX().height(70f);
}), new Label("$waves.none"){{
}), new Label("@waves.none"){{
visible(() -> groups.isEmpty());
this.touchable = Touchable.disabled;
setWrap(true);
@@ -180,7 +180,7 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(100f);
spawns.add("$waves.to").padLeft(4).padRight(4);
spawns.add("@waves.to").padLeft(4).padRight(4);
spawns.field(group.end == never ? "" : (group.end + 1) + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
group.end = Strings.parseInt(text) - 1;
@@ -193,14 +193,14 @@ public class WaveInfoDialog extends BaseDialog{
});
t.row();
t.table(p -> {
p.add("$waves.every").padRight(4);
p.add("@waves.every").padRight(4);
p.field(group.spacing + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text) && Strings.parseInt(text) > 0){
group.spacing = Strings.parseInt(text);
updateWaves();
}
}).width(100f);
p.add("$waves.waves").padLeft(4);
p.add("@waves.waves").padLeft(4);
});
t.row();
@@ -219,7 +219,7 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(80f);
a.add("$waves.perspawn").padLeft(4);
a.add("@waves.perspawn").padLeft(4);
});
t.row();
t.table(a -> {
@@ -237,17 +237,17 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(80f);
a.add("$waves.shields").padLeft(4);
a.add("@waves.shields").padLeft(4);
});
t.row();
t.check("$waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
t.check("@waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
}).width(340f).pad(8);
table.row();
}
}else{
table.add("$editor.default");
table.add("@editor.default");
}
updateWaves();

View File

@@ -20,6 +20,7 @@ import static mindustry.Vars.*;
/** Utility class for damaging in an area. */
public class Damage{
private static Tile furthest;
private static Rect rect = new Rect();
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2();
@@ -30,24 +31,26 @@ public class Damage{
private static Unit tmpUnit;
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color, boolean damage){
if(damage){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
}
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
for(int i = 0; i < waves; i++){
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
for(int i = 0; i < waves; i++){
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
}
}
if(explosiveness > 15f){
@@ -59,7 +62,7 @@ public class Damage{
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effect.shake(shake, shake, x, y);
Fx.dynamicExplosion.at(x, y, radius / 8f);
}
@@ -74,6 +77,28 @@ public class Damage{
}
}
public static float findLaserLength(Bullet b, float length){
Tmp.v1.trns(b.rotation(), length);
furthest = null;
boolean found = world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
}
/** 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, boolean large){
float resultLength = findLaserLength(b, length);
collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), resultLength, large);
b.fdata = resultLength;
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);
}
@@ -83,11 +108,13 @@ public class Damage{
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
length = findLaserLength(hitter, length);
collidedBlocks.clear();
tr.trns(angle, length);
Intc2 collider = (cx, cy) -> {
Building tile = world.build(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team() != team && tile.collide(hitter)){
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team != team && tile.collide(hitter)){
tile.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.type.hit(hitter, tile.x, tile.y);
@@ -130,13 +157,8 @@ public class Damage{
if(!e.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround)) return;
e.hitbox(hitrect);
Rect other = hitrect;
other.y -= expand;
other.x -= expand;
other.width += expand * 2;
other.height += expand * 2;
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, other);
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, hitrect.grow(expand * 2));
if(vec != null){
effect.at(vec.x, vec.y);
@@ -162,7 +184,6 @@ public class Damage{
Building tile = world.build(cx, cy);
if(tile != null && tile.team != hitter.team){
tmpBuilding = tile;
//TODO return tile
return true;
}
return false;

View File

@@ -1,15 +1,26 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Effect{
private static final float shakeFalloff = 10000f;
private static final EffectContainer container = new EffectContainer();
private static int lastid = 0;
private static final Seq<Effect> all = new Seq<>();
public final int id;
public final Cons<EffectContainer> renderer;
@@ -21,14 +32,15 @@ public class Effect{
public float groundDuration;
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
this.id = lastid++;
this.id = all.size;
this.lifetime = life;
this.renderer = renderer;
this.size = clipsize;
all.add(this);
}
public Effect(float life, Cons<EffectContainer> renderer){
this(life, 28f, renderer);
this(life,50f, renderer);
}
public Effect ground(){
@@ -43,42 +55,123 @@ public class Effect{
}
public void at(Position pos){
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
create(this, pos.getX(), pos.getY(), 0, Color.white, null);
}
public void at(Position pos, float rotation){
Effects.create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
}
public void at(float x, float y){
Effects.create(this, x, y, 0, Color.white, null);
create(this, x, y, 0, Color.white, null);
}
public void at(float x, float y, float rotation){
Effects.create(this, x, y, rotation, Color.white, null);
create(this, x, y, rotation, Color.white, null);
}
public void at(float x, float y, float rotation, Color color){
Effects.create(this, x, y, rotation, color, null);
create(this, x, y, rotation, color, null);
}
public void at(float x, float y, Color color){
Effects.create(this, x, y, 0, color, null);
create(this, x, y, 0, color, null);
}
public void at(float x, float y, float rotation, Color color, Object data){
Effects.create(this, x, y, rotation, color, data);
create(this, x, y, rotation, color, data);
}
public void at(float x, float y, float rotation, Object data){
Effects.create(this, x, y, rotation, Color.white, data);
create(this, x, y, rotation, Color.white, data);
}
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
public float render(int id, Color color, float life, float lifetime, 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();
return container.lifetime;
}
public static @Nullable Effect get(int id){
return id >= all.size || id < 0 ? null : all.get(id);
}
private static void shake(float intensity, float duration){
if(!headless){
Vars.renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
EffectState entity = EffectState.create();
entity.effect = effect;
entity.rotation = rotation;
entity.data = (data);
entity.lifetime = (effect.lifetime);
entity.set(x, y);
entity.color.set(color);
if(data instanceof Posc) entity.parent = ((Posc)data);
entity.add();
}
}
}
public static void decal(TextureRegion region, float x, float y, float rotation){
decal(region, x, y, rotation, 3600f, Pal.rubble);
}
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
if(headless || region == null || !Core.atlas.isFound(region)) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
Decal decal = Decal.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public static void scorch(float x, float y, int size){
if(headless) return;
size = Mathf.clamp(size, 0, 9);
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static void rubble(float x, float y, int blockSize){
if(headless) return;
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static class EffectContainer implements Scaled{

View File

@@ -1,92 +0,0 @@
package mindustry.entities;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Effects{
private static final float shakeFalloff = 10000f;
private static void shake(float intensity, float duration){
if(!headless){
renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
if(Core.camera == null) return;
float distance = Core.camera.position.dst(x, y);
if(distance < 1) distance = 1;
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
}
public static void shake(float intensity, float duration, Position loc){
shake(intensity, duration, loc.getX(), loc.getY());
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
EffectState entity = EffectState.create();
entity.effect(effect);
entity.rotation(rotation);
entity.data(data);
entity.lifetime(effect.lifetime);
entity.set(x, y);
entity.color().set(color);
if(data instanceof Posc) entity.parent((Posc)data);
entity.add();
}
}
}
public static void decal(TextureRegion region, float x, float y, float rotation){
decal(region, x, y, rotation, 3600f, Pal.rubble);
}
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
if(headless || region == null || !Core.atlas.isFound(region)) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
Decal decal = Decal.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public static void scorch(float x, float y, int size){
if(headless) return;
size = Mathf.clamp(size, 0, 9);
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static void rubble(float x, float y, int blockSize){
if(headless) return;
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
}

View File

@@ -8,7 +8,7 @@ import mindustry.gen.*;
import java.util.*;
import static mindustry.Vars.collisions;
import static mindustry.Vars.*;
/** Represents a group of a certain type of entity.*/
@SuppressWarnings("unchecked")
@@ -57,7 +57,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
each(Entityc::update);
}
public void copy(Seq arr){
public void copy(Seq<T> arr){
arr.addAll(array);
}
@@ -101,7 +101,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
T t = map.get(id);
if(t != null){ //remove if present in map already
remove(t);
t.remove();
}
}

View File

@@ -17,7 +17,7 @@ public class Fires{
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null) return; //not clientside.
if(net.client() || tile == null || !state.rules.fire) return; //not clientside.
Fire fire = map.get(tile.pos());

View File

@@ -8,7 +8,7 @@ class GroupDefs<G>{
@GroupDef(value = Playerc.class, mapping = true) G player;
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
@GroupDef(value = Buildingc.class) G tile;
@GroupDef(value = Buildingc.class) G build;
@GroupDef(value = Syncc.class, mapping = true) G sync;
@GroupDef(value = Drawc.class) G draw;
@GroupDef(value = WeatherStatec.class) G weather;

View File

@@ -24,17 +24,15 @@ public class Lightning{
/** Create a lighting branch at a location. Use Team.derelict to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
createLightingInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
createLightningInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
}
/** Create a lighting branch at a location. Uses bullet parameters. */
public static void create(Bullet bullet, Color color, float damage, float x, float y, float targetAngle, int length){
createLightingInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
createLightningInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
}
//TODO remote method
//@Remote(called = Loc.server, unreliable = true)
private static void createLightingInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
private static void createLightningInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
random.setSeed(seed);
hit.clear();

View File

@@ -83,7 +83,7 @@ public class Puddles{
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
Fires.create(tile);
if(Mathf.chance(0.006 * amount)){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
}
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot Puddle
if(Mathf.chance(0.5f * amount)){

View File

@@ -1,5 +1,6 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.math.geom.*;
import mindustry.annotations.Annotations.*;
@@ -19,8 +20,41 @@ public class Units{
private static boolean boolResult;
@Remote(called = Loc.server)
public static void unitDeath(Unit unit){
unit.killed();
public static void unitCapDeath(Unit unit){
if(unit != null){
unit.dead = true;
Fx.unitCapKill.at(unit);
Core.app.post(() -> Call.unitDestroy(unit.id));
}
}
@Remote(called = Loc.server)
public static void unitDeath(int uid){
Unit unit = Groups.unit.getByID(uid);
//if there's no unit don't add it later and get it stuck as a ghost
if(netClient != null){
netClient.addRemovedEntity(uid);
}
if(unit != null){
unit.killed();
}
}
//destroys immediately
@Remote(called = Loc.server)
public static void unitDestroy(int uid){
Unit unit = Groups.unit.getByID(uid);
//if there's no unit don't add it later and get it stuck as a ghost
if(netClient != null){
netClient.addRemovedEntity(uid);
}
if(unit != null){
unit.destroy();
}
}
@Remote(called = Loc.server)
@@ -36,7 +70,7 @@ public class Units{
public static int getCap(Team team){
//wave team has no cap
if((team == state.rules.waveTeam && state.rules.waves) || (state.isCampaign() && team == state.rules.waveTeam)){
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
return Integer.MAX_VALUE;
}
return state.rules.unitCap + indexer.getExtraUnits(team);
@@ -90,7 +124,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)){
@@ -141,6 +175,18 @@ public class Units{
}
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static Teamc bestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Building> tilePred, Sortf sort){
if(team == Team.derelict) return null;
Unit unit = bestEnemy(team, x, y, range, unitPred, sort);
if(unit != null){
return unit;
}else{
return findEnemyTile(team, x, y, range, tilePred);
}
}
/** Returns the closest enemy of this team. Filter by predicate. */
public static Unit closestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate){
if(team == Team.derelict) return null;
@@ -161,6 +207,26 @@ public class Units{
return result;
}
/** Returns the closest enemy of this team using a custom comparison function. Filter by predicate. */
public static Unit bestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate, Sortf sort){
if(team == Team.derelict) return null;
result = null;
cdist = 0f;
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.dead() || !predicate.get(e) || !e.within(x, y, range)) return;
float cost = sort.cost(e, x, y);
if(result == null || cost < cdist){
result = e;
cdist = cost;
}
});
return result;
}
/** Returns the closest ally of this team. Filter by predicate. No range. */
public static Unit closest(Team team, float x, float y, Boolf<Unit> predicate){
result = null;
@@ -242,9 +308,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. */
@@ -252,4 +329,7 @@ public class Units{
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
}
public interface Sortf{
float cost(Unit unit, float x, float y);
}
}

View File

@@ -2,7 +2,16 @@ package mindustry.entities.abilities;
import mindustry.gen.*;
public interface Ability{
default void update(Unit unit){}
default void draw(Unit unit){}
public abstract class Ability implements Cloneable{
public void update(Unit unit){}
public void draw(Unit unit){}
public Ability copy(){
try{
return (Ability)clone();
}catch(CloneNotSupportedException e){
//I am disgusted
throw new RuntimeException("java sucks", e);
}
}
}

View File

@@ -11,7 +11,7 @@ import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class ForceFieldAbility implements Ability{
public class ForceFieldAbility extends Ability{
/** Shield radius. */
public float radius = 60f;
/** Shield regen speed in damage/tick. */
@@ -21,21 +21,26 @@ public class ForceFieldAbility implements Ability{
/** Cooldown after the shield is broken, in ticks. */
public float cooldown = 60f * 5;
private float realRad;
private Unit paramUnit;
private final Cons<Shielderc> shieldConsumer = trait -> {
if(trait.team() != paramUnit.team && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
/** State. */
protected float radiusScale, alpha;
private static float realRad;
private static Unit paramUnit;
private static ForceFieldAbility paramField;
private static final Cons<Bullet> shieldConsumer = trait -> {
if(trait.team != paramUnit.team && trait.type.absorbable && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
trait.absorb();
Fx.absorb.at(trait);
//break shield
if(paramUnit.shield <= trait.damage()){
paramUnit.shield -= cooldown * regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, radius, paramUnit.team.color);
paramUnit.shield -= paramField.cooldown * paramField.regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color);
}
paramUnit.shield -= trait.damage();
paramUnit.shieldAlpha = 1f;
paramField.alpha = 1f;
}
};
@@ -54,14 +59,17 @@ public class ForceFieldAbility implements Ability{
unit.shield += Time.delta * regen;
}
alpha = Math.max(alpha - Time.delta/10f, 0f);
if(unit.shield > 0){
unit.timer2 = Mathf.lerpDelta(unit.timer2, 1f, 0.06f);
radiusScale = Mathf.lerpDelta(radiusScale, 1f, 0.06f);
paramUnit = unit;
paramField = this;
checkRadius(unit);
Groups.bullet.intersect(unit.x - realRad, unit.y - realRad, realRad * 2f, realRad * 2f, shieldConsumer);
}else{
unit.timer2 = 0f;
radiusScale = 0f;
}
}
@@ -72,7 +80,7 @@ public class ForceFieldAbility implements Ability{
if(unit.shield > 0){
Draw.z(Layer.shields);
Draw.color(unit.team.color, Color.white, Mathf.clamp(unit.shieldAlpha));
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
if(Core.settings.getBool("animatedshields")){
Fill.poly(unit.x, unit.y, 6, realRad);
@@ -88,6 +96,6 @@ public class ForceFieldAbility implements Ability{
private void checkRadius(Unit unit){
//timer2 is used to store radius scale as an effect
realRad = unit.timer2 * radius;
realRad = radiusScale * radius;
}
}

View File

@@ -5,12 +5,13 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class HealFieldAbility implements Ability{
public class HealFieldAbility extends Ability{
public float amount = 1, reload = 100, range = 60;
public Effect healEffect = Fx.heal;
public Effect activeEffect = Fx.healWave;
public Effect activeEffect = Fx.healWaveDynamic;
private boolean wasHealed = false;
protected float timer;
protected boolean wasHealed = false;
HealFieldAbility(){}
@@ -22,9 +23,9 @@ public class HealFieldAbility implements Ability{
@Override
public void update(Unit unit){
unit.timer1 += Time.delta;
timer += Time.delta;
if(unit.timer1 >= reload){
if(timer >= reload){
wasHealed = false;
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
@@ -36,10 +37,10 @@ public class HealFieldAbility implements Ability{
});
if(wasHealed){
activeEffect.at(unit);
activeEffect.at(unit, range);
}
unit.timer1 = 0f;
timer = 0f;
}
}
}

View File

@@ -5,12 +5,13 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class ShieldFieldAbility implements Ability{
public class ShieldFieldAbility extends Ability{
public float amount = 1, max = 100f, reload = 100, range = 60;
public Effect applyEffect = Fx.shieldApply;
public Effect activeEffect = Fx.shieldWave;
private boolean applied = false;
protected float timer;
protected boolean applied = false;
ShieldFieldAbility(){}
@@ -23,9 +24,9 @@ public class ShieldFieldAbility implements Ability{
@Override
public void update(Unit unit){
unit.timer1 += Time.delta;
timer += Time.delta;
if(unit.timer1 >= reload){
if(timer >= reload){
applied = false;
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
@@ -41,7 +42,7 @@ public class ShieldFieldAbility implements Ability{
activeEffect.at(unit);
}
unit.timer1 = 0f;
timer = 0f;
}
}
}

View File

@@ -1,21 +1,22 @@
package mindustry.entities.abilities;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
public class StatusFieldAbility implements Ability{
public @NonNull StatusEffect effect;
public class StatusFieldAbility extends Ability{
public StatusEffect effect;
public float duration = 60, reload = 100, range = 20;
public Effect applyEffect = Fx.heal;
public Effect activeEffect = Fx.overdriveWave;
protected float timer;
StatusFieldAbility(){}
public StatusFieldAbility(@NonNull StatusEffect effect, float duration, float reload, float range){
public StatusFieldAbility(StatusEffect effect, float duration, float reload, float range){
this.duration = duration;
this.reload = reload;
this.range = range;
@@ -24,9 +25,9 @@ public class StatusFieldAbility implements Ability{
@Override
public void update(Unit unit){
unit.timer2 += Time.delta;
timer += Time.delta;
if(unit.timer2 >= reload){
if(timer >= reload){
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
other.apply(effect, duration);
@@ -34,7 +35,7 @@ public class StatusFieldAbility implements Ability{
activeEffect.at(unit);
unit.timer2 = 0f;
timer = 0f;
}
}
}

View File

@@ -0,0 +1,59 @@
package mindustry.entities.abilities;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
public class UnitSpawnAbility extends Ability{
public UnitType type;
public float spawnTime = 60f, spawnX, spawnY;
public Effect spawnEffect = Fx.spawn;
protected float timer;
public UnitSpawnAbility(UnitType type, float spawnTime, float spawnX, float spawnY){
this.type = type;
this.spawnTime = spawnTime;
this.spawnX = spawnX;
this.spawnY = spawnY;
}
public UnitSpawnAbility(){
}
@Override
public void update(Unit unit){
timer += Time.delta;
if(timer >= spawnTime && Units.canCreate(unit.team, type)){
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
spawnEffect.at(x, y);
Unit u = type.create(unit.team);
u.set(x, y);
u.rotation = unit.rotation;
if(!Vars.net.client()){
u.add();
}
timer = 0f;
}
}
@Override
public void draw(Unit unit){
if(Units.canCreate(unit.team, type)){
Draw.draw(Draw.z(), () -> {
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
Drawf.construct(x, y, type.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
});
}
}
}

View File

@@ -1,16 +1,17 @@
package mindustry.entities.bullet;
import arc.Core;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.graphics.*;
/** An extended BulletType for most ammo-based bullets shot from turrets and units. */
public class BasicBulletType extends BulletType{
public Color backColor = Pal.bulletYellowBack, frontColor = Pal.bulletYellow;
public Color mixColorFrom = new Color(1f, 1f, 1f, 0f), mixColorTo = new Color(1f, 1f, 1f, 0f);
public float width = 5f, height = 7f;
public float shrinkX = 0f, shrinkY = 0.5f;
public float spin = 0;
@@ -45,10 +46,15 @@ public class BasicBulletType extends BulletType{
float width = this.width * ((1f - shrinkX) + shrinkX * b.fout());
float offset = -90 + (spin != 0 ? Mathf.randomSeed(b.id, 360f) + b.time * spin : 0f);
Color mix = Tmp.c1.set(mixColorFrom).lerp(mixColorTo, b.fin());
Draw.mixcol(mix, mix.a);
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, width, height, b.rotation() + offset);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, width, height, b.rotation() + offset);
Draw.color();
Draw.reset();
}
}

View File

@@ -24,7 +24,7 @@ public abstract class BulletType extends Content{
public float hitSize = 4;
public float drawSize = 40f;
public float drag = 0f;
public boolean pierce;
public boolean pierce, pierceBuilding;
public Effect hitEffect, despawnEffect;
/** Effect created when shooting. */
@@ -54,7 +54,7 @@ public abstract class BulletType extends Content{
/** Status effect applied on hit. */
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect in terms of duration. */
public float statusDuration = 60 * 10f;
public float statusDuration = 60 * 8f;
/** Whether this bullet type collides with tiles. */
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */
@@ -69,13 +69,23 @@ public abstract class BulletType extends Content{
public boolean scaleVelocity;
/** Whether this bullet can be hit by point defense. */
public boolean hittable = true;
/** Whether this bullet can be reflected. */
public boolean reflectable = true;
/** Whether this projectile can be absorbed by shields. */
public boolean absorbable = true;
/** Whether to move the bullet back depending on delta to fix some delta-time realted issues.
* Do not change unless you know what you're doing. */
public boolean backMove = true;
/** Bullet range override. */
public float range = -1f;
//additional effects
public float fragCone = 360f;
public float fragAngle = 0f;
public int fragBullets = 9;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
public BulletType fragBullet = null;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f, fragLifeMin = 1f, fragLifeMax = 1f;
public @Nullable BulletType fragBullet = null;
public Color hitColor = Color.white;
public Color trailColor = Pal.missileYellowBack;
@@ -92,14 +102,17 @@ public abstract class BulletType extends Content{
public float homingPower = 0f;
public float homingRange = 50f;
public Color lightningColor = Pal.surge;
public int lightning;
public int lightningLength = 5;
public int lightningLength = 5, lightningLengthRand = 0;
/** Use a negative value to use default bullet damage. */
public float lightningDamage = -1;
public float lightningCone = 360f;
public float lightningAngle = 0f;
public float weaveScale = 1f;
public float weaveMag = -1f;
public float hitShake = 0f;
public float hitShake = 0f, despawnShake = 0f;
public int puddles;
public float puddleRange;
@@ -123,20 +136,24 @@ public abstract class BulletType extends Content{
/** Returns maximum distance the bullet this bullet type has can travel. */
public float range(){
return speed * lifetime * (1f - drag);
return Math.max(speed * lifetime * (1f - drag), range);
}
public boolean collides(Bullet bullet, Building tile){
return true;
}
public void hitTile(Bullet b, Building tile){
public void hitTile(Bullet b, Building tile, float initialHealth){
if(status == StatusEffects.burning) {
Damage.createIncend(b.x, b.y, damage/10f, (int) damage/10);
}
hit(b);
}
public void hitEntity(Bullet b, Hitboxc other, float initialHealth){
}
public void hit(Bullet b){
hit(b, b.x, b.y);
}
@@ -145,13 +162,13 @@ public abstract class BulletType extends Content{
hitEffect.at(x, y, b.rotation(), hitColor);
hitSound.at(b);
Effects.shake(hitShake, hitShake, b);
Effect.shake(hitShake, hitShake, b);
if(fragBullet != null){
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));
float a = b.rotation() + Mathf.range(fragCone/2) + fragAngle;
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
}
}
@@ -175,7 +192,7 @@ public abstract class BulletType extends Content{
}
for(int i = 0; i < lightning; i++){
Lightning.create(b, Pal.surge, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, b.rotation() + Mathf.range(lightningCone/2) + lightningAngle, lightningLength + Mathf.random(lightningLengthRand));
}
}
@@ -183,6 +200,8 @@ public abstract class BulletType extends Content{
despawnEffect.at(b.x, b.y, b.rotation(), hitColor);
hitSound.at(b);
Effect.shake(despawnShake, despawnShake, b);
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
hit(b);
}
@@ -201,7 +220,7 @@ public abstract class BulletType extends Content{
}
if(instantDisappear){
b.time(lifetime);
b.time = lifetime;
}
}
@@ -249,6 +268,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);
}
@@ -259,7 +282,11 @@ public abstract class BulletType extends Content{
bullet.owner = owner;
bullet.team = team;
bullet.vel.trns(angle, speed * velocityScl);
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
if(backMove){
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
}else{
bullet.set(x, y);
}
bullet.lifetime = lifetime * lifetimeScl;
bullet.data = data;
bullet.drag = drag;
@@ -267,13 +294,12 @@ public abstract class BulletType extends Content{
bullet.damage = damage < 0 ? this.damage : damage;
bullet.add();
if(keepVelocity && owner instanceof Hitboxc) bullet.vel.add(((Hitboxc)owner).deltaX(), ((Hitboxc)owner).deltaY());
if(keepVelocity && owner instanceof Velc) bullet.vel.add(((Velc)owner).vel().x, ((Velc)owner).vel().y);
return bullet;
}
public void createNet(Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
Call.createBullet(this, team, x, y, damage, angle, velocityScl, lifetimeScl);
Call.createBullet(this, team, x, y, angle, damage, velocityScl, lifetimeScl);
}
@Remote(called = Loc.server, unreliable = true)

View File

@@ -12,10 +12,13 @@ import mindustry.graphics.*;
public class ContinuousLaserBulletType extends BulletType{
public float length = 220f;
public float shake = 1f;
public float fadeTime = 16f;
public Color[] colors = {Color.valueOf("ec745855"), Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
public float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
public float[] strokes = {2f, 1.5f, 1f, 0.3f};
public float[] lenscales = {1f, 1.12f, 1.15f, 1.17f};
public float width = 9f, oscScl = 0.8f, oscMag = 1.5f;
public boolean largeHit = true;
public ContinuousLaserBulletType(float damage){
super(0.001f, damage);
@@ -25,6 +28,7 @@ public class ContinuousLaserBulletType extends BulletType{
hitSize = 4;
drawSize = 420f;
lifetime = 16f;
keepVelocity = false;
pierce = true;
hittable = false;
hitColor = colors[2];
@@ -32,6 +36,8 @@ public class ContinuousLaserBulletType extends BulletType{
incendAmount = 1;
incendSpread = 5;
incendChance = 0.4f;
lightColor = Color.orange;
absorbable = false;
}
protected ContinuousLaserBulletType(){
@@ -43,37 +49,45 @@ public class ContinuousLaserBulletType extends BulletType{
return length;
}
@Override
public void init(){
super.init();
drawSize = Math.max(drawSize, length*2f);
}
@Override
public void update(Bullet b){
//TODO possible laser absorption from blocks
//damage every 5 ticks
if(b.timer(1, 5f)){
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length, true);
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length, largeHit);
}
if(shake > 0){
Effects.shake(shake, shake, b);
Effect.shake(shake, shake, b);
}
}
@Override
public void draw(Bullet b){
float baseLen = length * b.fout();
float realLength = Damage.findLaserLength(b, length);
float fout = Mathf.clamp(b.time > b.lifetime - fadeTime ? 1f - (b.time - (lifetime - fadeTime)) / fadeTime : 1f);
float baseLen = realLength * fout;
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen);
for(int s = 0; s < colors.length; s++){
Draw.color(Tmp.c1.set(colors[s]).mul(1f + Mathf.absin(Time.time(), 1f, 0.1f)));
for(int i = 0; i < tscales.length; i++){
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
Lines.stroke((9f + Mathf.absin(Time.time(), 0.8f, 1.5f)) * b.fout() * strokes[s] * tscales[i]);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], CapStyle.none);
Lines.stroke((width + Mathf.absin(Time.time(), oscScl, oscMag)) * fout * strokes[s] * tscales[i]);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], false);
}
}
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, Color.orange, 0.7f);
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, lightColor, 0.7f);
Draw.reset();
}

View File

@@ -21,6 +21,7 @@ public class HealBulletType extends BulletType{
despawnEffect = Fx.hitLaser;
collidesTeam = true;
hittable = false;
reflectable = false;
}
public HealBulletType(){
@@ -29,7 +30,7 @@ public class HealBulletType extends BulletType{
@Override
public boolean collides(Bullet b, Building tile){
return tile.team() != b.team || tile.healthf() < 1f;
return tile.team != b.team || tile.healthf() < 1f;
}
@Override
@@ -43,11 +44,11 @@ public class HealBulletType extends BulletType{
}
@Override
public void hitTile(Bullet b, Building tile){
public void hitTile(Bullet b, Building tile, float initialHealth){
super.hit(b);
if(tile.team() == b.team && !(tile.block() instanceof BuildBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block().size, Pal.heal);
if(tile.team == b.team && !(tile.block instanceof ConstructBlock)){
Fx.healBlockFull.at(tile.x, tile.y, tile.block.size, Pal.heal);
tile.heal(healPercent / 100f * tile.maxHealth());
}
}

View File

@@ -8,20 +8,17 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.world;
public class LaserBulletType extends BulletType{
protected static Tile furthest;
protected Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
protected Effect laserEffect = Fx.lancerLaserShootSmoke;
protected float length = 160f;
protected float width = 15f;
protected float lengthFalloff = 0.5f;
protected float sideLength = 29f, sideWidth = 0.7f;
protected float sideAngle = 90f;
public Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
public Effect laserEffect = Fx.lancerLaserShootSmoke;
public float length = 160f;
public float width = 15f;
public float lengthFalloff = 0.5f;
public float sideLength = 29f, sideWidth = 0.7f;
public float sideAngle = 90f;
public float lightningSpacing = -1, lightningDelay = 0.1f, lightningAngleRand;
public boolean largeHit = false;
public LaserBulletType(float damage){
super(0.01f, damage);
@@ -42,6 +39,13 @@ public class LaserBulletType extends BulletType{
this(1f);
}
@Override
public void init(){
super.init();
drawSize = Math.max(drawSize, length*2f);
}
@Override
public float range(){
return length;
@@ -49,24 +53,35 @@ public class LaserBulletType extends BulletType{
@Override
public void init(Bullet b){
Tmp.v1.trns(b.rotation(), length);
float resultLength = Damage.collideLaser(b, length, largeHit), rot = b.rotation();
furthest = null;
laserEffect.at(b.x, b.y, rot, resultLength * 0.75f);
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);
if(lightningSpacing > 0){
int idx = 0;
for(float i = 0; i <= resultLength; i += lightningSpacing){
float cx = b.x + Angles.trnsx(rot, i),
cy = b.y + Angles.trnsy(rot, i);
float resultLength = furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
int f = idx++;
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), resultLength);
if(furthest != null) b.data(resultLength);
laserEffect.at(b.x, b.y, b.rotation(), resultLength * 0.75f);
for(int s : Mathf.signs){
Time.run(f * lightningDelay, () -> {
if(b.isAdded() && b.type == this){
Lightning.create(b, lightningColor,
lightningDamage < 0 ? damage : lightningDamage,
cx, cy, rot + 90*s + Mathf.range(lightningAngleRand),
lightningLength + Mathf.random(lightningLengthRand));
}
});
}
}
}
}
@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;
@@ -74,11 +89,10 @@ public class LaserBulletType extends BulletType{
float compound = 1f;
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen);
Lines.precise(true);
for(Color color : colors){
Draw.color(color);
Lines.stroke((cwidth *= lengthFalloff) * b.fout());
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, CapStyle.none);
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, false);
Tmp.v1.trns(b.rotation(), baseLen);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, Lines.getStroke() * 1.22f, cwidth * 2f + width / 2f, b.rotation());
@@ -89,7 +103,6 @@ public class LaserBulletType extends BulletType{
compound *= lengthFalloff;
}
Lines.precise(false);
Draw.reset();
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);

View File

@@ -13,7 +13,7 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class LiquidBulletType extends BulletType{
public @NonNull Liquid liquid;
public Liquid liquid;
public float puddleSize = 6f;
public LiquidBulletType(@Nullable Liquid liquid){
@@ -24,8 +24,9 @@ public class LiquidBulletType extends BulletType{
this.status = liquid.effect;
}
lifetime = 74f;
statusDuration = 90f;
ammoMultiplier = 1f;
lifetime = 34f;
statusDuration = 60f * 2f;
despawnEffect = Fx.none;
hitEffect = Fx.hitLiquid;
smokeEffect = Fx.none;
@@ -61,7 +62,7 @@ public class LiquidBulletType extends BulletType{
public void draw(Bullet b){
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, 3f);
Fill.circle(b.x, b.y, puddleSize / 2);
}
@Override
@@ -69,7 +70,7 @@ public class LiquidBulletType extends BulletType{
super.despawned(b);
//don't create liquids when the projectile despawns
hitEffect.at(b.x, b.y, liquid.color);
hitEffect.at(b.x, b.y, b.rotation(), liquid.color);
}
@Override
@@ -78,7 +79,7 @@ public class LiquidBulletType extends BulletType{
Puddles.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){
float intensity = 400f;
float intensity = 400f * puddleSize/6f;
Fires.extinguish(world.tileWorld(hitx, hity), intensity);
for(Point2 p : Geometry.d4){
Fires.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);

View File

@@ -1,15 +1,14 @@
package mindustry.entities.bullet;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.math.Angles;
import arc.math.Mathf;
import mindustry.content.Fx;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
import mindustry.graphics.*;
import mindustry.world.blocks.distribution.MassDriver.*;
import static mindustry.Vars.content;
import static mindustry.Vars.*;
public class MassDriverBolt extends BulletType{

View File

@@ -15,6 +15,7 @@ public class MissileBulletType extends BasicBulletType{
height = 8f;
hitSound = Sounds.explosion;
trailChance = 0.2f;
lifetime = 49f;
}
public MissileBulletType(float speed, float damage){

View File

@@ -0,0 +1,70 @@
package mindustry.entities.bullet;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class PointBulletType extends BulletType{
private static float cdist = 0f;
private static Unit result;
public float trailSpacing = 10f;
public PointBulletType(){
scaleVelocity = true;
lifetime = 100f;
collides = false;
keepVelocity = false;
backMove = false;
}
@Override
public void init(Bullet b){
super.init(b);
float px = b.x + b.lifetime * b.vel.x,
py = b.y + b.lifetime * b.vel.y,
rot = b.rotation();
Geometry.iterateLine(0f, b.x, b.y, px, py, trailSpacing, (x, y) -> {
trailEffect.at(x, y, rot);
});
b.time = b.lifetime;
b.set(px, py);
//calculate hit entity
cdist = 0f;
result = null;
float range = 1f;
Units.nearbyEnemies(b.team, px - range, py - range, range*2f, range*2f, e -> {
if(e.dead()) return;
e.hitbox(Tmp.r1);
if(!Tmp.r1.contains(px, py)) return;
float dst = e.dst(px, py) - e.hitSize;
if((result == null || dst < cdist)){
result = e;
cdist = dst;
}
});
if(result != null){
b.collision(result, px, py);
}else{
Building build = Vars.world.buildWorld(px, py);
if(build != null && build.team != b.team){
build.collision(b);
}
}
b.remove();
b.vel.setZero();
}
}

View File

@@ -0,0 +1,60 @@
package mindustry.entities.bullet;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
//TODO this class is bad for multiple reasons, remove/replace it.
//- effects unreliable
//- not really hitscan but works like it
//- buggy trails
//- looks bad
//- generally unreliable
public class RailBulletType extends BulletType{
public Effect pierceEffect = Fx.hitBulletSmall, updateEffect = Fx.none;
/** Multiplier of damage decreased per health pierced. */
public float pierceDamageFactor = 1f;
public RailBulletType(){
pierceBuilding = true;
pierce = true;
reflectable = false;
hitEffect = Fx.none;
despawnEffect = Fx.none;
}
void handle(Bullet b, Posc pos, float initialHealth){
float sub = initialHealth*pierceDamageFactor;
if(sub >= b.damage){
//cause a despawn
b.remove();
}
//subtract health from each consecutive pierce
b.damage -= Math.min(b.damage, sub);
if(b.damage > 0){
pierceEffect.at(pos.getX(), pos.getY(), b.rotation());
}
hitEffect.at(pos.getX(), pos.getY());
}
@Override
public void update(Bullet b){
if(b.timer(1, 0.9f)){
updateEffect.at(b.x, b.y, b.rotation());
}
}
@Override
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
handle(b, entity, initialHealth);
}
@Override
public void hitTile(Bullet b, Building tile, float initialHealth){
handle(b, tile, initialHealth);
}
}

View File

@@ -39,10 +39,15 @@ public class SapBulletType extends BulletType{
Draw.reset();
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 15f * b.fout(), lightColor, 0.6f);
Drawf.light(b.team, b.x, b.y, Tmp.v1.x, Tmp.v1.y, 15f * b.fout(), lightColor, 0.6f);
}
}
@Override
public void drawLight(Bullet b){
}
@Override
public float range(){
return length;

View File

@@ -13,9 +13,10 @@ public class ShrapnelBulletType extends BulletType{
public float length = 100f;
public float width = 20f;
public Color fromColor = Color.white, toColor = Pal.lancerLaser;
public boolean hitLarge = false;
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 +25,39 @@ public class ShrapnelBulletType extends BulletType{
lifetime = 10f;
despawnEffect = Fx.none;
pierce = true;
keepVelocity = false;
hittable = false;
}
@Override
public void init(Bullet b){
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length);
Damage.collideLaser(b, length, hitLarge);
}
@Override
public void init(){
super.init();
drawSize = Math.max(drawSize, length*2f);
}
@Override
public float range(){
return length;
}
@Override
public void draw(Bullet b){
float realLength = b.fdata;
Draw.color(fromColor, toColor, b.fin());
for(int i = 0; i < serrations; i++){
for(int i = 0; i < (int)(serrations * realLength / length); i++){
Tmp.v1.trns(b.rotation(), i * serrationSpacing);
float sl = Mathf.clamp(b.fout() - 0.5f) * (serrationSpaceOffset - i * serrationLenScl);
float sl = Mathf.clamp(b.fout() - serrationFadeOffset) * (serrationSpaceOffset - i * serrationLenScl);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() + 90);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, serrationWidth, sl, b.rotation() - 90);
}
Drawf.tri(b.x, b.y, width * b.fout(), (length + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), (realLength + 50), b.rotation());
Drawf.tri(b.x, b.y, width * b.fout(), 10f, b.rotation() + 180f);
Draw.reset();
}

View File

@@ -0,0 +1,28 @@
package mindustry.entities.comp;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.units.*;
@Component
abstract class AmmoDistributeComp implements Unitc{
@Import float x, y;
@Import UnitType type;
@Import Team team;
@Import float ammo;
private transient float ammoCooldown;
@Override
public void update(){
if(ammoCooldown > 0f) ammoCooldown -= Time.delta;
if(ammo > 0 && ammoCooldown <= 0f && ResupplyPoint.resupply(team, x, y, type.ammoResupplyRange, Math.min(type.ammoResupplyAmount, ammo), type.ammoType.color, u -> u != self())){
ammo -= Math.min(type.ammoResupplyAmount, ammo);
ammoCooldown = 5f;
}
}
}

View File

@@ -18,16 +18,16 @@ 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);
}
@Override
public void update(){
if(tile != null){
team = tile.team();
team = tile.team;
}
}
@@ -61,7 +61,7 @@ abstract class BlockUnitComp implements Unitc{
public void team(Team team){
if(tile != null && this.team != team){
this.team = team;
if(tile.team() != team){
if(tile.team != team){
tile.team(team);
}
}

View File

@@ -16,7 +16,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import java.util.*;
@@ -29,13 +29,7 @@ abstract class BuilderComp implements Unitc{
@Import float x, y, rotation;
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
transient boolean updateBuilding = true;
@Override
public void controller(UnitController next){
//reset building state so AI controlled units will always start off building
updateBuilding = true;
}
@SyncLocal transient boolean updateBuilding = true;
@Override
public void update(){
@@ -77,20 +71,20 @@ abstract class BuilderComp implements Unitc{
Tile tile = world.tile(current.x, current.y);
if(within(tile, finalPlaceDst)){
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
lookAt(angleTo(tile));
}
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));
if(hasAll){
Build.beginPlace(current.block, team(), current.x, current.y, current.rotation);
Call.beginPlace(current.block, team(), current.x, current.y, current.rotation);
}else{
current.stuck = true;
}
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
Build.beginBreak(team(), current.x, current.y);
Call.beginBreak(team(), current.x, current.y);
}else{
plans.removeFirst();
return;
@@ -100,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);
entity.deconstruct(self(), 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(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.config);
}
current.stuck = Mathf.equal(current.progress, entity.progress);
@@ -150,7 +140,6 @@ abstract class BuilderComp implements Unitc{
boolean shouldSkip(BuildPlan request, @Nullable Building core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || team().rules().infiniteResources || request.breaking || core == null) return false;
//TODO these are bad criteria
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item)) && !request.initialized);
}
@@ -190,8 +179,8 @@ abstract class BuilderComp implements Unitc{
plans.remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.build instanceof BuildEntity){
place.progress = tile.<BuildEntity>bc().progress;
if(tile != null && tile.build instanceof ConstructBuild){
place.progress = tile.<ConstructBuild>bc().progress;
}
if(tail){
plans.addLast(place);

View File

@@ -18,11 +18,13 @@ import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -37,9 +39,9 @@ import static mindustry.Vars.*;
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
@Component(base = true)
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable{
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Senseable, Controllable{
//region vars and initialization
static final float timeToSleep = 60f * 1;
static final float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
static final Seq<Building> tempTileEnts = new Seq<>();
static final Seq<Tile> tempTiles = new Seq<>();
@@ -54,6 +56,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient boolean updateFlow;
transient byte dump;
transient int rotation;
transient boolean enabled = true;
transient float enabledControlTime;
PowerModule power;
ItemModule items;
@@ -72,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(self());
}
}
this.rotation = rotation;
this.tile = tile;
@@ -84,7 +94,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
created();
return base();
return self();
}
/** Sets up all the necessary variables, but does not add this entity anywhere. */
@@ -97,21 +107,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
}
health(block.health);
health = block.health;
maxHealth(block.health);
timer(new Interval(block.timers));
cons = new ConsumeModule(base());
cons = new ConsumeModule(self());
if(block.hasItems) items = new ItemModule();
if(block.hasLiquids) liquids = new LiquidModule();
if(block.hasPower){
power = new PowerModule();
power.graph.add(base());
power.graph.add(self());
}
initialized = true;
return base();
return self();
}
@Override
@@ -131,8 +141,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void writeBase(Writes write){
write.f(health);
write.b(rotation);
write.b(rotation | 0b10000000);
write.b(team.id);
write.b(0); //extra padding for later use
if(items != null) items.write(write);
if(power != null) power.write(write);
if(liquids != null) liquids.write(write);
@@ -141,12 +152,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public final void readBase(Reads read){
health = read.f();
rotation(read.b());
byte rot = read.b();
team = Team.get(read.b());
if(items != null) items.read(read);
if(power != null) power.read(read);
if(liquids != null) liquids.read(read);
if(cons != null) cons.read(read);
rotation = rot & 0b01111111;
boolean legacy = true;
if((rot & 0b10000000) != 0){
read.b(); //padding
legacy = false;
}
if(items != null) items.read(read, legacy);
if(power != null) power.read(read, legacy);
if(liquids != null) liquids.read(read, legacy);
if(cons != null) cons.read(read, legacy);
}
public void writeAll(Writes write){
@@ -176,17 +195,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void configure(Object value){
//save last used config
block.lastConfig = value;
Call.tileConfig(player, base(), value);
Call.tileConfig(player, self(), value);
}
/** Configure from a server. */
public void configureAny(Object value){
Call.tileConfig(null, base(), value);
Call.tileConfig(null, self(), value);
}
/** Deselect this tile from configuration. */
public void deselect(){
if(!headless && control.input.frag.config.getSelectedTile() == base()){
if(!headless && control.input.frag.config.getSelectedTile() == self()){
control.input.frag.config.hideConfig();
}
}
@@ -294,6 +313,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
public float efficiency(){
//disabled -> 0 efficiency
if(!enabled) return 0;
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
@@ -325,10 +346,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** Called when this block is dropped as a payload. */
public void dropped(){
}
/** This is for logic blocks. */
public void handleString(Object value){
}
public void created(){}
public boolean shouldConsume(){
return true;
return enabled;
}
public boolean productionValid(){
@@ -341,7 +372,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Returns the amount of items this block can accept. */
public int acceptStack(Item item, int amount, Teamc source){
if(acceptItem(base(), item) && block.hasItems && (source == null || source.team() == team)){
if(acceptItem(self(), item) && block.hasItems && (source == null || source.team() == team)){
return Math.min(getMaximumAccepted(item) - items.get(item), amount);
}else{
return 0;
@@ -390,12 +421,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* @param todump payload to dump.
* @return whether the payload was moved successfully
*/
public boolean movePayload(@NonNull Payload todump){
public boolean movePayload(Payload todump){
int trns = block.size/2 + 1;
Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
if(next != null && next.build != null && next.build.team() == team && next.build.acceptPayload(base(), todump)){
next.build.handlePayload(base(), todump);
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
next.build.handlePayload(self(), todump);
return true;
}
@@ -407,7 +438,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* @param todump payload to dump.
* @return whether the payload was moved successfully
*/
public boolean dumpPayload(@NonNull Payload todump){
public boolean dumpPayload(Payload todump){
if(proximity.size == 0) return false;
int dump = this.dump;
@@ -415,8 +446,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptPayload(base(), todump)){
other.handlePayload(base(), todump);
if(other.team == team && other.acceptPayload(self(), todump)){
other.handlePayload(self(), todump);
incrementDump(proximity.size);
return true;
}
@@ -449,7 +480,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
other = other.getLiquidDestination(base(), liquid);
other = other.getLiquidDestination(self(), liquid);
if(other != null && other.team == team && other.block.hasLiquids && canDumpLiquid(other, liquid) && other.liquids != null){
float ofract = other.liquids.get(liquid) / other.block.liquidCapacity;
@@ -468,8 +499,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void transferLiquid(Building next, float amount, Liquid liquid){
float flow = Math.min(next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f, amount);
if(next.acceptLiquid(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
if(next.acceptLiquid(self(), liquid, flow)){
next.handleLiquid(self(), liquid, flow);
liquids.remove(liquid, flow);
}
}
@@ -492,36 +523,33 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public float moveLiquid(Building next, Liquid liquid){
if(next == null) return 0;
next = next.getLiquidDestination(base(), liquid);
next = next.getLiquidDestination(self(), liquid);
if(next.team() == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
if(next.team == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
float ofract = next.liquids.get(liquid) / next.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f);
if(next.acceptLiquid(base(), liquid, 0f)){
float ofract = next.liquids().get(liquid) / next.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
flow = Math.min(flow, next.block.liquidCapacity - next.liquids().get(liquid) - 0.001f);
if(flow > 0f && ofract <= fract && next.acceptLiquid(self(), liquid, flow)){
next.handleLiquid(self(), liquid, flow);
liquids.remove(liquid, flow);
return flow;
}else if(next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
//TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
if(flow > 0f && ofract <= fract && next.acceptLiquid(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
liquids.remove(liquid, flow);
return flow;
}else if(ofract > 0.1f && fract > 0.1f){
//TODO these are incorrect effect positions
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
Liquid other = next.liquids().current();
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damage(1 * Time.delta);
next.damage(1 * Time.delta);
if(Mathf.chance(0.1 * Time.delta)){
Fx.fire.at(fx, fy);
}
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
if(Mathf.chance(0.2f * Time.delta)){
Fx.steam.at(fx, fy);
}
Liquid other = next.liquids.current();
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
damage(1 * Time.delta);
next.damage(1 * Time.delta);
if(Mathf.chance(0.1 * Time.delta)){
Fx.fire.at(fx, fy);
}
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
if(Mathf.chance(0.2f * Time.delta)){
Fx.steam.at(fx, fy);
}
}
}
@@ -530,7 +558,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public Building getLiquidDestination(Building from, Liquid liquid){
return base();
return self();
}
public @Nullable Payload getPayload(){
@@ -552,13 +580,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
if(other.team == team && other.acceptItem(self(), item) && canDump(other, item)){
other.handleItem(self(), item);
return;
}
}
handleItem(base(), item);
handleItem(self(), item);
}
/**
@@ -570,8 +598,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int i = 0; i < proximity.size; i++){
incrementDump(proximity.size);
Building other = proximity.get((i + dump) % proximity.size);
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
if(other.team == team && other.acceptItem(self(), item) && canDump(other, item)){
other.handleItem(self(), item);
return true;
}
}
@@ -603,16 +631,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(int ii = 0; ii < content.items().size; ii++){
Item item = content.item(ii);
if(other.team() == team && items.has(item) && other.acceptItem(base(), item) && canDump(other, item)){
other.handleItem(base(), item);
if(other.team == team && items.has(item) && other.acceptItem(self(), item) && canDump(other, item)){
other.handleItem(self(), item);
items.remove(item, 1);
incrementDump(proximity.size);
return true;
}
}
}else{
if(other.team() == team && other.acceptItem(base(), todump) && canDump(other, todump)){
other.handleItem(base(), todump);
if(other.team == team && other.acceptItem(self(), todump) && canDump(other, todump)){
other.handleItem(self(), todump);
items.remove(todump, 1);
incrementDump(proximity.size);
return true;
@@ -637,8 +665,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Try offloading an item to a nearby container in its facing direction. Returns true if success. */
public boolean moveForward(Item item){
Building other = front();
if(other != null && other.team() == team && other.acceptItem(base(), item)){
other.handleItem(base(), item);
if(other != null && other.team == team && other.acceptItem(self(), item)){
other.handleItem(self(), item);
return true;
}
return false;
@@ -668,13 +696,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return;
}
power.graph.remove(base());
power.graph.remove(self());
for(int i = 0; i < power.links.size; i++){
Tile other = world.tile(power.links.get(i));
if(other != null && other.build != null && other.build.power != null){
other.build.power.links.removeValue(pos());
}
}
power.links.clear();
}
public Seq<Building> getPowerConnections(Seq<Building> out){
@@ -715,11 +744,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void drawStatus(){
if(block.consumes.any()){
if(block.enableDrawStatus && block.consumes.any()){
float brcx = tile.drawx() + (block.size * tilesize / 2f) - (tilesize / 2f);
float brcy = tile.drawy() - (block.size * tilesize / 2f) + (tilesize / 2f);
Draw.z(Layer.blockOver);
Draw.z(Layer.power + 1);
Draw.color(Pal.gray);
Fill.square(brcx, brcy, 2.5f, 45);
Draw.color(cons.status().color);
@@ -741,6 +770,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void drawSelect(){
}
public void drawDisabled(){
Draw.color(Color.scarlet);
Draw.alpha(0.8f);
float size = 6f;
Draw.rect(Icon.cancel.getRegion(), x, y, size, size);
Draw.reset();
}
public void draw(){
Draw.rect(block.region, x, y, block.rotate ? rotdeg() : 0);
@@ -780,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. */
@@ -796,10 +833,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
tempTiles.clear();
Geometry.circle(tileX(), tileY(), range, (x, y) -> {
Building other = world.build(x, y);
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, base()) && !PowerNode.insulated(other, base())
&& !other.proximity().contains(this.<Building>base()) &&
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, self()) && !PowerNode.insulated(other, self())
&& !other.proximity().contains(this.<Building>self()) &&
!(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)));
@@ -812,6 +849,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
/**
* Called when a block is placed over some other blocks. This seq will always have at least one item.
* Should load some previous state, if necessary. */
public void overwrote(Seq<Building> previous){
}
public void onRemoved(){
}
@@ -824,7 +868,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();
@@ -833,8 +877,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
/** Called when the block is tapped.*/
public void tapped(Player player){
/** Called when the block is tapped by the local player. */
public void tapped(){
}
@@ -861,7 +905,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
power += this.power.status * block.consumes.getPower().capacity;
}
if(block.hasLiquids){
if(block.hasLiquids && state.rules.damageExplosions){
liquids.each((liquid, amount) -> {
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
@@ -877,9 +921,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
});
}
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame);
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame, state.rules.damageExplosions);
if(!floor().solid && !floor().isLiquid){
Effects.rubble(x, y, block.size);
Effect.rubble(x, y, block.size);
}
}
@@ -934,7 +979,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();
@@ -972,9 +1017,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();
}
}
@@ -987,13 +1044,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
table.left();
for(Consume cons : block.consumes.all()){
if(cons.isOptional() && cons.isBoost()) continue;
cons.build(base(), table);
cons.build(self(), table);
}
}
public void displayBars(Table table){
for(Func<Building, Bar> bar : block.bars.list()){
table.add(bar.get(base())).growX();
table.add(bar.get(self())).growX();
table.row();
}
}
@@ -1019,7 +1076,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
* @return whether or not this block should be deselected.
*/
public boolean onConfigureTileTapped(Building other){
return base() != other;
return self() != other;
}
/** Returns whether this config menu should show when the specified player taps it. */
@@ -1060,6 +1117,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return true;
}
public boolean canPickup(){
return true;
}
public void removeFromProximity(){
onProximityRemoved();
tmpTiles.clear();
@@ -1074,7 +1135,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
for(Building other : tmpTiles){
other.proximity.remove(base(), true);
other.proximity.remove(self(), true);
other.onProximityUpdate();
}
}
@@ -1090,8 +1151,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(other == null || !(other.tile.interactable(team))) continue;
//add this tile to proximity of nearby tiles
if(!other.proximity.contains(base(), true)){
other.proximity.add(base());
if(!other.proximity.contains(self(), true)){
other.proximity.add(self());
}
tmpTiles.add(other);
@@ -1130,13 +1191,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Replace
@Override
public boolean isValid(){
return tile.build == base() && !dead();
return tile.build == self() && !dead();
}
@Replace
@Override
public void kill(){
Call.tileDestroyed(base());
Call.tileDestroyed(self());
}
@Replace
@@ -1150,10 +1211,59 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
damage /= state.rules.blockHealthMultiplier;
}
Call.tileDamage(base(), health - handleDamage(damage));
Call.tileDamage(self(), health - handleDamage(damage));
if(health <= 0){
Call.tileDestroyed(base());
Call.tileDestroyed(self());
}
}
@Override
public double sense(LAccess sensor){
return switch(sensor){
case x -> x;
case y -> y;
case team -> team.id;
case health -> health;
case maxHealth -> maxHealth();
case efficiency -> efficiency();
case rotation -> rotation;
case totalItems -> items == null ? 0 : items.total();
case totalLiquids -> liquids == null ? 0 : liquids.total();
case totalPower -> power == null || !block.consumes.hasPower() ? 0 : power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
case itemCapacity -> block.itemCapacity;
case liquidCapacity -> block.liquidCapacity;
case powerCapacity -> block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
case powerNetIn -> power == null ? 0 : power.graph.getLastScaledPowerIn() * 60;
case powerNetOut -> power == null ? 0 : power.graph.getLastScaledPowerOut() * 60;
case powerNetStored -> power == null ? 0 : power.graph.getLastPowerStored();
case powerNetCapacity -> power == null ? 0 : power.graph.getLastCapacity();
case enabled -> enabled ? 1 : 0;
default -> 0;
};
}
@Override
public Object senseObject(LAccess sensor){
return switch(sensor){
case type -> block;
default -> noSensed;
};
}
@Override
public double sense(Content content){
if(content instanceof Item && items != null) return items.get((Item)content);
if(content instanceof Liquid && liquids != null) return liquids.get((Liquid)content);
return 0;
}
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.enabled){
enabled = !Mathf.zero((float)p1);
enabledControlTime = timeToUncontrol;
}
}
@@ -1181,15 +1291,25 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
timeScale = 1f;
}
if(block.autoResetEnabled){
enabledControlTime -= Time.delta;
if(enabledControlTime <= 0){
enabled = true;
}
}
if(sound != null){
sound.update(x, y, shouldActiveSound());
}
if(block.idleSound != Sounds.none && shouldIdleSound()){
loops.play(block.idleSound, base(), block.idleSoundVolume);
loops.play(block.idleSound, self(), block.idleSoundVolume);
}
updateTile();
if(enabled || !block.noUpdateDisabled){
updateTile();
}
if(items != null){
items.update(updateFlow);

View File

@@ -25,27 +25,36 @@ 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));
}
}
}
}
@Override
public void drawBullets(){
type.draw(base());
type.draw(self());
}
@Override
public void add(){
type.init(base());
type.init(self());
}
@Override
public void remove(){
type.despawned(base());
type.despawned(self());
collided.clear();
}
@@ -83,10 +92,12 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@MethodPriority(100)
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(base(), x, y);
type.hit(self(), x, y);
float health = 0f;
if(other instanceof Healthc){
Healthc h = (Healthc)other;
health = h.health();
h.damage(damage);
}
@@ -102,30 +113,40 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}else{
collided.add(other.id());
}
type.hitEntity(self(), other, health);
}
@Override
public void update(){
type.update(base());
type.update(self());
if(type.collidesTiles && type.collides && type.collidesGround){
world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Building tile = world.build(x, y);
if(tile == null) return false;
if(tile == null || !isAdded()) return false;
if(tile.collide(base()) && type.collides(base(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team)){
if(tile.collide(self()) && type.collides(self(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team) && !(type.pierceBuilding && collided.contains(tile.id))){
boolean remove = false;
float health = tile.health;
if(tile.team != team){
remove = tile.collision(base());
remove = tile.collision(self());
}
if(remove || type.collidesTeam){
type.hitTile(base(), tile);
remove();
if(!type.pierceBuilding){
remove();
}else{
collided.add(tile.id);
}
}
return true;
type.hitTile(self(), tile, health);
return !type.pierceBuilding;
}
return false;
@@ -137,8 +158,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
public void draw(){
Draw.z(Layer.bullet);
type.draw(base());
type.drawLight(base());
type.draw(self());
type.drawLight(self());
}
/** Sets the bullet's rotation in degrees. */

View File

@@ -1,10 +1,13 @@
package mindustry.entities.comp;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.ai.formations.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -12,16 +15,19 @@ import mindustry.gen.*;
@Component
abstract class CommanderComp implements Unitc{
private static final Seq<FormationMember> members = new Seq<>();
private static final Seq<Unit> units = new Seq<>();
@Import float x, y, rotation;
transient @Nullable Formation formation;
transient Seq<Unit> controlling = new Seq<>();
/** minimum speed of any unit in the formation. */
transient float minFormationSpeed;
@Override
public void update(){
if(formation != null){
formation.anchor.set(x, y, rotation);
formation.anchor.set(x, y, /*rotation*/ 0); //TODO rotation set to 0 because rotating is pointless
formation.updateSlots();
}
}
@@ -42,21 +48,51 @@ abstract class CommanderComp implements Unitc{
clearCommand();
}
void commandNearby(FormationPattern pattern){
commandNearby(pattern, u -> true);
}
void commandNearby(FormationPattern pattern, Boolf<Unit> include){
Formation formation = new Formation(new Vec3(x, y, rotation), pattern);
formation.slotAssignmentStrategy = new DistanceAssignmentStrategy(pattern);
units.clear();
Units.nearby(team(), x, y, 200f, u -> {
if(u.isAI() && include.get(u) && u != self()){
units.add(u);
}
});
units.sort(u -> u.dst2(this));
units.truncate(type().commandLimit);
command(formation, units);
}
void command(Formation formation, Seq<Unit> units){
clearCommand();
float spacing = hitSize() * 0.65f;
minFormationSpeed = type().speed;
controlling.addAll(units);
for(Unit unit : units){
unit.controller(new FormationAI(base(), formation));
FormationAI ai;
unit.controller(ai = new FormationAI(self(), formation));
spacing = Math.max(spacing, ai.formationSize());
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
}
this.formation = formation;
//update formation spacing based on max size
formation.pattern.spacing = spacing;
members.clear();
for(Unitc u : units){
members.add((FormationAI)u.controller());
}
//TODO doesn't handle units that don't fit a formation
formation.addMembers(members);
}
@@ -68,7 +104,7 @@ abstract class CommanderComp implements Unitc{
void clearCommand(){
//reset controlled units
for(Unit unit : controlling){
if(unit.controller().isBeingControlled(base())){
if(unit.controller().isBeingControlled(self())){
unit.controller(unit.type().createController());
}
}

View File

@@ -27,7 +27,7 @@ abstract class DecalComp implements Drawc, Timedc, Rotc, Posc{
@Replace
public float clipSize(){
return region.getWidth()*2;
return region.width *2;
}
}

View File

@@ -8,13 +8,16 @@ import mindustry.gen.*;
@EntityDef(value = {EffectStatec.class, Childc.class}, pooled = true, serialize = false)
@Component(base = true)
abstract class EffectStateComp implements Posc, Drawc, Timedc, Rotc, Childc{
@Import float time, lifetime, rotation, x, y;
@Import int id;
Color color = new Color(Color.white);
Effect effect;
Object data;
@Override
public void draw(){
effect.render(id(), color, time(), rotation(), x(), y(), data);
lifetime = effect.render(id, color, time, lifetime, rotation, x, y, data);
}
@Replace

View File

@@ -1,23 +1,18 @@
package mindustry.entities.comp;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import static mindustry.Vars.collisions;
@Component
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
@Import float x, y;
@Replace
@Override
public void move(float cx, float cy){
if(isFlying()){
x += cx;
y += cy;
}else{
collisions.move(this, cx, cy);
}
public SolidPred solidity(){
return isFlying() ? null : EntityCollisions::solid;
}
}

View File

@@ -6,7 +6,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
import static mindustry.Vars.player;
import static mindustry.Vars.*;
@Component
@BaseComponent
@@ -40,7 +40,7 @@ abstract class EntityComp{
return false;
}
<T extends Entityc> T base(){
<T extends Entityc> T self(){
return (T)this;
}

View File

@@ -15,14 +15,14 @@ import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component(base = true)
abstract class FireComp implements Timedc, Posc, Firec{
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
private static final float spreadChance = 0.05f, fireballChance = 0.07f;
@Import float time, lifetime, x, y;
Tile tile;
private Block block;
private float baseFlammability = -1, puddleFlammability;
private transient Block block;
private transient float baseFlammability = -1, puddleFlammability;
@Override
public void update(){
@@ -97,6 +97,11 @@ abstract class FireComp implements Timedc, Posc, Firec{
@Override
public void afterRead(){
Fires.register(base());
Fires.register(self());
}
@Override
public void afterSync(){
Fires.register(self());
}
}

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