Merge branch 'master' into schematic

This commit is contained in:
J-VdS
2020-09-23 16:29:41 +02:00
committed by GitHub
563 changed files with 30174 additions and 22699 deletions

View File

@@ -37,7 +37,7 @@ public class Vars implements Loadable{
/** Whether the logger is loaded. */
public static boolean loadedLogger = false, loadedFileLogger = false;
/** Maximum extra padding around deployment schematics. */
public static final int maxLoadoutSchematicPad = 4;
public static final int maxLoadoutSchematicPad = 5;
/** Maximum schematic size.*/
public static final int maxSchematicSize = 32;
/** All schematic base64 starts with this string.*/
@@ -60,6 +60,8 @@ 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";
/** list of built-in servers.*/
@@ -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

@@ -1,6 +1,7 @@
package mindustry.ai;
import arc.*;
import arc.math.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
@@ -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

@@ -33,11 +33,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 +52,7 @@ public class BlockIndexer{
private Seq<Building> breturnArray = new Seq<>();
public BlockIndexer(){
Events.on(BuildinghangeEvent.class, event -> {
Events.on(TileChangeEvent.class, event -> {
if(typeMap.get(event.tile.pos()) != null){
TileIndex index = typeMap.get(event.tile.pos());
for(BlockFlag flag : index.flags){
@@ -70,9 +70,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 +139,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);
}
}
@@ -213,14 +214,17 @@ public class BlockIndexer{
public void notifyTileDamaged(Building entity){
if(damagedTiles[entity.team.id] == null){
damagedTiles[entity.team.id] = new BuildingArray();
damagedTiles[entity.team.id] = new ObjectSet<Building>();
}
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 +255,15 @@ public class BlockIndexer{
if(e == null) continue;
if(e.team != team || !pred.get(e) || !e.block().targetable)
if(e.team != team || !pred.get(e) || !e.block.targetable)
continue;
float ndst = e.dst2(x, y);
if(ndst < range2 && (closest == null ||
//this one is closer, and it is at least of equal priority
(ndst < dst && (!usePriority || closest.block().priority.ordinal() <= e.block().priority.ordinal())) ||
(ndst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
//priority is used, and new block has higher priority regardless of range
(usePriority && closest.block().priority.ordinal() < e.block().priority.ordinal()))){
(usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
dst = ndst;
closest = e;
}
@@ -472,35 +476,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,110 @@ public class Pathfinder implements Runnable{
private static final int impassable = -1;
private static final int fieldTimeout = 1000 * 60 * 2;
public static final int
fieldCore = 0,
fieldRally = 1;
public static final Seq<Prov<Flowfield>> fieldTypes = Seq.with(
EnemyCoreField::new,
RallyField::new
);
public static final int
costGround = 0,
costLegs = 1,
costWater = 2;
public static final Seq<PathCost> costTypes = Seq.with(
//ground
(team, tile) -> (PathTile.team(tile) == team.id || PathTile.team(tile) == 0) && PathTile.solid(tile) ? impassable : 1 +
PathTile.health(tile) * 5 +
(PathTile.nearSolid(tile) ? 2 : 0) +
(PathTile.nearLiquid(tile) ? 6 : 0) +
(PathTile.deep(tile) ? 70 : 0) +
(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 + //TODO cannot go through blocks - pathfinding isn't great
(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);
//TODO nearGround is just the inverse of nearLiquid?
boolean nearLiquid = false, nearSolid = false, nearGround = false;
for(int i = 0; i < 4; i++){
Tile other = tile.getNearby(i);
if(other != null){
if(other.floor().isLiquid) nearLiquid = true;
if(other.solid()) nearSolid = true;
if(!other.floor().isLiquid) nearGround = true;
}
}
return PathTile.get(
tile.build == null ? 0 : Math.min((int)(tile.build.health / 40), 127),
tile.getTeamID(),
tile.solid(),
tile.floor().isLiquid,
tile.staticDarkness() >= 2,
nearLiquid,
nearGround,
nearSolid,
tile.floor().isDeep(),
tile.floor().damageTaken > 0.00001f
);
}
/** Starts or restarts the pathfinding thread. */
@@ -104,7 +162,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 +189,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 +226,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 +279,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 +291,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 +306,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 +334,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 +374,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 +395,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 +410,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 +423,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();
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,6 +3,7 @@ package mindustry.ai;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@@ -17,10 +18,11 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class WaveSpawner{
private static final float margin = 40f, coreMargin = tilesize * 3; //how far away from the edge flying units spawn
private static final float margin = 40f, coreMargin = tilesize * 2f, maxSteps = 30;
private Seq<Tile> spawns = new Seq<>();
private boolean spawning = false;
private boolean any = false;
public WaveSpawner(){
Events.on(WorldLoadEvent.class, e -> reset());
@@ -91,8 +93,34 @@ public class WaveSpawner{
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
Building firstCore = state.teams.playerCores().first();
for(Building core : state.rules.waveTeam.cores()){
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block().size * tilesize);
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block.size * tilesize /2f * Mathf.sqrt2);
boolean valid = false;
int steps = 0;
//keep moving forward until the max step amount is reached
while(steps++ < maxSteps){
int tx = world.toTile(core.x + Tmp.v1.x), ty = world.toTile(core.y + Tmp.v1.y);
any = false;
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
if(world.solid(x, y)){
any = true;
}
});
//nothing is in the way, spawn it
if(!any){
valid = true;
break;
}else{
//make the vector longer
Tmp.v1.setLength(Tmp.v1.len() + tilesize*1.1f);
}
}
if(valid){
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
}
}
}
}

View File

@@ -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

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

View File

@@ -1,7 +1,6 @@
package mindustry.ai.types;
import arc.math.*;
import arc.util.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
@@ -12,14 +11,6 @@ public class FlyingAI extends AIController{
@Override
public void updateMovement(){
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
if(unit.isFlying()){
unit.wobble();
}
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
if(unit.type().weapons.first().rotate){
moveTo(target, unit.range() * 0.8f);
@@ -67,7 +58,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,38 +26,44 @@ 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();
}
}
@@ -66,7 +73,7 @@ public class FormationAI extends AIController implements FormationMember{
//TODO return formation size
//eturn ((Commanderc)unit).formation().
}
return unit.hitSize * 1.7f;
return unit.hitSize * 1f;
}
@Override

View File

@@ -1,10 +1,9 @@
package mindustry.ai.types;
import arc.math.*;
import mindustry.ai.Pathfinder.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -35,24 +34,25 @@ public class GroundAI extends AIController{
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
}
if(move) moveToCore(FlagTarget.enemyCores);
if(move) moveTo(Pathfinder.fieldCore);
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
moveToCore(FlagTarget.rallyPoints);
moveTo(Pathfinder.fieldRally);
}
}
if(unit.type().canBoost){
if(unit.type().canBoost && unit.canPassOn()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
}
if(!Units.invalidateTarget(target, unit, unit.range())){
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));
//TODO certain units should not look at the target, e.g. ships
unit.lookAt(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
@@ -74,40 +74,14 @@ public class GroundAI extends AIController{
}*/
}
protected void moveToCore(FlagTarget path){
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, unit.team, path);
if(tile == targetTile) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}
protected void moveAwayFromCore(){
Team enemy = null;
for(Team team : unit.team().enemies()){
if(team.active()){
enemy = team;
break;
}
}
if(enemy == null){
for(Team team : unit.team().enemies()){
enemy = team;
break;
}
}
if(enemy == null) return;
protected void moveTo(int 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

@@ -15,14 +15,6 @@ public class MinerAI extends AIController{
@Override
protected void updateMovement(){
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
if(unit.isFlying()){
unit.wobble();
}
Building core = unit.closestCore();
if(!(unit instanceof Minerc) || core == null) return;
@@ -39,6 +31,7 @@ public class MinerAI extends AIController{
//core full of the target item, do nothing
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
unit.clearItem();
miner.mineTile(null);
return;
}
@@ -51,7 +44,7 @@ public class MinerAI extends AIController{
}
if(ore != null){
moveTo(ore, unit.type().range / 1.5f);
moveTo(ore, unit.type().range / 2f);
if(unit.within(ore, unit.type().range)){
miner.mineTile(ore);
@@ -63,6 +56,8 @@ public class MinerAI extends AIController{
}
}
}else{
miner.mineTile(null);
if(unit.stack.amount == 0){
mining = true;
return;

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;
@@ -27,7 +29,7 @@ public class SuicideAI extends GroundAI{
if(!Units.invalidateTarget(target, unit, unit.range())){
rotate = true;
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
(target instanceof Building ? ((Building)target).block().size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
@@ -58,8 +60,14 @@ public class SuicideAI extends GroundAI{
}
}else{
if(core != null){
moveToCore(FlagTarget.enemyCores);
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
moveTo(Pathfinder.fieldRally);
}
}else if(command() == UnitCommand.attack && core != null){
moveTo(Pathfinder.fieldCore);
}
if(unit.moving()) unit.lookAt(unit.vel().angle());

View File

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

View File

@@ -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

@@ -34,10 +34,10 @@ public class Blocks implements ContentList{
public static Block
//environment
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, sandBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, dirt, 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,
@@ -64,7 +64,7 @@ public class Blocks implements ContentList{
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
//power
combustionGenerator, thermalGenerator, turbineGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
combustionGenerator, thermalGenerator, steamGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
impactReactor, battery, batteryLarge, powerNode, powerNodeLarge, surgeTower, diode,
//production
@@ -83,7 +83,7 @@ public class Blocks implements ContentList{
repairPoint, resupplyPoint,
//logic
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, logicDisplay, memoryCell,
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, largeLogicDisplay, logicDisplay, memoryCell,
//campaign
launchPad, launchPadLarge,
@@ -101,13 +101,12 @@ public class Blocks implements ContentList{
hasShadow = false;
}
public void drawBase(Tile tile){}
public void load(){}
public void init(){}
public boolean isHidden(){
return true;
}
@Override public void drawBase(Tile tile){}
@Override public void load(){}
@Override public void init(){}
@Override public boolean isHidden(){ return true; }
@Override
public TextureRegion[] variantRegions(){
if(variantRegions == null){
variantRegions = new TextureRegion[]{Core.atlas.find("clear")};
@@ -131,8 +130,8 @@ public class Blocks implements ContentList{
//Registers build blocks
//no reference is needed here since they can be looked up by name later
for(int i = 1; i <= BuildBlock.maxSize; i++){
new BuildBlock(i);
for(int i = 1; i <= ConstructBlock.maxSize; i++){
new ConstructBlock(i);
}
deepwater = new Floor("deepwater"){{
@@ -211,9 +210,7 @@ public class Blocks implements ContentList{
cacheLayer = CacheLayer.slag;
}};
stone = new Floor("stone"){{
}};
stone = new Floor("stone");
craters = new Floor("craters"){{
variants = 3;
@@ -224,14 +221,14 @@ public class Blocks implements ContentList{
blendGroup = stone;
}};
ignarock = new Floor("ignarock"){{
attributes.set(Attribute.water, -0.1f);
basalt = new Floor("basalt"){{
attributes.set(Attribute.water, -0.25f);
}};
hotrock = new Floor("hotrock"){{
attributes.set(Attribute.heat, 0.5f);
attributes.set(Attribute.water, -0.2f);
blendGroup = ignarock;
attributes.set(Attribute.water, -0.5f);
blendGroup = basalt;
emitLight = true;
lightRadius = 30f;
@@ -240,9 +237,9 @@ public class Blocks implements ContentList{
magmarock = new Floor("magmarock"){{
attributes.set(Attribute.heat, 0.75f);
attributes.set(Attribute.water, -0.5f);
attributes.set(Attribute.water, -0.75f);
updateEffect = Fx.magmasmoke;
blendGroup = ignarock;
blendGroup = basalt;
emitLight = true;
lightRadius = 60f;
@@ -261,13 +258,23 @@ public class Blocks implements ContentList{
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, 2f);
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);
@@ -295,46 +302,50 @@ 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;
}};
icerocks = new StaticWall("icerocks"){{
dirtWall = new StaticWall("dirt-wall"){{
variants = 2;
}};
daciteWall = new StaticWall("dacite-wall"){{
variants = 2;
}};
iceWall = new StaticWall("ice-wall"){{
variants = 2;
iceSnow.asFloor().wall = this;
}};
snowrocks = new StaticWall("snowrocks"){{
snowWall = new StaticWall("snow-wall"){{
variants = 2;
}};
duneRocks = new StaticWall("dunerocks"){{
duneWall = new StaticWall("dune-wall"){{
variants = 2;
basalt.asFloor().wall = 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;
@@ -348,17 +359,13 @@ public class Blocks implements ContentList{
variants = 0;
}};
shrubs = new StaticWall("shrubs"){{
shrubs = new StaticWall("shrubs");
}};
whiteTreeDead = new TreeBlock("white-tree-dead");
whiteTreeDead = new TreeBlock("white-tree-dead"){{
}};
whiteTree = new TreeBlock("white-tree");
whiteTree = new TreeBlock("white-tree"){{
}};
sporeCluster = new Rock("spore-cluster"){{
sporeCluster = new Boulder("spore-cluster"){{
variants = 3;
}};
@@ -367,15 +374,19 @@ public class Blocks implements ContentList{
attributes.set(Attribute.oil, 1f);
}};
shaleRocks = new StaticWall("shalerocks"){{
shaleWall = new StaticWall("shale-wall"){{
variants = 2;
}};
shaleBoulder = new Rock("shale-boulder"){{
shaleBoulder = new Boulder("shale-boulder"){{
variants = 2;
}};
sandBoulder = new Rock("sand-boulder"){{
sandBoulder = new Boulder("sand-boulder"){{
variants = 2;
}};
daciteBoulder = new Boulder("dacite-boulder"){{
variants = 2;
}};
@@ -388,7 +399,7 @@ public class Blocks implements ContentList{
sporeMoss = new Floor("spore-moss"){{
variants = 3;
attributes.set(Attribute.spores, 0.3f);
wall = sporerocks;
wall = sporeWall;
}};
metalFloor = new Floor("metal-floor"){{
@@ -507,7 +518,7 @@ public class Blocks implements ContentList{
siliconCrucible = new AttributeSmelter("silicon-crucible"){{
requirements(Category.crafting, with(Items.titanium, 120, Items.metaglass, 80, Items.plastanium, 35, Items.silicon, 60));
craftEffect = Fx.smeltsmoke;
outputItem = new ItemStack(Items.silicon, 6);
outputItem = new ItemStack(Items.silicon, 8);
craftTime = 90f;
size = 3;
hasPower = true;
@@ -516,7 +527,7 @@ public class Blocks implements ContentList{
itemCapacity = 30;
boostScale = 0.15f;
consumes.items(new ItemStack(Items.coal, 3), new ItemStack(Items.sand, 6), new ItemStack(Items.pyratite, 1));
consumes.items(new ItemStack(Items.coal, 4), new ItemStack(Items.sand, 6), new ItemStack(Items.pyratite, 1));
consumes.power(4f);
}};
@@ -767,14 +778,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"){{
@@ -791,7 +802,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;
}};
@@ -1011,7 +1022,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"){{
@@ -1086,14 +1097,14 @@ public class Blocks implements ContentList{
powerNode = new PowerNode("power-node"){{
requirements(Category.power, with(Items.copper, 1, Items.lead, 3));
maxNodes = 20;
maxNodes = 10;
laserRange = 6;
}};
powerNodeLarge = new PowerNode("power-node-large"){{
requirements(Category.power, with(Items.titanium, 5, Items.lead, 10, Items.silicon, 3));
size = 2;
maxNodes = 30;
maxNodes = 15;
laserRange = 9.5f;
}};
@@ -1132,32 +1143,32 @@ public class Blocks implements ContentList{
size = 2;
}};
turbineGenerator = new BurnerGenerator("turbine-generator"){{
steamGenerator = new BurnerGenerator("steam-generator"){{
requirements(Category.power, with(Items.copper, 35, Items.graphite, 25, Items.lead, 40, Items.silicon, 30));
powerProduction = 6f;
powerProduction = 5.5f;
itemDuration = 90f;
consumes.liquid(Liquids.water, 0.05f);
consumes.liquid(Liquids.water, 0.07f);
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"){{
@@ -1176,7 +1187,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);
@@ -1269,7 +1280,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"){{
@@ -1309,7 +1320,7 @@ 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;
@@ -1320,7 +1331,7 @@ public class Blocks implements ContentList{
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;
@@ -1366,7 +1377,7 @@ public class Blocks implements ContentList{
ammoUseEffect = Fx.shellEjectSmall;
health = 250;
inaccuracy = 2f;
rotatespeed = 10f;
rotateSpeed = 10f;
}};
scatter = new ItemTurret("scatter"){{
@@ -1384,7 +1395,7 @@ public class Blocks implements ContentList{
targetGround = false;
recoilAmount = 2f;
rotatespeed = 15f;
rotateSpeed = 15f;
inaccuracy = 17f;
shootCone = 35f;
@@ -1481,13 +1492,13 @@ public class Blocks implements ContentList{
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;
rotateSpeed = 8f;
powerUse = 4f;
targetAir = false;
range = 90f;
@@ -1504,14 +1515,14 @@ public class Blocks implements ContentList{
hasPower = true;
size = 2;
force = 3f;
force = 4.5f;
scaledForce = 5.5f;
range = 170f;
damage = 0.08f;
range = 110f;
damage = 0.1f;
health = 160 * size * size;
rotateSpeed = 10;
consumes.powerCond(3f, (TractorBeamEntity e) -> e.target != null);
consumes.powerCond(3f, (TractorBeamBuild e) -> e.target != null);
}};
swarmer = new ItemTurret("swarmer"){{
@@ -1558,15 +1569,16 @@ public class Blocks implements ContentList{
}};
segment = new PointDefenseTurret("segment"){{
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 25));
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 40));
health = 250 * size * size;
range = 140f;
hasPower = true;
consumes.power(3f);
consumes.power(8f);
size = 2;
shootLength = 5f;
bulletDamage = 12f;
reloadTime = 20f;
health = 190 * size * size;
bulletDamage = 25f;
reloadTime = 10f;
}};
fuse = new ItemTurret("fuse"){{
@@ -1585,8 +1597,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;
}});
@@ -1609,6 +1623,7 @@ public class Blocks implements ContentList{
reloadTime = 60f;
ammoEjectBack = 5f;
ammoUseEffect = Fx.shellEjectBig;
ammoPerShot = 2;
cooldown = 0.03f;
velocityInaccuracy = 0.2f;
restitution = 0.02f;
@@ -1634,7 +1649,7 @@ public class Blocks implements ContentList{
range = 200f;
size = 3;
recoilAmount = 3f;
rotatespeed = 10f;
rotateSpeed = 10f;
inaccuracy = 10f;
shootCone = 30f;
shootSound = Sounds.shootSnap;
@@ -1643,7 +1658,7 @@ public class Blocks implements ContentList{
}};
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,
@@ -1669,7 +1684,7 @@ public class Blocks implements ContentList{
}};
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;
@@ -1685,7 +1700,7 @@ public class Blocks implements ContentList{
activeSoundVolume = 2f;
shootType = new ContinuousLaserBulletType(70){{
length = 220f;
length = 200f;
hitEffect = Fx.hitMeltdown;
drawSize = 420f;
@@ -1731,7 +1746,7 @@ public class Blocks implements ContentList{
navalFactory = new UnitFactory("naval-factory"){{
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.risso, 60f * 30f, with(Items.silicon, 20, Items.metaglass, 25)),
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35)),
};
size = 3;
requiresWater = true;
@@ -1758,11 +1773,11 @@ public class Blocks implements ContentList{
}};
multiplicativeReconstructor = new Reconstructor("multiplicative-reconstructor"){{
requirements(Category.units, with(Items.lead, 650, Items.silicon, 350, Items.titanium, 350, Items.thorium, 650));
requirements(Category.units, with(Items.lead, 650, Items.silicon, 450, Items.titanium, 350, Items.thorium, 650));
size = 5;
consumes.power(6f);
consumes.items(with(Items.silicon, 130, Items.titanium, 80, Items.metaglass, 30));
consumes.items(with(Items.silicon, 130, Items.titanium, 80, Items.metaglass, 40));
constructTime = 60f * 30f;
@@ -1777,11 +1792,11 @@ public class Blocks implements ContentList{
}};
exponentialReconstructor = new Reconstructor("exponential-reconstructor"){{
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 750, Items.titanium, 950, Items.thorium, 450, Items.plastanium, 350, Items.phasefabric, 450));
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 1000, Items.titanium, 2000, Items.thorium, 750, Items.plastanium, 450, Items.phasefabric, 600));
size = 7;
consumes.power(13f);
consumes.items(with(Items.silicon, 450, Items.titanium, 550, Items.plastanium, 550));
consumes.items(with(Items.silicon, 850, Items.titanium, 750, Items.plastanium, 650));
consumes.liquid(Liquids.cryofluid, 1f);
constructTime = 60f * 60f * 1.5f;
@@ -1790,15 +1805,19 @@ 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, 450, Items.phasefabric, 600, Items.surgealloy, 500));
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 3000, Items.thorium, 1000, Items.plastanium, 600, Items.phasefabric, 600, Items.surgealloy, 800));
size = 9;
consumes.power(25f);
consumes.items(with(Items.silicon, 350, Items.plastanium, 550, Items.surgealloy, 350, Items.phasefabric, 150));
consumes.items(with(Items.silicon, 1000, Items.plastanium, 600, Items.surgealloy, 500, Items.phasefabric, 350));
consumes.liquid(Liquids.cryofluid, 3f);
constructTime = 60f * 60f * 4;
@@ -1806,6 +1825,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}
};
}};
@@ -1821,6 +1845,8 @@ public class Blocks implements ContentList{
size = 2;
range = 80f;
itemCapacity = 20;
ammoAmount = 5;
consumes.item(Items.copper, 1);
}};
@@ -1916,9 +1942,9 @@ public class Blocks implements ContentList{
logicProcessor = new LogicBlock("logic-processor"){{
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 100, Items.graphite, 60, Items.thorium, 50));
instructionsPerTick = 5;
instructionsPerTick = 8;
range = 8 * 20;
range = 8 * 22;
size = 2;
}};
@@ -1931,15 +1957,7 @@ public class Blocks implements ContentList{
instructionsPerTick = 25;
range = 8 * 40;
size = 3;
}};
logicDisplay = new LogicDisplay("logic-display"){{
requirements(Category.logic, with(Items.copper, 200, Items.lead, 120, Items.silicon, 100, Items.metaglass, 50));
displaySize = 80;
range = 8 * 42;
size = 3;
}};
@@ -1950,6 +1968,22 @@ public class Blocks implements ContentList{
memoryCapacity = 64;
}};
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

View File

@@ -199,7 +199,7 @@ public class Bullets implements ContentList{
hitEffect = Fx.flakExplosion;
splashDamage = 18f;
splashDamageRadius = 16f;
fragBullet = flakGlassFrag;
fragBullet = fragGlassFrag;
fragBullets = 3;
explodeRange = 20f;
collidesGround = true;
@@ -269,13 +269,13 @@ public class Bullets implements ContentList{
status = StatusEffects.burning;
}};
missileSurge = new MissileBulletType(3.7f, 20){{
missileSurge = new MissileBulletType(3.7f, 18){{
width = 8f;
height = 8f;
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 28f;
splashDamage = 40f;
splashDamageRadius = 25f;
splashDamage = 25f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
lightning = 2;
@@ -433,6 +433,7 @@ public class Bullets implements ContentList{
hitSize = 7f;
lifetime = 18f;
pierce = true;
collidesAir = false;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
hitEffect = Fx.hitFlameSmall;
@@ -447,6 +448,7 @@ public class Bullets implements ContentList{
hitSize = 7f;
lifetime = 18f;
pierce = true;
collidesAir = false;
statusDuration = 60f * 6;
shootEffect = Fx.shootPyraFlame;
hitEffect = Fx.hitFlameSmall;

View File

@@ -36,7 +36,7 @@ public class Fx{
TextureRegion region = unit.icon(Cicon.full);
rect(region, e.x, e.y,
region.getWidth() * Draw.scl * scl, region.getHeight() * Draw.scl * scl, 180f);
region.width * Draw.scl * scl, region.height * Draw.scl * scl, 180f);
}),
@@ -52,10 +52,11 @@ public class Fx{
if(!(e.data instanceof Unit)) return;
Unit select = e.data();
boolean block = select instanceof BlockUnitc;
mixcol(Pal.accent, 1f);
alpha(e.fout());
rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type().icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
alpha(1f);
Lines.stroke(e.fslope() * 1f);
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
@@ -144,7 +145,7 @@ public class Fx{
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);
}
}),
@@ -289,6 +290,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);
@@ -399,6 +444,17 @@ 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);
});
}),
hitLaser = new Effect(8, e -> {
color(Color.white, Pal.heal, e.fin());
stroke(0.5f + e.fout());
@@ -685,6 +741,13 @@ public class Fx{
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 -> {
color(Pal.sap);
@@ -900,7 +963,7 @@ public class Fx{
}),
shootSmallFlame = new Effect(32f, e -> {
shootSmallFlame = new Effect(32f, 80f, e -> {
color(Pal.lightFlame, Pal.darkFlame, Color.gray, e.fin());
randLenVectors(e.id, 8, e.finpow() * 60f, e.rotation, 10f, (x, y) -> {
@@ -909,7 +972,7 @@ public class Fx{
}),
shootPyraFlame = new Effect(33f, e -> {
shootPyraFlame = new Effect(33f, 80f, e -> {
color(Pal.lightPyraFlame, Pal.darkPyraFlame, Color.gray, e.fin());
randLenVectors(e.id, 10, e.finpow() * 70f, e.rotation, 10f, (x, y) -> {
@@ -918,7 +981,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) -> {
@@ -988,6 +1051,53 @@ 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 -> {
if(true){
color(Pal.orangeSpark);
for(int i : Mathf.signs){
Drawf.tri(e.x, e.y, 10f * e.fout(), 60f, e.rotation + 140f * i);
}
}else{
e.scaled(7f, b -> {
color(Color.white, Color.lightGray, b.fin());
stroke(b.fout() * 2f + 0.2f);
Lines.circle(b.x, b.y, b.fin() * 28f);
});
color(Pal.orangeSpark);
float rot = e.rotation + Mathf.randomSeedRange(e.id, 20f);
float w = 9f * e.fout();
Drawf.tri(e.x, e.y, w, 100f, rot);
Drawf.tri(e.x, e.y, w, 10f, rot + 180f);
}
}),
lancerLaserShoot = new Effect(21f, e -> {
color(Pal.lancerLaser);
@@ -999,7 +1109,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 == null || !(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);
@@ -1069,6 +1179,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;
@@ -1165,6 +1284,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) -> {
@@ -1294,10 +1421,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

@@ -9,7 +9,7 @@ import mindustry.type.StatusEffect;
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, sporeSlowed;
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(){
@@ -45,6 +45,14 @@ 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.94f;
@@ -53,7 +61,7 @@ public class StatusEffects implements ContentList{
init(() -> {
trans(shocked, ((unit, time, newTime, result) -> {
unit.damagePierce(20f);
unit.damagePierce(14f);
if(unit.team() == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
@@ -62,6 +70,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;
@@ -121,13 +136,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,18 +1,15 @@
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.*;
@@ -208,7 +205,9 @@ public class TechTree implements ContentList{
node(switchBlock, () -> {
node(message, () -> {
node(logicDisplay, () -> {
node(largeLogicDisplay, () -> {
});
});
node(memoryCell, () -> {
@@ -261,10 +260,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, () -> {
});
@@ -366,7 +365,11 @@ public class TechTree implements ContentList{
node(dagger, () -> {
node(mace, () -> {
node(fortress, () -> {
node(scepter, () -> {
node(reign, () -> {
});
});
});
});
@@ -381,7 +384,11 @@ public class TechTree implements ContentList{
node(crawler, () -> {
node(atrax, () -> {
node(spiroct, () -> {
node(arkyid, () -> {
node(toxopid, () -> {
});
});
});
});
});
@@ -402,7 +409,11 @@ public class TechTree implements ContentList{
node(mono, () -> {
node(poly, () -> {
node(mega, () -> {
node(quad, () -> {
node(oct, () -> {
});
});
});
});
});
@@ -412,7 +423,11 @@ public class TechTree implements ContentList{
node(risso, () -> {
node(minke, () -> {
node(bryde, () -> {
node(sei, () -> {
node(omura, () -> {
});
});
});
});
});
@@ -490,7 +505,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),
@@ -512,42 +527,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, () -> {});
}

View File

@@ -11,17 +11,22 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import static mindustry.Vars.*;
public class UnitTypes implements ContentList{
//region definitions
//ground
public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, vestige, cataclyst;
//mech
public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, scepter, reign;
//ground + builder
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class}) UnitType nova;
//mech + builder + miner + commander
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class, Minerc.class, Commanderc.class}) UnitType nova, pulsar, quasar;
//ground + builder + miner + commander
public static @EntityDef({Unitc.class, Mechc.class, Builderc.class, Minerc.class, Commanderc.class}) UnitType pulsar, quasar;
//mech + commander
public static @EntityDef({Unitc.class, Mechc.class, Commanderc.class}) UnitType vela;
//legs + commander
public static @EntityDef({Unitc.class, Legsc.class, Commanderc.class}) UnitType corvus;
//legs
public static @EntityDef({Unitc.class, Legsc.class}) UnitType atrax;
@@ -41,11 +46,14 @@ public class UnitTypes implements ContentList{
//air + building + mining + payload
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class, Payloadc.class}) UnitType mega;
//air + building + payload TODO implement
public static @EntityDef({Unitc.class, Builderc.class, Payloadc.class}) UnitType quad, oct;
//air + building + mining
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class}) UnitType alpha, beta, gamma;
//water
public static @EntityDef({Unitc.class, WaterMovec.class, Commanderc.class}) UnitType risso, minke, bryde;
//water + commander
public static @EntityDef({Unitc.class, WaterMovec.class, Commanderc.class}) UnitType risso, minke, bryde, sei, omura;
//special block unit type
public static @EntityDef({Unitc.class, BlockUnitc.class}) UnitType block;
@@ -64,6 +72,7 @@ public class UnitTypes implements ContentList{
reload = 14f;
x = 4f;
y = 2f;
top = false;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}});
@@ -78,6 +87,7 @@ public class UnitTypes implements ContentList{
immunities.add(StatusEffects.burning);
weapons.add(new Weapon("flamethrower"){{
top = false;
shootSound = Sounds.flame;
shootY = 2f;
reload = 14f;
@@ -106,8 +116,10 @@ public class UnitTypes implements ContentList{
targetAir = false;
health = 790;
armor = 9f;
mechFrontSway = 0.55f;
weapons.add(new Weapon("artillery"){{
top = false;
y = 1f;
x = 9f;
reload = 60f;
@@ -130,6 +142,122 @@ public class UnitTypes implements ContentList{
}});
}};
scepter = new UnitType("scepter"){{
speed = 0.35f;
hitsize = 20f;
rotateSpeed = 2.1f;
health = 9000;
armor = 11f;
canDrown = false;
mechFrontSway = 1f;
mechStepParticles = true;
mechStepShake = 0.15f;
weapons.add(
new Weapon("scepter-weapon"){{
top = false;
y = 1f;
x = 16f;
shootY = 8f;
reload = 50f;
recoil = 5f;
shake = 2f;
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.artillery;
shots = 3;
inaccuracy = 3f;
shotDelay = 4f;
bullet = new BasicBulletType(7f, 45){{
width = 11f;
height = 20f;
lifetime = 25f;
shootEffect = Fx.shootBig;
lightning = 2;
lightningLength = 6;
lightningColor = Pal.surge;
//standard bullet damage is far too much for lightning
lightningDamage = 25;
}};
}},
new Weapon("mount-weapon"){{
reload = 13f;
x = 8.5f;
y = 6f;
rotate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}},
new Weapon("mount-weapon"){{
reload = 16f;
x = 8.5f;
y = -7f;
rotate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}}
);
}};
reign = new UnitType("reign"){{
speed = 0.35f;
hitsize = 26f;
rotateSpeed = 1.65f;
health = 24000;
armor = 14f;
mechStepParticles = true;
mechStepShake = 0.75f;
canDrown = false;
mechFrontSway = 1.9f;
mechSideSway = 0.6f;
weapons.add(
new Weapon("reign-weapon"){{
top = false;
y = 1f;
x = 21.5f;
shootY = 11f;
reload = 9f;
recoil = 5f;
shake = 2f;
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.artillery;
bullet = new BasicBulletType(13f, 55){{
pierce = true;
width = 14f;
height = 33f;
lifetime = 15f;
shootEffect = Fx.shootBig;
fragVelocityMin = 0.4f;
hitEffect = Fx.blastExplosion;
splashDamage = 18f;
splashDamageRadius = 30f;
fragBullets = 2;
fragLifeMin = 0f;
fragCone = 30f;
fragBullet = new BasicBulletType(9f, 15){{
width = 10f;
height = 10f;
pierce = true;
lifetime = 20f;
hitEffect = Fx.flakExplosion;
splashDamage = 15f;
splashDamageRadius = 15f;
}};
}};
}}
);
}};
//endregion
//region ground support
@@ -137,15 +265,17 @@ public class UnitTypes implements ContentList{
itemCapacity = 60;
canBoost = true;
boostMultiplier = 1.5f;
speed = 0.52f;
speed = 0.55f;
hitsize = 8f;
health = 110f;
buildSpeed = 0.8f;
armor = 1f;
commandLimit = 8;
abilities.add(new HealFieldAbility(10f, 60f * 4, 60f));
weapons.add(new Weapon("heal-weapon"){{
top = false;
shootY = 2f;
reload = 24f;
x = 4.5f;
@@ -161,7 +291,7 @@ public class UnitTypes implements ContentList{
itemCapacity = 60;
canBoost = true;
boostMultiplier = 1.5f;
speed = 0.48f;
speed = 0.65f;
hitsize = 10f;
health = 320f;
buildSpeed = 0.9f;
@@ -169,11 +299,12 @@ public class UnitTypes implements ContentList{
mineTier = 2;
mineSpeed = 5f;
commandLimit = 8;
commandLimit = 15;
abilities.add(new ShieldFieldAbility(20f, 40f, 60f * 5, 60f));
weapons.add(new Weapon("heal-shotgun-weapon"){{
top = false;
x = 5f;
shake = 2.2f;
y = 0.5f;
@@ -210,6 +341,9 @@ public class UnitTypes implements ContentList{
armor = 9f;
landShake = 2f;
commandLimit = 24;
mechFrontSway = 0.55f;
speed = 0.4f;
hitsize = 10f;
@@ -217,9 +351,10 @@ public class UnitTypes implements ContentList{
mineSpeed = 7f;
drawShields = false;
abilities.add(new ForceFieldAbility(60f, 0.2f, 300f, 60f * 7));
abilities.add(new ForceFieldAbility(60f, 0.3f, 400f, 60f * 6));
weapons.add(new Weapon("beam-weapon"){{
top = false;
shake = 2f;
shootY = 4f;
x = 6.5f;
@@ -228,7 +363,7 @@ public class UnitTypes implements ContentList{
shootSound = Sounds.laser;
bullet = new LaserBulletType(){{
damage = 30f;
damage = 40f;
recoil = 1f;
sideAngle = 45f;
sideWidth = 1f;
@@ -238,6 +373,136 @@ public class UnitTypes implements ContentList{
}});
}};
vela = new UnitType("vela"){{
hitsize = 23f;
rotateSpeed = 1.6f;
canDrown = false;
mechFrontSway = 1f;
mechStepParticles = true;
mechStepShake = 0.15f;
speed = 0.35f;
boostMultiplier = 2.1f;
engineOffset = 12f;
engineSize = 6f;
lowAltitude = true;
health = 6000f;
armor = 7f;
canBoost = true;
landShake = 4f;
commandLimit = 32;
weapons.add(new Weapon("vela-weapon"){{
mirror = false;
top = false;
shake = 4f;
shootY = 13f;
x = y = 0f;
firstShotDelay = Fx.greenLaserChargeSmall.lifetime - 1f;
reload = 320f;
recoil = 0f;
shootSound = Sounds.laser;
continuous = true;
cooldownTime = 200f;
bullet = new ContinuousLaserBulletType(16){{
length = 150f;
hitEffect = Fx.hitMeltHeal;
drawSize = 420f;
lifetime = 160f;
shake = 1f;
despawnEffect = Fx.smokeCloud;
smokeEffect = Fx.none;
shootEffect = Fx.greenLaserChargeSmall;
incendChance = 0.02f;
incendSpread = 5f;
incendAmount = 1;
colors = new Color[]{Pal.heal.cpy().a(.2f), Pal.heal.cpy().a(.5f), Pal.heal.cpy().mul(1.2f), Color.white};
}};
shootStatus = StatusEffects.slow;
shootStatusDuration = bullet.lifetime + firstShotDelay;
}});
}};
corvus = new UnitType("corvus"){{
mineTier = 1;
hitsize = 29f;
itemCapacity = 80;
health = 19000f;
buildSpeed = 1.7f;
armor = 9f;
landShake = 1.5f;
rotateSpeed = 1.5f;
commandLimit = 24;
legCount = 4;
legLength = 14f;
legBaseOffset = 11f;
legMoveSpace = 1.5f;
legTrns = 0.58f;
hovering = true;
visualElevation = 0.2f;
allowLegStep = true;
speed = 0.3f;
mineTier = 2;
mineSpeed = 7f;
drawShields = false;
weapons.add(new Weapon("corvus-weapon"){{
top = false;
mirror = false;
shake = 14f;
shootY = 5f;
x = y = 0;
reload = 350f;
recoil = 0f;
shootSound = Sounds.laser;
cooldownTime = 350f;
shootStatusDuration = 60f * 2f;
shootStatus = StatusEffects.unmoving;
firstShotDelay = Fx.greenLaserCharge.lifetime;
bullet = new LaserBulletType(){{
length = 500f;
damage = 520f;
width = 75f;
lifetime = 65f;
lightningSpacing = 35f;
lightningLength = 5;
lightningDelay = 1.1f;
lightningLengthRand = 15;
lightningDamage = 50;
lightningAngleRand = 40f;
largeHit = true;
lightColor = lightningColor = Pal.heal;
shootEffect = Fx.greenLaserCharge;
sideAngle = 15f;
sideWidth = 0f;
sideLength = 0f;
colors = new Color[]{Pal.heal.cpy().a(0.4f), Pal.heal, Color.white};
}};
}});
}};
//endregion
//region ground legs
@@ -246,8 +511,8 @@ public class UnitTypes implements ContentList{
speed = 0.85f;
hitsize = 8f;
health = 170;
sway = 0.25f;
health = 180;
mechSideSway = 0.25f;
range = 40f;
weapons.add(new Weapon(){{
@@ -261,7 +526,7 @@ public class UnitTypes implements ContentList{
speed = 1f;
splashDamageRadius = 55f;
instantDisappear = true;
splashDamage = 45f;
splashDamage = 55f;
killShooter = true;
hittable = false;
collidesAir = true;
@@ -278,6 +543,7 @@ public class UnitTypes implements ContentList{
targetAir = false;
health = 600;
immunities = ObjectSet.with(StatusEffects.burning, StatusEffects.melting);
legCount = 4;
legLength = 9f;
legTrns = 0.6f;
@@ -290,6 +556,7 @@ public class UnitTypes implements ContentList{
groundLayer = Layer.legUnit - 1f;
weapons.add(new Weapon("eruption"){{
top = false;
shootY = 3f;
reload = 10f;
ejectEffect = Fx.none;
@@ -309,7 +576,6 @@ public class UnitTypes implements ContentList{
}};
spiroct = new UnitType("spiroct"){{
itemCapacity = 200;
speed = 0.4f;
drag = 0.4f;
hitsize = 12f;
@@ -381,6 +647,8 @@ public class UnitTypes implements ContentList{
health = 8000;
armor = 6f;
rotateSpeed = 2.7f;
legCount = 6;
legMoveSpace = 1f;
legPairOffset = 3;
@@ -389,7 +657,7 @@ public class UnitTypes implements ContentList{
legBaseOffset = 10f;
landShake = 1f;
legSpeed = 0.1f;
legLengthScl = 1f;
legLengthScl = 0.96f;
rippleScale = 2f;
legSpeed = 0.2f;
@@ -398,11 +666,11 @@ public class UnitTypes implements ContentList{
hovering = true;
allowLegStep = true;
visualElevation = 0.4f;
visualElevation = 0.65f;
groundLayer = Layer.legUnit;
BulletType sapper = new SapBulletType(){{
sapStrength = 0.8f;
sapStrength = 0.83f;
length = 55f;
damage = 34;
shootEffect = Fx.shootSmall;
@@ -453,10 +721,10 @@ public class UnitTypes implements ContentList{
knockback = 0.8f;
lifetime = 70f;
width = height = 19f;
collidesTiles = false;
collidesTiles = true;
ammoMultiplier = 4f;
splashDamageRadius = 95f;
splashDamage = 55f;
splashDamage = 65f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 3;
@@ -470,6 +738,124 @@ public class UnitTypes implements ContentList{
}});
}};
toxopid = new UnitType("toxopid"){{
drag = 0.1f;
speed = 0.5f;
hitsize = 21f;
health = 22000;
armor = 13f;
rotateSpeed = 1.9f;
legCount = 8;
legMoveSpace = 0.8f;
legPairOffset = 3;
legLength = 75f;
legExtension = -20;
legBaseOffset = 8f;
landShake = 1f;
legSpeed = 0.1f;
legLengthScl = 0.93f;
rippleScale = 3f;
legSpeed = 0.19f;
legSplashDamage = 80;
legSplashRange = 60;
hovering = true;
allowLegStep = true;
visualElevation = 0.95f;
groundLayer = Layer.legUnit;
weapons.add(
new Weapon("large-purple-mount"){{
y = -5f;
x = 11f;
shootY = 7f;
reload = 30;
shake = 4f;
rotateSpeed = 2f;
ejectEffect = Fx.shellEjectSmall;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 12f;
recoil = 3f;
shots = 2;
spacing = 17f;
bullet = new ShrapnelBulletType(){{
length = 90f;
damage = 110f;
width = 25f;
serrationLenScl = 7f;
serrationSpaceOffset = 60f;
serrationFadeOffset = 0f;
serrations = 10;
serrationWidth = 6f;
fromColor = Pal.sapBullet;
toColor = Pal.sapBulletBack;
shootEffect = smokeEffect = Fx.sparkShoot;
}};
}});
weapons.add(new Weapon("toxopid-cannon"){{
y = -14f;
x = 0f;
shootY = 22f;
mirror = false;
reload = 210;
shake = 10f;
recoil = 10f;
rotateSpeed = 1f;
ejectEffect = Fx.shellEjectBig;
shootSound = Sounds.shootBig;
rotate = true;
occlusion = 30f;
bullet = new ArtilleryBulletType(3f, 50){{
hitEffect = Fx.sapExplosion;
knockback = 0.8f;
lifetime = 80f;
width = height = 25f;
collidesTiles = collides = true;
ammoMultiplier = 4f;
splashDamageRadius = 90f;
splashDamage = 75f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 5;
lightningLength = 20;
smokeEffect = Fx.shootBigSmoke2;
hitShake = 10f;
status = StatusEffects.sapped;
statusDuration = 60f * 10;
fragLifeMin = 0.3f;
fragBullets = 9;
fragBullet = new ArtilleryBulletType(2.3f, 30){{
hitEffect = Fx.sapExplosion;
knockback = 0.8f;
lifetime = 90f;
width = height = 20f;
collidesTiles = false;
splashDamageRadius = 80f;
splashDamage = 40f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
lightning = 2;
lightningLength = 5;
smokeEffect = Fx.shootBigSmoke2;
hitShake = 5f;
status = StatusEffects.sapped;
statusDuration = 60f * 10;
}};
}};
}});
}};
//endregion
//region air attack
@@ -482,6 +868,7 @@ public class UnitTypes implements ContentList{
faceTarget = false;
engineOffset = 5.5f;
range = 140f;
weapons.add(new Weapon(){{
y = 0f;
x = 2f;
@@ -498,6 +885,7 @@ public class UnitTypes implements ContentList{
accel = 0.08f;
drag = 0.016f;
flying = true;
hitsize = 9f;
targetAir = false;
engineOffset = 7.8f;
range = 140f;
@@ -651,7 +1039,7 @@ public class UnitTypes implements ContentList{
rotateSpeed = 1f;
flying = true;
lowAltitude = true;
health = 18000;
health = 20000;
engineOffset = 38;
engineSize = 7.3f;
hitsize = 58f;
@@ -735,6 +1123,7 @@ public class UnitTypes implements ContentList{
engineOffset = 5.7f;
itemCapacity = 30;
range = 50f;
isCounted = false;
mineTier = 1;
mineSpeed = 2.5f;
@@ -755,6 +1144,7 @@ public class UnitTypes implements ContentList{
engineOffset = 6.5f;
hitsize = 8f;
lowAltitude = true;
isCounted = false;
mineTier = 2;
mineSpeed = 3.5f;
@@ -762,6 +1152,7 @@ public class UnitTypes implements ContentList{
abilities.add(new HealFieldAbility(5f, 60f * 5, 50f));
weapons.add(new Weapon("heal-weapon-mount"){{
top = false;
y = -2.5f;
x = 3.5f;
reload = 30f;
@@ -790,8 +1181,12 @@ public class UnitTypes implements ContentList{
}};
mega = new UnitType("mega"){{
defaultController = RepairAI::new;
mineTier = 2;
health = 500;
armor = 2f;
armor = 5f;
speed = 1.8f;
accel = 0.06f;
drag = 0.017f;
@@ -801,7 +1196,8 @@ public class UnitTypes implements ContentList{
rotateShooting = false;
hitsize = 15f;
engineSize = 3f;
payloadCapacity = 4;
payloadCapacity = (2 * 2) * tilePayload;
buildSpeed = 2.5f;
weapons.add(
new Weapon("heal-weapon-mount"){{
@@ -820,6 +1216,86 @@ public class UnitTypes implements ContentList{
}});
}};
quad = new UnitType("quad"){{
armor = 4f;
health = 6000;
speed = 1.2f;
rotateSpeed = 2f;
accel = 0.05f;
drag = 0.017f;
lowAltitude = false;
flying = true;
engineOffset = 12f;
engineSize = 6f;
rotateShooting = false;
hitsize = 32f;
payloadCapacity = (3 * 3) * tilePayload;
buildSpeed = 2.5f;
range = 140f;
targetAir = false;
weapons.add(
new Weapon(){{
x = y = 0f;
mirror = false;
reload = 60f;
minShootVelocity = 0.01f;
bullet = new BasicBulletType(){{
sprite = "large-bomb";
width = height = 120/4f;
range = 30f;
ignoreRotation = true;
backColor = Pal.heal;
frontColor = Color.white;
mixColorTo = Color.white;
shootCone = 180f;
ejectEffect = Fx.none;
shootSound = Sounds.none;
despawnShake = 4f;
collidesAir = false;
lifetime = 70f;
despawnEffect = Fx.greenBomb;
hitEffect = Fx.massiveExplosion;
keepVelocity = false;
spin = 2f;
shrinkX = shrinkY = 0.7f;
speed = 0.001f;
collides = false;
splashDamage = 240f;
splashDamageRadius = 115f;
}};
}});
}};
oct = new UnitType("oct"){{
armor = 16f;
health = 24000;
speed = 0.6f;
rotateSpeed = 1f;
accel = 0.04f;
drag = 0.018f;
flying = true;
engineOffset = 46f;
engineSize = 7.8f;
rotateShooting = false;
hitsize = 60f;
payloadCapacity = (5.3f * 5.3f) * tilePayload;
buildSpeed = 4f;
drawShields = false;
abilities.add(new ForceFieldAbility(140f, 4f, 7000f, 60f * 8), new HealFieldAbility(130f, 60f * 2, 140f));
}};
//endregion
//region naval attack
@@ -830,8 +1306,8 @@ public class UnitTypes implements ContentList{
health = 280;
accel = 0.4f;
rotateSpeed = 3.3f;
immunities = ObjectSet.with(StatusEffects.wet);
trailLength = 20;
rotateShooting = false;
armor = 2f;
@@ -881,7 +1357,7 @@ public class UnitTypes implements ContentList{
armor = 4f;
accel = 0.3f;
rotateSpeed = 2.6f;
immunities = ObjectSet.with(StatusEffects.wet);
rotateShooting = false;
trailLength = 20;
trailX = 5.5f;
@@ -920,16 +1396,16 @@ public class UnitTypes implements ContentList{
accel = 0.2f;
rotateSpeed = 1.8f;
drag = 0.17f;
hitsize = 14f;
armor = 6f;
immunities = ObjectSet.with(StatusEffects.wet);
hitsize = 16f;
armor = 7f;
rotateShooting = false;
trailLength = 22;
trailX = 7f;
trailY = -9f;
trailScl = 1.5f;
abilities.add(new HealFieldAbility(22f, 60f * 4, 70f), new ShieldFieldAbility(20f, 40f, 60f * 4, 60f));
abilities.add(new ShieldFieldAbility(20f, 40f, 60f * 4, 60f));
weapons.add(new Weapon("large-artillery"){{
reload = 65f;
@@ -941,7 +1417,7 @@ public class UnitTypes implements ContentList{
shootY = 7f;
shake = 5f;
recoil = 4f;
occlusion = 17f;
occlusion = 12f;
shots = 1;
inaccuracy = 3f;
@@ -1007,6 +1483,140 @@ public class UnitTypes implements ContentList{
}});
}};
sei = new UnitType("sei"){{
health = 10000;
armor = 12f;
speed = 0.73f;
drag = 0.17f;
hitsize = 39f;
accel = 0.2f;
rotateSpeed = 1.3f;
rotateShooting = false;
trailLength = 50;
trailX = 18f;
trailY = -21f;
trailScl = 3f;
weapons.add(new Weapon("sei-launcher"){{
x = 0f;
y = 0f;
rotate = true;
rotateSpeed = 4f;
mirror = false;
occlusion = 20f;
shootY = 2f;
recoil = 4f;
reload = 45f;
shots = 6;
spacing = 10f;
velocityRnd = 0.4f;
inaccuracy = 7f;
ejectEffect = Fx.none;
shake = 3f;
shootSound = Sounds.shootBig;
xRand = 8f;
shotDelay = 1f;
bullet = new MissileBulletType(4.2f, 25){{
homingPower = 0.12f;
width = 8f;
height = 8f;
shrinkX = shrinkY = 0f;
drag = -0.003f;
homingRange = 80f;
keepVelocity = false;
splashDamageRadius = 25f;
splashDamage = 25f;
lifetime = 56f;
trailColor = Pal.bulletYellowBack;
backColor = Pal.bulletYellowBack;
frontColor = Pal.bulletYellow;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
weaveScale = 8f;
weaveMag = 2f;
}};
}});
weapons.add(new Weapon("large-bullet-mount"){{
reload = 80f;
cooldownTime = 90f;
x = 70f/4f;
y = -66f/4f;
rotateSpeed = 4f;
rotate = true;
shootY = 7f;
shake = 2f;
recoil = 3f;
occlusion = 12f;
ejectEffect = Fx.shellEjectBig;
shots = 3;
shotDelay = 4f;
inaccuracy = 1f;
bullet = new BasicBulletType(7f, 50){{
width = 13f;
height = 19f;
shootEffect = Fx.shootBig;
lifetime = 30f;
}};
}});
}};
omura = new UnitType("omura"){{
health = 22000;
speed = 0.62f;
drag = 0.18f;
hitsize = 50f;
armor = 16f;
accel = 0.19f;
rotateSpeed = 0.9f;
rotateShooting = false;
float spawnTime = 60f * 15f;
abilities.add(new UnitSpawnAbility(flare, spawnTime, 19.25f, -31.75f), new UnitSpawnAbility(flare, spawnTime, -19.25f, -31.75f));
trailLength = 70;
trailX = 23f;
trailY = -32f;
trailScl = 3.5f;
weapons.add(new Weapon("omura-cannon"){{
reload = 110f;
cooldownTime = 90f;
mirror = false;
x = 0f;
y = -3.5f;
rotateSpeed = 1.4f;
rotate = true;
shootY = 23f;
shake = 6f;
recoil = 10.5f;
occlusion = 50f;
shots = 1;
ejectEffect = Fx.none;
bullet = new RailBulletType(){{
shootEffect = Fx.railShoot;
speed = 67f;
lifetime = 8f;
pierceEffect = Fx.railHit;
updateEffect = Fx.railTrail;
hitEffect = Fx.massiveExplosion;
smokeEffect = Fx.shootBig2;
damage = 1250;
pierceDamageFactor = 0.5f;
}};
}});
}};
//endregion
//region core
@@ -1031,6 +1641,7 @@ public class UnitTypes implements ContentList{
reload = 17f;
x = 2.75f;
y = 1f;
top = false;
bullet = new BasicBulletType(2.5f, 9){{
width = 7f;
@@ -1063,6 +1674,7 @@ public class UnitTypes implements ContentList{
lowAltitude = true;
weapons.add(new Weapon("small-mount-weapon"){{
top = false;
reload = 20f;
x = 3f;
y = 0.5f;
@@ -1100,6 +1712,7 @@ public class UnitTypes implements ContentList{
hitsize = 10f;
weapons.add(new Weapon("small-mount-weapon"){{
top = false;
reload = 15f;
x = 1f;
y = 2f;

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.*;
@@ -170,8 +169,8 @@ 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.45f;
Color color = Color.valueOf("f7cba4");
Texture noise;
@@ -194,22 +193,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 +227,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 +250,13 @@ 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;
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;
}
@@ -269,9 +271,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);
}
}
@@ -285,12 +289,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);
@@ -306,8 +313,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

@@ -182,7 +182,7 @@ public class Control implements ApplicationListener, Loadable{
app.post(() -> ui.hudfrag.showLand());
renderer.zoomIn(Fx.coreLand.lifetime);
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block()));
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
Time.run(Fx.coreLand.lifetime, () -> {
Fx.launch.at(core);
Effect.shake(5f, 5f, core);
@@ -191,9 +191,13 @@ public class Control implements ApplicationListener, Loadable{
}
void resetCamera(){
}
@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);
@@ -280,6 +284,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
@@ -329,6 +334,7 @@ public class Control implements ApplicationListener, Loadable{
}
public void playTutorial(){
ui.showInfo("@indev.notready");
//TODO implement
//ui.showInfo("death");
/*
@@ -428,19 +434,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));
}

View File

@@ -17,7 +17,7 @@ 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, launched = false, serverPaused = false, wasTimeout;
/** Map that is currently being played on. */
public @NonNull Map map = emptyMap;
/** The current game rules. */

View File

@@ -15,7 +15,7 @@ import mindustry.type.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
@@ -41,9 +41,9 @@ public class Logic implements ApplicationListener{
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
if(block instanceof BuildBlock){
if(block instanceof ConstructBlock){
BuildEntity entity = tile.bc();
ConstructBuild entity = tile.bc();
//update block to reflect the fact that something was being constructed
if(entity.cblock != null && entity.cblock.synthetic()){
@@ -187,7 +187,7 @@ public class Logic implements ApplicationListener{
//campaign maps do not have a 'win' state!
if(state.isCampaign()){
//gameover only when cores are dead
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
if(state.teams.playerCores().size == 0 && !state.gameOver){
state.gameOver = true;
Events.fire(new GameOverEvent(state.rules.waveTeam));
}
@@ -245,7 +245,8 @@ 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);
}
}
}
@@ -336,7 +337,14 @@ public class Logic implements ApplicationListener{
//force pausing when the player is out of sector time
if(state.isOutOfTime()){
state.set(State.paused);
if(!state.wasTimeout){
universe.displayTimeEnd();
state.wasTimeout = true;
}
//if no turn was run.
if(state.isOutOfTime()){
state.set(State.paused);
}
}
if(!state.isPaused()){

View File

@@ -440,7 +440,7 @@ 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, boolean gameOver, 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;
@@ -453,6 +453,8 @@ public class NetClient implements ApplicationListener{
state.enemies = enemies;
state.serverPaused = paused;
universe.updateNetSeconds(timeData);
netClient.byteStream.setBytes(net.decompressSnapshot(coreData, coreDataLen));
DataInputStream input = netClient.dataStream;
@@ -547,6 +549,10 @@ public class NetClient implements ApplicationListener{
quiet = true;
}
public void clearRemovedEntity(int id){
removed.remove(id);
}
public void addRemovedEntity(int id){
removed.add(id);
}
@@ -585,8 +591,11 @@ public class NetClient implements ApplicationListener{
}
Unit unit = player.dead() ? Nulls.unit : player.unit();
int uid = player.dead() ? -1 : unit.id;
Call.clientSnapshot(lastSent++,
Call.clientSnapshot(
lastSent++,
uid,
player.dead(),
unit.x, unit.y,
player.unit().aimX(), player.unit().aimY(),
@@ -597,7 +606,8 @@ public class NetClient implements ApplicationListener{
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);
Core.camera.width * viewScale, Core.camera.height * viewScale
);
}
if(timer.get(1, 60)){

View File

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

@@ -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;
@@ -271,9 +275,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);
@@ -285,7 +289,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();
}

View File

@@ -491,7 +491,7 @@ public class UI implements ApplicationListener, Loadable{
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), Actions.remove());
Core.scene.add(t);

View File

@@ -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.*;
@@ -33,11 +34,17 @@ public class World{
public @NonNull 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;
}
@@ -105,7 +112,9 @@ public class World{
public Tile tileBuilding(int x, int y){
Tile tile = tiles.get(x, y);
if(tile == null) return null;
if(tile.build != null) return tile.build.tile();
if(tile.build != null){
return tile.build.tile();
}
return tile;
}
@@ -185,16 +194,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 +262,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,6 +339,12 @@ 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){
@@ -317,7 +390,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 +519,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;

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

@@ -64,8 +64,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 +78,7 @@ public class DrawOperation{
tile.setOverlayID(to);
}
});
editor.renderer().updatePoint(tile.x, tile.y);
editor.renderer.updatePoint(tile.x, tile.y);
}
@Struct

View File

@@ -20,7 +20,7 @@ public class EditorTile extends Tile{
@Override
public void setFloor(@NonNull Floor type){
if(state.isGame()){
if(skip()){
super.setFloor(type);
return;
}
@@ -39,29 +39,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 +71,7 @@ public class EditorTile extends Tile{
@Override
public void setOverlay(Block overlay){
if(state.isGame()){
if(skip()){
super.setOverlay(overlay);
return;
}
@@ -85,20 +83,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;
}
@@ -112,13 +114,21 @@ public class EditorTile extends Tile{
if(block.hasEntity()){
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

@@ -116,9 +116,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
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))),
(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.getTags().get("name", "unknown"), "png", file -> {
(Runnable)() -> platform.export(editor.tags.get("name", "unknown"), "png", file -> {
Pixmap out = MapIO.writeImage(editor.tiles());
file.writePNG(out);
out.dispose();
@@ -129,16 +129,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(steam){
menu.cont.button("@editor.publish.workshop", Icon.link, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
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;
}
@@ -156,7 +156,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
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();
}
@@ -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,9 @@ 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();
logic.play();
});
}
@@ -269,10 +264,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();
@@ -286,7 +281,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(map != null && !map.custom){
handleSaveBuiltin(map);
}else{
returned = maps.saveMap(editor.getTags());
returned = maps.saveMap(editor.tags);
ui.showInfoFade("@editor.saved");
}
}
@@ -357,7 +352,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
@Override
public void dispose(){
editor.renderer().dispose();
editor.renderer.dispose();
}
public void beginEditMap(Fi file){
@@ -576,22 +571,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 +612,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);
}
}
}

View File

@@ -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();
}
@@ -275,6 +275,8 @@ public class MapGenerateDialog extends BaseDialog{
}
}).grow().left().pad(6).top();
}).width(280f).pad(3).top().left().fillY();
if(++i % cols == 0){
filterTable.row();
}
@@ -348,6 +350,7 @@ public class MapGenerateDialog extends BaseDialog{
result = executor.submit(() -> {
try{
world.setGenerating(true);
generating = true;
if(!filters.isEmpty()){
@@ -400,7 +403,7 @@ public class MapGenerateDialog extends BaseDialog{
generating = false;
e.printStackTrace();
}
return null;
world.setGenerating(false);
});
}

View File

@@ -29,7 +29,7 @@ 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();
@@ -73,8 +73,8 @@ public class MapInfoDialog extends BaseDialog{
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)));
generate.show(Vars.maps.readFilters(editor.tags.get("genfilters", "")),
filters -> editor.tags.put("genfilters", JsonIO.write(filters)));
hide();
}).left().width(200f);

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

@@ -248,14 +248,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

@@ -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();

View File

@@ -20,6 +20,7 @@ import static mindustry.Vars.*;
/** Utility class for damaging in an area. */
public class Damage{
private static Tile furthest;
private static Rect rect = new Rect();
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2();
@@ -76,6 +77,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);
}
@@ -85,6 +108,8 @@ 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) -> {
@@ -132,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);

View File

@@ -40,7 +40,7 @@ public class Effect{
}
public Effect(float life, Cons<EffectContainer> renderer){
this(life, 28f, renderer);
this(life,50f, renderer);
}
public Effect ground(){
@@ -86,11 +86,14 @@ public class Effect{
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){
@@ -124,13 +127,13 @@ public class Effect{
if(view.overlaps(pos)){
EffectState entity = EffectState.create();
entity.effect(effect);
entity.rotation(rotation);
entity.data(data);
entity.lifetime(effect.lifetime);
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.color.set(color);
if(data instanceof Posc) entity.parent = ((Posc)data);
entity.add();
}
}

View File

@@ -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);
}

View File

@@ -1,5 +1,6 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.math.geom.*;
import mindustry.annotations.Annotations.*;
@@ -18,6 +19,15 @@ public class Units{
private static float cdist;
private static boolean boolResult;
@Remote(called = Loc.server)
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);
@@ -32,6 +42,21 @@ public class Units{
}
}
//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)
public static void unitDespawn(Unit unit){
Fx.unitDespawn.at(unit.x, unit.y, 0, unit);
@@ -99,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)){
@@ -251,9 +276,20 @@ public class Units{
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
for(Team enemy : state.teams.enemiesOf(team)){
nearby(enemy, x, y, width, height, cons);
if(team.active()){
for(Team enemy : state.teams.enemiesOf(team)){
nearby(enemy, x, y, width, height, cons);
}
}else{
//inactive teams have no cache, check everything
//TODO cache all teams with units OR blocks
for(Team other : Team.all){
if(other != team && teamIndex.count(other) > 0){
nearby(other, x, y, width, height, cons);
}
}
}
}
/** Iterates over all units that are enemies of this team. */

View File

@@ -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,22 +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 -> {
/** State. */
protected float radiusScale, alpha;
private static float realRad;
private static Unit paramUnit;
private static ForceFieldAbility paramField;
private static 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){
trait.absorb();
Fx.absorb.at(trait);
//break shield
if(paramUnit.shield <= trait.damage()){
paramUnit.shield -= cooldown * regen;
paramUnit.shield -= paramField.cooldown * paramField.regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, radius, paramUnit.team.color);
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color);
}
paramUnit.shield -= trait.damage();
paramUnit.shieldAlpha = 1f;
paramField.alpha = 1f;
}
};
@@ -55,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;
}
}
@@ -73,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);
@@ -89,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

@@ -7,12 +7,14 @@ import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
public class StatusFieldAbility implements Ability{
public class StatusFieldAbility extends Ability{
public @NonNull 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){
@@ -24,9 +26,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 +36,7 @@ public class StatusFieldAbility implements Ability{
activeEffect.at(unit);
unit.timer2 = 0f;
timer = 0f;
}
}
}

View File

@@ -0,0 +1,60 @@
package mindustry.entities.abilities;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.ArcAnnotate.*;
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 @NonNull UnitType type;
public float spawnTime = 60f, spawnX, spawnY;
public Effect spawnEffect = Fx.spawn;
protected float timer;
public UnitSpawnAbility(@NonNull 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. */
@@ -69,12 +69,16 @@ 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;
/** Bullet range override. */
public float range = -1f;
//additional effects
public float fragCone = 360f;
public int fragBullets = 9;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f, fragLifeMin = 1f, fragLifeMax = 1f;
public BulletType fragBullet = null;
public Color hitColor = Color.white;
@@ -94,13 +98,13 @@ public abstract class BulletType extends Content{
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 weaveScale = 1f;
public float weaveMag = -1f;
public float hitShake = 0f;
public float hitShake = 0f, despawnShake = 0f;
public int puddles;
public float puddleRange;
@@ -124,17 +128,21 @@ 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){
hit(b);
}
public void hitEntity(Bullet b, Hitboxc other, float initialHealth){
}
public void hit(Bullet b){
hit(b, b.x, b.y);
}
@@ -149,7 +157,7 @@ public abstract class BulletType extends Content{
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(1f, 7f);
float a = b.rotation() + Mathf.range(fragCone/2);
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
}
}
@@ -173,7 +181,7 @@ public abstract class BulletType extends Content{
}
for(int i = 0; i < lightning; i++){
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength + Mathf.random(lightningLengthRand));
}
}
@@ -181,6 +189,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);
}
@@ -247,6 +257,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);
}
@@ -265,9 +279,8 @@ 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() / Time.delta, ((Hitboxc)owner).deltaY() / Time.delta);
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){

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,7 @@ public class ContinuousLaserBulletType extends BulletType{
incendAmount = 1;
incendSpread = 5;
incendChance = 0.4f;
lightColor = Color.orange;
}
protected ContinuousLaserBulletType(){
@@ -43,13 +48,20 @@ 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){
@@ -59,21 +71,23 @@ public class ContinuousLaserBulletType extends BulletType{
@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.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

@@ -43,11 +43,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,7 +89,6 @@ 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());
@@ -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

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

View File

@@ -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

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

View File

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

View File

@@ -76,6 +76,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public Building init(Tile tile, Team team, boolean shouldAdd, int rotation){
if(!initialized){
create(tile.block(), team);
}else{
if(block.hasPower){
//reinit power graph
power.graph = new PowerGraph();
power.graph.add(self());
}
}
this.rotation = rotation;
this.tile = tile;
@@ -88,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. */
@@ -105,17 +111,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
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
@@ -189,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();
}
}
@@ -366,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;
@@ -419,8 +425,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
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;
}
@@ -440,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;
}
@@ -474,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;
@@ -493,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);
}
}
@@ -517,7 +523,7 @@ 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){
float ofract = next.liquids.get(liquid) / next.block.liquidCapacity;
@@ -525,8 +531,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
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(base(), liquid, flow)){
next.handleLiquid(base(), liquid, flow);
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){
@@ -552,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(){
@@ -574,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);
}
/**
@@ -592,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;
}
}
@@ -625,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;
@@ -659,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;
@@ -690,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){
@@ -812,10 +819,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Called after the block is placed by this client. */
@CallSuper
public void playerPlaced(){
if(block.saveConfig && block.lastConfig != null){
configure(block.lastConfig);
}
public void playerPlaced(Object config){
}
/** Called after the block is placed by anyone. */
@@ -828,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)));
@@ -856,7 +861,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** Called when arbitrary configuration is applied to a tile. */
public void configured(@Nullable Player player, @Nullable Object value){
public void configured(@Nullable Unit builder, @Nullable Object value){
//null is of type void.class; anonymous classes use their superclass.
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass();
@@ -967,7 +972,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
table.row();
table.table(this::displayConsumption).growX();
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow");
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow") && block.displayFlow;
if(displayFlow){
String ps = " " + StatUnit.perSecond.localized();
@@ -1005,9 +1010,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(liquids != null){
table.row();
table.table(l -> {
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
boolean[] had = {false};
Runnable rebuild = () -> {
l.clearChildren();
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
};
l.update(() -> {
if(!had[0] && liquids.hadFlow()){
had[0] = true;
rebuild.run();
}
});
}).left();
}
}
@@ -1020,13 +1037,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();
}
}
@@ -1052,7 +1069,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. */
@@ -1111,7 +1128,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();
}
}
@@ -1127,8 +1144,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);
@@ -1167,13 +1184,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
@@ -1187,10 +1204,10 @@ 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());
}
}
@@ -1200,6 +1217,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(sensor == LAccess.y) return y;
if(sensor == LAccess.team) return team.id;
if(sensor == LAccess.health) return health;
if(sensor == LAccess.maxHealth) return maxHealth();
if(sensor == LAccess.efficiency) return efficiency();
if(sensor == LAccess.rotation) return rotation;
if(sensor == LAccess.totalItems && items != null) return items.total();
@@ -1208,13 +1226,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(sensor == LAccess.itemCapacity) return block.itemCapacity;
if(sensor == LAccess.liquidCapacity) return block.liquidCapacity;
if(sensor == LAccess.powerCapacity) return block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getPowerProduced();
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getPowerNeeded();
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getLastScaledPowerIn() * 60;
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getLastScaledPowerOut() * 60;
if(sensor == LAccess.powerNetStored && power != null) return power.graph.getLastPowerStored();
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getBatteryCapacity();
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getLastCapacity();
return 0;
}
@Override
public Object senseObject(LAccess sensor){
if(sensor == LAccess.type) return block;
return noSensed;
}
@Override
public double sense(Content content){
if(content instanceof Item && items != null) return items.get((Item)content);
@@ -1267,7 +1292,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
if(block.idleSound != Sounds.none && shouldIdleSound()){
loops.play(block.idleSound, base(), block.idleSoundVolume);
loops.play(block.idleSound, self(), block.idleSoundVolume);
}
if(enabled || !block.noUpdateDisabled){

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

@@ -27,7 +27,7 @@ abstract class CommanderComp implements Unitc{
@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();
}
}
@@ -59,7 +59,7 @@ abstract class CommanderComp implements Unitc{
units.clear();
Units.nearby(team(), x, y, 200f, u -> {
if(u.isAI() && include.get(u) && u != base()){
if(u.isAI() && include.get(u) && u != self()){
units.add(u);
}
});
@@ -73,13 +73,13 @@ abstract class CommanderComp implements Unitc{
void command(Formation formation, Seq<Unit> units){
clearCommand();
float spacing = hitSize() * 1.7f;
float spacing = hitSize() * 1f;
minFormationSpeed = type().speed;
controlling.addAll(units);
for(Unit unit : units){
FormationAI ai;
unit.controller(ai = new FormationAI(base(), formation));
unit.controller(ai = new FormationAI(self(), formation));
spacing = Math.max(spacing, ai.formationSize());
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
}
@@ -104,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.*;
@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

@@ -40,7 +40,7 @@ abstract class EntityComp{
return false;
}
<T extends Entityc> T base(){
<T extends Entityc> T self(){
return (T)this;
}

View File

@@ -97,11 +97,11 @@ abstract class FireComp implements Timedc, Posc, Firec, Syncc{
@Override
public void afterRead(){
Fires.register(base());
Fires.register(self());
}
@Override
public void afterSync(){
Fires.register(base());
Fires.register(self());
}
}

View File

@@ -74,7 +74,7 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
}
if(!hovering && isGrounded() && floor.isLiquid){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= 7f){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= (7f + hitSize()/8f)){
floor.walkEffect.at(x, y, hitSize() / 8f, floor.mapColor);
splashTimer = 0f;
}

View File

@@ -61,7 +61,8 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
}
public void hitboxTile(Rect rect){
float scale = 0.66f;
rect.setCentered(x, y, hitSize * scale, hitSize * scale);
//tile hitboxes are never bigger than a tile, otherwise units get stuck
float size = Math.min(hitSize * 0.66f, 7.9f);
rect.setCentered(x, y, size, size);
}
}

View File

@@ -44,7 +44,7 @@ abstract class LaunchCoreComp implements Drawc, Timedc{
Draw.z(Layer.weather - 1);
TextureRegion region = block.icon(Cicon.full);
float rw = region.getWidth() * Draw.scl * scale, rh = region.getHeight() * Draw.scl * scale;
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
Draw.alpha(alpha);
Draw.rect(region, cx, cy, rw, rh, rotation - 45);

View File

@@ -4,16 +4,16 @@ import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Import float x, y;
@@ -26,34 +26,52 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Replace
@Override
public void move(float cx, float cy){
collisions.moveCheck(this, cx, cy, !type.allowLegStep ? EntityCollisions::solid : EntityCollisions::legsSolid);
public SolidPred solidity(){
return !type.allowLegStep ? EntityCollisions::solid : EntityCollisions::legsSolid;
}
@Override
@Replace
public int pathType(){
return Pathfinder.costLegs;
}
@Override
public void add(){
resetLegs();
}
public void resetLegs(){
float rot = baseRotation;
int count = type.legCount;
float legLength = type.legLength;
this.legs = new Leg[count];
float spacing = 360f / count;
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
l.joint.trns(i * spacing + rot, legLength/2f + type.legBaseOffset).add(x, y);
l.base.trns(i * spacing + rot, legLength + type.legBaseOffset).add(x, y);
legs[i] = l;
}
}
@Override
public void update(){
if(Mathf.dst(deltaX(), deltaY()) > 0.001f){
baseRotation = Mathf.slerpDelta(baseRotation, Mathf.angle(deltaX(), deltaY()), 0.1f);
baseRotation = Angles.moveToward(baseRotation, Mathf.angle(deltaX(), deltaY()), type.rotateSpeed);
}
float rot = baseRotation;
int count = type.legCount;
float legLength = type.legLength;
//set up initial leg positions
if(legs.length != type.legCount){
this.legs = new Leg[count];
float spacing = 360f / count;
for(int i = 0; i < legs.length; i++){
Leg l = new Leg();
l.joint.trns(i * spacing + rot, legLength/2f + type.legBaseOffset).add(x, y);
l.base.trns(i * spacing + rot, legLength + type.legBaseOffset).add(x, y);
legs[i] = l;
}
resetLegs();
}
float moveSpeed = type.legSpeed;

View File

@@ -1,6 +1,7 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@@ -8,12 +9,25 @@ import mindustry.gen.*;
@Component
abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, ElevationMovec{
@SyncField(false) @SyncLocal float baseRotation;
transient float walkTime;
transient float walkTime, walkExtension;
transient private boolean walked;
@Override
public void update(){
float len = deltaLen();
baseRotation = Angles.moveToward(baseRotation, deltaAngle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed / Time.delta) * Time.delta);
walkTime += len;
//trigger animation only when walking manually
if(walked){
float len = deltaLen();
baseRotation = Angles.moveToward(baseRotation, deltaAngle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed / Time.delta) * Time.delta);
walkTime += len;
walked = false;
}
}
@Override
public void moveAt(Vec2 vector, float acceleration){
if(!vector.isZero()){
//mark walking state when moving in a controlled manner
walked = true;
}
}
}

View File

@@ -33,7 +33,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
}
boolean mining(){
return mineTile != null;
return mineTile != null && !(((Object)this) instanceof Builderc && ((Builderc)(Object)this).activelyBuilding());
}
@Override
@@ -51,13 +51,12 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
}
if(mineTile == null || core == null || mineTile.block() != Blocks.air || dst(mineTile.worldx(), mineTile.worldy()) > miningRange
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).activelyBuilding())
|| mineTile.drop() == null || !canMine(mineTile.drop())){
mineTile = null;
mineTimer = 0f;
}else{
}else if(mining()){
Item item = mineTile.drop();
rotation(Mathf.slerpDelta(rotation(), angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f));
rotation = Mathf.slerpDelta(rotation, angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f);
mineTimer += Time.delta *type.mineSpeed;
if(Mathf.chance(0.06 * Time.delta)){

View File

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

View File

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

View File

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

View File

@@ -128,6 +128,6 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
@Override
public void afterRead(){
Puddles.register(base());
Puddles.register(self());
}
}

View File

@@ -1,7 +1,6 @@
package mindustry.entities.comp;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import arc.util.pooling.*;
@@ -22,10 +21,7 @@ abstract class StatusComp implements Posc, Flyingc{
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, armorMultiplier = 1, reloadMultiplier = 1;
/** @return damage taken based on status armor multipliers */
float getShieldDamage(float amount){
return amount * Mathf.clamp(1f - armorMultiplier / 100f);
}
@Import UnitType type;
/** Apply a status effect for 1 tick (for permanent effects) **/
void apply(StatusEffect effect){
@@ -46,7 +42,7 @@ abstract class StatusComp implements Posc, Flyingc{
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
StatusEntry.tmp.effect = entry.effect;
entry.effect.getTransition(base(), effect, entry.time, duration, StatusEntry.tmp);
entry.effect.getTransition(self(), effect, entry.time, duration, StatusEntry.tmp);
entry.time = StatusEntry.tmp.time;
if(StatusEntry.tmp.effect != entry.effect){
@@ -102,7 +98,7 @@ abstract class StatusComp implements Posc, Flyingc{
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded()){
if(isGrounded() && !type.hovering){
//apply effect
apply(floor.status, floor.statusDuration);
}
@@ -129,14 +125,14 @@ abstract class StatusComp implements Posc, Flyingc{
armorMultiplier *= entry.effect.armorMultiplier;
damageMultiplier *= entry.effect.damageMultiplier;
reloadMultiplier *= entry.effect.reloadMultiplier;
entry.effect.update(base(), entry.time);
entry.effect.update(self(), entry.time);
}
}
}
public void draw(){
for(StatusEntry e : statuses){
e.effect.draw(base());
e.effect.draw(self());
}
}

View File

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

View File

@@ -5,12 +5,15 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.abilities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -35,9 +38,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
private UnitController controller;
private UnitType type;
boolean spawnedByCore, deactivated;
boolean spawnedByCore;
transient float timer1, timer2;
transient Seq<Ability> abilities = new Seq<>(0);
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
@@ -67,7 +70,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Replace
public float clipSize(){
return type.region.getWidth() * 2f;
return Math.max(type.region.width * 2f, type.clipSize);
}
@Override
@@ -75,6 +78,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(sensor == LAccess.totalItems) return stack().amount;
if(sensor == LAccess.rotation) return rotation;
if(sensor == LAccess.health) return health;
if(sensor == LAccess.maxHealth) return maxHealth;
if(sensor == LAccess.x) return x;
if(sensor == LAccess.y) return y;
if(sensor == LAccess.team) return team.id;
@@ -84,12 +88,32 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return 0;
}
@Override
public Object senseObject(LAccess sensor){
if(sensor == LAccess.type) return type;
return noSensed;
}
@Override
public double sense(Content content){
if(content == stack().item) return stack().amount;
return 0;
}
@Override
@Replace
public boolean canDrown(){
return isGrounded() && !hovering && type.canDrown;
}
@Override
@Replace
public boolean canShoot(){
//cannot shoot while boosting
return !(type.canBoost && isFlying());
}
@Override
public int itemCapacity(){
return type.itemCapacity;
@@ -103,7 +127,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void controller(UnitController next){
this.controller = next;
if(controller.unit() != base()) controller.unit(base());
if(controller.unit() != self()) controller.unit(self());
}
@Override
@@ -133,8 +157,13 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type;
}
/** @return pathfinder path type for calculating costs */
public int pathType(){
return Pathfinder.costGround;
}
public void lookAt(float angle){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta);
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta * speedMultiplier());
}
public void lookAt(Position pos){
@@ -157,7 +186,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return Units.getCap(team);
}
private void setStats(UnitType type){
public void setStats(UnitType type){
this.type = type;
this.maxHealth = type.health;
this.drag = type.drag;
@@ -167,13 +196,16 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(controller == null) controller(type.createController());
if(mounts().length != type.weapons.size) setupWeapons(type);
if(abilities.size != type.abilities.size){
abilities = type.abilities.map(Ability::copy);
}
}
@Override
public void afterSync(){
//set up type info after reading
setStats(this.type);
controller.unit(base());
controller.unit(self());
}
@Override
@@ -185,20 +217,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void add(){
teamIndex.updateCount(team, type, 1);
//check if over unit cap
if(count() > cap() && !spawnedByCore){
deactivated = true;
}else{
teamIndex.updateActiveCount(team, type, 1);
if(count() > cap() && !spawnedByCore && !dead){
Call.unitCapDeath(self());
teamIndex.updateCount(team, type, -1);
}
}
@Override
public void remove(){
teamIndex.updateCount(team, type, -1);
controller.removed(base());
controller.removed(self());
}
@Override
@@ -207,18 +237,19 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
Effect.shake(type.landShake, type.landShake, this);
}
type.landed(base());
type.landed(self());
}
@Override
public void update(){
//activate the unit when possible
if(!net.client() && deactivated && teamIndex.countActive(team, type) < Units.getCap(team)){
teamIndex.updateActiveCount(team, type, 1);
deactivated = false;
}
if(!deactivated) type.update(base());
type.update(self());
if(abilities.size > 0){
for(Ability a : abilities){
a.update(self());
}
}
drag = type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f);
@@ -233,7 +264,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
//simulate falling down
if(dead){
if(dead || health <= 0){
//less drag when dead
drag = 0.01f;
@@ -268,25 +299,27 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(tile != null && isGrounded() && !type.hovering){
//unit block update
if(tile.build != null){
tile.build.unitOn(base());
tile.build.unitOn(self());
}
//apply damage
if(floor.damageTaken > 0f){
damageContinuous(floor.damageTaken);
}
}
if(tile.solid()){
if(type.canBoost){
elevation = 1f;
}else if(!net.client()){
kill();
}
//kill entities on tiles that are solid to them
if(tile != null && !canPassOn()){
//boost if possible
if(type.canBoost){
elevation = 1f;
}else if(!net.client()){
kill();
}
}
//AI only updates on the server
if(!net.client() && !dead && !deactivated){
if(!net.client() && !dead){
controller.updateUnit();
}
@@ -295,14 +328,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
resetController();
}
//do not control anything when deactivated
if(deactivated){
controlWeapons(false, false);
}
//remove units spawned by the core
if(spawnedByCore && !isPlayer()){
Call.unitDespawn(base());
Call.unitDespawn(self());
}
}
@@ -313,8 +341,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** Actually destroys the unit, removing it and creating explosions. **/
public void destroy(){
float explosiveness = 2f + item().explosiveness * stack().amount;
float flammability = item().flammability * stack().amount;
float explosiveness = 2f + item().explosiveness * stack().amount / 2f;
float flammability = item().flammability * stack().amount / 2f;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame, state.rules.damageExplosions);
float shake = hitSize / 3f;
@@ -324,7 +352,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
Effect.shake(shake, shake, this);
type.deathSound.at(this);
Events.fire(new UnitDestroyEvent(base()));
Events.fire(new UnitDestroyEvent(self()));
if(explosiveness > 7f && isLocal()){
Events.fire(Trigger.suicideBomb);
@@ -332,7 +360,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//if this unit crash landed (was flying), damage stuff in a radius
if(type.flying){
Damage.damage(team,x, y, hitSize * 1.1f, hitSize * type.crashDamageMultiplier, true, false, true);
Damage.damage(team,x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f, true, false, true);
}
if(!headless){
@@ -350,7 +378,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void display(Table table){
type.display(base(), table);
type.display(self(), table);
}
@Override
@@ -360,7 +388,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void draw(){
type.draw(base());
type.draw(self());
}
@Override

View File

@@ -3,9 +3,13 @@ package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@Component
abstract class VelComp implements Posc{
@Import float x, y;
@@ -22,12 +26,35 @@ abstract class VelComp implements Posc{
vel.scl(Mathf.clamp(1f - drag * Time.delta));
}
/** @return function to use for check solid state. if null, no checking is done. */
@Nullable
SolidPred solidity(){
return null;
}
/** @return whether this entity can move through a location*/
boolean canPass(int tileX, int tileY){
SolidPred s = solidity();
return s == null || !s.solid(tileX, tileY);
}
/** @return whether this entity can exist on its current location*/
boolean canPassOn(){
return canPass(tileX(), tileY());
}
boolean moving(){
return !vel.isZero(0.01f);
}
void move(float cx, float cy){
x += cx;
y += cy;
SolidPred check = solidity();
if(check != null){
collisions.move(self(), cx, cy, check);
}else{
x += cx;
y += cy;
}
}
}

View File

@@ -4,17 +4,17 @@ import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.ai.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.EntityCollisions.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
//just a proof of concept
@Component
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@@ -38,16 +38,8 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@Override
@Replace
public void lookAt(float angle){
if(onLiquid()){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta);
}
}
@Override
@Replace
public boolean canShoot(){
return onLiquid();
public int pathType(){
return Pathfinder.costWater;
}
@Override
@@ -74,19 +66,8 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
@Replace
@Override
public void move(float cx, float cy){
if(isGrounded()){
collisions.moveCheck(this, cx, cy, EntityCollisions::waterSolid);
}else{
x += cx;
y += cy;
}
}
@Replace
@Override
public boolean canDrown(){
return false;
public SolidPred solidity(){
return isFlying() ? null : EntityCollisions::waterSolid;
}
@Replace

View File

@@ -13,9 +13,10 @@ import mindustry.type.*;
import static mindustry.Vars.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Import float x, y, rotation, reloadMultiplier;
@Import Vec2 vel;
@Import UnitType type;
/** minimum cursor distance from unit, fixes 'cross-eyed' shooting */
static final float minAimDst = 18f;
@@ -29,6 +30,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
boolean isShooting;
float ammo;
float ammof(){
return ammo / type.ammoCapacity;
}
void setWeaponRotation(float rotation){
for(WeaponMount mount : mounts){
mount.rotation = rotation;
@@ -80,6 +85,16 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
return true;
}
@Override
public void remove(){
for(WeaponMount mount : mounts){
if(mount.bullet != null){
mount.bullet.time = mount.bullet.lifetime - 10f;
mount.bullet = null;
}
}
}
/** Update shooting and rotation for this unit. */
@Override
public void update(){
@@ -89,6 +104,27 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
float weaponRotation = this.rotation - 90 + (weapon.rotate ? mount.rotation : 0);
float mountX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
mountY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
//update continuous state
if(weapon.continuous && mount.bullet != null){
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime){
mount.bullet = null;
}else{
mount.bullet.rotation(weaponRotation + 90);
mount.bullet.set(shootX, shootY);
vel.add(Tmp.v1.trns(rotation + 180f, mount.bullet.type.recoil));
}
}else{
//heat decreases when not firing
mount.heat = Math.max(mount.heat - Time.delta * reloadMultiplier / mount.weapon.cooldownTime, 0);
}
//flip weapon shoot side for alternating weapons at half reload
if(weapon.otherSide != -1 && weapon.alternate && mount.side == weapon.flipSprite &&
mount.reload + Time.delta > weapon.reload/2f && mount.reload <= weapon.reload/2f){
@@ -98,10 +134,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
float axisX = this.x + Angles.trnsx(rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(rotation - 90, weapon.x, weapon.y);
float axisX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation;
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - this.rotation;
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
}else if(!weapon.rotate){
mount.rotation = 0;
@@ -118,19 +154,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
mount.reload <= 0.0001f && //reload has to be 0
Angles.within(weapon.rotate ? mount.rotation : this.rotation, mount.targetRotation, mount.weapon.shootCone) //has to be within the cone
){
float rotation = this.rotation - 90;
float weaponRotation = rotation + (weapon.rotate ? mount.rotation : 0);
//m a t h
float mountX = this.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = this.y + Angles.trnsy(rotation, weapon.x, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
shoot(weapon, shootX, shootY, mount.aimX, mount.aimY, shootAngle, Mathf.sign(weapon.x));
shoot(mount, shootX, shootY, mount.aimX, mount.aimY, shootAngle, Mathf.sign(weapon.x));
mount.reload = weapon.reload;
@@ -140,7 +164,8 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
}
}
private void shoot(Weapon weapon, float x, float y, float aimX, float aimY, float rotation, int side){
private void shoot(WeaponMount mount, float x, float y, float aimX, float aimY, float rotation, int side){
Weapon weapon = mount.weapon;
float baseX = this.x, baseY = this.y;
@@ -150,27 +175,46 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
float lifeScl = ammo.scaleVelocity ? Mathf.clamp(Mathf.dst(x, y, aimX, aimY) / ammo.range()) : 1f;
sequenceNum = 0;
if(weapon.shotDelay > 0.01f){
if(weapon.shotDelay + weapon.firstShotDelay > 0.01f){
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> {
Time.run(sequenceNum * weapon.shotDelay, () -> bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy), lifeScl));
Time.run(sequenceNum * weapon.shotDelay + weapon.firstShotDelay, () -> {
if(!isAdded()) return;
mount.bullet = bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy), lifeScl);
});
sequenceNum++;
});
}else{
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> mount.bullet = bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
}
if(this instanceof Velc){
((Velc)this).vel().add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
}
boolean parentize = ammo.keepVelocity;
Effect.shake(weapon.shake, weapon.shake, x, y);
if(weapon.firstShotDelay > 0){
Time.run(weapon.firstShotDelay, () -> {
if(!isAdded()) return;
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
});
}else{
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
}
weapon.ejectEffect.at(x, y, rotation * side);
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);
apply(weapon.shootStatus, weapon.shootStatusDuration);
}
private void bullet(Weapon weapon, float x, float y, float angle, float lifescl){
weapon.bullet.create(this, team(), x, y, angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
private Bullet bullet(Weapon weapon, float x, float y, float angle, float lifescl){
float xr = Mathf.range(weapon.xRand);
return weapon.bullet.create(this, team(),
x + Angles.trnsx(angle, 0, xr),
y + Angles.trnsy(angle, 0, xr),
angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
}
}

View File

@@ -16,6 +16,8 @@ import static mindustry.Vars.*;
public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0;
protected static final int timerTarget2 = 1;
protected static final int timerTarget3 = 2;
protected Unit unit;
protected Interval timer = new Interval(4);
@@ -27,10 +29,12 @@ public class AIController implements UnitController{
{
timer.reset(0, Mathf.random(40f));
timer.reset(1, Mathf.random(60f));
}
@Override
public void updateUnit(){
updateVisuals();
updateTargeting();
updateMovement();
}
@@ -39,13 +43,23 @@ public class AIController implements UnitController{
return unit.team.data().command;
}
protected void updateVisuals(){
if(unit.isFlying()){
unit.wobble();
if(unit.moving()){
unit.lookAt(unit.vel.angle());
}
}
}
protected void updateMovement(){
}
protected void updateTargeting(){
if(unit.hasWeapons()){
updateWeapons();
}
}
@@ -138,7 +152,7 @@ public class AIController implements UnitController{
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Time.delta);
vec.setLength(speed);
unit.moveAt(vec);
}
@@ -150,7 +164,7 @@ public class AIController implements UnitController{
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta * length);
vec.setLength(unit.type().speed * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){

View File

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

View File

@@ -1,5 +1,7 @@
package mindustry.entities.units;
import arc.util.ArcAnnotate.*;
import mindustry.gen.*;
import mindustry.type.*;
public class WeaponMount{
@@ -11,6 +13,8 @@ public class WeaponMount{
public float rotation;
/** destination rotation; do not modify! */
public float targetRotation;
/** current heat, 0 to 1*/
public float heat;
/** aiming position in world coordinates */
public float aimX, aimY;
/** whether to shoot right now */
@@ -19,6 +23,8 @@ public class WeaponMount{
public boolean rotate = false;
/** extra state for alternating weapons */
public boolean side;
/** current bullet for continuous weapons */
public @Nullable Bullet bullet;
public WeaponMount(Weapon weapon){
this.weapon = weapon;

View File

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

View File

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

View File

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

View File

@@ -61,6 +61,9 @@ public class Saves{
//automatically assign sector save slots
for(SaveSlot slot : saves){
if(slot.getSector() != null){
if(slot.getSector().save != null){
Log.warn("Sector @ has two corresponding saves: @ and @", slot.getSector(), slot.getSector().save.file, slot.file);
}
slot.getSector().save = slot;
}
}
@@ -75,7 +78,6 @@ public class Saves{
}
public void update(){
SaveSlot current = this.current;
if(current != null && state.isGame()
&& !(state.isPaused() && Core.scene.hasDialog())){
@@ -90,14 +92,13 @@ public class Saves{
if(time > Core.settings.getInt("saveinterval") * 60){
saving = true;
Time.runTask(2f, () -> {
try{
current.save();
}catch(Throwable e){
e.printStackTrace();
}
saving = false;
});
try{
current.save();
}catch(Throwable e){
e.printStackTrace();
}
Time.runTask(3f, () -> saving = false);
time = 0;
}
@@ -128,6 +129,7 @@ public class Saves{
sector.save.setName(sector.save.file.nameWithoutExtension());
saves.add(sector.save);
}
sector.save.setAutosave(true);
sector.save.save();
lastSectorSave = sector.save;
Core.settings.put("last-sector-save", sector.save.getName());

View File

@@ -2,9 +2,7 @@ package mindustry.game;
import arc.files.*;
import arc.struct.*;
import arc.struct.IntIntMap.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.mod.Mods.*;
import mindustry.type.*;
@@ -12,7 +10,6 @@ import mindustry.world.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -39,22 +36,16 @@ public class Schematic implements Publishable, Comparable<Schematic>{
return tiles.sumf(s -> s.block.consumes.has(ConsumeType.power) ? s.block.consumes.getPower().usage : 0f);
}
public Seq<ItemStack> requirements(){
IntIntMap amounts = new IntIntMap();
public ItemSeq requirements(){
ItemSeq requirements = new ItemSeq();
tiles.each(t -> {
if(t.block.buildVisibility == BuildVisibility.hidden) return;
for(ItemStack stack : t.block.requirements){
amounts.increment(stack.item.id, stack.amount);
requirements.add(stack.item, stack.amount);
}
});
Seq<ItemStack> stacks = new Seq<>();
for(Entry ent : amounts.entries()){
stacks.add(new ItemStack(Vars.content.item(ent.key), ent.value));
}
stacks.sort();
return stacks;
return requirements;
}
public boolean hasCore(){

View File

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

View File

@@ -2,9 +2,10 @@ package mindustry.game;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
@@ -32,6 +33,8 @@ public class SectorInfo{
public boolean hasCore = true;
/** Sector that was launched from. */
public @Nullable Sector origin;
/** Resources known to occur at this sector. */
public Seq<UnlockableContent> resources = new Seq<>();
/** Time spent at this sector. Do not use unless you know what you're doing. */
public transient float internalTimeSpent;
@@ -84,12 +87,10 @@ public class SectorInfo{
/** Update averages of various stats, updates some special sector logic.
* Called every frame. */
public void update(){
internalTimeSpent += Time.delta;
//updating in multiplayer as a client doesn't make sense
if(net.client()) return;
//time spent exceeds turn duration!
if(internalTimeSpent >= turnDuration && internalTimeSpent - Time.delta < turnDuration){
universe.displayTimeEnd();
}
internalTimeSpent += Time.delta;
//create last stored core items
if(lastCoreItems == null){

View File

@@ -39,7 +39,7 @@ public class Stats{
//weigh used fractions
float frac = 0f;
Seq<Item> obtainable = Seq.select(zone.data.resources, i -> i instanceof Item).as();
Seq<Item> obtainable = zone.save == null ? new Seq<>() : zone.save.meta.secinfo.resources.select(i -> i instanceof Item).as();
for(Item item : obtainable){
frac += Mathf.clamp((float)itemsDelivered.get(item, 0) / capacity) / (float)obtainable.size;
}

View File

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

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