Merge remote-tracking branch 'upstream/master' into burning-affects-tiles
This commit is contained in:
@@ -33,6 +33,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
|
||||
@Override
|
||||
public void setup(){
|
||||
loadLogger();
|
||||
|
||||
loader = new LoadRenderer();
|
||||
Events.fire(new ClientCreateEvent());
|
||||
|
||||
|
||||
@@ -37,11 +37,11 @@ public class Vars implements Loadable{
|
||||
/** Whether the logger is loaded. */
|
||||
public static boolean loadedLogger = false, loadedFileLogger = false;
|
||||
/** Maximum extra padding around deployment schematics. */
|
||||
public static final int maxLoadoutSchematicPad = 4;
|
||||
public static final int maxLoadoutSchematicPad = 5;
|
||||
/** Maximum schematic size.*/
|
||||
public static final int maxSchematicSize = 32;
|
||||
/** All schematic base64 starts with this string.*/
|
||||
public static final String schematicBaseStart ="bXNjaAB";
|
||||
public static final String schematicBaseStart ="bXNjaA";
|
||||
/** IO buffer size. */
|
||||
public static final int bufferSize = 8192;
|
||||
/** global charset, since Android doesn't support the Charsets class */
|
||||
@@ -60,8 +60,10 @@ public class Vars implements Loadable{
|
||||
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers.json";
|
||||
/** URL to the JSON file containing all the BE servers. Only queried in BE. */
|
||||
public static final String serverJsonBeURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_be.json";
|
||||
/** URL to the JSON file containing all the BE servers. Only queried in the V6 alpha (will be removed once it's out). */
|
||||
public static final String serverJsonV6URL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_v6.json";
|
||||
/** URL of the github issue report template.*/
|
||||
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?template=bug_report.md";
|
||||
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?labels=bug&template=bug_report.md";
|
||||
/** list of built-in servers.*/
|
||||
public static final Seq<String> defaultServers = Seq.with();
|
||||
/** maximum distance between mine and core that supports automatic transferring */
|
||||
@@ -78,20 +80,24 @@ public class Vars implements Loadable{
|
||||
public static final float miningRange = 70f;
|
||||
/** range for building */
|
||||
public static final float buildingRange = 220f;
|
||||
/** range for moving items */
|
||||
public static final float itemTransferRange = 220f;
|
||||
/** duration of time between turns in ticks */
|
||||
public static final float turnDuration = 20 * Time.toMinutes;
|
||||
/** turns needed to destroy a sector completely */
|
||||
public static final float sectorDestructionTurns = 3f;
|
||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||
public static final float minArmorDamage = 0.05f;
|
||||
public static final float minArmorDamage = 0.1f;
|
||||
/** launch animation duration */
|
||||
public static final float launchDuration = 140f;
|
||||
/** size of tiles in units */
|
||||
public static final int tilesize = 8;
|
||||
/** size of one tile payload (^2) */
|
||||
public static final float tilePayload = tilesize * tilesize;
|
||||
/** tile used in certain situations, instead of null */
|
||||
public static Tile emptyTile;
|
||||
/** for map generator dialog */
|
||||
public static boolean updateEditorOnChange = false;
|
||||
/** size of tiles in units */
|
||||
public static final int tilesize = 8;
|
||||
/** all choosable player colors in join/host dialog */
|
||||
public static final Color[] playerColors = {
|
||||
Color.valueOf("82759a"),
|
||||
@@ -199,8 +205,7 @@ public class Vars implements Loadable{
|
||||
public static NetServer netServer;
|
||||
public static NetClient netClient;
|
||||
|
||||
public static
|
||||
Player player;
|
||||
public static Player player;
|
||||
|
||||
@Override
|
||||
public void loadAsync(){
|
||||
|
||||
@@ -6,7 +6,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.world;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Astar{
|
||||
public static final DistanceHeuristic manhattan = (x1, y1, x2, y2) -> Math.abs(x1 - x2) + Math.abs(y1 - y2);
|
||||
|
||||
@@ -63,6 +63,7 @@ public class BaseAI{
|
||||
int range = 150;
|
||||
|
||||
Position pos = randomPosition();
|
||||
|
||||
//when there are no random positions, do nothing.
|
||||
if(pos == null) return;
|
||||
|
||||
@@ -159,7 +160,10 @@ public class BaseAI{
|
||||
|
||||
private void tryWalls(){
|
||||
Block wall = Blocks.copperWall;
|
||||
Tile spawn = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core().tile : data.team.core().tile;
|
||||
Building spawnt = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core() : data.team.core();
|
||||
Tile spawn = spawnt == null ? null : spawnt.tile;
|
||||
|
||||
if(spawn == null) return;
|
||||
|
||||
for(int wx = lastX; wx <= lastX + lastW; wx++){
|
||||
for(int wy = lastY; wy <= lastY + lastH; wy++){
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.ai;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
@@ -16,7 +17,7 @@ import mindustry.world.meta.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BaseRegistry{
|
||||
public Seq<BasePart> cores = new Seq<>();
|
||||
@@ -68,7 +69,7 @@ public class BaseRegistry{
|
||||
}
|
||||
schem.tiles.removeAll(s -> s.block.buildVisibility == BuildVisibility.sandboxOnly);
|
||||
|
||||
part.tier = schem.tiles.sumf(s -> s.block.buildCost / s.block.buildCostMultiplier);
|
||||
part.tier = schem.tiles.sumf(s -> Mathf.pow(s.block.buildCost / s.block.buildCostMultiplier, 1.2f));
|
||||
|
||||
if(part.core != null){
|
||||
cores.add(part);
|
||||
@@ -92,7 +93,7 @@ public class BaseRegistry{
|
||||
}
|
||||
}
|
||||
|
||||
cores.sort(Structs.comps(Structs.comparingFloat(b -> b.core.health), Structs.comparingFloat(b -> b.tier)));
|
||||
cores.sort(b -> b.tier);
|
||||
parts.sort();
|
||||
reqParts.each((key, arr) -> arr.sort());
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.EnumSet;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -33,11 +34,11 @@ public class BlockIndexer{
|
||||
/** Maps each team ID to a quarant. A quadrant is a grid of bits, where each bit is set if and only if there is a block of that team in that quadrant. */
|
||||
private GridBits[] structQuadrants;
|
||||
/** Stores all damaged tile entities by team. */
|
||||
private BuildingArray[] damagedTiles = new BuildingArray[Team.all.length];
|
||||
private ObjectSet<Building>[] damagedTiles = new ObjectSet[Team.all.length];
|
||||
/** All ores available on this map. */
|
||||
private ObjectSet<Item> allOres = new ObjectSet<>();
|
||||
/** Stores teams that are present here as tiles. */
|
||||
private Seq<Team> activeTeams = new Seq<>();
|
||||
private Seq<Team> activeTeams = new Seq<>(Team.class);
|
||||
/** Maps teams to a map of flagged tiles by flag. */
|
||||
private TileArray[][] flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
|
||||
/** Max units by team. */
|
||||
@@ -52,7 +53,7 @@ public class BlockIndexer{
|
||||
private Seq<Building> breturnArray = new Seq<>();
|
||||
|
||||
public BlockIndexer(){
|
||||
Events.on(BuildinghangeEvent.class, event -> {
|
||||
Events.on(TileChangeEvent.class, event -> {
|
||||
if(typeMap.get(event.tile.pos()) != null){
|
||||
TileIndex index = typeMap.get(event.tile.pos());
|
||||
for(BlockFlag flag : index.flags){
|
||||
@@ -70,9 +71,10 @@ public class BlockIndexer{
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
scanOres.clear();
|
||||
scanOres.addAll(Item.getAllOres());
|
||||
damagedTiles = new BuildingArray[Team.all.length];
|
||||
damagedTiles = new ObjectSet[Team.all.length];
|
||||
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
|
||||
unitCaps = new int[Team.all.length];
|
||||
activeTeams = new Seq<>(Team.class);
|
||||
|
||||
for(int i = 0; i < flagMap.length; i++){
|
||||
for(int j = 0; j < BlockFlag.all.length; j++){
|
||||
@@ -138,16 +140,16 @@ public class BlockIndexer{
|
||||
}
|
||||
|
||||
/** Returns all damaged tiles by team. */
|
||||
public BuildingArray getDamaged(Team team){
|
||||
returnArray.clear();
|
||||
public ObjectSet<Building> getDamaged(Team team){
|
||||
breturnArray.clear();
|
||||
|
||||
if(damagedTiles[team.id] == null){
|
||||
damagedTiles[team.id] = new BuildingArray();
|
||||
damagedTiles[team.id] = new ObjectSet<>();
|
||||
}
|
||||
|
||||
BuildingArray set = damagedTiles[team.id];
|
||||
ObjectSet<Building> set = damagedTiles[team.id];
|
||||
for(Building build : set){
|
||||
if((!build.isValid() || build.team != team || !build.damaged()) || build.block instanceof BuildBlock){
|
||||
if((!build.isValid() || build.team != team || !build.damaged()) || build.block instanceof ConstructBlock){
|
||||
breturnArray.add(build);
|
||||
}
|
||||
}
|
||||
@@ -164,6 +166,11 @@ public class BlockIndexer{
|
||||
return flagMap[team.id][type.ordinal()];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Tile findClosestFlag(float x, float y, Team team, BlockFlag flag){
|
||||
return Geometry.findClosest(x, y, getAllied(team, flag));
|
||||
}
|
||||
|
||||
public boolean eachBlock(Teamc team, float range, Boolf<Building> pred, Cons<Building> cons){
|
||||
return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons);
|
||||
}
|
||||
@@ -185,7 +192,7 @@ public class BlockIndexer{
|
||||
|
||||
if(other == null) continue;
|
||||
|
||||
if((other.team() == team || team == null) && pred.get(other) && intSet.add(other.pos())){
|
||||
if(other.team == team && pred.get(other) && intSet.add(other.pos())){
|
||||
cons.get(other);
|
||||
any = true;
|
||||
}
|
||||
@@ -212,15 +219,18 @@ public class BlockIndexer{
|
||||
}
|
||||
|
||||
public void notifyTileDamaged(Building entity){
|
||||
if(damagedTiles[entity.team().id] == null){
|
||||
damagedTiles[entity.team().id] = new BuildingArray();
|
||||
if(damagedTiles[entity.team.id] == null){
|
||||
damagedTiles[entity.team.id] = new ObjectSet<>();
|
||||
}
|
||||
|
||||
damagedTiles[entity.team().id].add(entity);
|
||||
damagedTiles[entity.team.id].add(entity);
|
||||
}
|
||||
|
||||
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
|
||||
for(Team enemy : team.enemies()){
|
||||
for(int i = 0; i < activeTeams.size; i++){
|
||||
Team enemy = activeTeams.items[i];
|
||||
|
||||
if(enemy == team) continue;
|
||||
|
||||
Building entity = indexer.findTile(enemy, x, y, range, pred, true);
|
||||
if(entity != null){
|
||||
@@ -251,15 +261,15 @@ public class BlockIndexer{
|
||||
|
||||
if(e == null) continue;
|
||||
|
||||
if(e.team() != team || !pred.get(e) || !e.block().targetable)
|
||||
if(e.team != team || !pred.get(e) || !e.block.targetable)
|
||||
continue;
|
||||
|
||||
float ndst = e.dst2(x, y);
|
||||
if(ndst < range2 && (closest == null ||
|
||||
//this one is closer, and it is at least of equal priority
|
||||
(ndst < dst && (!usePriority || closest.block().priority.ordinal() <= e.block().priority.ordinal())) ||
|
||||
(ndst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
|
||||
//priority is used, and new block has higher priority regardless of range
|
||||
(usePriority && closest.block().priority.ordinal() < e.block().priority.ordinal()))){
|
||||
(usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
|
||||
dst = ndst;
|
||||
closest = e;
|
||||
}
|
||||
@@ -390,7 +400,7 @@ public class BlockIndexer{
|
||||
for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){
|
||||
Building result = world.build(x, y);
|
||||
//when a targetable block is found, mark this quadrant as occupied and stop searching
|
||||
if(result != null && result.team() == team){
|
||||
if(result != null && result.team == team){
|
||||
bits.set(quadrantX, quadrantY);
|
||||
break outer;
|
||||
}
|
||||
@@ -472,35 +482,4 @@ public class BlockIndexer{
|
||||
return tiles.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO copy-pasted code, generics would be nice here
|
||||
public static class BuildingArray implements Iterable<Building>{
|
||||
private Seq<Building> tiles = new Seq<>(false, 16);
|
||||
private IntSet contained = new IntSet();
|
||||
|
||||
public void add(Building tile){
|
||||
if(contained.add(tile.pos())){
|
||||
tiles.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Building tile){
|
||||
if(contained.remove(tile.pos())){
|
||||
tiles.remove(tile);
|
||||
}
|
||||
}
|
||||
|
||||
public int size(){
|
||||
return tiles.size;
|
||||
}
|
||||
|
||||
public Building first(){
|
||||
return tiles.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Building> iterator(){
|
||||
return tiles.iterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,52 +23,109 @@ public class Pathfinder implements Runnable{
|
||||
private static final int impassable = -1;
|
||||
private static final int fieldTimeout = 1000 * 60 * 2;
|
||||
|
||||
public static final int
|
||||
fieldCore = 0,
|
||||
fieldRally = 1;
|
||||
|
||||
public static final Seq<Prov<Flowfield>> fieldTypes = Seq.with(
|
||||
EnemyCoreField::new,
|
||||
RallyField::new
|
||||
);
|
||||
|
||||
public static final int
|
||||
costGround = 0,
|
||||
costLegs = 1,
|
||||
costWater = 2;
|
||||
|
||||
public static final Seq<PathCost> costTypes = Seq.with(
|
||||
//ground
|
||||
(team, tile) -> (PathTile.team(tile) == team.id || PathTile.team(tile) == 0) && PathTile.solid(tile) ? impassable : 1 +
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0) +
|
||||
(PathTile.nearLiquid(tile) ? 6 : 0) +
|
||||
(PathTile.deep(tile) ? 6000 : 0) +
|
||||
(PathTile.damages(tile) ? 30 : 0),
|
||||
|
||||
//legs
|
||||
(team, tile) -> PathTile.legSolid(tile) ? impassable : 1 +
|
||||
(PathTile.solid(tile) ? 5 : 0),
|
||||
|
||||
//water
|
||||
(team, tile) -> PathTile.solid(tile) || !PathTile.liquid(tile) ? 200 : 2 +
|
||||
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
|
||||
(PathTile.deep(tile) ? -1 : 0) +
|
||||
(PathTile.damages(tile) ? 35 : 0)
|
||||
);
|
||||
|
||||
//maps team, cost, type to flow field
|
||||
Flowfield[][][] cache;
|
||||
|
||||
/** tile data, see PathTileStruct */
|
||||
private int[][] tiles;
|
||||
int[][] tiles = new int[0][0];
|
||||
/** unordered array of path data for iteration only. DO NOT iterate or access this in the main thread. */
|
||||
private Seq<Flowfield> threadList = new Seq<>(), mainList = new Seq<>();
|
||||
/** Maps team ID and target to to a flowfield.*/
|
||||
private ObjectMap<PathTarget, Flowfield>[] fieldMap = new ObjectMap[Team.all.length];
|
||||
/** Used field maps. */
|
||||
private ObjectSet<PathTarget>[] fieldMapUsed = new ObjectSet[Team.all.length];
|
||||
Seq<Flowfield> threadList = new Seq<>(), mainList = new Seq<>();
|
||||
/** handles task scheduling on the update thread. */
|
||||
private TaskQueue queue = new TaskQueue();
|
||||
/** Stores path target for a position. Main thread only.*/
|
||||
private ObjectMap<Position, PathTarget> targetCache = new ObjectMap<>();
|
||||
TaskQueue queue = new TaskQueue();
|
||||
/** Current pathfinding thread */
|
||||
private @Nullable Thread thread;
|
||||
private IntSeq tmpArray = new IntSeq();
|
||||
@Nullable Thread thread;
|
||||
IntSeq tmpArray = new IntSeq();
|
||||
|
||||
public Pathfinder(){
|
||||
clearCache();
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
stop();
|
||||
|
||||
//reset and update internal tile array
|
||||
tiles = new int[world.width()][world.height()];
|
||||
fieldMap = new ObjectMap[Team.all.length];
|
||||
fieldMapUsed = new ObjectSet[Team.all.length];
|
||||
targetCache = new ObjectMap<>();
|
||||
threadList = new Seq<>();
|
||||
mainList = new Seq<>();
|
||||
clearCache();
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
tiles[tile.x][tile.y] = packTile(tile);
|
||||
}
|
||||
|
||||
//special preset which may help speed things up; this is optional
|
||||
preloadPath(state.rules.waveTeam, FlagTarget.enemyCores);
|
||||
preloadPath(getField(state.rules.waveTeam, costGround, fieldCore));
|
||||
|
||||
start();
|
||||
});
|
||||
|
||||
Events.on(ResetEvent.class, event -> stop());
|
||||
|
||||
Events.on(BuildinghangeEvent.class, event -> updateTile(event.tile));
|
||||
Events.on(TileChangeEvent.class, event -> updateTile(event.tile));
|
||||
}
|
||||
|
||||
private void clearCache(){
|
||||
cache = new Flowfield[256][5][5];
|
||||
}
|
||||
|
||||
/** Packs a tile into its internal representation. */
|
||||
private int packTile(Tile tile){
|
||||
return PathTile.get(tile.cost, (byte)tile.getTeamID(), !tile.solid() && tile.floor().drownTime <= 0f, !tile.solid() && tile.floor().isLiquid);
|
||||
boolean nearLiquid = false, nearSolid = false, nearGround = false;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Tile other = tile.getNearby(i);
|
||||
if(other != null){
|
||||
if(other.floor().isLiquid) nearLiquid = true;
|
||||
if(other.solid()) nearSolid = true;
|
||||
if(!other.floor().isLiquid) nearGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
return PathTile.get(
|
||||
tile.build == null ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
||||
tile.getTeamID(),
|
||||
tile.solid(),
|
||||
tile.floor().isLiquid,
|
||||
tile.staticDarkness() >= 2,
|
||||
nearLiquid,
|
||||
nearGround,
|
||||
nearSolid,
|
||||
tile.floor().isDeep(),
|
||||
tile.floor().damageTaken > 0.00001f
|
||||
);
|
||||
}
|
||||
|
||||
/** Starts or restarts the pathfinding thread. */
|
||||
@@ -104,7 +161,7 @@ public class Pathfinder implements Runnable{
|
||||
if(path != null){
|
||||
synchronized(path.targets){
|
||||
path.targets.clear();
|
||||
path.target.getPositions(path.team, path.targets);
|
||||
path.getPositions(path.targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,18 +188,19 @@ public class Pathfinder implements Runnable{
|
||||
updateFrontier(data, maxUpdate / threadList.size);
|
||||
|
||||
//remove flowfields that have 'timed out' so they can be garbage collected and no longer waste space
|
||||
if(data.target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
|
||||
if(data.refreshRate > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
|
||||
//make sure it doesn't get removed twice
|
||||
data.lastUpdateTime = Time.millis();
|
||||
|
||||
Team team = data.team;
|
||||
|
||||
Core.app.post(() -> {
|
||||
//TODO ?????
|
||||
//remove its used state
|
||||
if(fieldMap[team.id] != null){
|
||||
fieldMap[team.id].remove(data.target);
|
||||
fieldMapUsed[team.id].remove(data.target);
|
||||
}
|
||||
//if(fieldMap[team.id] != null){
|
||||
// fieldMap[team.id].remove(data.target);
|
||||
// fieldMapUsed[team.id].remove(data.target);
|
||||
//}
|
||||
//remove from main thread list
|
||||
mainList.remove(data);
|
||||
});
|
||||
@@ -167,51 +225,49 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Tile getTargetTile(Tile tile, Team team, Position target){
|
||||
return getTargetTile(tile, team, getTarget(target));
|
||||
public Flowfield getField(Team team, int costType, int fieldType){
|
||||
if(cache[team.id][costType][fieldType] == null){
|
||||
Flowfield field = fieldTypes.get(fieldType).get();
|
||||
field.team = team;
|
||||
field.cost = costTypes.get(costType);
|
||||
field.targets.clear();
|
||||
field.getPositions(field.targets);
|
||||
|
||||
cache[team.id][costType][fieldType] = field;
|
||||
queue.post(() -> registerPath(field));
|
||||
}
|
||||
return cache[team.id][costType][fieldType];
|
||||
}
|
||||
|
||||
/** Gets next tile to travel to. Main thread only. */
|
||||
public @Nullable Tile getTargetTile(Tile tile, Team team, PathTarget target){
|
||||
public @Nullable Tile getTargetTile(Tile tile, Flowfield path){
|
||||
if(tile == null) return null;
|
||||
|
||||
if(fieldMap[team.id] == null){
|
||||
fieldMap[team.id] = new ObjectMap<>();
|
||||
fieldMapUsed[team.id] = new ObjectSet<>();
|
||||
}
|
||||
|
||||
Flowfield data = fieldMap[team.id].get(target);
|
||||
|
||||
if(data == null){
|
||||
//if this combination is not found, create it on request
|
||||
if(fieldMapUsed[team.id].add(target)){
|
||||
//grab targets since this is run on main thread
|
||||
IntSeq targets = target.getPositions(team, new IntSeq());
|
||||
queue.post(() -> createPath(team, target, targets));
|
||||
}
|
||||
//uninitialized flowfields are not applicable
|
||||
if(!path.initialized){
|
||||
return tile;
|
||||
}
|
||||
|
||||
//if refresh rate is positive, queue a refresh
|
||||
if(target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > target.refreshRate()){
|
||||
data.lastUpdateTime = Time.millis();
|
||||
if(path.refreshRate > 0 && Time.timeSinceMillis(path.lastUpdateTime) > path.refreshRate){
|
||||
path.lastUpdateTime = Time.millis();
|
||||
|
||||
tmpArray.clear();
|
||||
data.target.getPositions(data.team, tmpArray);
|
||||
path.getPositions(tmpArray);
|
||||
|
||||
synchronized(data.targets){
|
||||
synchronized(path.targets){
|
||||
//make sure the position actually changed
|
||||
if(!(data.targets.size == 1 && tmpArray.size == 1 && data.targets.first() == tmpArray.first())){
|
||||
data.targets.clear();
|
||||
data.target.getPositions(data.team, data.targets);
|
||||
if(!(path.targets.size == 1 && tmpArray.size == 1 && path.targets.first() == tmpArray.first())){
|
||||
path.targets.clear();
|
||||
path.getPositions(path.targets);
|
||||
|
||||
//queue an update
|
||||
queue.post(() -> updateTargets(data));
|
||||
queue.post(() -> updateTargets(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[][] values = data.weights;
|
||||
int[][] values = path.weights;
|
||||
int value = values[tile.x][tile.y];
|
||||
|
||||
Tile current = null;
|
||||
@@ -222,8 +278,8 @@ public class Pathfinder implements Runnable{
|
||||
Tile other = world.tile(dx, dy);
|
||||
if(other == null) continue;
|
||||
|
||||
if(values[dx][dy] < value && (current == null || values[dx][dy] < tl) && !other.solid() && other.floor().drownTime <= 0 &&
|
||||
!(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap
|
||||
if(values[dx][dy] < value && (current == null || values[dx][dy] < tl) && path.passable(dx, dy) &&
|
||||
!(point.x != 0 && point.y != 0 && (!path.passable(tile.x + point.x, tile.y) || !path.passable(tile.x, tile.y + point.y)))){ //diagonal corner trap
|
||||
current = other;
|
||||
tl = values[dx][dy];
|
||||
}
|
||||
@@ -234,16 +290,6 @@ public class Pathfinder implements Runnable{
|
||||
return current;
|
||||
}
|
||||
|
||||
private PathTarget getTarget(Position position){
|
||||
return targetCache.get(position, () -> new PositionTarget(position));
|
||||
}
|
||||
|
||||
/** @return whether a tile can be passed through by this team. Pathfinding thread only. */
|
||||
private boolean passable(int x, int y, Team team){
|
||||
int tile = tiles[x][y];
|
||||
return PathTile.passable(tile) || (PathTile.team(tile) != team.id && PathTile.team(tile) != (int)Team.derelict.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the frontier, increments the search and sets up all flow sources.
|
||||
* This only occurs for active teams.
|
||||
@@ -259,10 +305,8 @@ public class Pathfinder implements Runnable{
|
||||
return;
|
||||
}
|
||||
|
||||
//assign impassability to the tile
|
||||
if(!passable(x, y, path.team)){
|
||||
path.weights[x][y] = impassable;
|
||||
}
|
||||
//update cost of the tile TODO maybe only update the cost when it's not passable
|
||||
path.weights[x][y] = path.cost.getCost(path.team, tiles[x][y]);
|
||||
|
||||
//clear frontier to prevent contamination
|
||||
path.frontier.clear();
|
||||
@@ -289,34 +333,33 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
}
|
||||
|
||||
private void preloadPath(Team team, PathTarget target){
|
||||
updateFrontier(createPath(team, target, target.getPositions(team, new IntSeq())), -1);
|
||||
private void preloadPath(Flowfield path){
|
||||
path.targets.clear();
|
||||
path.getPositions(path.targets);
|
||||
registerPath(path);
|
||||
updateFrontier(path, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO wrong docs
|
||||
* Created a new flowfield that aims to get to a certain target for a certain team.
|
||||
* Pathfinding thread only.
|
||||
*/
|
||||
private Flowfield createPath(Team team, PathTarget target, IntSeq targets){
|
||||
Flowfield path = new Flowfield(team, target, world.width(), world.height());
|
||||
private void registerPath(Flowfield path){
|
||||
path.lastUpdateTime = Time.millis();
|
||||
path.setup(tiles.length, tiles[0].length);
|
||||
|
||||
threadList.add(path);
|
||||
|
||||
//add to main thread's list of paths
|
||||
Core.app.post(() -> {
|
||||
mainList.add(path);
|
||||
if(fieldMap[team.id] != null){
|
||||
fieldMap[team.id].put(target, path);
|
||||
}
|
||||
//TODO
|
||||
//if(fieldMap[team.id] != null){
|
||||
// fieldMap[team.id].put(target, path);
|
||||
//}
|
||||
});
|
||||
|
||||
//grab targets from passed array
|
||||
synchronized(path.targets){
|
||||
path.targets.clear();
|
||||
path.targets.addAll(targets);
|
||||
}
|
||||
|
||||
//fill with impassables by default
|
||||
for(int x = 0; x < world.width(); x++){
|
||||
for(int y = 0; y < world.height(); y++){
|
||||
@@ -330,8 +373,6 @@ public class Pathfinder implements Runnable{
|
||||
path.weights[Point2.x(pos)][Point2.y(pos)] = 0;
|
||||
path.frontier.addFirst(pos);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/** Update the frontier for a path. Pathfinding thread only. */
|
||||
@@ -353,12 +394,14 @@ public class Pathfinder implements Runnable{
|
||||
for(Point2 point : Geometry.d4){
|
||||
|
||||
int dx = tile.x + point.x, dy = tile.y + point.y;
|
||||
Tile other = world.tile(dx, dy);
|
||||
|
||||
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search) && passable(dx, dy, path.team)){
|
||||
if(other.cost < 0) throw new IllegalArgumentException("Tile cost cannot be negative! " + other);
|
||||
if(dx < 0 || dy < 0 || dx >= tiles.length || dy >= tiles[0].length) continue;
|
||||
|
||||
int otherCost = path.cost.getCost(path.team, tiles[dx][dy]);
|
||||
|
||||
if((path.weights[dx][dy] > cost + otherCost || path.searches[dx][dy] < path.search) && otherCost != impassable){
|
||||
path.frontier.addFirst(Point2.pack(dx, dy));
|
||||
path.weights[dx][dy] = cost + other.cost;
|
||||
path.weights[dx][dy] = cost + otherCost;
|
||||
path.searches[dx][dy] = (short)path.search;
|
||||
}
|
||||
}
|
||||
@@ -366,9 +409,9 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
}
|
||||
|
||||
/** A path target defines a set of targets for a path. */
|
||||
public enum FlagTarget implements PathTarget{
|
||||
enemyCores((team, out) -> {
|
||||
public static class EnemyCoreField extends Flowfield{
|
||||
@Override
|
||||
protected void getPositions(IntSeq out){
|
||||
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
|
||||
out.add(other.pos());
|
||||
}
|
||||
@@ -379,98 +422,101 @@ public class Pathfinder implements Runnable{
|
||||
out.add(other.pos());
|
||||
}
|
||||
}
|
||||
}),
|
||||
rallyPoints((team, out) -> {
|
||||
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
|
||||
out.add(other.pos());
|
||||
}
|
||||
});
|
||||
|
||||
public static final FlagTarget[] all = values();
|
||||
|
||||
private final Cons2<Team, IntSeq> targeter;
|
||||
|
||||
FlagTarget(Cons2<Team, IntSeq> targeter){
|
||||
this.targeter = targeter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntSeq getPositions(Team team, IntSeq out){
|
||||
targeter.get(team, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refreshRate(){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PositionTarget implements PathTarget{
|
||||
public static class RallyField extends Flowfield{
|
||||
@Override
|
||||
protected void getPositions(IntSeq out){
|
||||
for(Tile other : indexer.getAllied(team, BlockFlag.rally)){
|
||||
out.add(other.pos());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PositionTarget extends Flowfield{
|
||||
public final Position position;
|
||||
|
||||
public PositionTarget(Position position){
|
||||
this.position = position;
|
||||
this.refreshRate = 900;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntSeq getPositions(Team team, IntSeq out){
|
||||
public void getPositions(IntSeq out){
|
||||
out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refreshRate(){
|
||||
return 900;
|
||||
}
|
||||
}
|
||||
|
||||
public interface PathTarget{
|
||||
/** Gets targets to pathfind towards. This must run on the main thread. */
|
||||
IntSeq getPositions(Team team, IntSeq out);
|
||||
/**
|
||||
* Data for a flow field to some set of destinations.
|
||||
* Concrete subclasses must specify a way to fetch costs and destinations.
|
||||
* */
|
||||
static abstract class Flowfield{
|
||||
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
||||
int refreshRate();
|
||||
}
|
||||
protected int refreshRate;
|
||||
/** Team this path is for. Set before using. */
|
||||
protected Team team = Team.derelict;
|
||||
/** Function for calculating path cost. Set before using. */
|
||||
protected PathCost cost = costTypes.get(costGround);
|
||||
|
||||
/** Data for a specific flow field to some set of destinations. */
|
||||
static class Flowfield{
|
||||
/** Team this path is for. */
|
||||
final Team team;
|
||||
/** Flag that is being targeted. */
|
||||
final PathTarget target;
|
||||
/** costs of getting to a specific tile */
|
||||
final int[][] weights;
|
||||
int[][] weights;
|
||||
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
||||
final int[][] searches;
|
||||
int[][] searches;
|
||||
/** search frontier, these are Pos objects */
|
||||
final IntQueue frontier = new IntQueue();
|
||||
IntQueue frontier = new IntQueue();
|
||||
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
|
||||
final IntSeq targets = new IntSeq();
|
||||
/** current search ID */
|
||||
int search = 1;
|
||||
/** last updated time */
|
||||
long lastUpdateTime;
|
||||
/** whether this flow field is ready to be used */
|
||||
boolean initialized;
|
||||
|
||||
Flowfield(Team team, PathTarget target, int width, int height){
|
||||
this.team = team;
|
||||
this.target = target;
|
||||
|
||||
void setup(int width, int height){
|
||||
this.weights = new int[width][height];
|
||||
this.searches = new int[width][height];
|
||||
this.frontier.ensureCapacity((width + height) * 3);
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
protected boolean passable(int x, int y){
|
||||
return cost.getCost(team, pathfinder.tiles[x][y]) != impassable;
|
||||
}
|
||||
|
||||
/** Gets targets to pathfind towards. This must run on the main thread. */
|
||||
protected abstract void getPositions(IntSeq out);
|
||||
}
|
||||
|
||||
interface PathCost{
|
||||
int getCost(Team traversing, int tile);
|
||||
}
|
||||
|
||||
/** Holds a copy of tile data for a specific tile position. */
|
||||
@Struct
|
||||
class PathTileStruct{
|
||||
//traversal cost
|
||||
short cost;
|
||||
//scaled block health
|
||||
@StructField(8) int health;
|
||||
//team of block, if applicable (0 by default)
|
||||
byte team;
|
||||
//whether it's viable to pass this block
|
||||
boolean passable;
|
||||
//whether it's viable to pass this block through water
|
||||
boolean passableWater;
|
||||
@StructField(8) int team;
|
||||
//general solid state
|
||||
boolean solid;
|
||||
//whether this block is a liquid that boats can move on
|
||||
boolean liquid;
|
||||
//whether this block is solid for leg units that can move over some solid blocks
|
||||
boolean legSolid;
|
||||
//whether this block is near liquids
|
||||
boolean nearLiquid;
|
||||
//whether this block is near a solid floor tile
|
||||
boolean nearGround;
|
||||
//whether this block is near a solid object
|
||||
boolean nearSolid;
|
||||
//whether this block is deep / drownable
|
||||
boolean deep;
|
||||
//whether the floor damages
|
||||
boolean damages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,26 @@ package mindustry.ai;
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class WaveSpawner{
|
||||
private static final float margin = 40f, coreMargin = tilesize * 3; //how far away from the edge flying units spawn
|
||||
private static final float margin = 40f, coreMargin = tilesize * 2f, maxSteps = 30;
|
||||
|
||||
private Seq<Tile> spawns = new Seq<>();
|
||||
private boolean spawning = false;
|
||||
private boolean any = false;
|
||||
|
||||
public WaveSpawner(){
|
||||
Events.on(WorldLoadEvent.class, e -> reset());
|
||||
@@ -41,6 +45,8 @@ public class WaveSpawner{
|
||||
spawning = true;
|
||||
|
||||
for(SpawnGroup group : state.rules.spawns){
|
||||
if(group.type == null) continue;
|
||||
|
||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
||||
|
||||
if(group.type.flying){
|
||||
@@ -87,15 +93,41 @@ public class WaveSpawner{
|
||||
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
|
||||
Building firstCore = state.teams.playerCores().first();
|
||||
for(Building core : state.rules.waveTeam.cores()){
|
||||
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block().size * tilesize);
|
||||
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
|
||||
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block.size * tilesize /2f * Mathf.sqrt2);
|
||||
|
||||
boolean valid = false;
|
||||
int steps = 0;
|
||||
|
||||
//keep moving forward until the max step amount is reached
|
||||
while(steps++ < maxSteps){
|
||||
int tx = world.toTile(core.x + Tmp.v1.x), ty = world.toTile(core.y + Tmp.v1.y);
|
||||
any = false;
|
||||
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
|
||||
if(world.solid(x, y)){
|
||||
any = true;
|
||||
}
|
||||
});
|
||||
|
||||
//nothing is in the way, spawn it
|
||||
if(!any){
|
||||
valid = true;
|
||||
break;
|
||||
}else{
|
||||
//make the vector longer
|
||||
Tmp.v1.setLength(Tmp.v1.len() + tilesize*1.1f);
|
||||
}
|
||||
}
|
||||
|
||||
if(valid){
|
||||
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void eachFlyerSpawn(Floatc2 cons){
|
||||
for(Tile tile : spawns){
|
||||
float angle = Angles.angle(tile.x, tile.y, world.width() / 2, world.height() / 2);
|
||||
float angle = Angles.angle(world.width() / 2, world.height() / 2, tile.x, tile.y);
|
||||
|
||||
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
|
||||
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
|
||||
@@ -125,14 +157,18 @@ public class WaveSpawner{
|
||||
}
|
||||
|
||||
private void spawnEffect(Unit unit){
|
||||
Fx.unitSpawn.at(unit.x(), unit.y(), 0f, unit);
|
||||
Time.run(30f, () -> {
|
||||
unit.add();
|
||||
Fx.spawn.at(unit);
|
||||
});
|
||||
Call.spawnEffect(unit.x, unit.y, unit.type());
|
||||
Time.run(30f, unit::add);
|
||||
}
|
||||
|
||||
private interface SpawnConsumer{
|
||||
void accept(float x, float y, boolean shockwave);
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
public static void spawnEffect(float x, float y, UnitType type){
|
||||
Fx.unitSpawn.at(x, y, 0f, type);
|
||||
|
||||
Time.run(30f, () -> Fx.spawn.at(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,6 @@ import arc.math.geom.*;
|
||||
public interface FormationMember{
|
||||
/** Returns the target location of this formation member. */
|
||||
Vec3 formationPos();
|
||||
|
||||
float formationSize();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import arc.math.geom.*;
|
||||
*/
|
||||
public abstract class FormationPattern{
|
||||
public int slots;
|
||||
/** Spacing between members. */
|
||||
public float spacing = 20f;
|
||||
|
||||
/** Returns the location of the given slot index. */
|
||||
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -5,7 +5,6 @@ import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class SquareFormation extends FormationPattern{
|
||||
public float spacing = 20;
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 out, int slot){
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.BuildBlock.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BuilderAI extends AIController{
|
||||
float buildRadius = 1500;
|
||||
boolean found = false;
|
||||
@Nullable Builderc following;
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
@@ -22,12 +24,29 @@ public class BuilderAI extends AIController{
|
||||
builder.lookAt(builder.vel().angle());
|
||||
}
|
||||
|
||||
//approach request if building
|
||||
builder.updateBuilding(true);
|
||||
|
||||
if(following != null){
|
||||
//try to follow and mimic someone
|
||||
|
||||
//validate follower
|
||||
if(!following.isValid() || !following.activelyBuilding()){
|
||||
following = null;
|
||||
builder.plans().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
//set to follower's first build plan, whatever that is
|
||||
builder.plans().clear();
|
||||
builder.plans().addFirst(following.buildPlan());
|
||||
}
|
||||
|
||||
if(builder.buildPlan() != null){
|
||||
//approach request if building
|
||||
BuildPlan req = builder.buildPlan();
|
||||
|
||||
boolean valid =
|
||||
(req.tile().build instanceof BuildEntity && req.tile().<BuildEntity>bc().cblock == req.block) ||
|
||||
(req.tile().build instanceof ConstructBuild && req.tile().<ConstructBuild>bc().cblock == req.block) ||
|
||||
(req.breaking ?
|
||||
Build.validBreak(unit.team(), req.x, req.y) :
|
||||
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation));
|
||||
@@ -40,9 +59,36 @@ public class BuilderAI extends AIController{
|
||||
builder.plans().removeFirst();
|
||||
}
|
||||
}else{
|
||||
|
||||
//follow someone and help them build
|
||||
if(timer.get(timerTarget2, 60f)){
|
||||
found = false;
|
||||
|
||||
Units.nearby(unit.team, unit.x, unit.y, buildRadius, u -> {
|
||||
if(found) return;
|
||||
|
||||
if(u instanceof Builderc && u != unit && ((Builderc)u).activelyBuilding()){
|
||||
Builderc b = (Builderc)u;
|
||||
BuildPlan plan = b.buildPlan();
|
||||
|
||||
Building build = world.build(plan.x, plan.y);
|
||||
if(build instanceof ConstructBuild){
|
||||
ConstructBuild cons = (ConstructBuild)build;
|
||||
float dist = Math.min(cons.dst(unit) - buildingRange, 0);
|
||||
|
||||
//make sure you can reach the request in time
|
||||
if(dist / unit.type().speed < cons.buildCost * 0.9f){
|
||||
following = b;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//find new request
|
||||
if(!unit.team().data().blocks.isEmpty()){
|
||||
Queue<BlockPlan> blocks = unit.team().data().blocks;
|
||||
if(!unit.team.data().blocks.isEmpty() && following == null && timer.get(timerTarget3, 60 * 2f)){
|
||||
Queue<BlockPlan> blocks = unit.team.data().blocks;
|
||||
BlockPlan block = blocks.first();
|
||||
|
||||
//check if it's already been placed
|
||||
@@ -50,32 +96,14 @@ public class BuilderAI extends AIController{
|
||||
blocks.removeFirst();
|
||||
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
|
||||
//add build request.
|
||||
BuildPlan req = new BuildPlan(block.x, block.y, block.rotation, content.block(block.block));
|
||||
if(block.config != null){
|
||||
req.configure(block.config);
|
||||
}
|
||||
builder.addBuild(req);
|
||||
builder.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
|
||||
}else{
|
||||
//shift head of queue to tail, try something else next time
|
||||
blocks.removeFirst();
|
||||
blocks.addLast(block);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected void moveTo(Position target, float circleLength){
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
|
||||
|
||||
vec.setLength(unit.type().speed * Time.delta * length);
|
||||
if(length < -0.5f){
|
||||
vec.rotate(180f);
|
||||
}else if(length < 0){
|
||||
vec.setZero();
|
||||
}
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,50 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class FlyingAI extends AIController{
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
if(unit.moving()){
|
||||
unit.rotation(unit.vel().angle());
|
||||
}
|
||||
|
||||
if(unit.isFlying()){
|
||||
unit.wobble();
|
||||
}
|
||||
|
||||
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y())){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
targetClosest();
|
||||
|
||||
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
|
||||
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
|
||||
}
|
||||
|
||||
boolean shoot = false;
|
||||
|
||||
if(target != null && unit.hasWeapons()){
|
||||
public void updateMovement(){
|
||||
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
|
||||
if(unit.type().weapons.first().rotate){
|
||||
moveTo(unit.range() * 0.85f);
|
||||
moveTo(target, unit.range() * 0.8f);
|
||||
unit.lookAt(target);
|
||||
}else{
|
||||
attack(80f);
|
||||
}
|
||||
|
||||
shoot = unit.inRange(target);
|
||||
|
||||
if(shoot && unit.type().hasWeapons()){
|
||||
Vec2 to = Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed);
|
||||
unit.aim(to);
|
||||
attack(100f);
|
||||
}
|
||||
}
|
||||
|
||||
unit.controlWeapons(shoot, shoot);
|
||||
if(target == null && command() == UnitCommand.attack && state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
moveTo(getClosestSpawner(), state.rules.dropZoneRadius + 120f);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
moveTo(targetFlag(unit.x, unit.y, BlockFlag.rally, false), 60f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
Teamc result = target(x, y, range, air, ground);
|
||||
if(result != null) return result;
|
||||
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.producer, true);
|
||||
if(result != null) return result;
|
||||
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.turret, true);
|
||||
if(result != null) return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO clean up
|
||||
|
||||
protected void circle(float circleLength){
|
||||
circle(circleLength, unit.type().speed);
|
||||
}
|
||||
|
||||
protected void circle(float circleLength, float speed){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
if(vec.len() < circleLength){
|
||||
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
|
||||
}
|
||||
|
||||
vec.setLength(speed * Time.delta);
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
protected void moveTo(float circleLength){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
|
||||
|
||||
vec.setLength(unit.type().speed * Time.delta * length);
|
||||
if(length < -0.5f){
|
||||
vec.rotate(180f);
|
||||
}else if(length < 0){
|
||||
vec.setZero();
|
||||
}
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
protected void attack(float circleLength){
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
@@ -100,7 +57,7 @@ public class FlyingAI extends AIController{
|
||||
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
|
||||
}
|
||||
|
||||
vec.setLength(unit.type().speed * Time.delta);
|
||||
vec.setLength(unit.type().speed);
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.formations.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class FormationAI extends AIController implements FormationMember{
|
||||
public Unit leader;
|
||||
@@ -25,41 +26,56 @@ public class FormationAI extends AIController implements FormationMember{
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
UnitType type = unit.type();
|
||||
|
||||
if(leader.dead){
|
||||
unit.resetController();
|
||||
return;
|
||||
}
|
||||
|
||||
unit.controlWeapons(leader.isRotate(), leader.isShooting);
|
||||
if(unit.type().canBoost && unit.canPassOn()){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
||||
}
|
||||
|
||||
unit.controlWeapons(true, leader.isShooting);
|
||||
// unit.moveAt(Tmp.v1.set(deltaX, deltaY).limit(unit.type().speed));
|
||||
if(leader.isShooting){
|
||||
unit.aimLook(leader.aimX(), leader.aimY());
|
||||
}else{
|
||||
if(!leader.moving() || !unit.type().rotateShooting){
|
||||
if(unit.moving()){
|
||||
unit.lookAt(unit.vel.angle());
|
||||
}
|
||||
}else{
|
||||
unit.lookAt(leader.rotation);
|
||||
}
|
||||
|
||||
unit.aim(leader.aimX(), leader.aimY());
|
||||
|
||||
if(unit.type().rotateShooting){
|
||||
unit.lookAt(leader.aimX(), leader.aimY());
|
||||
}else if(unit.moving()){
|
||||
unit.lookAt(unit.vel.angle());
|
||||
}
|
||||
|
||||
Vec2 realtarget = vec.set(target);
|
||||
|
||||
if(unit.isGrounded() && Vars.world.raycast(unit.tileX(), unit.tileY(), leader.tileX(), leader.tileY(), Vars.world::solid)){
|
||||
realtarget.set(Vars.pathfinder.getTargetTile(unit.tileOn(), unit.team, leader));
|
||||
}
|
||||
float margin = 3f;
|
||||
|
||||
unit.moveAt(realtarget.sub(unit).limit(unit.type().speed));
|
||||
if(unit.dst(realtarget) <= margin){
|
||||
unit.vel.approachDelta(Vec2.ZERO, type.speed * type.accel / 2f);
|
||||
}else{
|
||||
unit.moveAt(realtarget.sub(unit).limit(type.speed));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Unit unit){
|
||||
if(formation != null){
|
||||
formation.removeMember(this);
|
||||
unit.resetController();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float formationSize(){
|
||||
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
|
||||
//TODO return formation size
|
||||
//eturn ((Commanderc)unit).formation().
|
||||
}
|
||||
return unit.hitSize * 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeingControlled(Unit player){
|
||||
return leader == player;
|
||||
|
||||
@@ -1,89 +1,86 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import mindustry.ai.Pathfinder.*;
|
||||
import arc.math.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.pathfinder;
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class GroundAI extends AIController{
|
||||
//static final float commandCooldown = 60f * 10;
|
||||
//float commandTimer = 60*3;
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
|
||||
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
targetClosest();
|
||||
}
|
||||
public void updateMovement(){
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
if(core != null){
|
||||
if(unit.within(core,unit.range() / 1.1f)){
|
||||
target = core;
|
||||
if(core != null && unit.within(core, unit.range() / 1.1f + core.block.size * tilesize / 2f)){
|
||||
target = core;
|
||||
Arrays.fill(targets, core);
|
||||
}
|
||||
|
||||
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
|
||||
boolean move = true;
|
||||
|
||||
if(state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
Tile spawner = getClosestSpawner();
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
|
||||
}
|
||||
|
||||
if(!unit.within(core, unit.range() * 0.5f)){
|
||||
moveToCore(FlagTarget.enemyCores);
|
||||
if(move) moveTo(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
moveTo(Pathfinder.fieldRally);
|
||||
}
|
||||
}
|
||||
|
||||
boolean rotate = false, shoot = false;
|
||||
|
||||
if(!Units.invalidateTarget(target, unit, unit.range())){
|
||||
rotate = true;
|
||||
shoot = unit.within(target, unit.range());
|
||||
if(unit.type().canBoost && unit.tileOn() != null && !unit.tileOn().solid()){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
||||
}
|
||||
|
||||
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type().rotateShooting){
|
||||
if(unit.type().hasWeapons()){
|
||||
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||
unit.lookAt(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||
}
|
||||
}else if(unit.moving()){
|
||||
unit.lookAt(unit.vel().angle());
|
||||
}
|
||||
|
||||
unit.controlWeapons(rotate, shoot);
|
||||
//auto-command works but it's very buggy
|
||||
/*
|
||||
if(unit instanceof Commanderc){
|
||||
Commanderc c = (Commanderc)unit;
|
||||
//try to command when missing members
|
||||
if(c.controlling().size <= unit.type().commandLimit/2){
|
||||
commandTimer -= Time.delta;
|
||||
|
||||
if(commandTimer <= 0){
|
||||
c.commandNearby(new SquareFormation(), u -> !(u.controller() instanceof FormationAI) && !(u instanceof Commanderc));
|
||||
commandTimer = commandCooldown;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
protected void moveToCore(FlagTarget path){
|
||||
Tile tile = unit.tileOn();
|
||||
if(tile == null) return;
|
||||
Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path);
|
||||
|
||||
if(tile == targetTile) return;
|
||||
|
||||
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
|
||||
}
|
||||
|
||||
protected void moveAwayFromCore(){
|
||||
Team enemy = null;
|
||||
for(Team team : unit.team().enemies()){
|
||||
if(team.active()){
|
||||
enemy = team;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(enemy == null){
|
||||
for(Team team : unit.team().enemies()){
|
||||
enemy = team;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(enemy == null) return;
|
||||
protected void moveTo(int pathTarget){
|
||||
int costType = unit.pathType();
|
||||
|
||||
Tile tile = unit.tileOn();
|
||||
if(tile == null) return;
|
||||
Tile targetTile = pathfinder.getTargetTile(tile, enemy, FlagTarget.enemyCores);
|
||||
Building core = unit.closestCore();
|
||||
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
|
||||
|
||||
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
|
||||
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
|
||||
|
||||
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
|
||||
}
|
||||
|
||||
83
core/src/mindustry/ai/types/MinerAI.java
Normal file
83
core/src/mindustry/ai/types/MinerAI.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MinerAI extends AIController{
|
||||
boolean mining = true;
|
||||
Item targetItem;
|
||||
Tile ore;
|
||||
|
||||
@Override
|
||||
protected void updateMovement(){
|
||||
Building core = unit.closestCore();
|
||||
|
||||
if(!(unit instanceof Minerc) || core == null) return;
|
||||
|
||||
Minerc miner = (Minerc)unit;
|
||||
|
||||
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type().range)){
|
||||
miner.mineTile(null);
|
||||
}
|
||||
|
||||
if(mining){
|
||||
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
||||
|
||||
//core full of the target item, do nothing
|
||||
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
||||
unit.clearItem();
|
||||
miner.mineTile(null);
|
||||
return;
|
||||
}
|
||||
|
||||
//if inventory is full, drop it off.
|
||||
if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
|
||||
mining = false;
|
||||
}else{
|
||||
if(retarget() && targetItem != null){
|
||||
ore = indexer.findClosestOre(unit.x, unit.y, targetItem);
|
||||
}
|
||||
|
||||
if(ore != null){
|
||||
moveTo(ore, unit.type().range / 2f);
|
||||
|
||||
if(unit.within(ore, unit.type().range)){
|
||||
miner.mineTile(ore);
|
||||
}
|
||||
|
||||
if(ore.block() != Blocks.air){
|
||||
mining = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
miner.mineTile(null);
|
||||
|
||||
if(unit.stack.amount == 0){
|
||||
mining = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.within(core, unit.type().range)){
|
||||
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||
}
|
||||
|
||||
unit.clearItem();
|
||||
mining = true;
|
||||
}
|
||||
|
||||
circle(core, unit.type().range / 1.8f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTargeting(){
|
||||
}
|
||||
|
||||
}
|
||||
34
core/src/mindustry/ai/types/RepairAI.java
Normal file
34
core/src/mindustry/ai/types/RepairAI.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import mindustry.*;
|
||||
import mindustry.ai.Pathfinder.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class SuicideAI extends GroundAI{
|
||||
static boolean blockedByBlock;
|
||||
@@ -17,17 +19,17 @@ public class SuicideAI extends GroundAI{
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
targetClosest();
|
||||
target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
|
||||
}
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
boolean rotate = false, shoot = false;
|
||||
boolean rotate = false, shoot = false, moveToTarget = false;
|
||||
|
||||
if(!Units.invalidateTarget(target, unit, unit.range())){
|
||||
rotate = true;
|
||||
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
|
||||
(target instanceof Building ? ((Building)target).block().size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
||||
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
||||
|
||||
if(unit.type().hasWeapons()){
|
||||
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||
@@ -39,7 +41,7 @@ public class SuicideAI extends GroundAI{
|
||||
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
|
||||
Tile tile = Vars.world.tile(x, y);
|
||||
if(tile != null && tile.build == target) return false;
|
||||
if(tile != null && tile.build != null && tile.build.team() != unit.team()){
|
||||
if(tile != null && tile.build != null && tile.build.team != unit.team()){
|
||||
blockedByBlock = true;
|
||||
return true;
|
||||
}else{
|
||||
@@ -53,13 +55,22 @@ public class SuicideAI extends GroundAI{
|
||||
}
|
||||
|
||||
if(!blocked){
|
||||
moveToTarget = true;
|
||||
//move towards target directly
|
||||
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
|
||||
}
|
||||
|
||||
}else{
|
||||
if(core != null){
|
||||
moveToCore(FlagTarget.enemyCores);
|
||||
}
|
||||
|
||||
if(!moveToTarget){
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
moveTo(Pathfinder.fieldRally);
|
||||
}
|
||||
}else if(command() == UnitCommand.attack && core != null){
|
||||
moveTo(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(unit.moving()) unit.lookAt(unit.vel().angle());
|
||||
|
||||
@@ -7,7 +7,7 @@ import mindustry.game.EventType.*;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static mindustry.Vars.state;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class AsyncCore{
|
||||
//all processes to be executed each frame
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
package mindustry.async;
|
||||
|
||||
import arc.*;
|
||||
import arc.box2d.*;
|
||||
import arc.box2d.BodyDef.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.math.geom.QuadTree.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
import mindustry.async.PhysicsProcess.PhysicsWorld.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class PhysicsProcess implements AsyncProcess{
|
||||
private Physics physics;
|
||||
private static final int
|
||||
layers = 3,
|
||||
layerGround = 0,
|
||||
layerLegs = 1,
|
||||
layerFlying = 2;
|
||||
|
||||
private PhysicsWorld physics;
|
||||
private Seq<PhysicRef> refs = new Seq<>(false);
|
||||
private BodyDef def;
|
||||
|
||||
private EntityGroup<? extends Physicsc> group;
|
||||
private Filter flying = new Filter(){{
|
||||
maskBits = categoryBits = 2;
|
||||
}}, ground = new Filter(){{
|
||||
maskBits = categoryBits = 1;
|
||||
}};
|
||||
|
||||
public PhysicsProcess(){
|
||||
def = new BodyDef();
|
||||
def.type = BodyType.dynamicBody;
|
||||
|
||||
//currently only enabled for units
|
||||
group = Groups.unit;
|
||||
}
|
||||
//currently only enabled for units
|
||||
private EntityGroup<Unit> group = Groups.unit;
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
@@ -35,46 +28,39 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
//remove stale entities
|
||||
refs.removeAll(ref -> {
|
||||
if(!ref.entity.isAdded()){
|
||||
physics.destroyBody(ref.body);
|
||||
physics.remove(ref.body);
|
||||
ref.entity.physref(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
//find entities without bodies and assign them
|
||||
for(Physicsc entity : group){
|
||||
boolean grounded = entity.isGrounded();
|
||||
//find Unit without bodies and assign them
|
||||
for(Unit entity : group){
|
||||
|
||||
if(entity.physref() == null){
|
||||
//add bodies to entities that have none
|
||||
FixtureDef fd = new FixtureDef();
|
||||
fd.shape = new CircleShape(entity.hitSize() / 2f);
|
||||
fd.density = 5f;
|
||||
fd.restitution = 0.0f;
|
||||
fd.filter.maskBits = fd.filter.categoryBits = (grounded ? ground : flying).maskBits;
|
||||
|
||||
def.position.set(entity);
|
||||
|
||||
Body body = physics.createBody(def);
|
||||
body.createFixture(fd);
|
||||
PhysicsBody body = new PhysicsBody();
|
||||
body.x = entity.x();
|
||||
body.y = entity.y();
|
||||
body.mass = entity.mass();
|
||||
body.radius = entity.hitSize() / 2f;
|
||||
|
||||
PhysicRef ref = new PhysicRef(entity, body);
|
||||
refs.add(ref);
|
||||
|
||||
entity.physref(ref);
|
||||
|
||||
physics.add(body);
|
||||
}
|
||||
|
||||
//save last position
|
||||
PhysicRef ref = entity.physref();
|
||||
|
||||
if(ref.wasGround != grounded){
|
||||
//set correct filter
|
||||
ref.body.getFixtureList().first().setFilterData(grounded ? ground : flying);
|
||||
ref.wasGround = grounded;
|
||||
}
|
||||
|
||||
ref.velocity.set(entity.deltaX(), entity.deltaY());
|
||||
ref.position.set(entity);
|
||||
ref.body.layer =
|
||||
entity.type().allowLegStep ? layerLegs :
|
||||
entity.isGrounded() ? layerGround : layerFlying;
|
||||
ref.x = entity.x();
|
||||
ref.y = entity.y();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,26 +71,11 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
//get last position vectors before step
|
||||
for(PhysicRef ref : refs){
|
||||
//force set target position
|
||||
ref.body.setPosition(ref.position.x, ref.position.y);
|
||||
|
||||
//save last position for delta
|
||||
ref.lastPosition.set(ref.body.getPosition());
|
||||
|
||||
//write velocity
|
||||
ref.body.setLinearVelocity(ref.velocity);
|
||||
|
||||
ref.lastVelocity.set(ref.velocity);
|
||||
ref.body.x = ref.x;
|
||||
ref.body.y = ref.y;
|
||||
}
|
||||
|
||||
physics.step(Core.graphics.getDeltaTime(), 5, 8);
|
||||
|
||||
//get delta vectors
|
||||
for(PhysicRef ref : refs){
|
||||
//get delta vector
|
||||
ref.delta.set(ref.body.getPosition()).sub(ref.lastPosition);
|
||||
|
||||
ref.velocity.set(ref.body.getLinearVelocity());
|
||||
}
|
||||
physics.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,13 +86,8 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
for(PhysicRef ref : refs){
|
||||
Physicsc entity = ref.entity;
|
||||
|
||||
entity.move(ref.delta.x, ref.delta.y);
|
||||
|
||||
//save last position
|
||||
ref.position.set(entity);
|
||||
|
||||
//add delta velocity - this doesn't work very well yet
|
||||
//entity.vel().add(ref.velocity).sub(ref.lastVelocity);
|
||||
//move by delta
|
||||
entity.move(ref.body.x - ref.x, ref.body.y - ref.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +95,6 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
public void reset(){
|
||||
if(physics != null){
|
||||
refs.clear();
|
||||
physics.dispose();
|
||||
physics = null;
|
||||
}
|
||||
}
|
||||
@@ -138,18 +103,95 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
public void init(){
|
||||
reset();
|
||||
|
||||
physics = new Physics(new Vec2(), true);
|
||||
physics = new PhysicsWorld(Vars.world.getQuadBounds(new Rect()));
|
||||
}
|
||||
|
||||
public static class PhysicRef{
|
||||
Physicsc entity;
|
||||
Body body;
|
||||
boolean wasGround = true;
|
||||
Vec2 lastPosition = new Vec2(), delta = new Vec2(), velocity = new Vec2(), lastVelocity = new Vec2(), position = new Vec2();
|
||||
public Physicsc entity;
|
||||
public PhysicsBody body;
|
||||
public float x, y;
|
||||
|
||||
public PhysicRef(Physicsc entity, Body body){
|
||||
public PhysicRef(Physicsc entity, PhysicsBody body){
|
||||
this.entity = entity;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
//world for simulating physics in a different thread
|
||||
public static class PhysicsWorld{
|
||||
//how much to soften movement by
|
||||
private static final float scl = 1.25f;
|
||||
|
||||
private final QuadTree<PhysicsBody>[] trees = new QuadTree[layers];
|
||||
private final Seq<PhysicsBody> bodies = new Seq<>(false, 16, PhysicsBody.class);
|
||||
private final Seq<PhysicsBody> seq = new Seq<>(PhysicsBody.class);
|
||||
private final Rect rect = new Rect();
|
||||
private final Vec2 vec = new Vec2();
|
||||
|
||||
public PhysicsWorld(Rect bounds){
|
||||
for(int i = 0; i < layers; i++){
|
||||
trees[i] = new QuadTree<>(new Rect(bounds));
|
||||
}
|
||||
}
|
||||
|
||||
public void add(PhysicsBody body){
|
||||
bodies.add(body);
|
||||
}
|
||||
|
||||
public void remove(PhysicsBody body){
|
||||
bodies.remove(body);
|
||||
}
|
||||
|
||||
public void update(){
|
||||
for(int i = 0; i < layers; i++){
|
||||
trees[i].clear();
|
||||
}
|
||||
|
||||
for(int i = 0; i < bodies.size; i++){
|
||||
PhysicsBody body = bodies.items[i];
|
||||
body.collided = false;
|
||||
trees[body.layer].insert(body);
|
||||
}
|
||||
|
||||
for(int i = 0; i < bodies.size; i++){
|
||||
PhysicsBody body = bodies.items[i];
|
||||
body.hitbox(rect);
|
||||
|
||||
seq.size = 0;
|
||||
trees[body.layer].intersect(rect, seq);
|
||||
|
||||
for(int j = 0; j < seq.size; j++){
|
||||
PhysicsBody other = seq.items[j];
|
||||
|
||||
if(other == body || other.collided) continue;
|
||||
|
||||
float rs = body.radius + other.radius;
|
||||
float dst = Mathf.dst(body.x, body.y, other.x, other.y);
|
||||
|
||||
if(dst < rs){
|
||||
vec.set(body.x - other.x, body.y - other.y).setLength(rs - dst);
|
||||
float ms = body.mass + other.mass;
|
||||
float m1 = other.mass / ms, m2 = body.mass / ms;
|
||||
|
||||
body.x += vec.x * m1 / scl;
|
||||
body.y += vec.y * m1 / scl;
|
||||
other.x -= vec.x * m2 / scl;
|
||||
other.y -= vec.y * m2 / scl;
|
||||
}
|
||||
}
|
||||
body.collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PhysicsBody implements QuadTreeObject{
|
||||
public float x, y, radius, mass;
|
||||
public int layer = 0;
|
||||
public boolean collided = false;
|
||||
|
||||
@Override
|
||||
public void hitbox(Rect out){
|
||||
out.setCentered(x, y, radius * 2, radius * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ package mindustry.audio;
|
||||
|
||||
import arc.*;
|
||||
import arc.audio.*;
|
||||
import arc.struct.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
|
||||
public class LoopControl{
|
||||
|
||||
@@ -19,6 +19,7 @@ public class MusicControl{
|
||||
public Seq<Music> ambientMusic = Seq.with();
|
||||
/** darker music, used in times of conflict */
|
||||
public Seq<Music> darkMusic = Seq.with();
|
||||
|
||||
protected Music lastRandomPlayed;
|
||||
protected Interval timer = new Interval();
|
||||
protected @Nullable Music current;
|
||||
|
||||
@@ -18,6 +18,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.experimental.*;
|
||||
import mindustry.world.blocks.legacy.*;
|
||||
import mindustry.world.blocks.liquid.*;
|
||||
import mindustry.world.blocks.logic.*;
|
||||
import mindustry.world.blocks.power.*;
|
||||
import mindustry.world.blocks.production.*;
|
||||
import mindustry.world.blocks.sandbox.*;
|
||||
@@ -33,10 +34,10 @@ public class Blocks implements ContentList{
|
||||
public static Block
|
||||
|
||||
//environment
|
||||
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
|
||||
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
|
||||
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, sandBoulder, grass, salt,
|
||||
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
|
||||
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater,
|
||||
dacite, stoneWall, dirtWall, sporeWall, iceWall, daciteWall, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
|
||||
iceSnow, sandWater, darksandWater, duneWall, sandWall, moss, sporeMoss, shale, shaleWall, shaleBoulder, sandBoulder, daciteBoulder, grass, salt,
|
||||
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, basalt, magmarock, hotrock, snowWall, boulder, snowBoulder, saltWall,
|
||||
darkPanel1, darkPanel2, darkPanel3, darkPanel4, darkPanel5, darkPanel6, darkMetal,
|
||||
pebbles, tendrils,
|
||||
|
||||
@@ -48,7 +49,7 @@ public class Blocks implements ContentList{
|
||||
melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge,
|
||||
|
||||
//sandbox
|
||||
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, message, illuminator,
|
||||
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, illuminator,
|
||||
|
||||
//defense
|
||||
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
|
||||
@@ -63,7 +64,7 @@ public class Blocks implements ContentList{
|
||||
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
|
||||
|
||||
//power
|
||||
combustionGenerator, thermalGenerator, turbineGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
|
||||
combustionGenerator, thermalGenerator, steamGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor,
|
||||
impactReactor, battery, batteryLarge, powerNode, powerNodeLarge, surgeTower, diode,
|
||||
|
||||
//production
|
||||
@@ -73,15 +74,19 @@ public class Blocks implements ContentList{
|
||||
coreShard, coreFoundation, coreNucleus, vault, container, unloader,
|
||||
|
||||
//turrets
|
||||
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown, segment, parallax,
|
||||
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, foreshadow, spectre, meltdown, segment, parallax, tsunami,
|
||||
|
||||
//units
|
||||
commandCenter,
|
||||
groundFactory, airFactory, navalFactory,
|
||||
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
|
||||
repairPoint, resupplyPoint,
|
||||
|
||||
//logic
|
||||
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, largeLogicDisplay, logicDisplay, memoryCell, memoryBank,
|
||||
|
||||
//campaign
|
||||
launchPad, launchPadLarge, dataProcessor,
|
||||
launchPad, launchPadLarge,
|
||||
|
||||
//misc experimental
|
||||
blockForge, blockLoader, blockUnloader;
|
||||
@@ -96,13 +101,12 @@ public class Blocks implements ContentList{
|
||||
hasShadow = false;
|
||||
}
|
||||
|
||||
public void drawBase(Tile tile){}
|
||||
public void load(){}
|
||||
public void init(){}
|
||||
public boolean isHidden(){
|
||||
return true;
|
||||
}
|
||||
@Override public void drawBase(Tile tile){}
|
||||
@Override public void load(){}
|
||||
@Override public void init(){}
|
||||
@Override public boolean isHidden(){ return true; }
|
||||
|
||||
@Override
|
||||
public TextureRegion[] variantRegions(){
|
||||
if(variantRegions == null){
|
||||
variantRegions = new TextureRegion[]{Core.atlas.find("clear")};
|
||||
@@ -126,14 +130,15 @@ public class Blocks implements ContentList{
|
||||
|
||||
//Registers build blocks
|
||||
//no reference is needed here since they can be looked up by name later
|
||||
for(int i = 1; i <= BuildBlock.maxSize; i++){
|
||||
new BuildBlock(i);
|
||||
for(int i = 1; i <= ConstructBlock.maxSize; i++){
|
||||
new ConstructBlock(i);
|
||||
}
|
||||
|
||||
deepwater = new Floor("deepwater"){{
|
||||
speedMultiplier = 0.2f;
|
||||
variants = 0;
|
||||
liquidDrop = Liquids.water;
|
||||
liquidMultiplier = 1.5f;
|
||||
isLiquid = true;
|
||||
status = StatusEffects.wet;
|
||||
statusDuration = 120f;
|
||||
@@ -203,11 +208,14 @@ public class Blocks implements ContentList{
|
||||
liquidDrop = Liquids.slag;
|
||||
isLiquid = true;
|
||||
cacheLayer = CacheLayer.slag;
|
||||
attributes.set(Attribute.heat, 0.85f);
|
||||
|
||||
emitLight = true;
|
||||
lightRadius = 40f;
|
||||
lightColor = Color.orange.cpy().a(0.38f);
|
||||
}};
|
||||
|
||||
stone = new Floor("stone"){{
|
||||
|
||||
}};
|
||||
stone = new Floor("stone");
|
||||
|
||||
craters = new Floor("craters"){{
|
||||
variants = 3;
|
||||
@@ -218,14 +226,14 @@ public class Blocks implements ContentList{
|
||||
blendGroup = stone;
|
||||
}};
|
||||
|
||||
ignarock = new Floor("ignarock"){{
|
||||
attributes.set(Attribute.water, -0.1f);
|
||||
basalt = new Floor("basalt"){{
|
||||
attributes.set(Attribute.water, -0.25f);
|
||||
}};
|
||||
|
||||
hotrock = new Floor("hotrock"){{
|
||||
attributes.set(Attribute.heat, 0.5f);
|
||||
attributes.set(Attribute.water, -0.2f);
|
||||
blendGroup = ignarock;
|
||||
attributes.set(Attribute.water, -0.5f);
|
||||
blendGroup = basalt;
|
||||
|
||||
emitLight = true;
|
||||
lightRadius = 30f;
|
||||
@@ -234,40 +242,53 @@ public class Blocks implements ContentList{
|
||||
|
||||
magmarock = new Floor("magmarock"){{
|
||||
attributes.set(Attribute.heat, 0.75f);
|
||||
attributes.set(Attribute.water, -0.5f);
|
||||
attributes.set(Attribute.water, -0.75f);
|
||||
updateEffect = Fx.magmasmoke;
|
||||
blendGroup = ignarock;
|
||||
blendGroup = basalt;
|
||||
|
||||
emitLight = true;
|
||||
lightRadius = 60f;
|
||||
lightRadius = 50f;
|
||||
lightColor = Color.orange.cpy().a(0.3f);
|
||||
}};
|
||||
|
||||
sand = new Floor("sand"){{
|
||||
itemDrop = Items.sand;
|
||||
playerUnmineable = true;
|
||||
attributes.set(Attribute.oil, 0.7f);
|
||||
}};
|
||||
|
||||
darksand = new Floor("darksand"){{
|
||||
itemDrop = Items.sand;
|
||||
playerUnmineable = true;
|
||||
attributes.set(Attribute.oil, 1.5f);
|
||||
}};
|
||||
|
||||
dirt = new Floor("dirt");
|
||||
|
||||
mud = new Floor("mud"){{
|
||||
speedMultiplier = 0.6f;
|
||||
variants = 3;
|
||||
status = StatusEffects.muddy;
|
||||
statusDuration = 30f;
|
||||
attributes.set(Attribute.water, 1f);
|
||||
cacheLayer = CacheLayer.mud;
|
||||
albedo = 0.35f;
|
||||
}};
|
||||
|
||||
((ShallowLiquid)darksandTaintedWater).set(Blocks.taintedWater, Blocks.darksand);
|
||||
((ShallowLiquid)sandWater).set(Blocks.water, Blocks.sand);
|
||||
((ShallowLiquid)darksandWater).set(Blocks.water, Blocks.darksand);
|
||||
|
||||
holostone = new Floor("holostone"){{
|
||||
|
||||
}};
|
||||
dacite = new Floor("dacite");
|
||||
|
||||
grass = new Floor("grass"){{
|
||||
|
||||
attributes.set(Attribute.water, 0.1f);
|
||||
}};
|
||||
|
||||
salt = new Floor("salt"){{
|
||||
variants = 0;
|
||||
attributes.set(Attribute.water, -0.2f);
|
||||
attributes.set(Attribute.water, -0.25f);
|
||||
attributes.set(Attribute.oil, 0.3f);
|
||||
}};
|
||||
|
||||
snow = new Floor("snow"){{
|
||||
@@ -286,46 +307,51 @@ public class Blocks implements ContentList{
|
||||
attributes.set(Attribute.water, 0.3f);
|
||||
}};
|
||||
|
||||
cliffs = new StaticWall("cliffs"){{
|
||||
variants = 1;
|
||||
fillsTile = false;
|
||||
}};
|
||||
|
||||
rocks = new StaticWall("rocks"){{
|
||||
stoneWall = new StaticWall("stone-wall"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
sporerocks = new StaticWall("sporerocks"){{
|
||||
sporeWall = new StaticWall("spore-wall"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
rock = new Rock("rock"){{
|
||||
boulder = new Boulder("boulder"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
snowrock = new Rock("snowrock"){{
|
||||
snowBoulder = new Boulder("snow-boulder"){{
|
||||
variants = 2;
|
||||
snow.asFloor().decoration = ice.asFloor().decoration = iceSnow.asFloor().decoration = this;
|
||||
}};
|
||||
|
||||
dirtWall = new StaticWall("dirt-wall"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
icerocks = new StaticWall("icerocks"){{
|
||||
daciteWall = new StaticWall("dacite-wall"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
iceWall = new StaticWall("ice-wall"){{
|
||||
variants = 2;
|
||||
iceSnow.asFloor().wall = this;
|
||||
}};
|
||||
|
||||
snowrocks = new StaticWall("snowrocks"){{
|
||||
snowWall = new StaticWall("snow-wall"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
duneRocks = new StaticWall("dunerocks"){{
|
||||
duneWall = new StaticWall("dune-wall"){{
|
||||
variants = 2;
|
||||
basalt.asFloor().wall = darksandWater.asFloor().wall = darksandTaintedWater.asFloor().wall = this;
|
||||
}};
|
||||
|
||||
sandRocks = new StaticWall("sandrocks"){{
|
||||
sandWall = new StaticWall("sand-wall"){{
|
||||
variants = 2;
|
||||
sandWater.asFloor().wall = this;
|
||||
}};
|
||||
|
||||
saltRocks = new StaticWall("saltrocks"){{
|
||||
}};
|
||||
saltWall = new StaticWall("salt-wall");
|
||||
|
||||
sporePine = new StaticTree("spore-pine"){{
|
||||
variants = 0;
|
||||
@@ -339,34 +365,34 @@ public class Blocks implements ContentList{
|
||||
variants = 0;
|
||||
}};
|
||||
|
||||
shrubs = new StaticWall("shrubs"){{
|
||||
shrubs = new StaticWall("shrubs");
|
||||
|
||||
}};
|
||||
whiteTreeDead = new TreeBlock("white-tree-dead");
|
||||
|
||||
whiteTreeDead = new TreeBlock("white-tree-dead"){{
|
||||
}};
|
||||
whiteTree = new TreeBlock("white-tree");
|
||||
|
||||
whiteTree = new TreeBlock("white-tree"){{
|
||||
}};
|
||||
|
||||
sporeCluster = new Rock("spore-cluster"){{
|
||||
sporeCluster = new Boulder("spore-cluster"){{
|
||||
variants = 3;
|
||||
}};
|
||||
|
||||
shale = new Floor("shale"){{
|
||||
variants = 3;
|
||||
attributes.set(Attribute.oil, 0.15f);
|
||||
attributes.set(Attribute.oil, 1f);
|
||||
}};
|
||||
|
||||
shaleRocks = new StaticWall("shalerocks"){{
|
||||
shaleWall = new StaticWall("shale-wall"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
shaleBoulder = new Rock("shale-boulder"){{
|
||||
shaleBoulder = new Boulder("shale-boulder"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
sandBoulder = new Rock("sand-boulder"){{
|
||||
sandBoulder = new Boulder("sand-boulder"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
daciteBoulder = new Boulder("dacite-boulder"){{
|
||||
variants = 2;
|
||||
}};
|
||||
|
||||
@@ -379,7 +405,7 @@ public class Blocks implements ContentList{
|
||||
sporeMoss = new Floor("spore-moss"){{
|
||||
variants = 3;
|
||||
attributes.set(Attribute.spores, 0.3f);
|
||||
wall = sporerocks;
|
||||
wall = sporeWall;
|
||||
}};
|
||||
|
||||
metalFloor = new Floor("metal-floor"){{
|
||||
@@ -498,8 +524,8 @@ public class Blocks implements ContentList{
|
||||
siliconCrucible = new AttributeSmelter("silicon-crucible"){{
|
||||
requirements(Category.crafting, with(Items.titanium, 120, Items.metaglass, 80, Items.plastanium, 35, Items.silicon, 60));
|
||||
craftEffect = Fx.smeltsmoke;
|
||||
outputItem = new ItemStack(Items.silicon, 5);
|
||||
craftTime = 140f;
|
||||
outputItem = new ItemStack(Items.silicon, 8);
|
||||
craftTime = 90f;
|
||||
size = 3;
|
||||
hasPower = true;
|
||||
hasLiquids = false;
|
||||
@@ -507,7 +533,7 @@ public class Blocks implements ContentList{
|
||||
itemCapacity = 30;
|
||||
boostScale = 0.15f;
|
||||
|
||||
consumes.items(new ItemStack(Items.coal, 3), new ItemStack(Items.sand, 6), new ItemStack(Items.pyratite, 1));
|
||||
consumes.items(new ItemStack(Items.coal, 4), new ItemStack(Items.sand, 6), new ItemStack(Items.pyratite, 1));
|
||||
consumes.power(4f);
|
||||
}};
|
||||
|
||||
@@ -568,7 +594,7 @@ public class Blocks implements ContentList{
|
||||
consumes.items(new ItemStack(Items.copper, 3), new ItemStack(Items.lead, 4), new ItemStack(Items.titanium, 2), new ItemStack(Items.silicon, 3));
|
||||
}};
|
||||
|
||||
cryofluidMixer = new LiquidConverter("cryofluidmixer"){{
|
||||
cryofluidMixer = new LiquidConverter("cryofluid-mixer"){{
|
||||
requirements(Category.crafting, with(Items.lead, 65, Items.silicon, 40, Items.titanium, 60));
|
||||
outputLiquid = new LiquidStack(Liquids.cryofluid, 0.2f);
|
||||
craftTime = 120f;
|
||||
@@ -758,14 +784,14 @@ public class Blocks implements ContentList{
|
||||
phaseWall = new Wall("phase-wall"){{
|
||||
requirements(Category.defense, with(Items.phasefabric, 6));
|
||||
health = 150 * wallHealthMultiplier;
|
||||
flashWhite = deflect = true;
|
||||
flashHit = deflect = true;
|
||||
}};
|
||||
|
||||
phaseWallLarge = new Wall("phase-wall-large"){{
|
||||
requirements(Category.defense, ItemStack.mult(phaseWall.requirements, 4));
|
||||
health = 150 * 4 * wallHealthMultiplier;
|
||||
size = 2;
|
||||
flashWhite = deflect = true;
|
||||
flashHit = deflect = true;
|
||||
}};
|
||||
|
||||
surgeWall = new Wall("surge-wall"){{
|
||||
@@ -782,7 +808,7 @@ public class Blocks implements ContentList{
|
||||
}};
|
||||
|
||||
door = new Door("door"){{
|
||||
requirements(Category.defense, with(Items.graphite, 6, Items.silicon, 4));
|
||||
requirements(Category.defense, with(Items.titanium, 6, Items.silicon, 4));
|
||||
health = 100 * wallHealthMultiplier;
|
||||
}};
|
||||
|
||||
@@ -844,7 +870,8 @@ public class Blocks implements ContentList{
|
||||
size = 2;
|
||||
reload = 250f;
|
||||
range = 85f;
|
||||
healPercent = 14f;
|
||||
healPercent = 11f;
|
||||
phaseBoost = 15f;
|
||||
health = 80 * size * size;
|
||||
consumes.item(Items.phasefabric).boost();
|
||||
}};
|
||||
@@ -885,10 +912,10 @@ public class Blocks implements ContentList{
|
||||
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
|
||||
hasShadow = false;
|
||||
health = 40;
|
||||
damage = 11;
|
||||
damage = 23;
|
||||
tileDamage = 7f;
|
||||
length = 10;
|
||||
tendrils = 5;
|
||||
tendrils = 4;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
@@ -899,6 +926,7 @@ public class Blocks implements ContentList{
|
||||
health = 45;
|
||||
speed = 0.03f;
|
||||
displayedSpeed = 4.2f;
|
||||
buildCostMultiplier = 2f;
|
||||
}};
|
||||
|
||||
titaniumConveyor = new Conveyor("titanium-conveyor"){{
|
||||
@@ -986,12 +1014,14 @@ public class Blocks implements ContentList{
|
||||
consumes.power(1.75f);
|
||||
}};
|
||||
|
||||
payloadConveyor = new PayloadConveyor("mass-conveyor"){{
|
||||
requirements(Category.distribution, with(Items.copper, 1));
|
||||
payloadConveyor = new PayloadConveyor("payload-conveyor"){{
|
||||
requirements(Category.distribution, with(Items.graphite, 10, Items.copper, 20));
|
||||
canOverdrive = false;
|
||||
}};
|
||||
|
||||
payloadRouter = new PayloadRouter("payload-router"){{
|
||||
requirements(Category.distribution, with(Items.copper, 1));
|
||||
requirements(Category.distribution, with(Items.graphite, 15, Items.copper, 20));
|
||||
canOverdrive = false;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
@@ -999,7 +1029,7 @@ public class Blocks implements ContentList{
|
||||
|
||||
mechanicalPump = new Pump("mechanical-pump"){{
|
||||
requirements(Category.liquid, with(Items.copper, 15, Items.metaglass, 10));
|
||||
pumpAmount = 0.1f;
|
||||
pumpAmount = 0.11f;
|
||||
}};
|
||||
|
||||
rotaryPump = new Pump("rotary-pump"){{
|
||||
@@ -1074,14 +1104,14 @@ public class Blocks implements ContentList{
|
||||
|
||||
powerNode = new PowerNode("power-node"){{
|
||||
requirements(Category.power, with(Items.copper, 1, Items.lead, 3));
|
||||
maxNodes = 20;
|
||||
maxNodes = 10;
|
||||
laserRange = 6;
|
||||
}};
|
||||
|
||||
powerNodeLarge = new PowerNode("power-node-large"){{
|
||||
requirements(Category.power, with(Items.titanium, 5, Items.lead, 10, Items.silicon, 3));
|
||||
size = 2;
|
||||
maxNodes = 30;
|
||||
maxNodes = 15;
|
||||
laserRange = 9.5f;
|
||||
}};
|
||||
|
||||
@@ -1118,34 +1148,35 @@ public class Blocks implements ContentList{
|
||||
powerProduction = 1.8f;
|
||||
generateEffect = Fx.redgeneratespark;
|
||||
size = 2;
|
||||
floating = true;
|
||||
}};
|
||||
|
||||
turbineGenerator = new BurnerGenerator("turbine-generator"){{
|
||||
steamGenerator = new BurnerGenerator("steam-generator"){{
|
||||
requirements(Category.power, with(Items.copper, 35, Items.graphite, 25, Items.lead, 40, Items.silicon, 30));
|
||||
powerProduction = 6f;
|
||||
powerProduction = 5.5f;
|
||||
itemDuration = 90f;
|
||||
consumes.liquid(Liquids.water, 0.05f);
|
||||
consumes.liquid(Liquids.water, 0.1f);
|
||||
hasLiquids = true;
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
differentialGenerator = new SingleTypeGenerator("differential-generator"){{
|
||||
requirements(Category.power, with(Items.copper, 70, Items.titanium, 50, Items.lead, 100, Items.silicon, 65, Items.metaglass, 50));
|
||||
powerProduction = 17f;
|
||||
itemDuration = 200f;
|
||||
powerProduction = 18f;
|
||||
itemDuration = 220f;
|
||||
hasLiquids = true;
|
||||
hasItems = true;
|
||||
size = 3;
|
||||
|
||||
consumes.item(Items.pyratite).optional(true, false);
|
||||
consumes.liquid(Liquids.cryofluid, 0.14f);
|
||||
consumes.liquid(Liquids.cryofluid, 0.1f);
|
||||
}};
|
||||
|
||||
rtgGenerator = new DecayGenerator("rtg-generator"){{
|
||||
requirements(Category.power, with(Items.lead, 100, Items.silicon, 75, Items.phasefabric, 25, Items.plastanium, 75, Items.thorium, 50));
|
||||
size = 2;
|
||||
powerProduction = 4f;
|
||||
itemDuration = 500f;
|
||||
powerProduction = 4.5f;
|
||||
itemDuration = 60 * 15f;
|
||||
}};
|
||||
|
||||
solarPanel = new SolarGenerator("solar-panel"){{
|
||||
@@ -1164,7 +1195,7 @@ public class Blocks implements ContentList{
|
||||
size = 3;
|
||||
health = 700;
|
||||
itemDuration = 360f;
|
||||
powerProduction = 14f;
|
||||
powerProduction = 15f;
|
||||
consumes.item(Items.thorium);
|
||||
heating = 0.02f;
|
||||
consumes.liquid(Liquids.cryofluid, heating / coolantPower).update(false);
|
||||
@@ -1244,7 +1275,7 @@ public class Blocks implements ContentList{
|
||||
rotateSpeed = 1.4f;
|
||||
attribute = Attribute.water;
|
||||
|
||||
consumes.power(1f);
|
||||
consumes.power(1.5f);
|
||||
}};
|
||||
|
||||
cultivator = new Cultivator("cultivator"){{
|
||||
@@ -1257,7 +1288,7 @@ public class Blocks implements ContentList{
|
||||
hasItems = true;
|
||||
|
||||
consumes.power(0.80f);
|
||||
consumes.liquid(Liquids.water, 0.18f);
|
||||
consumes.liquid(Liquids.water, 0.2f);
|
||||
}};
|
||||
|
||||
oilExtractor = new Fracker("oil-extractor"){{
|
||||
@@ -1270,6 +1301,8 @@ public class Blocks implements ContentList{
|
||||
size = 3;
|
||||
liquidCapacity = 30f;
|
||||
attribute = Attribute.oil;
|
||||
baseEfficiency = 0f;
|
||||
itemUseTime = 60f;
|
||||
|
||||
consumes.item(Items.sand);
|
||||
consumes.power(3f);
|
||||
@@ -1295,39 +1328,42 @@ public class Blocks implements ContentList{
|
||||
requirements(Category.effect, with(Items.copper, 3000, Items.lead, 3000, Items.silicon, 2000));
|
||||
|
||||
unitType = UnitTypes.beta;
|
||||
health = 2000;
|
||||
health = 3500;
|
||||
itemCapacity = 9000;
|
||||
size = 4;
|
||||
|
||||
unitCapModifier = 16;
|
||||
unitCapModifier = 14;
|
||||
}};
|
||||
|
||||
coreNucleus = new CoreBlock("core-nucleus"){{
|
||||
requirements(Category.effect, with(Items.copper, 1000, Items.lead, 1000));
|
||||
requirements(Category.effect, with(Items.copper, 8000, Items.lead, 8000, Items.silicon, 5000, Items.thorium, 4000));
|
||||
|
||||
unitType = UnitTypes.gamma;
|
||||
health = 4000;
|
||||
health = 6000;
|
||||
itemCapacity = 13000;
|
||||
size = 5;
|
||||
|
||||
unitCapModifier = 24;
|
||||
unitCapModifier = 20;
|
||||
}};
|
||||
|
||||
vault = new StorageBlock("vault"){{
|
||||
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
|
||||
size = 3;
|
||||
itemCapacity = 1000;
|
||||
group = BlockGroup.storage;
|
||||
}};
|
||||
|
||||
container = new StorageBlock("container"){{
|
||||
requirements(Category.effect, with(Items.titanium, 100));
|
||||
size = 2;
|
||||
itemCapacity = 300;
|
||||
group = BlockGroup.storage;
|
||||
}};
|
||||
|
||||
unloader = new Unloader("unloader"){{
|
||||
requirements(Category.effect, with(Items.titanium, 25, Items.silicon, 30));
|
||||
speed = 7f;
|
||||
speed = 6f;
|
||||
group = BlockGroup.transportation;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
@@ -1352,7 +1388,7 @@ public class Blocks implements ContentList{
|
||||
ammoUseEffect = Fx.shellEjectSmall;
|
||||
health = 250;
|
||||
inaccuracy = 2f;
|
||||
rotatespeed = 10f;
|
||||
rotateSpeed = 10f;
|
||||
}};
|
||||
|
||||
scatter = new ItemTurret("scatter"){{
|
||||
@@ -1370,7 +1406,7 @@ public class Blocks implements ContentList{
|
||||
targetGround = false;
|
||||
|
||||
recoilAmount = 2f;
|
||||
rotatespeed = 15f;
|
||||
rotateSpeed = 15f;
|
||||
inaccuracy = 17f;
|
||||
shootCone = 35f;
|
||||
|
||||
@@ -1441,7 +1477,7 @@ public class Blocks implements ContentList{
|
||||
recoilAmount = 2f;
|
||||
reloadTime = 90f;
|
||||
cooldown = 0.03f;
|
||||
powerUse = 2.5f;
|
||||
powerUse = 6f;
|
||||
shootShake = 2f;
|
||||
shootEffect = Fx.lancerLaserShoot;
|
||||
smokeEffect = Fx.none;
|
||||
@@ -1460,20 +1496,21 @@ public class Blocks implements ContentList{
|
||||
hitSize = 4;
|
||||
lifetime = 16f;
|
||||
drawSize = 400f;
|
||||
collidesAir = false;
|
||||
}};
|
||||
}};
|
||||
|
||||
arc = new PowerTurret("arc"){{
|
||||
requirements(Category.turret, with(Items.copper, 35, Items.lead, 50));
|
||||
shootType = new LightningBulletType(){{
|
||||
damage = 21;
|
||||
damage = 20;
|
||||
lightningLength = 25;
|
||||
collidesAir = false;
|
||||
}};
|
||||
reloadTime = 35f;
|
||||
shootCone = 40f;
|
||||
rotatespeed = 8f;
|
||||
powerUse = 1.5f;
|
||||
rotateSpeed = 8f;
|
||||
powerUse = 3f;
|
||||
targetAir = false;
|
||||
range = 90f;
|
||||
shootEffect = Fx.lightningShoot;
|
||||
@@ -1489,14 +1526,14 @@ public class Blocks implements ContentList{
|
||||
|
||||
hasPower = true;
|
||||
size = 2;
|
||||
force = 2.5f;
|
||||
scaledForce = 5f;
|
||||
range = 170f;
|
||||
damage = 0.08f;
|
||||
force = 4.5f;
|
||||
scaledForce = 5.5f;
|
||||
range = 110f;
|
||||
damage = 0.4f;
|
||||
health = 160 * size * size;
|
||||
rotateSpeed = 10;
|
||||
|
||||
consumes.power(3f);
|
||||
consumes.powerCond(3f, (TractorBeamBuild e) -> e.target != null);
|
||||
}};
|
||||
|
||||
swarmer = new ItemTurret("swarmer"){{
|
||||
@@ -1543,15 +1580,40 @@ public class Blocks implements ContentList{
|
||||
}};
|
||||
|
||||
segment = new PointDefenseTurret("segment"){{
|
||||
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 50));
|
||||
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phasefabric, 40));
|
||||
|
||||
health = 250 * size * size;
|
||||
range = 160f;
|
||||
hasPower = true;
|
||||
consumes.power(3f);
|
||||
consumes.powerCond(8f, (PointDefenseBuild b) -> b.target != null);
|
||||
size = 2;
|
||||
shootLength = 5f;
|
||||
bulletDamage = 12f;
|
||||
reloadTime = 25f;
|
||||
health = 190 * size * size;
|
||||
bulletDamage = 25f;
|
||||
reloadTime = 9f;
|
||||
}};
|
||||
|
||||
tsunami = new LiquidTurret("tsunami"){{
|
||||
requirements(Category.turret, with(Items.metaglass, 100, Items.lead, 400, Items.titanium, 250, Items.thorium, 100));
|
||||
ammo(
|
||||
Liquids.water, Bullets.heavyWaterShot,
|
||||
Liquids.slag, Bullets.heavySlagShot,
|
||||
Liquids.cryofluid, Bullets.heavyCryoShot,
|
||||
Liquids.oil, Bullets.heavyOilShot
|
||||
);
|
||||
size = 3;
|
||||
recoilAmount = 0f;
|
||||
reloadTime = 2f;
|
||||
shots = 2;
|
||||
velocityInaccuracy = 0.1f;
|
||||
inaccuracy = 4f;
|
||||
recoilAmount = 1f;
|
||||
restitution = 0.04f;
|
||||
shootCone = 45f;
|
||||
liquidCapacity = 40f;
|
||||
shootEffect = Fx.shootLiquid;
|
||||
range = 190f;
|
||||
health = 250 * size * size;
|
||||
shootSound = Sounds.splash;
|
||||
}};
|
||||
|
||||
fuse = new ItemTurret("fuse"){{
|
||||
@@ -1570,8 +1632,10 @@ public class Blocks implements ContentList{
|
||||
health = 220 * size * size;
|
||||
shootSound = Sounds.shotgun;
|
||||
|
||||
float brange = range + 10f;
|
||||
|
||||
ammo(Items.thorium, new ShrapnelBulletType(){{
|
||||
length = range + 10f;
|
||||
length = brange;
|
||||
damage = 105f;
|
||||
ammoMultiplier = 6f;
|
||||
}});
|
||||
@@ -1594,6 +1658,7 @@ public class Blocks implements ContentList{
|
||||
reloadTime = 60f;
|
||||
ammoEjectBack = 5f;
|
||||
ammoUseEffect = Fx.shellEjectBig;
|
||||
ammoPerShot = 2;
|
||||
cooldown = 0.03f;
|
||||
velocityInaccuracy = 0.2f;
|
||||
restitution = 0.02f;
|
||||
@@ -1619,7 +1684,7 @@ public class Blocks implements ContentList{
|
||||
range = 200f;
|
||||
size = 3;
|
||||
recoilAmount = 3f;
|
||||
rotatespeed = 10f;
|
||||
rotateSpeed = 10f;
|
||||
inaccuracy = 10f;
|
||||
shootCone = 30f;
|
||||
shootSound = Sounds.shootSnap;
|
||||
@@ -1627,8 +1692,50 @@ public class Blocks implements ContentList{
|
||||
health = 145 * size * size;
|
||||
}};
|
||||
|
||||
foreshadow = new ItemTurret("foreshadow"){{
|
||||
float brange = range = 500f;
|
||||
|
||||
requirements(Category.turret, with(Items.copper, 1000, Items.metaglass, 600, Items.surgealloy, 300, Items.plastanium, 200, Items.silicon, 600));
|
||||
ammo(
|
||||
Items.surgealloy, new PointBulletType(){{
|
||||
shootEffect = Fx.instShoot;
|
||||
hitEffect = Fx.instHit;
|
||||
smokeEffect = Fx.smokeCloud;
|
||||
trailEffect = Fx.instTrail;
|
||||
despawnEffect = Fx.instBomb;
|
||||
trailSpacing = 20f;
|
||||
damage = 1350;
|
||||
tileDamageMultiplier = 0.5f;
|
||||
speed = brange;
|
||||
hitShake = 6f;
|
||||
ammoMultiplier = 1f;
|
||||
}}
|
||||
);
|
||||
|
||||
rotateSpeed = 2.5f;
|
||||
reloadTime = 200f;
|
||||
restitution = 0.2f;
|
||||
ammoUseEffect = Fx.shellEjectBig;
|
||||
recoilAmount = 5f;
|
||||
restitution = 0.009f;
|
||||
cooldown = 0.009f;
|
||||
shootShake = 4f;
|
||||
shots = 1;
|
||||
size = 4;
|
||||
shootCone = 2f;
|
||||
shootSound = Sounds.shootBig;
|
||||
unitSort = (u, x, y) -> -u.maxHealth;
|
||||
|
||||
coolantMultiplier = 0.09f;
|
||||
|
||||
health = 150 * size * size;
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 2f)).update(false).optional(true, true);
|
||||
|
||||
consumes.powerCond(10f, (TurretBuild entity) -> entity.target != null || (entity.logicControlled() && entity.logicShooting));
|
||||
}};
|
||||
|
||||
spectre = new ItemTurret("spectre"){{
|
||||
requirements(Category.turret, with(Items.copper, 350, Items.graphite, 300, Items.surgealloy, 250, Items.plastanium, 175, Items.thorium, 250));
|
||||
requirements(Category.turret, with(Items.copper, 900, Items.graphite, 300, Items.surgealloy, 250, Items.plastanium, 175, Items.thorium, 250));
|
||||
ammo(
|
||||
Items.graphite, Bullets.standardDenseBig,
|
||||
Items.pyratite, Bullets.standardIncendiaryBig,
|
||||
@@ -1649,12 +1756,12 @@ public class Blocks implements ContentList{
|
||||
shootCone = 24f;
|
||||
shootSound = Sounds.shootBig;
|
||||
|
||||
health = 155 * size * size;
|
||||
health = 160 * size * size;
|
||||
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 2f)).update(false).optional(true, true);
|
||||
}};
|
||||
|
||||
meltdown = new LaserTurret("meltdown"){{
|
||||
requirements(Category.turret, with(Items.copper, 250, Items.lead, 350, Items.graphite, 300, Items.surgealloy, 325, Items.silicon, 325));
|
||||
requirements(Category.turret, with(Items.copper, 1200, Items.lead, 350, Items.graphite, 300, Items.surgealloy, 325, Items.silicon, 325));
|
||||
shootEffect = Fx.shootBigSmoke2;
|
||||
shootCone = 40f;
|
||||
recoilAmount = 4f;
|
||||
@@ -1664,13 +1771,13 @@ public class Blocks implements ContentList{
|
||||
reloadTime = 90f;
|
||||
firingMoveFract = 0.5f;
|
||||
shootDuration = 220f;
|
||||
powerUse = 14f;
|
||||
powerUse = 17f;
|
||||
shootSound = Sounds.laserbig;
|
||||
activeSound = Sounds.beam;
|
||||
activeSoundVolume = 2f;
|
||||
|
||||
shootType = new ContinuousLaserBulletType(70){{
|
||||
length = 220f;
|
||||
length = 200f;
|
||||
hitEffect = Fx.hitMeltdown;
|
||||
drawSize = 420f;
|
||||
|
||||
@@ -1686,11 +1793,17 @@ public class Blocks implements ContentList{
|
||||
//endregion
|
||||
//region units
|
||||
|
||||
commandCenter = new CommandCenter("command-center"){{
|
||||
requirements(Category.units, ItemStack.with(Items.copper, 200, Items.lead, 250, Items.silicon, 250, Items.graphite, 100));
|
||||
size = 2;
|
||||
health = size * size * 55;
|
||||
}};
|
||||
|
||||
groundFactory = new UnitFactory("ground-factory"){{
|
||||
requirements(Category.units, with(Items.copper, 50, Items.lead, 120, Items.silicon, 80));
|
||||
plans = new UnitPlan[]{
|
||||
new UnitPlan(UnitTypes.dagger, 60f * 20, with(Items.silicon, 10, Items.lead, 10)),
|
||||
new UnitPlan(UnitTypes.crawler, 60f * 15, with(Items.silicon, 10, Items.blastCompound, 10)),
|
||||
new UnitPlan(UnitTypes.dagger, 60f * 15, with(Items.silicon, 10, Items.lead, 10)),
|
||||
new UnitPlan(UnitTypes.crawler, 60f * 12, with(Items.silicon, 10, Items.coal, 20)),
|
||||
new UnitPlan(UnitTypes.nova, 60f * 40, with(Items.silicon, 30, Items.lead, 20, Items.titanium, 20)),
|
||||
};
|
||||
size = 3;
|
||||
@@ -1698,9 +1811,9 @@ public class Blocks implements ContentList{
|
||||
}};
|
||||
|
||||
airFactory = new UnitFactory("air-factory"){{
|
||||
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
|
||||
requirements(Category.units, with(Items.copper, 60, Items.lead, 70));
|
||||
plans = new UnitPlan[]{
|
||||
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 10)),
|
||||
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 15)),
|
||||
new UnitPlan(UnitTypes.mono, 60f * 35, with(Items.silicon, 30, Items.lead, 15)),
|
||||
};
|
||||
size = 3;
|
||||
@@ -1708,13 +1821,13 @@ public class Blocks implements ContentList{
|
||||
}};
|
||||
|
||||
navalFactory = new UnitFactory("naval-factory"){{
|
||||
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
|
||||
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
|
||||
plans = new UnitPlan[]{
|
||||
new UnitPlan(UnitTypes.risso, 60f * 30f, with(Items.silicon, 20, Items.metaglass, 25)),
|
||||
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35)),
|
||||
};
|
||||
size = 3;
|
||||
requiresWater = true;
|
||||
consumes.power(1.2f);
|
||||
floating = true;
|
||||
}};
|
||||
|
||||
additiveReconstructor = new Reconstructor("additive-reconstructor"){{
|
||||
@@ -1737,11 +1850,11 @@ public class Blocks implements ContentList{
|
||||
}};
|
||||
|
||||
multiplicativeReconstructor = new Reconstructor("multiplicative-reconstructor"){{
|
||||
requirements(Category.units, with(Items.lead, 650, Items.silicon, 350, Items.titanium, 350, Items.thorium, 650));
|
||||
requirements(Category.units, with(Items.lead, 650, Items.silicon, 450, Items.titanium, 350, Items.thorium, 650));
|
||||
|
||||
size = 5;
|
||||
consumes.power(6f);
|
||||
consumes.items(with(Items.silicon, 130, Items.titanium, 80, Items.metaglass, 30));
|
||||
consumes.items(with(Items.silicon, 130, Items.titanium, 80, Items.metaglass, 40));
|
||||
|
||||
constructTime = 60f * 30f;
|
||||
|
||||
@@ -1756,11 +1869,11 @@ public class Blocks implements ContentList{
|
||||
}};
|
||||
|
||||
exponentialReconstructor = new Reconstructor("exponential-reconstructor"){{
|
||||
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 750, Items.titanium, 950, Items.thorium, 450, Items.plastanium, 350, Items.phasefabric, 250));
|
||||
requirements(Category.units, with(Items.lead, 2000, Items.silicon, 1000, Items.titanium, 2000, Items.thorium, 750, Items.plastanium, 450, Items.phasefabric, 600));
|
||||
|
||||
size = 7;
|
||||
consumes.power(12f);
|
||||
consumes.items(with(Items.silicon, 250, Items.titanium, 500, Items.plastanium, 400));
|
||||
consumes.power(13f);
|
||||
consumes.items(with(Items.silicon, 850, Items.titanium, 750, Items.plastanium, 650));
|
||||
consumes.liquid(Liquids.cryofluid, 1f);
|
||||
|
||||
constructTime = 60f * 60f * 1.5f;
|
||||
@@ -1768,15 +1881,20 @@ public class Blocks implements ContentList{
|
||||
|
||||
upgrades = new UnitType[][]{
|
||||
{UnitTypes.zenith, UnitTypes.antumbra},
|
||||
{UnitTypes.spiroct, UnitTypes.arkyid},
|
||||
{UnitTypes.fortress, UnitTypes.scepter},
|
||||
{UnitTypes.bryde, UnitTypes.sei},
|
||||
{UnitTypes.mega, UnitTypes.quad},
|
||||
{UnitTypes.quasar, UnitTypes.vela},
|
||||
};
|
||||
}};
|
||||
|
||||
tetrativeReconstructor = new Reconstructor("tetrative-reconstructor"){{
|
||||
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 1500, Items.thorium, 500, Items.plastanium, 50, Items.phasefabric, 600, Items.surgealloy, 500));
|
||||
requirements(Category.units, with(Items.lead, 4000, Items.silicon, 3000, Items.thorium, 1000, Items.plastanium, 600, Items.phasefabric, 600, Items.surgealloy, 800));
|
||||
|
||||
size = 9;
|
||||
consumes.power(25f);
|
||||
consumes.items(with(Items.silicon, 350, Items.plastanium, 450, Items.surgealloy, 400, Items.phasefabric, 150));
|
||||
consumes.items(with(Items.silicon, 1000, Items.plastanium, 600, Items.surgealloy, 500, Items.phasefabric, 350));
|
||||
consumes.liquid(Liquids.cryofluid, 3f);
|
||||
|
||||
constructTime = 60f * 60f * 4;
|
||||
@@ -1784,6 +1902,11 @@ public class Blocks implements ContentList{
|
||||
|
||||
upgrades = new UnitType[][]{
|
||||
{UnitTypes.antumbra, UnitTypes.eclipse},
|
||||
{UnitTypes.arkyid, UnitTypes.toxopid},
|
||||
{UnitTypes.scepter, UnitTypes.reign},
|
||||
{UnitTypes.sei, UnitTypes.omura},
|
||||
{UnitTypes.quad, UnitTypes.oct},
|
||||
{UnitTypes.vela, UnitTypes.corvus}
|
||||
};
|
||||
}};
|
||||
|
||||
@@ -1799,6 +1922,8 @@ public class Blocks implements ContentList{
|
||||
|
||||
size = 2;
|
||||
range = 80f;
|
||||
itemCapacity = 20;
|
||||
ammoAmount = 5;
|
||||
|
||||
consumes.item(Items.copper, 1);
|
||||
}};
|
||||
@@ -1836,10 +1961,6 @@ public class Blocks implements ContentList{
|
||||
alwaysUnlocked = true;
|
||||
}};
|
||||
|
||||
message = new MessageBlock("message"){{
|
||||
requirements(Category.effect, with(Items.graphite, 5));
|
||||
}};
|
||||
|
||||
illuminator = new LightBlock("illuminator"){{
|
||||
requirements(Category.effect, BuildVisibility.lightingOnly, with(Items.graphite, 12, Items.silicon, 8));
|
||||
brightness = 0.67f;
|
||||
@@ -1853,7 +1974,6 @@ public class Blocks implements ContentList{
|
||||
//looked up by name, no ref needed
|
||||
new LegacyMechPad("legacy-mech-pad");
|
||||
new LegacyUnitFactory("legacy-unit-factory");
|
||||
new LegacyCommandCenter("legacy-command-center");
|
||||
|
||||
//endregion
|
||||
//region campaign
|
||||
@@ -1877,14 +1997,78 @@ public class Blocks implements ContentList{
|
||||
consumes.power(6f);
|
||||
}};
|
||||
|
||||
dataProcessor = new ResearchBlock("data-processor"){{
|
||||
//requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 200, Items.lead, 100));
|
||||
//endregion campaign
|
||||
//region logic
|
||||
|
||||
size = 3;
|
||||
alwaysUnlocked = true;
|
||||
message = new MessageBlock("message"){{
|
||||
requirements(Category.logic, with(Items.graphite, 5));
|
||||
}};
|
||||
|
||||
//endregion campaign
|
||||
switchBlock = new SwitchBlock("switch"){{
|
||||
requirements(Category.logic, with(Items.graphite, 5));
|
||||
}};
|
||||
|
||||
microProcessor = new LogicBlock("micro-processor"){{
|
||||
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 50));
|
||||
|
||||
instructionsPerTick = 2;
|
||||
|
||||
size = 1;
|
||||
}};
|
||||
|
||||
logicProcessor = new LogicBlock("logic-processor"){{
|
||||
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 100, Items.graphite, 60, Items.thorium, 50));
|
||||
|
||||
instructionsPerTick = 8;
|
||||
|
||||
range = 8 * 22;
|
||||
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
hyperProcessor = new LogicBlock("hyper-processor"){{
|
||||
requirements(Category.logic, with(Items.lead, 450, Items.silicon, 150, Items.thorium, 75, Items.surgealloy, 50));
|
||||
|
||||
consumes.liquid(Liquids.cryofluid, 0.08f);
|
||||
hasLiquids = true;
|
||||
|
||||
instructionsPerTick = 25;
|
||||
|
||||
range = 8 * 42;
|
||||
|
||||
size = 3;
|
||||
}};
|
||||
|
||||
memoryCell = new MemoryBlock("memory-cell"){{
|
||||
requirements(Category.logic, with(Items.graphite, 30, Items.silicon, 30));
|
||||
|
||||
memoryCapacity = 64;
|
||||
}};
|
||||
|
||||
memoryBank = new MemoryBlock("memory-bank"){{
|
||||
requirements(Category.logic, with(Items.graphite, 80, Items.silicon, 80, Items.phasefabric, 30));
|
||||
|
||||
memoryCapacity = 512;
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
logicDisplay = new LogicDisplay("logic-display"){{
|
||||
requirements(Category.logic, with(Items.lead, 100, Items.silicon, 50, Items.metaglass, 50));
|
||||
|
||||
displaySize = 80;
|
||||
|
||||
size = 3;
|
||||
}};
|
||||
|
||||
largeLogicDisplay = new LogicDisplay("large-logic-display"){{
|
||||
requirements(Category.logic, with(Items.lead, 200, Items.silicon, 150, Items.metaglass, 100, Items.phasefabric, 75));
|
||||
|
||||
displaySize = 176;
|
||||
|
||||
size = 6;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
//region experimental
|
||||
|
||||
blockForge = new BlockForge("block-forge"){{
|
||||
|
||||
@@ -34,7 +34,7 @@ public class Bullets implements ContentList{
|
||||
standardGlaive, standardDenseBig, standardThoriumBig, standardIncendiaryBig,
|
||||
|
||||
//liquid
|
||||
waterShot, cryoShot, slagShot, oilShot,
|
||||
waterShot, cryoShot, slagShot, oilShot, heavyWaterShot, heavyCryoShot, heavySlagShot, heavyOilShot,
|
||||
|
||||
//environment, misc.
|
||||
damageLightning, damageLightningGround, fireball, basicFlame, pyraFlame, driverBolt, healBullet, healBulletBig, frag;
|
||||
@@ -42,7 +42,7 @@ public class Bullets implements ContentList{
|
||||
@Override
|
||||
public void load(){
|
||||
|
||||
artilleryDense = new ArtilleryBulletType(3f, 12, "shell"){{
|
||||
artilleryDense = new ArtilleryBulletType(3f, 20, "shell"){{
|
||||
hitEffect = Fx.flakExplosion;
|
||||
knockback = 0.8f;
|
||||
lifetime = 80f;
|
||||
@@ -63,7 +63,7 @@ public class Bullets implements ContentList{
|
||||
collidesAir = false;
|
||||
}};
|
||||
|
||||
artilleryPlastic = new ArtilleryBulletType(3.4f, 12, "shell"){{
|
||||
artilleryPlastic = new ArtilleryBulletType(3.4f, 20, "shell"){{
|
||||
hitEffect = Fx.plasticExplosion;
|
||||
knockback = 1f;
|
||||
lifetime = 80f;
|
||||
@@ -77,7 +77,7 @@ public class Bullets implements ContentList{
|
||||
frontColor = Pal.plastaniumFront;
|
||||
}};
|
||||
|
||||
artilleryHoming = new ArtilleryBulletType(3f, 12, "shell"){{
|
||||
artilleryHoming = new ArtilleryBulletType(3f, 20, "shell"){{
|
||||
hitEffect = Fx.flakExplosion;
|
||||
knockback = 0.8f;
|
||||
lifetime = 80f;
|
||||
@@ -91,7 +91,7 @@ public class Bullets implements ContentList{
|
||||
homingRange = 50f;
|
||||
}};
|
||||
|
||||
artilleryIncendiary = new ArtilleryBulletType(3f, 12, "shell"){{
|
||||
artilleryIncendiary = new ArtilleryBulletType(3f, 20, "shell"){{
|
||||
hitEffect = Fx.blastExplosion;
|
||||
knockback = 0.8f;
|
||||
lifetime = 80f;
|
||||
@@ -105,7 +105,7 @@ public class Bullets implements ContentList{
|
||||
trailEffect = Fx.incendTrail;
|
||||
}};
|
||||
|
||||
artilleryExplosive = new ArtilleryBulletType(2f, 12, "shell"){{
|
||||
artilleryExplosive = new ArtilleryBulletType(2f, 20, "shell"){{
|
||||
hitEffect = Fx.blastExplosion;
|
||||
knockback = 0.8f;
|
||||
lifetime = 80f;
|
||||
@@ -163,7 +163,7 @@ public class Bullets implements ContentList{
|
||||
width = 6f;
|
||||
height = 8f;
|
||||
hitEffect = Fx.flakExplosion;
|
||||
splashDamage = 20f;
|
||||
splashDamage = 22f;
|
||||
splashDamageRadius = 20f;
|
||||
fragBullet = flakGlassFrag;
|
||||
fragBullets = 5;
|
||||
@@ -191,7 +191,7 @@ public class Bullets implements ContentList{
|
||||
|
||||
fragGlass = new FlakBulletType(4f, 3){{
|
||||
lifetime = 70f;
|
||||
ammoMultiplier = 5f;
|
||||
ammoMultiplier = 3f;
|
||||
shootEffect = Fx.shootSmall;
|
||||
reloadMultiplier = 0.8f;
|
||||
width = 6f;
|
||||
@@ -199,7 +199,7 @@ public class Bullets implements ContentList{
|
||||
hitEffect = Fx.flakExplosion;
|
||||
splashDamage = 18f;
|
||||
splashDamageRadius = 16f;
|
||||
fragBullet = flakGlassFrag;
|
||||
fragBullet = fragGlassFrag;
|
||||
fragBullets = 3;
|
||||
explodeRange = 20f;
|
||||
collidesGround = true;
|
||||
@@ -221,8 +221,8 @@ public class Bullets implements ContentList{
|
||||
fragExplosive = new FlakBulletType(4f, 5){{
|
||||
shootEffect = Fx.shootBig;
|
||||
ammoMultiplier = 4f;
|
||||
splashDamage = 15f;
|
||||
splashDamageRadius = 34f;
|
||||
splashDamage = 18f;
|
||||
splashDamageRadius = 55f;
|
||||
collidesGround = true;
|
||||
|
||||
status = StatusEffects.blasted;
|
||||
@@ -230,7 +230,8 @@ public class Bullets implements ContentList{
|
||||
}};
|
||||
|
||||
fragSurge = new FlakBulletType(4.5f, 13){{
|
||||
splashDamage = 45f;
|
||||
ammoMultiplier = 4f;
|
||||
splashDamage = 50f;
|
||||
splashDamageRadius = 40f;
|
||||
lightning = 2;
|
||||
lightningLength = 7;
|
||||
@@ -239,7 +240,7 @@ public class Bullets implements ContentList{
|
||||
explodeRange = 20f;
|
||||
}};
|
||||
|
||||
missileExplosive = new MissileBulletType(2.7f, 10){{
|
||||
missileExplosive = new MissileBulletType(3.7f, 10){{
|
||||
width = 8f;
|
||||
height = 8f;
|
||||
shrinkY = 0f;
|
||||
@@ -247,7 +248,6 @@ public class Bullets implements ContentList{
|
||||
splashDamageRadius = 30f;
|
||||
splashDamage = 30f;
|
||||
ammoMultiplier = 4f;
|
||||
lifetime = 100f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
despawnEffect = Fx.blastExplosion;
|
||||
|
||||
@@ -255,7 +255,7 @@ public class Bullets implements ContentList{
|
||||
statusDuration = 60f;
|
||||
}};
|
||||
|
||||
missileIncendiary = new MissileBulletType(2.9f, 12){{
|
||||
missileIncendiary = new MissileBulletType(3.7f, 12){{
|
||||
frontColor = Pal.lightishOrange;
|
||||
backColor = Pal.lightOrange;
|
||||
width = 7f;
|
||||
@@ -265,23 +265,21 @@ public class Bullets implements ContentList{
|
||||
homingPower = 0.08f;
|
||||
splashDamageRadius = 20f;
|
||||
splashDamage = 20f;
|
||||
lifetime = 100f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
status = StatusEffects.burning;
|
||||
}};
|
||||
|
||||
missileSurge = new MissileBulletType(4.4f, 20){{
|
||||
missileSurge = new MissileBulletType(3.7f, 18){{
|
||||
width = 8f;
|
||||
height = 8f;
|
||||
shrinkY = 0f;
|
||||
drag = -0.01f;
|
||||
splashDamageRadius = 28f;
|
||||
splashDamage = 40f;
|
||||
lifetime = 100f;
|
||||
splashDamageRadius = 25f;
|
||||
splashDamage = 25f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
despawnEffect = Fx.blastExplosion;
|
||||
lightning = 2;
|
||||
lightningLength = 14;
|
||||
lightningLength = 10;
|
||||
}};
|
||||
|
||||
standardCopper = new BasicBulletType(2.5f, 9){{
|
||||
@@ -377,10 +375,9 @@ public class Bullets implements ContentList{
|
||||
}};
|
||||
|
||||
//this is just a copy of the damage lightning bullet that doesn't damage air units
|
||||
damageLightningGround = new BulletType(0.0001f, 0f){{
|
||||
collidesAir = false;
|
||||
}};
|
||||
damageLightningGround = new BulletType(0.0001f, 0f){};
|
||||
JsonIO.copy(damageLightning, damageLightningGround);
|
||||
damageLightningGround.collidesAir = false;
|
||||
|
||||
healBullet = new HealBulletType(5.2f, 13){{
|
||||
healPercent = 3f;
|
||||
@@ -430,34 +427,27 @@ public class Bullets implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
basicFlame = new BulletType(3f, 15f){
|
||||
{
|
||||
ammoMultiplier = 3f;
|
||||
hitSize = 7f;
|
||||
lifetime = 42f;
|
||||
pierce = true;
|
||||
drag = 0.05f;
|
||||
statusDuration = 60f * 4;
|
||||
shootEffect = Fx.shootSmallFlame;
|
||||
hitEffect = Fx.hitFlameSmall;
|
||||
despawnEffect = Fx.none;
|
||||
status = StatusEffects.burning;
|
||||
keepVelocity = false;
|
||||
hittable = false;
|
||||
}
|
||||
basicFlame = new BulletType(3.35f, 15f){{
|
||||
ammoMultiplier = 3f;
|
||||
hitSize = 7f;
|
||||
lifetime = 18f;
|
||||
pierce = true;
|
||||
collidesAir = false;
|
||||
statusDuration = 60f * 4;
|
||||
shootEffect = Fx.shootSmallFlame;
|
||||
hitEffect = Fx.hitFlameSmall;
|
||||
despawnEffect = Fx.none;
|
||||
status = StatusEffects.burning;
|
||||
keepVelocity = false;
|
||||
hittable = false;
|
||||
}};
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return 50f;
|
||||
}
|
||||
};
|
||||
|
||||
pyraFlame = new BulletType(3.3f, 22f){{
|
||||
pyraFlame = new BulletType(3.35f, 22f){{
|
||||
ammoMultiplier = 4f;
|
||||
hitSize = 7f;
|
||||
lifetime = 42f;
|
||||
lifetime = 18f;
|
||||
pierce = true;
|
||||
drag = 0.05f;
|
||||
collidesAir = false;
|
||||
statusDuration = 60f * 6;
|
||||
shootEffect = Fx.shootPyraFlame;
|
||||
hitEffect = Fx.hitFlameSmall;
|
||||
@@ -483,6 +473,50 @@ public class Bullets implements ContentList{
|
||||
drag = 0.03f;
|
||||
}};
|
||||
|
||||
heavyWaterShot = new LiquidBulletType(Liquids.water){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.7f;
|
||||
puddleSize = 8f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 2f;
|
||||
statusDuration = 60f * 4f;
|
||||
damage = 0.1f;
|
||||
}};
|
||||
|
||||
heavyCryoShot = new LiquidBulletType(Liquids.cryofluid){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 2f;
|
||||
statusDuration = 60f * 4f;
|
||||
damage = 0.1f;
|
||||
}};
|
||||
|
||||
heavySlagShot = new LiquidBulletType(Liquids.slag){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
damage = 6f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 2f;
|
||||
statusDuration = 60f * 4f;
|
||||
}};
|
||||
|
||||
heavyOilShot = new LiquidBulletType(Liquids.oil){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 2f;
|
||||
statusDuration = 60f * 4f;
|
||||
damage = 0.1f;
|
||||
}};
|
||||
|
||||
driverBolt = new MassDriverBolt();
|
||||
|
||||
frag = new BasicBulletType(5f, 8, "bullet"){{
|
||||
|
||||
@@ -18,7 +18,7 @@ import static arc.graphics.g2d.Draw.rect;
|
||||
import static arc.graphics.g2d.Draw.*;
|
||||
import static arc.graphics.g2d.Lines.*;
|
||||
import static arc.math.Angles.*;
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Fx{
|
||||
public static final Effect
|
||||
@@ -26,15 +26,17 @@ public class Fx{
|
||||
none = new Effect(0, 0f, e -> {}),
|
||||
|
||||
unitSpawn = new Effect(30f, e -> {
|
||||
if(!(e.data instanceof Unit)) return;
|
||||
if(!(e.data instanceof UnitType)) return;
|
||||
|
||||
alpha(e.fin());
|
||||
|
||||
float scl = 1f + e.fout() * 2f;
|
||||
|
||||
Unit unit = e.data();
|
||||
rect(unit.type().region, e.x, e.y,
|
||||
unit.type().region.getWidth() * Draw.scl * scl, unit.type().region.getHeight() * Draw.scl * scl, 180f);
|
||||
UnitType unit = e.data();
|
||||
TextureRegion region = unit.icon(Cicon.full);
|
||||
|
||||
rect(region, e.x, e.y,
|
||||
region.width * Draw.scl * scl, region.height * Draw.scl * scl, 180f);
|
||||
|
||||
}),
|
||||
|
||||
@@ -50,10 +52,11 @@ public class Fx{
|
||||
if(!(e.data instanceof Unit)) return;
|
||||
|
||||
Unit select = e.data();
|
||||
boolean block = select instanceof BlockUnitc;
|
||||
|
||||
mixcol(Pal.accent, 1f);
|
||||
alpha(e.fout());
|
||||
rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
|
||||
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type().icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
|
||||
alpha(1f);
|
||||
Lines.stroke(e.fslope() * 1f);
|
||||
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
|
||||
@@ -102,7 +105,7 @@ public class Fx{
|
||||
Tmp.v1.set(e.x, e.y).interpolate(Tmp.v2.set(to), e.fin(), Interp.pow3)
|
||||
.add(Tmp.v2.sub(e.x, e.y).nor().rotate90(1).scl(Mathf.randomSeedRange(e.id, 1f) * e.fslope() * 10f));
|
||||
float x = Tmp.v1.x, y = Tmp.v1.y;
|
||||
float size = Math.min(0.8f + e.rotation / 5f, 2);
|
||||
float size = 1f;
|
||||
|
||||
stroke(e.fslope() * 2f * size, Pal.accent);
|
||||
Lines.circle(x, y, e.fslope() * 2f * size);
|
||||
@@ -135,14 +138,15 @@ public class Fx{
|
||||
stroke(3f * e.fout());
|
||||
color(e.color, Color.white, e.fin());
|
||||
|
||||
beginLine();
|
||||
lines.each(Lines::linePoint);
|
||||
linePoint(e.x, e.y);
|
||||
endLine();
|
||||
for(int i = 0; i < lines.size - 1; i++){
|
||||
Vec2 cur = lines.get(i);
|
||||
Vec2 next = lines.get(i + 1);
|
||||
|
||||
Lines.line(cur.x, cur.y, next.x, next.y, false);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for(Vec2 p : lines){
|
||||
Fill.square(p.x, p.y, (5f - (float)i++ / lines.size * 2f) * e.fout(), 45);
|
||||
Fill.circle(p.x, p.y, Lines.getStroke() / 2f);
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -287,6 +291,50 @@ public class Fx{
|
||||
Lines.spikes(e.x, e.y, 1f + e.fin() * 6f, e.fout() * 4f, 6);
|
||||
}),
|
||||
|
||||
greenBomb = new Effect(40f, 100f, e -> {
|
||||
color(Pal.heal);
|
||||
stroke(e.fout() * 2f);
|
||||
Lines.circle(e.x, e.y, 4f + e.finpow() * 65f);
|
||||
|
||||
color(Pal.heal);
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(e.x, e.y, 6f, 100f * e.fout(), i*90);
|
||||
}
|
||||
|
||||
color();
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(e.x, e.y, 3f, 35f * e.fout(), i*90);
|
||||
}
|
||||
}),
|
||||
|
||||
greenLaserCharge = new Effect(80f, 100f, e -> {
|
||||
color(Pal.heal);
|
||||
stroke(e.fin() * 2f);
|
||||
Lines.circle(e.x, e.y, 4f + e.fout() * 100f);
|
||||
|
||||
Fill.circle(e.x, e.y, e.fin() * 20);
|
||||
|
||||
randLenVectors(e.id, 20, 40f * e.fout(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fin() * 5f);
|
||||
});
|
||||
|
||||
color();
|
||||
|
||||
Fill.circle(e.x, e.y, e.fin() * 10);
|
||||
}),
|
||||
|
||||
greenLaserChargeSmall = new Effect(40f, 100f, e -> {
|
||||
color(Pal.heal);
|
||||
stroke(e.fin() * 2f);
|
||||
Lines.circle(e.x, e.y, e.fout() * 50f);
|
||||
}),
|
||||
|
||||
healWaveDynamic = new Effect(22, e -> {
|
||||
color(Pal.heal);
|
||||
stroke(e.fout() * 2f);
|
||||
Lines.circle(e.x, e.y, 4f + e.finpow() * e.rotation);
|
||||
}),
|
||||
|
||||
healWave = new Effect(22, e -> {
|
||||
color(Pal.heal);
|
||||
stroke(e.fout() * 2f);
|
||||
@@ -369,7 +417,7 @@ public class Fx{
|
||||
hitLiquid = new Effect(16, e -> {
|
||||
color(e.color);
|
||||
|
||||
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation + 180f, 60f, (x, y) -> {
|
||||
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 60f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
|
||||
});
|
||||
|
||||
@@ -397,6 +445,90 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
hitMeltHeal = new Effect(12, e -> {
|
||||
color(Pal.heal);
|
||||
stroke(e.fout() * 2f);
|
||||
|
||||
randLenVectors(e.id, 6, e.finpow() * 18f, e.rotation, 360f, (x, y) -> {
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
instBomb = new Effect(15f, 100f, e -> {
|
||||
color(Pal.bulletYellowBack);
|
||||
stroke(e.fout() * 4f);
|
||||
Lines.circle(e.x, e.y, 4f + e.finpow() * 20f);
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(e.x, e.y, 6f, 80f * e.fout(), i*90 + 45);
|
||||
}
|
||||
|
||||
color();
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(e.x, e.y, 3f, 30f * e.fout(), i*90 + 45);
|
||||
}
|
||||
}),
|
||||
|
||||
instTrail = new Effect(30, e -> {
|
||||
for(int i = 0; i < 2; i++){
|
||||
color(i == 0 ? Pal.bulletYellowBack : Pal.bulletYellow);
|
||||
|
||||
float m = i == 0 ? 1f : 0.5f;
|
||||
|
||||
float rot = e.rotation + 180f;
|
||||
float w = 15f * e.fout() * m;
|
||||
Drawf.tri(e.x, e.y, w, (30f + Mathf.randomSeedRange(e.id, 15f)) * m, rot);
|
||||
Drawf.tri(e.x, e.y, w, 10f * m, rot + 180f);
|
||||
}
|
||||
}),
|
||||
|
||||
instShoot = new Effect(24f, e -> {
|
||||
e.scaled(10f, b -> {
|
||||
color(Color.white, Pal.bulletYellowBack, b.fin());
|
||||
stroke(b.fout() * 3f + 0.2f);
|
||||
Lines.circle(b.x, b.y, b.fin() * 50f);
|
||||
});
|
||||
|
||||
color(Pal.bulletYellowBack);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 13f * e.fout(), 85f, e.rotation + 90f * i);
|
||||
Drawf.tri(e.x, e.y, 13f * e.fout(), 50f, e.rotation + 20f * i);
|
||||
}
|
||||
}),
|
||||
|
||||
instHit = new Effect(20f, 200f, e -> {
|
||||
color(Pal.bulletYellowBack);
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
color(i == 0 ? Pal.bulletYellowBack : Pal.bulletYellow);
|
||||
|
||||
float m = i == 0 ? 1f : 0.5f;
|
||||
|
||||
for(int j = 0; j < 5; j++){
|
||||
float rot = e.rotation + Mathf.randomSeedRange(e.id + j, 50f);
|
||||
float w = 23f * e.fout() * m;
|
||||
Drawf.tri(e.x, e.y, w, (80f + Mathf.randomSeedRange(e.id + j, 40f)) * m, rot);
|
||||
Drawf.tri(e.x, e.y, w, 20f * m, rot + 180f);
|
||||
}
|
||||
}
|
||||
|
||||
e.scaled(10f, c -> {
|
||||
color(Pal.bulletYellow);
|
||||
stroke(c.fout() * 2f + 0.2f);
|
||||
Lines.circle(e.x, e.y, c.fin() * 30f);
|
||||
});
|
||||
|
||||
e.scaled(12f, c -> {
|
||||
color(Pal.bulletYellowBack);
|
||||
randLenVectors(e.id, 25, 5f + e.fin() * 80f, e.rotation, 60f, (x, y) -> {
|
||||
Fill.square(e.x + x, e.y + y, c.fout() * 3f, 45f);
|
||||
});
|
||||
});
|
||||
}),
|
||||
|
||||
hitLaser = new Effect(8, e -> {
|
||||
color(Color.white, Pal.heal, e.fin());
|
||||
stroke(0.5f + e.fout());
|
||||
@@ -512,6 +644,29 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
sapExplosion = new Effect(25, e -> {
|
||||
|
||||
color(Pal.sapBullet);
|
||||
e.scaled(6, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 80f);
|
||||
});
|
||||
|
||||
color(Color.gray);
|
||||
|
||||
randLenVectors(e.id, 9, 2f + 70 * e.finpow(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f);
|
||||
});
|
||||
|
||||
color(Pal.sapBulletBack);
|
||||
stroke(1f * e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 8, 1f + 60f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
massiveExplosion = new Effect(30, e -> {
|
||||
|
||||
color(Pal.missileYellow);
|
||||
@@ -654,13 +809,18 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
wet = new Effect(40f, e -> {
|
||||
wet = new Effect(80f, e -> {
|
||||
color(Liquids.water.color);
|
||||
alpha(Mathf.clamp(e.fin() * 2f));
|
||||
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
|
||||
});
|
||||
Fill.circle(e.x, e.y, e.fout() * 1f);
|
||||
}),
|
||||
|
||||
muddy = new Effect(80f, e -> {
|
||||
color(Color.valueOf("432722"));
|
||||
alpha(Mathf.clamp(e.fin() * 2f));
|
||||
|
||||
Fill.circle(e.x, e.y, e.fout() * 1f);
|
||||
}),
|
||||
|
||||
sapped = new Effect(40f, e -> {
|
||||
@@ -672,6 +832,12 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
sporeSlowed = new Effect(40f, e -> {
|
||||
color(Pal.spore);
|
||||
|
||||
Fill.circle(e.x, e.y, e.fslope() * 1.1f);
|
||||
}),
|
||||
|
||||
oily = new Effect(42f, e -> {
|
||||
color(Liquids.oil.color);
|
||||
|
||||
@@ -871,7 +1037,7 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
shootSmallFlame = new Effect(32f, e -> {
|
||||
shootSmallFlame = new Effect(32f, 80f, e -> {
|
||||
color(Pal.lightFlame, Pal.darkFlame, Color.gray, e.fin());
|
||||
|
||||
randLenVectors(e.id, 8, e.finpow() * 60f, e.rotation, 10f, (x, y) -> {
|
||||
@@ -880,7 +1046,7 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
shootPyraFlame = new Effect(33f, e -> {
|
||||
shootPyraFlame = new Effect(33f, 80f, e -> {
|
||||
color(Pal.lightPyraFlame, Pal.darkPyraFlame, Color.gray, e.fin());
|
||||
|
||||
randLenVectors(e.id, 10, e.finpow() * 70f, e.rotation, 10f, (x, y) -> {
|
||||
@@ -889,7 +1055,7 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
shootLiquid = new Effect(40f, e -> {
|
||||
shootLiquid = new Effect(40f, 80f, e -> {
|
||||
color(e.color, Color.white, e.fout() / 6f + Mathf.randomSeedRange(e.id, 0.1f));
|
||||
|
||||
randLenVectors(e.id, 6, e.finpow() * 60f, e.rotation, 11f, (x, y) -> {
|
||||
@@ -959,6 +1125,36 @@ public class Fx{
|
||||
|
||||
}).ground(400f),
|
||||
|
||||
railShoot = new Effect(24f, e -> {
|
||||
e.scaled(10f, b -> {
|
||||
color(Color.white, Color.lightGray, b.fin());
|
||||
stroke(b.fout() * 3f + 0.2f);
|
||||
Lines.circle(b.x, b.y, b.fin() * 50f);
|
||||
});
|
||||
|
||||
color(Pal.orangeSpark);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 13f * e.fout(), 85f, e.rotation + 90f * i);
|
||||
}
|
||||
}),
|
||||
|
||||
railTrail = new Effect(16f, e -> {
|
||||
color(Pal.orangeSpark);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 10f * e.fout(), 24f, e.rotation + 90 + 90f * i);
|
||||
}
|
||||
}),
|
||||
|
||||
railHit = new Effect(18f, 200f, e -> {
|
||||
color(Pal.orangeSpark);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 10f * e.fout(), 60f, e.rotation + 140f * i);
|
||||
}
|
||||
}),
|
||||
|
||||
lancerLaserShoot = new Effect(21f, e -> {
|
||||
color(Pal.lancerLaser);
|
||||
|
||||
@@ -970,7 +1166,7 @@ public class Fx{
|
||||
|
||||
lancerLaserShootSmoke = new Effect(26f, e -> {
|
||||
color(Color.white);
|
||||
float length = e.data == null ? 70f : (Float)e.data;
|
||||
float length = !(e.data instanceof Float) ? 70f : (Float)e.data;
|
||||
|
||||
randLenVectors(e.id, 7, length, e.rotation, 0f, (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fout() * 9f);
|
||||
@@ -1040,6 +1236,15 @@ public class Fx{
|
||||
});
|
||||
}),
|
||||
|
||||
cloudsmoke = new Effect(70, e -> {
|
||||
randLenVectors(e.id, 12, 15f + e.fin() * 45f, (x, y) -> {
|
||||
float size = e.fslope() * 2f;
|
||||
color(Color.gray);
|
||||
alpha(e.fslope());
|
||||
Fill.circle(e.x + x, e.y + y, size);
|
||||
});
|
||||
}),
|
||||
|
||||
nuclearcloud = new Effect(90, 200f, e -> {
|
||||
randLenVectors(e.id, 10, e.finpow() * 90f, (x, y) -> {
|
||||
float size = e.fout() * 14f;
|
||||
@@ -1136,6 +1341,14 @@ public class Fx{
|
||||
Fill.square(e.x + x, e.y + y, 1f + e.fout() * 3f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
smokeCloud = new Effect(70, e -> {
|
||||
randLenVectors(e.id, e.fin(), 30, 30f, (x, y, fin, fout) -> {
|
||||
color(Color.gray);
|
||||
alpha((0.5f - Math.abs(fin - 0.5f)) * 2f);
|
||||
Fill.circle(e.x + x, e.y + y, 0.5f + fout * 4f);
|
||||
});
|
||||
}),
|
||||
|
||||
smeltsmoke = new Effect(15, e -> {
|
||||
randLenVectors(e.id, 6, 4f + e.fin() * 5f, (x, y) -> {
|
||||
@@ -1265,10 +1478,11 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
//TODO fix false in constructor
|
||||
ripple = new Effect(30, e -> {
|
||||
e.lifetime = 30f*e.rotation;
|
||||
|
||||
color(Tmp.c1.set(e.color).mul(1.5f));
|
||||
stroke(e.fout() + 0.4f);
|
||||
stroke(e.fout() * 1.4f);
|
||||
Lines.circle(e.x, e.y, (2f + e.fin() * 4f) * e.rotation);
|
||||
}).ground(),
|
||||
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.content;
|
||||
|
||||
import arc.graphics.Color;
|
||||
import mindustry.ctype.ContentList;
|
||||
import mindustry.type.Liquid;
|
||||
import arc.graphics.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class Liquids implements ContentList{
|
||||
public static Liquid water, slag, oil, cryofluid;
|
||||
|
||||
@@ -2,14 +2,15 @@ package mindustry.content;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.Mathf;
|
||||
import mindustry.ctype.ContentList;
|
||||
import arc.math.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.type.StatusEffect;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class StatusEffects implements ContentList{
|
||||
public static StatusEffect none, burning, freezing, wet, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss;
|
||||
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
@@ -17,7 +18,7 @@ public class StatusEffects implements ContentList{
|
||||
none = new StatusEffect("none");
|
||||
|
||||
burning = new StatusEffect("burning"){{
|
||||
damage = 0.08f; //over 10 seconds, this would be 48 damage
|
||||
damage = 0.12f; //over 8 seconds, this would be 60 damage
|
||||
effect = Fx.burning;
|
||||
|
||||
init(() -> {
|
||||
@@ -45,14 +46,23 @@ public class StatusEffects implements ContentList{
|
||||
});
|
||||
}};
|
||||
|
||||
unmoving = new StatusEffect("unmoving"){{
|
||||
speedMultiplier = 0.001f;
|
||||
}};
|
||||
|
||||
slow = new StatusEffect("slow"){{
|
||||
speedMultiplier = 0.4f;
|
||||
}};
|
||||
|
||||
wet = new StatusEffect("wet"){{
|
||||
color = Color.royal;
|
||||
speedMultiplier = 0.9f;
|
||||
speedMultiplier = 0.94f;
|
||||
effect = Fx.wet;
|
||||
effectChance = 0.09f;
|
||||
|
||||
init(() -> {
|
||||
trans(shocked, ((unit, time, newTime, result) -> {
|
||||
unit.damagePierce(20f);
|
||||
unit.damagePierce(14f);
|
||||
if(unit.team() == state.rules.waveTeam){
|
||||
Events.fire(Trigger.shock);
|
||||
}
|
||||
@@ -61,6 +71,13 @@ public class StatusEffects implements ContentList{
|
||||
opposite(burning);
|
||||
});
|
||||
}};
|
||||
|
||||
muddy = new StatusEffect("muddy"){{
|
||||
color = Color.valueOf("46382a");
|
||||
speedMultiplier = 0.94f;
|
||||
effect = Fx.muddy;
|
||||
effectChance = 0.09f;
|
||||
}};
|
||||
|
||||
melting = new StatusEffect("melting"){{
|
||||
speedMultiplier = 0.8f;
|
||||
@@ -81,6 +98,12 @@ public class StatusEffects implements ContentList{
|
||||
effectChance = 0.1f;
|
||||
}};
|
||||
|
||||
sporeSlowed = new StatusEffect("spore-slowed"){{
|
||||
speedMultiplier = 0.8f;
|
||||
effect = Fx.sapped;
|
||||
effectChance = 0.04f;
|
||||
}};
|
||||
|
||||
tarred = new StatusEffect("tarred"){{
|
||||
speedMultiplier = 0.6f;
|
||||
effect = Fx.oily;
|
||||
@@ -114,13 +137,14 @@ public class StatusEffects implements ContentList{
|
||||
|
||||
boss = new StatusEffect("boss"){{
|
||||
permanent = true;
|
||||
damageMultiplier = 1.5f;
|
||||
armorMultiplier = 1.5f;
|
||||
}};
|
||||
|
||||
shocked = new StatusEffect("shocked");
|
||||
|
||||
blasted = new StatusEffect("blasted");
|
||||
|
||||
//no effects, just small amounts of damage.
|
||||
corroded = new StatusEffect("corroded"){{
|
||||
damage = 0.1f;
|
||||
}};
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
package mindustry.content;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.content.Blocks.*;
|
||||
import static mindustry.content.SectorPresets.*;
|
||||
import static mindustry.content.SectorPresets.craters;
|
||||
import static mindustry.content.SectorPresets.*;
|
||||
import static mindustry.content.UnitTypes.*;
|
||||
import static mindustry.type.ItemStack.*;
|
||||
|
||||
public class TechTree implements ContentList{
|
||||
private static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
|
||||
static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
|
||||
|
||||
public static Seq<TechNode> all;
|
||||
public static TechNode root;
|
||||
@@ -38,7 +35,6 @@ public class TechTree implements ContentList{
|
||||
node(distributor);
|
||||
node(sorter, () -> {
|
||||
node(invertedSorter);
|
||||
node(message);
|
||||
node(overflowGate, () -> {
|
||||
node(underflowGate);
|
||||
});
|
||||
@@ -113,6 +109,9 @@ public class TechTree implements ContentList{
|
||||
|
||||
node(Items.coal, with(Items.lead, 3000), () -> {
|
||||
node(Items.graphite, with(Items.coal, 1000), () -> {
|
||||
node(illuminator, () -> {
|
||||
});
|
||||
|
||||
node(graphitePress, () -> {
|
||||
node(Items.titanium, with(Items.graphite, 6000, Items.copper, 10000, Items.lead, 10000), () -> {
|
||||
node(pneumaticDrill, () -> {
|
||||
@@ -204,6 +203,30 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(microProcessor, () -> {
|
||||
node(switchBlock, () -> {
|
||||
node(message, () -> {
|
||||
node(logicDisplay, () -> {
|
||||
node(largeLogicDisplay, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(memoryCell, () -> {
|
||||
node(memoryBank, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(logicProcessor, () -> {
|
||||
node(hyperProcessor, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -242,10 +265,10 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
|
||||
node(turbineGenerator, () -> {
|
||||
node(steamGenerator, () -> {
|
||||
node(thermalGenerator, () -> {
|
||||
node(differentialGenerator, () -> {
|
||||
node(thoriumReactor, () -> {
|
||||
node(thoriumReactor, Seq.with(new Research(Liquids.cryofluid)), () -> {
|
||||
node(impactReactor, () -> {
|
||||
|
||||
});
|
||||
@@ -324,11 +347,17 @@ public class TechTree implements ContentList{
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(tsunami, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(lancer, () -> {
|
||||
node(meltdown, () -> {
|
||||
node(foreshadow, () -> {
|
||||
node(meltdown, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(shockMine, () -> {
|
||||
@@ -340,17 +369,29 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
|
||||
node(groundFactory, () -> {
|
||||
node(commandCenter, () -> {
|
||||
|
||||
});
|
||||
|
||||
node(dagger, () -> {
|
||||
node(mace, () -> {
|
||||
node(fortress, () -> {
|
||||
node(scepter, () -> {
|
||||
node(reign, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(nova, () -> {
|
||||
node(pulsar, () -> {
|
||||
node(quasar, () -> {
|
||||
node(vela, () -> {
|
||||
node(corvus, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -358,7 +399,11 @@ public class TechTree implements ContentList{
|
||||
node(crawler, () -> {
|
||||
node(atrax, () -> {
|
||||
node(spiroct, () -> {
|
||||
node(arkyid, () -> {
|
||||
node(toxopid, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -379,7 +424,11 @@ public class TechTree implements ContentList{
|
||||
node(mono, () -> {
|
||||
node(poly, () -> {
|
||||
node(mega, () -> {
|
||||
node(quad, () -> {
|
||||
node(oct, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -389,7 +438,11 @@ public class TechTree implements ContentList{
|
||||
node(risso, () -> {
|
||||
node(minke, () -> {
|
||||
node(bryde, () -> {
|
||||
node(sei, () -> {
|
||||
node(omura, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -406,8 +459,6 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
|
||||
//TODO research sectors
|
||||
|
||||
node(groundZero, () -> {
|
||||
node(frozenForest, Seq.with(
|
||||
new SectorComplete(groundZero),
|
||||
@@ -469,7 +520,7 @@ public class TechTree implements ContentList{
|
||||
new SectorComplete(frozenForest),
|
||||
new Research(pneumaticDrill),
|
||||
new Research(powerNode),
|
||||
new Research(turbineGenerator)
|
||||
new Research(steamGenerator)
|
||||
), () -> {
|
||||
node(fungalPass, Seq.with(
|
||||
new SectorComplete(stainedMountains),
|
||||
@@ -491,42 +542,27 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
}
|
||||
|
||||
private static void setup(){
|
||||
public static void setup(){
|
||||
TechNode.context = null;
|
||||
map = new ObjectMap<>();
|
||||
all = new Seq<>();
|
||||
}
|
||||
|
||||
private static TechNode node(UnlockableContent content, Runnable children){
|
||||
ItemStack[] requirements;
|
||||
|
||||
if(content instanceof Block){
|
||||
Block block = (Block)content;
|
||||
|
||||
requirements = new ItemStack[block.requirements.length];
|
||||
for(int i = 0; i < requirements.length; i++){
|
||||
int quantity = 40 + Mathf.round(Mathf.pow(block.requirements[i].amount, 1.25f) * 20, 10);
|
||||
|
||||
requirements[i] = new ItemStack(block.requirements[i].item, UI.roundAmount(quantity));
|
||||
}
|
||||
}else{
|
||||
requirements = ItemStack.empty;
|
||||
}
|
||||
|
||||
return node(content, requirements, children);
|
||||
public static TechNode node(UnlockableContent content, Runnable children){
|
||||
return node(content, content.researchRequirements(), children);
|
||||
}
|
||||
|
||||
private static TechNode node(UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
public static TechNode node(UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
return new TechNode(content, requirements, children);
|
||||
}
|
||||
|
||||
private static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
|
||||
TechNode node = new TechNode(content, empty, children);
|
||||
public static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
|
||||
TechNode node = new TechNode(content, content.researchRequirements(), children);
|
||||
node.objectives = objectives;
|
||||
return node;
|
||||
}
|
||||
|
||||
private static TechNode node(UnlockableContent block){
|
||||
public static TechNode node(UnlockableContent block){
|
||||
return node(block, () -> {});
|
||||
}
|
||||
|
||||
@@ -544,7 +580,7 @@ public class TechTree implements ContentList{
|
||||
}
|
||||
|
||||
public static class TechNode{
|
||||
private static TechNode context;
|
||||
static TechNode context;
|
||||
|
||||
/** Depth in tech tree. */
|
||||
public int depth;
|
||||
@@ -556,25 +592,20 @@ public class TechTree implements ContentList{
|
||||
public ItemStack[] requirements;
|
||||
/** Requirements that have been fulfilled. Always the same length as the requirement array. */
|
||||
public final ItemStack[] finishedRequirements;
|
||||
/** Extra objectives needed to research this. TODO implement */
|
||||
/** Extra objectives needed to research this. */
|
||||
public Seq<Objective> objectives = new Seq<>();
|
||||
/** Time required to research this content, in seconds. */
|
||||
public float time;
|
||||
/** Nodes that depend on this node. */
|
||||
public final Seq<TechNode> children = new Seq<>();
|
||||
/** Research progress, in seconds. */
|
||||
public float progress;
|
||||
|
||||
TechNode(@Nullable TechNode ccontext, UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
if(ccontext != null){
|
||||
ccontext.children.add(this);
|
||||
}
|
||||
if(ccontext != null) ccontext.children.add(this);
|
||||
|
||||
this.parent = ccontext;
|
||||
this.content = content;
|
||||
this.requirements = requirements;
|
||||
this.depth = parent == null ? 0 : parent.depth + 1;
|
||||
this.progress = Core.settings == null ? 0 : Core.settings.getFloat("research-" + content.name, 0f);
|
||||
this.time = Seq.with(requirements).mapFloat(i -> i.item.cost * i.amount).sum() * 10;
|
||||
this.finishedRequirements = new ItemStack[requirements.length];
|
||||
|
||||
@@ -599,7 +630,6 @@ public class TechTree implements ContentList{
|
||||
|
||||
/** Flushes research progress to settings. */
|
||||
public void save(){
|
||||
Core.settings.put("research-" + content.name, progress);
|
||||
|
||||
//save finished requirements by item type
|
||||
for(ItemStack stack : finishedRequirements){
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -70,7 +69,6 @@ public class Weathers implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
//TODO should apply wet effect
|
||||
rain = new Weather("rain"){
|
||||
float yspeed = 5f, xspeed = 1.5f, padding = 16f, size = 40f, density = 1200f;
|
||||
TextureRegion[] splashes = new TextureRegion[12];
|
||||
@@ -78,6 +76,7 @@ public class Weathers implements ContentList{
|
||||
{
|
||||
attrs.set(Attribute.light, -0.2f);
|
||||
attrs.set(Attribute.water, 0.2f);
|
||||
status = StatusEffects.wet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,13 +169,14 @@ public class Weathers implements ContentList{
|
||||
|
||||
sandstorm = new Weather("sandstorm"){
|
||||
TextureRegion region;
|
||||
float yspeed = 0.3f, xspeed = 6f, size = 140f, padding = size, invDensity = 1500f;
|
||||
Vec2 force = new Vec2(0.45f, 0.01f);
|
||||
float size = 140f, padding = size, invDensity = 1500f, baseSpeed = 6.1f;
|
||||
float force = 0.4f * 0;
|
||||
Color color = Color.valueOf("f7cba4");
|
||||
Texture noise;
|
||||
|
||||
{
|
||||
attrs.set(Attribute.light, -0.1f);
|
||||
opacityMultiplier = 0.8f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,22 +194,26 @@ public class Weathers implements ContentList{
|
||||
|
||||
@Override
|
||||
public void update(WeatherState state){
|
||||
float speed = force * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
for(Unit unit : Groups.unit){
|
||||
unit.impulse(force.x * state.intensity(), force.y * state.intensity());
|
||||
unit.impulse(windx, windy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOver(WeatherState state){
|
||||
Draw.tint(color);
|
||||
float speed = baseSpeed * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
float scale = 1f / 2000f;
|
||||
float scroll = Time.time() * scale;
|
||||
Tmp.tr1.setTexture(noise);
|
||||
Tmp.tr1.texture = noise;
|
||||
Core.camera.bounds(Tmp.r1);
|
||||
Tmp.tr1.set(Tmp.r1.x*scale, Tmp.r1.y*scale, (Tmp.r1.x + Tmp.r1.width)*scale, (Tmp.r1.y + Tmp.r1.height)*scale);
|
||||
Tmp.tr1.scroll(-xspeed * scroll, -yspeed * scroll);
|
||||
Tmp.tr1.scroll(-windx * scroll, windy * scroll);
|
||||
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
|
||||
|
||||
rand.setSeed(0);
|
||||
@@ -224,8 +228,8 @@ public class Weathers implements ContentList{
|
||||
float scl = rand.random(0.5f, 1f);
|
||||
float scl2 = rand.random(0.5f, 1f);
|
||||
float sscl = rand.random(0.5f, 1f);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * windx * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) + Time.time() * windy * scl);
|
||||
float alpha = rand.random(0.2f);
|
||||
|
||||
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
|
||||
@@ -247,14 +251,16 @@ public class Weathers implements ContentList{
|
||||
|
||||
sporestorm = new Weather("sporestorm"){
|
||||
TextureRegion region;
|
||||
float yspeed = 1f, xspeed = 4f, size = 5f, padding = size, invDensity = 2000f;
|
||||
float size = 5f, padding = size, invDensity = 2000f, baseSpeed = 4.3f, force = 0.28f * 0;
|
||||
Color color = Color.valueOf("7457ce");
|
||||
Vec2 force = new Vec2(0.25f, 0.01f);
|
||||
Texture noise;
|
||||
|
||||
{
|
||||
attrs.set(Attribute.spores, 0.5f);
|
||||
attrs.set(Attribute.light, -0.1f);
|
||||
attrs.set(Attribute.spores, 1f);
|
||||
attrs.set(Attribute.light, -0.15f);
|
||||
status = StatusEffects.sporeSlowed;
|
||||
statusGround = false;
|
||||
opacityMultiplier = 0.85f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -267,9 +273,11 @@ public class Weathers implements ContentList{
|
||||
|
||||
@Override
|
||||
public void update(WeatherState state){
|
||||
float speed = force * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
for(Unit unit : Groups.unit){
|
||||
unit.impulse(force.x * state.intensity(), force.y * state.intensity());
|
||||
unit.impulse(windx, windy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,12 +291,15 @@ public class Weathers implements ContentList{
|
||||
Draw.alpha(state.opacity * 0.8f);
|
||||
Draw.tint(color);
|
||||
|
||||
float speed = baseSpeed * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
float scale = 1f / 2000f;
|
||||
float scroll = Time.time() * scale;
|
||||
Tmp.tr1.setTexture(noise);
|
||||
Tmp.tr1.texture = noise;
|
||||
Core.camera.bounds(Tmp.r1);
|
||||
Tmp.tr1.set(Tmp.r1.x*scale, Tmp.r1.y*scale, (Tmp.r1.x + Tmp.r1.width)*scale, (Tmp.r1.y + Tmp.r1.height)*scale);
|
||||
Tmp.tr1.scroll(-xspeed * scroll, -yspeed * scroll);
|
||||
Tmp.tr1.scroll(-windx * scroll, windy * scroll);
|
||||
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
|
||||
|
||||
rand.setSeed(0);
|
||||
@@ -304,8 +315,8 @@ public class Weathers implements ContentList{
|
||||
float scl = rand.random(0.5f, 1f);
|
||||
float scl2 = rand.random(0.5f, 1f);
|
||||
float sscl = rand.random(0.5f, 1f);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * windx * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) + Time.time() * windy * scl);
|
||||
float alpha = rand.random(0.1f, 0.8f);
|
||||
|
||||
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package mindustry.core;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
@@ -13,8 +13,8 @@ import mindustry.mod.Mods.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static arc.Core.files;
|
||||
import static mindustry.Vars.mods;
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/**
|
||||
* Loads all game content.
|
||||
@@ -33,6 +33,7 @@ public class ContentLoader{
|
||||
new StatusEffects(),
|
||||
new Liquids(),
|
||||
new Bullets(),
|
||||
new AmmoTypes(),
|
||||
new UnitTypes(),
|
||||
new Blocks(),
|
||||
new Loadouts(),
|
||||
@@ -243,11 +244,11 @@ public class ContentLoader{
|
||||
}
|
||||
|
||||
public Block block(int id){
|
||||
return (Block)getByID(ContentType.block, id);
|
||||
return getByID(ContentType.block, id);
|
||||
}
|
||||
|
||||
public Block block(String name){
|
||||
return (Block)getByName(ContentType.block, name);
|
||||
return getByName(ContentType.block, name);
|
||||
}
|
||||
|
||||
public Seq<Item> items(){
|
||||
@@ -255,7 +256,7 @@ public class ContentLoader{
|
||||
}
|
||||
|
||||
public Item item(int id){
|
||||
return (Item)getByID(ContentType.item, id);
|
||||
return getByID(ContentType.item, id);
|
||||
}
|
||||
|
||||
public Seq<Liquid> liquids(){
|
||||
@@ -263,7 +264,7 @@ public class ContentLoader{
|
||||
}
|
||||
|
||||
public Liquid liquid(int id){
|
||||
return (Liquid)getByID(ContentType.liquid, id);
|
||||
return getByID(ContentType.liquid, id);
|
||||
}
|
||||
|
||||
public Seq<BulletType> bullets(){
|
||||
@@ -271,7 +272,7 @@ public class ContentLoader{
|
||||
}
|
||||
|
||||
public BulletType bullet(int id){
|
||||
return (BulletType)getByID(ContentType.bullet, id);
|
||||
return getByID(ContentType.bullet, id);
|
||||
}
|
||||
|
||||
public Seq<SectorPreset> sectors(){
|
||||
|
||||
@@ -8,8 +8,8 @@ import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
@@ -94,7 +94,6 @@ public class Control implements ApplicationListener, Loadable{
|
||||
tutorial.reset();
|
||||
|
||||
hiscore = false;
|
||||
|
||||
saves.resetSave();
|
||||
});
|
||||
|
||||
@@ -109,20 +108,24 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
Events.on(GameOverEvent.class, event -> {
|
||||
state.stats.wavesLasted = state.wave;
|
||||
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
|
||||
Effect.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
|
||||
//the restart dialog can show info for any number of scenarios
|
||||
Call.gameOver(event.winner);
|
||||
});
|
||||
|
||||
//add player when world loads regardless
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
player.add();
|
||||
});
|
||||
|
||||
//autohost for pvp maps
|
||||
Events.on(WorldLoadEvent.class, event -> app.post(() -> {
|
||||
player.add();
|
||||
if(state.rules.pvp && !net.active()){
|
||||
try{
|
||||
net.host(port);
|
||||
player.admin(true);
|
||||
}catch(IOException e){
|
||||
ui.showException("$server.error", e);
|
||||
ui.showException("@server.error", e);
|
||||
state.set(State.menu);
|
||||
}
|
||||
}
|
||||
@@ -152,12 +155,6 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(ZoneRequireCompleteEvent.class, e -> {
|
||||
if(e.objective.display() != null){
|
||||
ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", e.zoneForMet.localizedName, e.objective.display()));
|
||||
}
|
||||
});
|
||||
|
||||
//delete save on campaign game over
|
||||
Events.on(GameOverEvent.class, e -> {
|
||||
if(state.isCampaign() && !net.client() && !headless){
|
||||
@@ -171,7 +168,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(Trigger.newGame, () -> {
|
||||
Events.run(Trigger.newGame, () -> {
|
||||
Building core = player.closestCore();
|
||||
|
||||
if(core == null) return;
|
||||
@@ -185,10 +182,10 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
app.post(() -> ui.hudfrag.showLand());
|
||||
renderer.zoomIn(Fx.coreLand.lifetime);
|
||||
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block()));
|
||||
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
|
||||
Time.run(Fx.coreLand.lifetime, () -> {
|
||||
Fx.launch.at(core);
|
||||
Effects.shake(5f, 5f, core);
|
||||
Effect.shake(5f, 5f, core);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,7 +193,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
@Override
|
||||
public void loadAsync(){
|
||||
Draw.scl = 1f / Core.atlas.find("scale_marker").getWidth();
|
||||
Draw.scl = 1f / Core.atlas.find("scale_marker").width;
|
||||
|
||||
Core.input.setCatch(KeyCode.back, true);
|
||||
|
||||
@@ -257,7 +254,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
|
||||
//TODO move
|
||||
public void handleLaunch(CoreEntity tile){
|
||||
public void handleLaunch(CoreBuild tile){
|
||||
LaunchCorec ent = LaunchCore.create();
|
||||
ent.set(tile);
|
||||
ent.block(Blocks.coreShard);
|
||||
@@ -283,6 +280,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
try{
|
||||
net.reset();
|
||||
slot.load();
|
||||
slot.setAutosave(true);
|
||||
state.rules.sector = sector;
|
||||
|
||||
//if there is no base, simulate a new game and place the right loadout at the spawn position
|
||||
@@ -312,7 +310,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}catch(SaveException e){
|
||||
Log.err(e);
|
||||
sector.save = null;
|
||||
Time.runTask(10f, () -> ui.showErrorMessage("$save.corrupted"));
|
||||
Time.runTask(10f, () -> ui.showErrorMessage("@save.corrupted"));
|
||||
slot.delete();
|
||||
playSector(origin, sector);
|
||||
}
|
||||
@@ -324,6 +322,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
state.rules.sector = sector;
|
||||
//assign origin when launching
|
||||
state.secinfo.origin = origin;
|
||||
state.secinfo.destination = origin;
|
||||
logic.play();
|
||||
control.saves.saveSector(sector);
|
||||
Events.fire(Trigger.newGame);
|
||||
@@ -332,6 +331,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
|
||||
public void playTutorial(){
|
||||
ui.showInfo("@indev.notready");
|
||||
//TODO implement
|
||||
//ui.showInfo("death");
|
||||
/*
|
||||
@@ -431,19 +431,12 @@ public class Control implements ApplicationListener, Loadable{
|
||||
//just a regular reminder
|
||||
if(!OS.prop("user.name").equals("anuke") && !OS.hasEnv("iknowwhatimdoing")){
|
||||
app.post(() -> app.post(() -> {
|
||||
ui.showStartupInfo("[accent]v6[] is currently in [accent]pre-alpha[].\n" +
|
||||
"[lightgray]This means:[]\n" +
|
||||
"- Content is missing\n" +
|
||||
"- [scarlet]Mobile[] is not supported.\n" +
|
||||
"- Most [scarlet]Unit AI[] does not work\n" +
|
||||
"- Many units are [scarlet]missing[] or unfinished\n" +
|
||||
"- The campaign is completely unfinished\n" +
|
||||
"- Everything you see is subject to change or removal." +
|
||||
"\n\nReport bugs or crashes on [accent]Github[].");
|
||||
ui.showStartupInfo("@indev.popup");
|
||||
}));
|
||||
}
|
||||
|
||||
//play tutorial on stop
|
||||
//play tutorial on start
|
||||
//TODO no tutorial right now
|
||||
if(!settings.getBool("playedtutorial", false)){
|
||||
//Core.app.post(() -> Core.app.post(this::playTutorial));
|
||||
}
|
||||
@@ -451,7 +444,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
//display UI scale changed dialog
|
||||
if(Core.settings.getBool("uiscalechanged", false)){
|
||||
Core.app.post(() -> Core.app.post(() -> {
|
||||
BaseDialog dialog = new BaseDialog("$confirm");
|
||||
BaseDialog dialog = new BaseDialog("@confirm");
|
||||
dialog.setFillParent(true);
|
||||
|
||||
float[] countdown = {60 * 11};
|
||||
@@ -470,9 +463,9 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}).pad(10f).expand().center();
|
||||
|
||||
dialog.buttons.defaults().size(200f, 60f);
|
||||
dialog.buttons.button("$uiscale.cancel", exit);
|
||||
dialog.buttons.button("@uiscale.cancel", exit);
|
||||
|
||||
dialog.buttons.button("$ok", () -> {
|
||||
dialog.buttons.button("@ok", () -> {
|
||||
Core.settings.put("uiscalechanged", false);
|
||||
dialog.hide();
|
||||
});
|
||||
@@ -488,7 +481,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
//TODO find out why this happens on Android
|
||||
//this happens on Android and nobody knows why
|
||||
if(assets == null) return;
|
||||
|
||||
saves.update();
|
||||
@@ -526,7 +519,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
platform.updateRPC();
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.pause) && !state.isOutOfTime() && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
|
||||
if(Core.input.keyTap(Binding.pause) && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
|
||||
state.set(state.is(State.playing) ? State.paused : State.playing);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.assets.loaders.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
|
||||
/** Handles files in a modded context. */
|
||||
public class FileTree implements FileHandleResolver{
|
||||
|
||||
@@ -17,9 +17,9 @@ public class GameState{
|
||||
/** Wave countdown in ticks. */
|
||||
public float wavetime;
|
||||
/** Whether the game is in game over state. */
|
||||
public boolean gameOver = false, launched = false, serverPaused = false;
|
||||
public boolean gameOver = false, serverPaused = false, wasTimeout;
|
||||
/** Map that is currently being played on. */
|
||||
public @NonNull Map map = emptyMap;
|
||||
public Map map = emptyMap;
|
||||
/** The current game rules. */
|
||||
public Rules rules = new Rules();
|
||||
/** Statistics for this save/game. Displayed after game over. */
|
||||
@@ -50,11 +50,6 @@ public class GameState{
|
||||
return rules.sector != null;
|
||||
}
|
||||
|
||||
/** @return whether the player is in a campaign and they are out of sector time */
|
||||
public boolean isOutOfTime(){
|
||||
return isCampaign() && isGame() && getSector().getTimeSpent() >= turnDuration;
|
||||
}
|
||||
|
||||
public boolean hasSector(){
|
||||
return rules.sector != null;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -15,7 +14,7 @@ import mindustry.type.*;
|
||||
import mindustry.type.Weather.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.BuildBlock.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.util.*;
|
||||
@@ -41,9 +40,9 @@ public class Logic implements ApplicationListener{
|
||||
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
|
||||
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
|
||||
|
||||
if(block instanceof BuildBlock){
|
||||
if(block instanceof ConstructBlock){
|
||||
|
||||
BuildEntity entity = tile.bc();
|
||||
ConstructBuild entity = tile.bc();
|
||||
|
||||
//update block to reflect the fact that something was being constructed
|
||||
if(entity.cblock != null && entity.cblock.synthetic()){
|
||||
@@ -89,7 +88,7 @@ public class Logic implements ApplicationListener{
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
if(state.isCampaign()){
|
||||
long seconds = state.rules.sector.getSecondsPassed();
|
||||
CoreEntity core = state.rules.defaultTeam.core();
|
||||
CoreBuild core = state.rules.defaultTeam.core();
|
||||
|
||||
//apply fractional damage based on how many turns have passed for this sector
|
||||
float turnsPassed = seconds / (turnDuration / 60f);
|
||||
@@ -178,7 +177,7 @@ public class Logic implements ApplicationListener{
|
||||
public void runWave(){
|
||||
spawner.spawnEnemies();
|
||||
state.wave++;
|
||||
state.wavetime = state.hasSector() && state.getSector().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
|
||||
Events.fire(new WaveEvent());
|
||||
}
|
||||
@@ -187,7 +186,7 @@ public class Logic implements ApplicationListener{
|
||||
//campaign maps do not have a 'win' state!
|
||||
if(state.isCampaign()){
|
||||
//gameover only when cores are dead
|
||||
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
||||
if(state.teams.playerCores().size == 0 && !state.gameOver){
|
||||
state.gameOver = true;
|
||||
Events.fire(new GameOverEvent(state.rules.waveTeam));
|
||||
}
|
||||
@@ -245,61 +244,15 @@ public class Logic implements ApplicationListener{
|
||||
if(entry.cooldown < 0 && !entry.weather.isActive()){
|
||||
float duration = Mathf.random(entry.minDuration, entry.maxDuration);
|
||||
entry.cooldown = duration + Mathf.random(entry.minFrequency, entry.maxFrequency);
|
||||
Call.createWeather(entry.weather, entry.intensity, duration);
|
||||
Tmp.v1.setToRandomDirection();
|
||||
Call.createWeather(entry.weather, entry.intensity, duration, Tmp.v1.x, Tmp.v1.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.both)
|
||||
public static void launchZone(){
|
||||
if(!state.isCampaign()) return;
|
||||
|
||||
if(!headless){
|
||||
ui.hudfrag.showLaunch();
|
||||
}
|
||||
|
||||
//TODO better core launch effect
|
||||
for(Building tile : state.teams.playerCores()){
|
||||
Fx.launch.at(tile);
|
||||
}
|
||||
|
||||
Sector sector = state.rules.sector;
|
||||
|
||||
//TODO containers must be launched too
|
||||
Time.runTask(30f, () -> {
|
||||
Sector origin = sector.save.meta.secinfo.origin;
|
||||
if(origin != null){
|
||||
ItemSeq stacks = origin.getExtraItems();
|
||||
|
||||
//add up all items into list
|
||||
for(Building entity : state.teams.playerCores()){
|
||||
entity.items.each(stacks::add);
|
||||
}
|
||||
|
||||
//save received items
|
||||
origin.setExtraItems(stacks);
|
||||
}
|
||||
|
||||
//remove all the cores
|
||||
state.teams.playerCores().each(b -> b.tile.remove());
|
||||
|
||||
state.launched = true;
|
||||
state.gameOver = true;
|
||||
|
||||
//save over the data w/o the cores
|
||||
sector.save.save();
|
||||
|
||||
//run a turn, since launching takes up a turn
|
||||
universe.runTurn();
|
||||
|
||||
//TODO apply extra damage to sector
|
||||
//sector.setTurnsPassed(sector.getTurnsPassed() + 3);
|
||||
|
||||
//TODO load the sector that was launched from
|
||||
Events.fire(new LaunchEvent());
|
||||
//manually fire game over event now
|
||||
Events.fire(new GameOverEvent(state.rules.defaultTeam));
|
||||
});
|
||||
public static void updateGameOver(Team winner){
|
||||
state.gameOver = true;
|
||||
}
|
||||
|
||||
@Remote(called = Loc.both)
|
||||
@@ -320,18 +273,19 @@ public class Logic implements ApplicationListener{
|
||||
Events.fire(Trigger.update);
|
||||
universe.updateGlobal();
|
||||
|
||||
if(Core.settings.modified() && !state.isPlaying()){
|
||||
Core.settings.forceSave();
|
||||
}
|
||||
|
||||
if(state.isGame()){
|
||||
if(!net.client()){
|
||||
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted);
|
||||
}
|
||||
|
||||
//force pausing when the player is out of sector time
|
||||
if(state.isOutOfTime()){
|
||||
state.set(State.paused);
|
||||
}
|
||||
|
||||
if(!state.isPaused()){
|
||||
state.secinfo.update();
|
||||
if(state.isCampaign()){
|
||||
state.secinfo.update();
|
||||
}
|
||||
|
||||
if(state.isCampaign()){
|
||||
universe.update();
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
@@ -11,6 +12,7 @@ import arc.util.serialization.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -63,7 +65,7 @@ public class NetClient implements ApplicationListener{
|
||||
reset();
|
||||
|
||||
ui.loadfrag.hide();
|
||||
ui.loadfrag.show("$connecting.data");
|
||||
ui.loadfrag.show("@connecting.data");
|
||||
|
||||
ui.loadfrag.setButton(() -> {
|
||||
ui.loadfrag.hide();
|
||||
@@ -80,7 +82,7 @@ public class NetClient implements ApplicationListener{
|
||||
c.uuid = platform.getUUID();
|
||||
|
||||
if(c.uuid == null){
|
||||
ui.showErrorMessage("$invalidid");
|
||||
ui.showErrorMessage("@invalidid");
|
||||
ui.loadfrag.hide();
|
||||
disconnectQuietly();
|
||||
return;
|
||||
@@ -103,15 +105,13 @@ public class NetClient implements ApplicationListener{
|
||||
Time.runTask(3f, ui.loadfrag::hide);
|
||||
|
||||
if(packet.reason != null){
|
||||
if(packet.reason.equals("closed")){
|
||||
ui.showSmall("$disconnect", "$disconnect.closed");
|
||||
}else if(packet.reason.equals("timeout")){
|
||||
ui.showSmall("$disconnect", "$disconnect.timeout");
|
||||
}else if(packet.reason.equals("error")){
|
||||
ui.showSmall("$disconnect", "$disconnect.error");
|
||||
switch(packet.reason){
|
||||
case "closed" -> ui.showSmall("@disconnect", "@disconnect.closed");
|
||||
case "timeout" -> ui.showSmall("@disconnect", "@disconnect.timeout");
|
||||
case "error" -> ui.showSmall("@disconnect", "@disconnect.error");
|
||||
}
|
||||
}else{
|
||||
ui.showErrorMessage("$disconnect");
|
||||
ui.showErrorMessage("@disconnect");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -261,7 +261,7 @@ public class NetClient implements ApplicationListener{
|
||||
if(reason.extraText() != null){
|
||||
ui.showText(reason.toString(), reason.extraText());
|
||||
}else{
|
||||
ui.showText("$disconnect", reason.toString());
|
||||
ui.showText("@disconnect", reason.toString());
|
||||
}
|
||||
}
|
||||
ui.loadfrag.hide();
|
||||
@@ -271,7 +271,7 @@ public class NetClient implements ApplicationListener{
|
||||
public static void kick(String reason){
|
||||
netClient.disconnectQuietly();
|
||||
logic.reset();
|
||||
ui.showText("$disconnect", reason, Align.left);
|
||||
ui.showText("@disconnect", reason, Align.left);
|
||||
ui.loadfrag.hide();
|
||||
}
|
||||
|
||||
@@ -314,7 +314,6 @@ public class NetClient implements ApplicationListener{
|
||||
ui.showLabel(message, duration, worldx, worldy);
|
||||
}
|
||||
|
||||
/*
|
||||
@Remote(variants = Variant.both, unreliable = true)
|
||||
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
|
||||
if(effect == null) return;
|
||||
@@ -325,7 +324,7 @@ public class NetClient implements ApplicationListener{
|
||||
@Remote(variants = Variant.both)
|
||||
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
|
||||
onEffect(effect, x, y, rotation, color);
|
||||
}*/
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void infoToast(String message, float duration){
|
||||
@@ -344,10 +343,11 @@ public class NetClient implements ApplicationListener{
|
||||
Groups.clear();
|
||||
netClient.removed.clear();
|
||||
logic.reset();
|
||||
netClient.connecting = true;
|
||||
|
||||
net.setClientLoaded(false);
|
||||
|
||||
ui.loadfrag.show("$connecting.data");
|
||||
ui.loadfrag.show("@connecting.data");
|
||||
|
||||
ui.loadfrag.setButton(() -> {
|
||||
ui.loadfrag.hide();
|
||||
@@ -364,6 +364,9 @@ public class NetClient implements ApplicationListener{
|
||||
|
||||
@Remote
|
||||
public static void playerDisconnect(int playerid){
|
||||
if(netClient != null){
|
||||
netClient.addRemovedEntity(playerid);
|
||||
}
|
||||
Groups.player.removeByID(playerid);
|
||||
}
|
||||
|
||||
@@ -435,18 +438,21 @@ public class NetClient implements ApplicationListener{
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
|
||||
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, short coreDataLen, byte[] coreData){
|
||||
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, boolean gameOver, int timeData, short coreDataLen, byte[] coreData){
|
||||
try{
|
||||
if(wave > state.wave){
|
||||
state.wave = wave;
|
||||
Events.fire(new WaveEvent());
|
||||
}
|
||||
|
||||
state.gameOver = gameOver;
|
||||
state.wavetime = waveTime;
|
||||
state.wave = wave;
|
||||
state.enemies = enemies;
|
||||
state.serverPaused = paused;
|
||||
|
||||
universe.updateNetSeconds(timeData);
|
||||
|
||||
netClient.byteStream.setBytes(net.decompressSnapshot(coreData, coreDataLen));
|
||||
DataInputStream input = netClient.dataStream;
|
||||
|
||||
@@ -481,7 +487,7 @@ public class NetClient implements ApplicationListener{
|
||||
Log.err("Failed to load data!");
|
||||
ui.loadfrag.hide();
|
||||
quiet = true;
|
||||
ui.showErrorMessage("$disconnect.data");
|
||||
ui.showErrorMessage("@disconnect.data");
|
||||
net.disconnect();
|
||||
timeoutTime = 0f;
|
||||
}
|
||||
@@ -541,6 +547,10 @@ public class NetClient implements ApplicationListener{
|
||||
quiet = true;
|
||||
}
|
||||
|
||||
public void clearRemovedEntity(int id){
|
||||
removed.remove(id);
|
||||
}
|
||||
|
||||
public void addRemovedEntity(int id){
|
||||
removed.add(id);
|
||||
}
|
||||
@@ -552,10 +562,26 @@ public class NetClient implements ApplicationListener{
|
||||
void sync(){
|
||||
if(timer.get(0, playerSyncTime)){
|
||||
BuildPlan[] requests = null;
|
||||
if(player.isBuilder() && control.input.isBuilding){
|
||||
if(player.isBuilder()){
|
||||
//limit to 10 to prevent buffer overflows
|
||||
int usedRequests = Math.min(player.builder().plans().size, 10);
|
||||
|
||||
int totalLength = 0;
|
||||
|
||||
//prevent buffer overflow by checking config length
|
||||
for(int i = 0; i < usedRequests; i++){
|
||||
BuildPlan plan = player.builder().plans().get(i);
|
||||
if(plan.config instanceof byte[]){
|
||||
int length = ((byte[])plan.config).length;
|
||||
totalLength += length;
|
||||
}
|
||||
|
||||
if(totalLength > 2048){
|
||||
usedRequests = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requests = new BuildPlan[usedRequests];
|
||||
for(int i = 0; i < usedRequests; i++){
|
||||
requests[i] = player.builder().plans().get(i);
|
||||
@@ -563,8 +589,11 @@ public class NetClient implements ApplicationListener{
|
||||
}
|
||||
|
||||
Unit unit = player.dead() ? Nulls.unit : player.unit();
|
||||
int uid = player.dead() ? -1 : unit.id;
|
||||
|
||||
Call.clientShapshot(lastSent++,
|
||||
Call.clientSnapshot(
|
||||
lastSent++,
|
||||
uid,
|
||||
player.dead(),
|
||||
unit.x, unit.y,
|
||||
player.unit().aimX(), player.unit().aimY(),
|
||||
@@ -572,10 +601,11 @@ public class NetClient implements ApplicationListener{
|
||||
unit instanceof Mechc ? ((Mechc)unit).baseRotation() : 0,
|
||||
unit.vel.x, unit.vel.y,
|
||||
player.miner().mineTile(),
|
||||
player.boosting, player.shooting, ui.chatfrag.shown(),
|
||||
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
|
||||
requests,
|
||||
Core.camera.position.x, Core.camera.position.y,
|
||||
Core.camera.width * viewScale, Core.camera.height * viewScale);
|
||||
Core.camera.width * viewScale, Core.camera.height * viewScale
|
||||
);
|
||||
}
|
||||
|
||||
if(timer.get(1, 60)){
|
||||
|
||||
@@ -34,8 +34,9 @@ import static arc.util.Log.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class NetServer implements ApplicationListener{
|
||||
private static final int maxSnapshotSize = 430, timerBlockSync = 0;
|
||||
private static final float serverSyncTime = 12, blockSyncTime = 60 * 8;
|
||||
/** note that snapshots are compressed, so the max snapshot size here is above the typical UDP safe limit */
|
||||
private static final int maxSnapshotSize = 800, timerBlockSync = 0;
|
||||
private static final float serverSyncTime = 12, blockSyncTime = 60 * 6;
|
||||
private static final FloatBuffer fbuffer = FloatBuffer.allocate(20);
|
||||
private static final Vec2 vector = new Vec2();
|
||||
private static final Rect viewport = new Rect();
|
||||
@@ -130,7 +131,7 @@ public class NetServer implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
if(Time.millis() < info.lastKicked){
|
||||
if(Time.millis() < admins.getKickTime(uuid, con.address)){
|
||||
con.kick(KickReason.recentKick);
|
||||
return;
|
||||
}
|
||||
@@ -173,7 +174,7 @@ public class NetServer implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
boolean preventDuplicates = headless && netServer.admins.getStrict();
|
||||
boolean preventDuplicates = headless && netServer.admins.isStrict();
|
||||
|
||||
if(preventDuplicates){
|
||||
if(Groups.player.contains(p -> p.name.trim().equalsIgnoreCase(packet.name.trim()))){
|
||||
@@ -332,6 +333,8 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
Call.sendMessage(Strings.format("[lightgray]A player has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
|
||||
target.name, votes, votesRequired()));
|
||||
|
||||
checkPass();
|
||||
}
|
||||
|
||||
boolean checkPass(){
|
||||
@@ -368,6 +371,11 @@ public class NetServer implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
if(currentlyKicking[0] != null){
|
||||
player.sendMessage("[scarlet]A vote is already in progress.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(args.length == 0){
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("[orange]Players to kick: \n");
|
||||
@@ -382,9 +390,7 @@ public class NetServer implements ApplicationListener{
|
||||
int id = Strings.parseInt(args[0].substring(1));
|
||||
found = Groups.player.find(p -> p.id() == id);
|
||||
}else{
|
||||
found = Groups.player.find(p -> {
|
||||
return p.name.equalsIgnoreCase(args[0]);
|
||||
});
|
||||
found = Groups.player.find(p -> p.name.equalsIgnoreCase(args[0]));
|
||||
}
|
||||
|
||||
if(found != null){
|
||||
@@ -479,7 +485,7 @@ public class NetServer implements ApplicationListener{
|
||||
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
||||
player.con.sendStream(data);
|
||||
|
||||
Log.debug("Packed @ compressed bytes of world data.", stream.size());
|
||||
Log.debug("Packed @ bytes of world data.", stream.size());
|
||||
}
|
||||
|
||||
public void addPacketHandler(String type, Cons2<Player, String> handler){
|
||||
@@ -524,25 +530,40 @@ public class NetServer implements ApplicationListener{
|
||||
public static void serverPacketUnreliable(Player player, String type, String contents){
|
||||
serverPacketReliable(player, type, contents);
|
||||
}
|
||||
|
||||
private static boolean invalid(float f){
|
||||
return Float.isInfinite(f) || Float.isNaN(f);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client, unreliable = true)
|
||||
public static void clientShapshot(
|
||||
public static void clientSnapshot(
|
||||
Player player,
|
||||
int snapshotID,
|
||||
int unitID,
|
||||
boolean dead,
|
||||
float x, float y,
|
||||
float pointerX, float pointerY,
|
||||
float rotation, float baseRotation,
|
||||
float xVelocity, float yVelocity,
|
||||
Tile mining,
|
||||
boolean boosting, boolean shooting, boolean chatting,
|
||||
boolean boosting, boolean shooting, boolean chatting, boolean building,
|
||||
@Nullable BuildPlan[] requests,
|
||||
float viewX, float viewY, float viewWidth, float viewHeight
|
||||
){
|
||||
NetConnection con = player.con;
|
||||
if(con == null || snapshotID < con.lastReceivedClientSnapshot) return;
|
||||
|
||||
boolean verifyPosition = !player.dead() && netServer.admins.getStrict() && headless;
|
||||
//validate coordinates just in case
|
||||
if(invalid(x)) x = 0f;
|
||||
if(invalid(y)) y = 0f;
|
||||
if(invalid(xVelocity)) xVelocity = 0f;
|
||||
if(invalid(yVelocity)) yVelocity = 0f;
|
||||
if(invalid(pointerX)) pointerX = 0f;
|
||||
if(invalid(pointerY)) pointerY = 0f;
|
||||
if(invalid(rotation)) rotation = 0f;
|
||||
if(invalid(baseRotation)) baseRotation = 0f;
|
||||
|
||||
boolean verifyPosition = netServer.admins.isStrict() && headless;
|
||||
|
||||
if(con.lastReceivedClientTime == 0) con.lastReceivedClientTime = Time.millis() - 16;
|
||||
|
||||
@@ -556,7 +577,10 @@ public class NetServer implements ApplicationListener{
|
||||
shooting = false;
|
||||
}
|
||||
|
||||
//TODO these need to be assigned elsewhere
|
||||
if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
|
||||
boosting = false;
|
||||
}
|
||||
|
||||
player.mouseX = pointerX;
|
||||
player.mouseY = pointerY;
|
||||
player.typing = chatting;
|
||||
@@ -568,80 +592,80 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
if(player.isBuilder()){
|
||||
player.builder().clearBuilding();
|
||||
player.builder().updateBuilding(building);
|
||||
|
||||
if(requests != null){
|
||||
for(BuildPlan req : requests){
|
||||
if(req == null) continue;
|
||||
Tile tile = world.tile(req.x, req.y);
|
||||
if(tile == null || (!req.breaking && req.block == null)) continue;
|
||||
//auto-skip done requests
|
||||
if(req.breaking && tile.block() == Blocks.air){
|
||||
continue;
|
||||
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || (tile.build != null && tile.build.rotation == req.rotation))){
|
||||
continue;
|
||||
}else if(con.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
|
||||
continue;
|
||||
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
|
||||
action.block = req.block;
|
||||
action.rotation = req.rotation;
|
||||
action.config = req.config;
|
||||
})){
|
||||
//force the player to remove this request if that's not the case
|
||||
Call.removeQueueBlock(player.con, req.x, req.y, req.breaking);
|
||||
con.rejectedRequests.add(req);
|
||||
continue;
|
||||
}
|
||||
player.builder().plans().addLast(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(player.isMiner()){
|
||||
player.miner().mineTile(mining);
|
||||
}
|
||||
|
||||
if(requests != null){
|
||||
for(BuildPlan req : requests){
|
||||
if(req == null) continue;
|
||||
Tile tile = world.tile(req.x, req.y);
|
||||
if(tile == null || (!req.breaking && req.block == null)) continue;
|
||||
//auto-skip done requests
|
||||
if(req.breaking && tile.block() == Blocks.air){
|
||||
continue;
|
||||
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || (tile.build != null && tile.build.rotation == req.rotation))){
|
||||
continue;
|
||||
}else if(con.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
|
||||
continue;
|
||||
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
|
||||
action.block = req.block;
|
||||
action.rotation = req.rotation;
|
||||
action.config = req.config;
|
||||
})){
|
||||
//force the player to remove this request if that's not the case
|
||||
Call.removeQueueBlock(player.con, req.x, req.y, req.breaking);
|
||||
con.rejectedRequests.add(req);
|
||||
continue;
|
||||
}
|
||||
player.builder().plans().addLast(req);
|
||||
}
|
||||
}
|
||||
|
||||
con.rejectedRequests.clear();
|
||||
|
||||
if(!player.dead()){
|
||||
Unit unit = player.unit();
|
||||
|
||||
unit.vel.set(xVelocity, yVelocity).limit(unit.type().speed);
|
||||
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
|
||||
float maxSpeed = player.unit().type().speed;
|
||||
float maxSpeed = ((player.unit().type().canBoost && player.unit().isFlying()) ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
|
||||
if(unit.isGrounded()){
|
||||
maxSpeed *= unit.floorSpeedMultiplier();
|
||||
}
|
||||
|
||||
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
|
||||
|
||||
if(con.lastUnit != unit){
|
||||
con.lastUnit = unit;
|
||||
con.lastPosition.set(unit);
|
||||
}
|
||||
|
||||
//if the player think they're dead their position should be ignored
|
||||
if(dead){
|
||||
x = unit.x;
|
||||
y = unit.y;
|
||||
}
|
||||
|
||||
vector.set(x, y).sub(con.lastPosition);
|
||||
vector.limit(maxMove);
|
||||
|
||||
float prevx = unit.x, prevy = unit.y;
|
||||
unit.set(con.lastPosition);
|
||||
if(!unit.isFlying()){
|
||||
unit.move(vector.x, vector.y);
|
||||
}else{
|
||||
unit.trns(vector.x, vector.y);
|
||||
}
|
||||
|
||||
//set last position after movement
|
||||
con.lastPosition.set(unit);
|
||||
//ignore the position if the player thinks they're dead, or the unit is wrong
|
||||
boolean ignorePosition = dead || unit.id != unitID;
|
||||
float newx = unit.x, newy = unit.y;
|
||||
|
||||
if(!verifyPosition){
|
||||
unit.set(prevx, prevy);
|
||||
newx = x;
|
||||
newy = y;
|
||||
}else if(!Mathf.within(x, y, newx, newy, correctDist) && !dead){
|
||||
Call.setPosition(player.con, newx, newy); //teleport and correct position when necessary
|
||||
if(!ignorePosition){
|
||||
unit.vel.set(xVelocity, yVelocity).limit(maxSpeed);
|
||||
|
||||
vector.set(x, y).sub(unit);
|
||||
vector.limit(maxMove);
|
||||
|
||||
float prevx = unit.x, prevy = unit.y;
|
||||
//unit.set(con.lastPosition);
|
||||
if(!unit.isFlying()){
|
||||
unit.move(vector.x, vector.y);
|
||||
}else{
|
||||
unit.trns(vector.x, vector.y);
|
||||
}
|
||||
|
||||
newx = unit.x;
|
||||
newy = unit.y;
|
||||
|
||||
if(!verifyPosition){
|
||||
unit.set(prevx, prevy);
|
||||
newx = x;
|
||||
newy = y;
|
||||
}else if(!Mathf.within(x, y, newx, newy, correctDist)){
|
||||
Call.setPosition(player.con, newx, newy); //teleport and correct position when necessary
|
||||
}
|
||||
}
|
||||
|
||||
//write sync data to the buffer
|
||||
@@ -671,8 +695,8 @@ public class NetServer implements ApplicationListener{
|
||||
public static void adminRequest(Player player, Player other, AdminAction action){
|
||||
|
||||
if(!player.admin){
|
||||
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action without proper security access.",
|
||||
player.name, player.con.address);
|
||||
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action '@' on '@' without proper security access.",
|
||||
player.name, player.con.address, action.name(), other == null ? null : other.name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -706,13 +730,15 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
public static void connectConfirm(Player player){
|
||||
player.add();
|
||||
|
||||
if(player.con == null || player.con.hasConnected) return;
|
||||
|
||||
player.add();
|
||||
player.con.hasConnected = true;
|
||||
|
||||
if(Config.showConnectMessages.bool()){
|
||||
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
|
||||
Log.info("&lm[@] &y@ has connected. ", player.uuid(), player.name);
|
||||
Log.info("&lm[@] &y@ has connected.", player.uuid(), player.name);
|
||||
}
|
||||
|
||||
if(!Config.motd.string().equalsIgnoreCase("off")){
|
||||
@@ -723,7 +749,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
public boolean isWaitingForPlayers(){
|
||||
if(state.rules.pvp){
|
||||
if(state.rules.pvp && !state.gameOver){
|
||||
int used = 0;
|
||||
for(TeamData t : state.teams.getActive()){
|
||||
if(Groups.player.count(p -> p.team() == t.team) > 0){
|
||||
@@ -740,7 +766,7 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
if(!headless && !closing && net.server() && state.isMenu()){
|
||||
closing = true;
|
||||
ui.loadfrag.show("$server.closing");
|
||||
ui.loadfrag.show("@server.closing");
|
||||
Time.runTask(5f, () -> {
|
||||
net.closeServer();
|
||||
ui.loadfrag.hide();
|
||||
@@ -749,6 +775,10 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
if(state.isGame() && net.server()){
|
||||
if(state.rules.pvp){
|
||||
state.serverPaused = isWaitingForPlayers();
|
||||
}
|
||||
|
||||
sync();
|
||||
}
|
||||
}
|
||||
@@ -778,11 +808,11 @@ public class NetServer implements ApplicationListener{
|
||||
syncStream.reset();
|
||||
|
||||
short sent = 0;
|
||||
for(Building entity : Groups.tile){
|
||||
if(!entity.block().sync) continue;
|
||||
for(Building entity : Groups.build){
|
||||
if(!entity.block.sync) continue;
|
||||
sent ++;
|
||||
|
||||
dataStream.writeInt(entity.tile().pos());
|
||||
dataStream.writeInt(entity.pos());
|
||||
entity.writeAll(Writes.get(dataStream));
|
||||
|
||||
if(syncStream.size() > maxSnapshotSize){
|
||||
@@ -803,12 +833,12 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
public void writeEntitySnapshot(Player player) throws IOException{
|
||||
syncStream.reset();
|
||||
Seq<CoreEntity> cores = state.teams.cores(player.team());
|
||||
Seq<CoreBuild> cores = state.teams.cores(player.team());
|
||||
|
||||
dataStream.writeByte(cores.size);
|
||||
|
||||
for(CoreEntity entity : cores){
|
||||
dataStream.writeInt(entity.tile().pos());
|
||||
for(CoreBuild entity : cores){
|
||||
dataStream.writeInt(entity.tile.pos());
|
||||
entity.items.write(Writes.get(dataStream));
|
||||
}
|
||||
|
||||
@@ -816,7 +846,7 @@ public class NetServer implements ApplicationListener{
|
||||
byte[] stateBytes = syncStream.toByteArray();
|
||||
|
||||
//write basic state data.
|
||||
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, (short)stateBytes.length, net.compressSnapshot(stateBytes));
|
||||
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, state.gameOver, universe.seconds(), (short)stateBytes.length, net.compressSnapshot(stateBytes));
|
||||
|
||||
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);
|
||||
|
||||
|
||||
@@ -14,10 +14,18 @@ import mindustry.type.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import rhino.*;
|
||||
|
||||
import java.net.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public interface Platform{
|
||||
|
||||
/** Dynamically loads a jar file. */
|
||||
default Class<?> loadJar(Fi jar, String mainClass) throws Exception{
|
||||
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
|
||||
return classLoader.loadClass(mainClass);
|
||||
}
|
||||
|
||||
/** Steam: Update lobby visibility.*/
|
||||
default void updateLobby(){}
|
||||
|
||||
@@ -111,7 +119,7 @@ public interface Platform{
|
||||
* @param extension File extension to filter
|
||||
*/
|
||||
default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
|
||||
new FileChooser(open ? "$open" : "$save", file -> file.extEquals(extension), open, file -> {
|
||||
new FileChooser(open ? "@open" : "@save", file -> file.extEquals(extension), open, file -> {
|
||||
if(!open){
|
||||
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
|
||||
}else{
|
||||
@@ -121,7 +129,7 @@ public interface Platform{
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a file chooser for multiple file types. Only supported on desktop.
|
||||
* Show a file chooser for multiple file types.
|
||||
* @param cons Selection listener
|
||||
* @param extensions File extensions to filter
|
||||
*/
|
||||
@@ -129,7 +137,7 @@ public interface Platform{
|
||||
if(mobile){
|
||||
showFileChooser(true, extensions[0], cons);
|
||||
}else{
|
||||
new FileChooser("$open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
||||
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ public class Renderer implements ApplicationListener{
|
||||
public PlanetRenderer planets;
|
||||
|
||||
public FrameBuffer effectBuffer = new FrameBuffer();
|
||||
public float laserOpacity = 1f;
|
||||
|
||||
private Bloom bloom;
|
||||
private FxProcessor fx = new FxProcessor();
|
||||
private Color clearColor = new Color(0f, 0f, 0f, 1f);
|
||||
@@ -59,8 +61,10 @@ public class Renderer implements ApplicationListener{
|
||||
@Override
|
||||
public void update(){
|
||||
Color.white.set(1f, 1f, 1f, 1f);
|
||||
Gl.clear(Gl.stencilBufferBit);
|
||||
|
||||
camerascale = Mathf.lerpDelta(camerascale, targetscale, 0.1f);
|
||||
laserOpacity = Core.settings.getInt("lasersopacity") / 100f;
|
||||
|
||||
if(landTime > 0){
|
||||
landTime -= Time.delta;
|
||||
@@ -138,7 +142,7 @@ public class Renderer implements ApplicationListener{
|
||||
}catch(Throwable e){
|
||||
e.printStackTrace();
|
||||
settings.put("bloom", false);
|
||||
ui.showErrorMessage("$error.bloom");
|
||||
ui.showErrorMessage("@error.bloom");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +189,8 @@ public class Renderer implements ApplicationListener{
|
||||
}
|
||||
|
||||
public void draw(){
|
||||
Events.fire(Trigger.preDraw);
|
||||
|
||||
camera.update();
|
||||
|
||||
if(Float.isNaN(camera.position.x) || Float.isNaN(camera.position.y)){
|
||||
@@ -205,12 +211,12 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
Draw.sort(true);
|
||||
|
||||
Events.fire(Trigger.draw);
|
||||
|
||||
if(pixelator.enabled()){
|
||||
pixelator.register();
|
||||
}
|
||||
|
||||
//TODO fx
|
||||
|
||||
Draw.draw(Layer.background, this::drawBackground);
|
||||
Draw.draw(Layer.floor, blocks.floor::drawFloor);
|
||||
Draw.draw(Layer.block - 1, blocks::drawShadows);
|
||||
@@ -254,6 +260,8 @@ public class Renderer implements ApplicationListener{
|
||||
Draw.reset();
|
||||
Draw.flush();
|
||||
Draw.sort(false);
|
||||
|
||||
Events.fire(Trigger.postDraw);
|
||||
}
|
||||
|
||||
private void drawBackground(){
|
||||
@@ -265,9 +273,9 @@ public class Renderer implements ApplicationListener{
|
||||
float fract = landTime / Fx.coreLand.lifetime;
|
||||
Building entity = player.closestCore();
|
||||
|
||||
TextureRegion reg = entity.block().icon(Cicon.full);
|
||||
TextureRegion reg = entity.block.icon(Cicon.full);
|
||||
float scl = Scl.scl(4f) / camerascale;
|
||||
float s = reg.getWidth() * Draw.scl * scl * 4f * fract;
|
||||
float s = reg.width * Draw.scl * scl * 4f * fract;
|
||||
|
||||
Draw.color(Pal.lightTrail);
|
||||
Draw.rect("circle-shadow", entity.getX(), entity.getY(), s, s);
|
||||
@@ -279,7 +287,7 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
Draw.color();
|
||||
Draw.mixcol(Color.white, fract);
|
||||
Draw.rect(reg, entity.getX(), entity.getY(), reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
|
||||
Draw.rect(reg, entity.getX(), entity.getY(), reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fract * 135f);
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
@@ -318,7 +326,7 @@ public class Renderer implements ApplicationListener{
|
||||
int memory = w * h * 4 / 1024 / 1024;
|
||||
|
||||
if(memory >= 65){
|
||||
ui.showInfo("$screenshot.invalid");
|
||||
ui.showInfo("@screenshot.invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import mindustry.editor.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.ui.fragments.*;
|
||||
@@ -67,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public SchematicsDialog schematics;
|
||||
public ModsDialog mods;
|
||||
public ColorPicker picker;
|
||||
public LogicDialog logic;
|
||||
|
||||
public Cursor drillCursor, unloadCursor;
|
||||
|
||||
@@ -126,6 +128,8 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public void update(){
|
||||
if(disableUI || Core.scene == null) return;
|
||||
|
||||
Events.fire(Trigger.uiDrawBegin);
|
||||
|
||||
Core.scene.act();
|
||||
Core.scene.draw();
|
||||
|
||||
@@ -141,6 +145,8 @@ public class UI implements ApplicationListener, Loadable{
|
||||
control.tutorial.draw();
|
||||
Draw.flush();
|
||||
}
|
||||
|
||||
Events.fire(Trigger.uiDrawEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,6 +185,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
research = new ResearchDialog();
|
||||
mods = new ModsDialog();
|
||||
schematics = new SchematicsDialog();
|
||||
logic = new LogicDialog();
|
||||
|
||||
Group group = Core.scene.root;
|
||||
|
||||
@@ -218,14 +225,17 @@ public class UI implements ApplicationListener, Loadable{
|
||||
}
|
||||
|
||||
public TextureRegionDrawable getIcon(String name){
|
||||
if(Icon.icons.containsKey(name)){
|
||||
return Icon.icons.get(name);
|
||||
}
|
||||
if(Icon.icons.containsKey(name)) return Icon.icons.get(name);
|
||||
return Core.atlas.getDrawable("error");
|
||||
}
|
||||
|
||||
public TextureRegionDrawable getIcon(String name, String def){
|
||||
if(Icon.icons.containsKey(name)) return Icon.icons.get(name);
|
||||
return getIcon(def);
|
||||
}
|
||||
|
||||
public void loadAnd(Runnable call){
|
||||
loadAnd("$loading", call);
|
||||
loadAnd("@loading", call);
|
||||
}
|
||||
|
||||
public void loadAnd(String text, Runnable call){
|
||||
@@ -239,7 +249,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Cons<String> confirmed){
|
||||
if(mobile){
|
||||
Core.input.getTextInput(new TextInput(){{
|
||||
this.title = (titleText.startsWith("$") ? Core.bundle.get(titleText.substring(1)) : titleText);
|
||||
this.title = (titleText.startsWith("@") ? Core.bundle.get(titleText.substring(1)) : titleText);
|
||||
this.text = def;
|
||||
this.numeric = inumeric;
|
||||
this.maxLength = textLength;
|
||||
@@ -252,11 +262,11 @@ public class UI implements ApplicationListener, Loadable{
|
||||
TextField field = cont.field(def, t -> {}).size(330f, 50f).get();
|
||||
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
|
||||
buttons.defaults().size(120, 54).pad(4);
|
||||
buttons.button("$ok", () -> {
|
||||
buttons.button("@ok", () -> {
|
||||
confirmed.get(field.getText());
|
||||
hide();
|
||||
}).disabled(b -> field.getText().isEmpty());
|
||||
buttons.button("$cancel", this::hide);
|
||||
buttons.button("@cancel", this::hide);
|
||||
keyDown(KeyCode.enter, () -> {
|
||||
String text = field.getText();
|
||||
if(!text.isEmpty()){
|
||||
@@ -283,6 +293,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
public void showInfoFade(String info){
|
||||
Table table = new Table();
|
||||
table.touchable = Touchable.disabled;
|
||||
table.setFillParent(true);
|
||||
table.actions(Actions.fadeOut(7f, Interp.fade), Actions.remove());
|
||||
table.top().add(info).style(Styles.outlineLabel).padTop(10);
|
||||
@@ -341,7 +352,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(""){{
|
||||
getCell(cont).growX();
|
||||
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
|
||||
buttons.button("$ok", () -> {
|
||||
buttons.button("@ok", () -> {
|
||||
hide();
|
||||
listener.run();
|
||||
}).size(110, 50).pad(4);
|
||||
@@ -352,7 +363,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(""){{
|
||||
getCell(cont).growX();
|
||||
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.left);
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -360,13 +371,13 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(""){{
|
||||
setFillParent(true);
|
||||
cont.margin(15f);
|
||||
cont.add("$error.title");
|
||||
cont.add("@error.title");
|
||||
cont.row();
|
||||
cont.image().width(300f).pad(2).height(4f).color(Color.scarlet);
|
||||
cont.row();
|
||||
cont.add(text).pad(2f).growX().wrap().get().setAlignment(Align.center);
|
||||
cont.row();
|
||||
cont.button("$ok", this::hide).size(120, 50).pad(4);
|
||||
cont.button("@ok", this::hide).size(120, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -381,17 +392,17 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
setFillParent(true);
|
||||
cont.margin(15);
|
||||
cont.add("$error.title").colspan(2);
|
||||
cont.add("@error.title").colspan(2);
|
||||
cont.row();
|
||||
cont.image().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
|
||||
cont.row();
|
||||
cont.add((text.startsWith("$") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
|
||||
cont.add((text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
|
||||
cont.row();
|
||||
|
||||
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(Strings.neatError(exc)).color(Color.lightGray).left()), true);
|
||||
|
||||
cont.button("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
|
||||
cont.button("$ok", this::hide).size(110, 50).fillX().left();
|
||||
cont.button("@details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
|
||||
cont.button("@ok", this::hide).size(110, 50).fillX().left();
|
||||
cont.row();
|
||||
cont.add(col).colspan(2).pad(2);
|
||||
}}.show();
|
||||
@@ -408,14 +419,14 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.row();
|
||||
cont.add(text).width(400f).wrap().get().setAlignment(align, align);
|
||||
cont.row();
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
public void showInfoText(String titleText, String text){
|
||||
new Dialog(titleText){{
|
||||
cont.margin(15).add(text).width(400f).wrap().left().get().setAlignment(Align.left, Align.left);
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -424,7 +435,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.margin(10).add(text);
|
||||
titleTable.row();
|
||||
titleTable.image().color(Pal.accent).height(3f).growX().pad(2f);
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -437,8 +448,8 @@ public class UI implements ApplicationListener, Loadable{
|
||||
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
|
||||
dialog.buttons.defaults().size(200f, 54f).pad(2f);
|
||||
dialog.setFillParent(false);
|
||||
dialog.buttons.button("$cancel", dialog::hide);
|
||||
dialog.buttons.button("$ok", () -> {
|
||||
dialog.buttons.button("@cancel", dialog::hide);
|
||||
dialog.buttons.button("@ok", () -> {
|
||||
dialog.hide();
|
||||
confirmed.run();
|
||||
});
|
||||
@@ -478,10 +489,11 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
public void announce(String text){
|
||||
Table t = new Table();
|
||||
t.touchable = Touchable.disabled;
|
||||
t.background(Styles.black3).margin(8f)
|
||||
.add(text).style(Styles.outlineLabel);
|
||||
.add(text).style(Styles.outlineLabel).labelAlign(Align.center);
|
||||
t.update(() -> t.setPosition(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Align.center));
|
||||
t.actions(Actions.fadeOut(3, Interp.pow4In));
|
||||
t.actions(Actions.fadeOut(3, Interp.pow4In), Actions.remove());
|
||||
Core.scene.add(t);
|
||||
}
|
||||
|
||||
@@ -490,7 +502,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
|
||||
dialog.buttons.defaults().size(200f, 54f).pad(2f);
|
||||
dialog.setFillParent(false);
|
||||
dialog.buttons.button("$ok", () -> {
|
||||
dialog.buttons.button("@ok", () -> {
|
||||
dialog.hide();
|
||||
confirmed.run();
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.Files.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
|
||||
@@ -45,4 +45,12 @@ public class Version{
|
||||
build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** get menu version without colors */
|
||||
public static String combined(){
|
||||
if(build == -1){
|
||||
return "custom build";
|
||||
}
|
||||
return (type.equals("official") ? modifier : type) + " build " + build + (revision == 0 ? "" : "." + revision);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.struct.ObjectIntMap.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import arc.util.noise.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -19,7 +21,6 @@ import mindustry.maps.*;
|
||||
import mindustry.maps.filters.*;
|
||||
import mindustry.maps.filters.GenerateFilter.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.type.Sector.*;
|
||||
import mindustry.type.Weather.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
@@ -30,14 +31,20 @@ import static mindustry.Vars.*;
|
||||
public class World{
|
||||
public final Context context = new Context();
|
||||
|
||||
public @NonNull Tiles tiles = new Tiles(0, 0);
|
||||
public Tiles tiles = new Tiles(0, 0);
|
||||
|
||||
private boolean generating, invalidMap;
|
||||
private ObjectMap<Map, Runnable> customMapLoaders = new ObjectMap<>();
|
||||
|
||||
public World(){
|
||||
|
||||
}
|
||||
|
||||
/** Adds a custom handler function for loading a custom map - usually a generated one. */
|
||||
public void addMapLoader(Map map, Runnable loader){
|
||||
customMapLoaders.put(map, loader);
|
||||
}
|
||||
|
||||
public boolean isInvalidMap(){
|
||||
return invalidMap;
|
||||
}
|
||||
@@ -79,13 +86,11 @@ public class World{
|
||||
return height()*tilesize;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Floor floor(int x, int y){
|
||||
Tile tile = tile(x, y);
|
||||
return tile == null ? Blocks.air.asFloor() : tile.floor();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Floor floorWorld(float x, float y){
|
||||
Tile tile = tileWorld(x, y);
|
||||
return tile == null ? Blocks.air.asFloor() : tile.floor();
|
||||
@@ -105,7 +110,9 @@ public class World{
|
||||
public Tile tileBuilding(int x, int y){
|
||||
Tile tile = tiles.get(x, y);
|
||||
if(tile == null) return null;
|
||||
if(tile.build != null) return tile.build.tile();
|
||||
if(tile.build != null){
|
||||
return tile.build.tile();
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
@@ -123,7 +130,6 @@ public class World{
|
||||
return tile.build;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Tile rawTile(int x, int y){
|
||||
return tiles.getn(x, y);
|
||||
}
|
||||
@@ -185,16 +191,12 @@ public class World{
|
||||
continue;
|
||||
}
|
||||
|
||||
tile.updateOcclusion();
|
||||
|
||||
if(tile.build != null){
|
||||
tile.build.updateProximity();
|
||||
}
|
||||
}
|
||||
|
||||
if(!headless){
|
||||
addDarkness(tiles);
|
||||
}
|
||||
addDarkness(tiles);
|
||||
|
||||
Groups.resize(-finalWorldBounds, -finalWorldBounds, tiles.width * tilesize + finalWorldBounds * 2, tiles.height * tilesize + finalWorldBounds * 2);
|
||||
|
||||
@@ -257,9 +259,71 @@ public class World{
|
||||
|
||||
state.rules.weather.clear();
|
||||
|
||||
if(sector.is(SectorAttribute.rainy)) state.rules.weather.add(new WeatherEntry(Weathers.rain));
|
||||
if(sector.is(SectorAttribute.snowy)) state.rules.weather.add(new WeatherEntry(Weathers.snow));
|
||||
if(sector.is(SectorAttribute.desert)) state.rules.weather.add(new WeatherEntry(Weathers.sandstorm));
|
||||
//apply weather based on terrain
|
||||
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
|
||||
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
||||
|
||||
float waterFloors = 0, totalFloors = 0;
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
if(world.getDarkness(tile.x, tile.y) >= 3){
|
||||
continue;
|
||||
}
|
||||
|
||||
Liquid liquid = tile.floor().liquidDrop;
|
||||
if(tile.floor().itemDrop != null) content.add(tile.floor().itemDrop);
|
||||
if(tile.overlay().itemDrop != null) content.add(tile.overlay().itemDrop);
|
||||
if(liquid != null) content.add(liquid);
|
||||
|
||||
if(!tile.block().isStatic()){
|
||||
totalFloors ++;
|
||||
if(liquid == Liquids.water){
|
||||
waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
|
||||
}
|
||||
floorc.increment(tile.floor());
|
||||
if(tile.overlay() != Blocks.air){
|
||||
floorc.increment(tile.overlay());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//sort counts in descending order
|
||||
Seq<Entry<Block>> entries = floorc.entries().toArray();
|
||||
entries.sort(e -> -e.value);
|
||||
//remove all blocks occuring < 30 times - unimportant
|
||||
entries.removeAll(e -> e.value < 30);
|
||||
|
||||
Block[] floors = new Block[entries.size];
|
||||
int[] floorCounts = new int[entries.size];
|
||||
for(int i = 0; i < entries.size; i++){
|
||||
floorCounts[i] = entries.get(i).value;
|
||||
floors[i] = entries.get(i).key;
|
||||
}
|
||||
|
||||
//TODO bad code
|
||||
boolean hasSnow = floors[0].name.contains("ice") || floors[0].name.contains("snow");
|
||||
boolean hasRain = !hasSnow && floors[0].name.contains("water");
|
||||
boolean hasDesert = !hasSnow && !hasRain && floors[0].name.contains("sand");
|
||||
boolean hasSpores = floors[0].name.contains("spore") || floors[0].name.contains("moss") || floors[0].name.contains("tainted");
|
||||
|
||||
if(hasSnow){
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.snow));
|
||||
}
|
||||
|
||||
if(hasRain){
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.rain));
|
||||
}
|
||||
|
||||
if(hasDesert){
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.sandstorm));
|
||||
}
|
||||
|
||||
if(hasSpores){
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
||||
}
|
||||
|
||||
state.secinfo.resources = content.asArray();
|
||||
state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||
|
||||
}
|
||||
|
||||
@@ -272,12 +336,18 @@ public class World{
|
||||
}
|
||||
|
||||
public void loadMap(Map map, Rules checkRules){
|
||||
//load using custom loader if possible
|
||||
if(customMapLoaders.containsKey(map)){
|
||||
customMapLoaders.get(map).run();
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
SaveIO.load(map.file, new FilterContext(map));
|
||||
}catch(Throwable e){
|
||||
Log.err(e);
|
||||
if(!headless){
|
||||
ui.showErrorMessage("$map.invalid");
|
||||
ui.showErrorMessage("@map.invalid");
|
||||
Core.app.post(() -> state.set(State.menu));
|
||||
invalidMap = true;
|
||||
}
|
||||
@@ -291,17 +361,17 @@ public class World{
|
||||
|
||||
if(!headless){
|
||||
if(state.teams.playerCores().size == 0 && !checkRules.pvp){
|
||||
ui.showErrorMessage("$map.nospawn");
|
||||
ui.showErrorMessage("@map.nospawn");
|
||||
invalidMap = true;
|
||||
}else if(checkRules.pvp){ //pvp maps need two cores to be valid
|
||||
if(state.teams.getActive().count(TeamData::hasCore) < 2){
|
||||
invalidMap = true;
|
||||
ui.showErrorMessage("$map.nospawn.pvp");
|
||||
ui.showErrorMessage("@map.nospawn.pvp");
|
||||
}
|
||||
}else if(checkRules.attackMode){ //attack maps need two cores to be valid
|
||||
invalidMap = state.teams.get(state.rules.waveTeam).noCores();
|
||||
if(invalidMap){
|
||||
ui.showErrorMessage("$map.nospawn.attack");
|
||||
ui.showErrorMessage("@map.nospawn.attack");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
@@ -317,7 +387,7 @@ public class World{
|
||||
|
||||
public void notifyChanged(Tile tile){
|
||||
if(!generating){
|
||||
Core.app.post(() -> Events.fire(new BuildinghangeEvent(tile)));
|
||||
Core.app.post(() -> Events.fire(new TileChangeEvent(tile)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +516,7 @@ public class World{
|
||||
dark = Math.max((edgeBlend - edgeDst) * (4f / edgeBlend), dark);
|
||||
}
|
||||
|
||||
if(state.hasSector()){
|
||||
if(state.hasSector() && state.getSector().preset == null){
|
||||
int circleBlend = 14;
|
||||
//quantized angle
|
||||
float offset = state.getSector().rect.rotation + 90;
|
||||
@@ -484,6 +554,9 @@ public class World{
|
||||
|
||||
private class Context implements WorldContext{
|
||||
|
||||
Context(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tile tile(int index){
|
||||
return tiles.geti(index);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.ctype;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.mod.Mods.*;
|
||||
|
||||
@@ -10,8 +10,7 @@ import mindustry.mod.Mods.*;
|
||||
public abstract class Content implements Comparable<Content>, Disposable{
|
||||
public final short id;
|
||||
/** Info on which mod this content was loaded from. */
|
||||
public @NonNull ModContentInfo minfo = new ModContentInfo();
|
||||
|
||||
public ModContentInfo minfo = new ModContentInfo();
|
||||
|
||||
public Content(){
|
||||
this.id = (short)Vars.content.getBy(getContentType()).size;
|
||||
|
||||
@@ -15,7 +15,8 @@ public enum ContentType{
|
||||
loadout_UNUSED,
|
||||
typeid_UNUSED,
|
||||
error,
|
||||
planet;
|
||||
planet,
|
||||
ammo;
|
||||
|
||||
public static final ContentType[] all = values();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import arc.struct.LongSeq;
|
||||
import mindustry.game.Team;
|
||||
import mindustry.gen.TileOp;
|
||||
import mindustry.world.Block;
|
||||
import mindustry.world.Tile;
|
||||
import mindustry.world.blocks.environment.Floor;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import static mindustry.Vars.content;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class DrawOperation{
|
||||
private MapEditor editor;
|
||||
@@ -64,8 +63,12 @@ public class DrawOperation{
|
||||
if(type == OpType.floor.ordinal()){
|
||||
tile.setFloor((Floor)content.block(to));
|
||||
}else if(type == OpType.block.ordinal()){
|
||||
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
|
||||
|
||||
Block block = content.block(to);
|
||||
tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation);
|
||||
|
||||
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
|
||||
}else if(type == OpType.rotation.ordinal()){
|
||||
if(tile.build != null) tile.build.rotation = to;
|
||||
}else if(type == OpType.team.ordinal()){
|
||||
@@ -74,7 +77,7 @@ public class DrawOperation{
|
||||
tile.setOverlayID(to);
|
||||
}
|
||||
});
|
||||
editor.renderer().updatePoint(tile.x, tile.y);
|
||||
editor.renderer.updatePoint(tile.x, tile.y);
|
||||
}
|
||||
|
||||
@Struct
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.editor.DrawOperation.*;
|
||||
import mindustry.game.*;
|
||||
@@ -19,8 +18,8 @@ public class EditorTile extends Tile{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloor(@NonNull Floor type){
|
||||
if(state.isGame()){
|
||||
public void setFloor(Floor type){
|
||||
if(skip()){
|
||||
super.setFloor(type);
|
||||
return;
|
||||
}
|
||||
@@ -39,29 +38,27 @@ public class EditorTile extends Tile{
|
||||
super.setFloor(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateOcclusion(){
|
||||
super.updateOcclusion();
|
||||
|
||||
ui.editor.editor.renderer().updatePoint(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(Block type, Team team, int rotation){
|
||||
if(state.isGame()){
|
||||
if(skip()){
|
||||
super.setBlock(type, team, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.block == type && (build == null || build.rotation == rotation)){
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
op(OpType.block, block.id);
|
||||
if(rotation != 0) op(OpType.rotation, (byte)rotation);
|
||||
if(team() != Team.derelict) op(OpType.team, (byte)team().id);
|
||||
if(team != Team.derelict) op(OpType.team, (byte)team.id);
|
||||
super.setBlock(type, team, rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTeam(Team team){
|
||||
if(state.isGame()){
|
||||
if(skip()){
|
||||
super.setTeam(team);
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +70,7 @@ public class EditorTile extends Tile{
|
||||
|
||||
@Override
|
||||
public void setOverlay(Block overlay){
|
||||
if(state.isGame()){
|
||||
if(skip()){
|
||||
super.setOverlay(overlay);
|
||||
return;
|
||||
}
|
||||
@@ -85,20 +82,24 @@ public class EditorTile extends Tile{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void preChanged(){
|
||||
super.preChanged();
|
||||
protected void fireChanged(){
|
||||
if(skip()){
|
||||
super.fireChanged();
|
||||
}else{
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recache(){
|
||||
if(state.isGame()){
|
||||
if(skip()){
|
||||
super.recache();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
|
||||
if(state.isGame()){
|
||||
if(skip()){
|
||||
super.changeEntity(team, entityprov, rotation);
|
||||
return;
|
||||
}
|
||||
@@ -110,15 +111,23 @@ public class EditorTile extends Tile{
|
||||
|
||||
Block block = block();
|
||||
|
||||
if(block.hasEntity()){
|
||||
if(block.hasBuilding()){
|
||||
build = entityprov.get().init(this, team, false, rotation);
|
||||
build.cons(new ConsumeModule(build));
|
||||
build.cons = new ConsumeModule(build);
|
||||
if(block.hasItems) build.items = new ItemModule();
|
||||
if(block.hasLiquids) build.liquids(new LiquidModule());
|
||||
if(block.hasPower) build.power(new PowerModule());
|
||||
}
|
||||
}
|
||||
|
||||
private void update(){
|
||||
ui.editor.editor.renderer.updatePoint(x, y);
|
||||
}
|
||||
|
||||
private boolean skip(){
|
||||
return state.isGame() || ui.editor.editor.isLoading();
|
||||
}
|
||||
|
||||
private void op(OpType type, short value){
|
||||
ui.editor.editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value));
|
||||
}
|
||||
|
||||
@@ -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){}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
@@ -14,6 +13,7 @@ import arc.scene.event.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
@@ -58,7 +58,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
infoDialog = new MapInfoDialog(editor);
|
||||
generateDialog = new MapGenerateDialog(editor, true);
|
||||
|
||||
menu = new BaseDialog("$menu");
|
||||
menu = new BaseDialog("@menu");
|
||||
menu.addCloseButton();
|
||||
|
||||
float swidth = 180f;
|
||||
@@ -66,41 +66,41 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
menu.cont.table(t -> {
|
||||
t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5);
|
||||
|
||||
t.button("$editor.savemap", Icon.save, this::save);
|
||||
t.button("@editor.savemap", Icon.save, this::save);
|
||||
|
||||
t.button("$editor.mapinfo", Icon.pencil, () -> {
|
||||
t.button("@editor.mapinfo", Icon.pencil, () -> {
|
||||
infoDialog.show();
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
t.row();
|
||||
|
||||
t.button("$editor.generate", Icon.terrain, () -> {
|
||||
t.button("@editor.generate", Icon.terrain, () -> {
|
||||
generateDialog.show(generateDialog::applyToEditor);
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
t.button("$editor.resize", Icon.resize, () -> {
|
||||
t.button("@editor.resize", Icon.resize, () -> {
|
||||
resizeDialog.show();
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
t.row();
|
||||
|
||||
t.button("$editor.import", Icon.download, () -> createDialog("$editor.import",
|
||||
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
|
||||
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
|
||||
t.button("@editor.import", Icon.download, () -> createDialog("@editor.import",
|
||||
"@editor.importmap", "@editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
|
||||
"@editor.importfile", "@editor.importfile.description", Icon.file, (Runnable)() ->
|
||||
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
|
||||
maps.tryCatchMapError(() -> {
|
||||
if(MapIO.isImage(file)){
|
||||
ui.showInfo("$editor.errorimage");
|
||||
ui.showInfo("@editor.errorimage");
|
||||
}else{
|
||||
editor.beginEdit(MapIO.createMap(file, true));
|
||||
}
|
||||
});
|
||||
})),
|
||||
|
||||
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
|
||||
"@editor.importimage", "@editor.importimage.description", Icon.fileImage, (Runnable)() ->
|
||||
platform.showFileChooser(true, "png", file ->
|
||||
ui.loadAnd(() -> {
|
||||
try{
|
||||
@@ -108,17 +108,17 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
editor.beginEdit(pixmap);
|
||||
pixmap.dispose();
|
||||
}catch(Exception e){
|
||||
ui.showException("$editor.errorload", e);
|
||||
ui.showException("@editor.errorload", e);
|
||||
Log.err(e);
|
||||
}
|
||||
})))
|
||||
);
|
||||
|
||||
t.button("$editor.export", Icon.upload, () -> createDialog("$editor.export",
|
||||
"$editor.exportfile", "$editor.exportfile.description", Icon.file,
|
||||
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
|
||||
"$editor.exportimage", "$editor.exportimage.description", Icon.fileImage,
|
||||
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), "png", file -> {
|
||||
t.button("@editor.export", Icon.upload, () -> createDialog("@editor.export",
|
||||
"@editor.exportfile", "@editor.exportfile.description", Icon.file,
|
||||
(Runnable)() -> platform.export(editor.tags.get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
|
||||
"@editor.exportimage", "@editor.exportimage.description", Icon.fileImage,
|
||||
(Runnable)() -> platform.export(editor.tags.get("name", "unknown"), "png", file -> {
|
||||
Pixmap out = MapIO.writeImage(editor.tiles());
|
||||
file.writePNG(out);
|
||||
out.dispose();
|
||||
@@ -128,17 +128,17 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
menu.cont.row();
|
||||
|
||||
if(steam){
|
||||
menu.cont.button("$editor.publish.workshop", Icon.link, () -> {
|
||||
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
|
||||
menu.cont.button("@editor.publish.workshop", Icon.link, () -> {
|
||||
Map builtin = maps.all().find(m -> m.name().equals(editor.tags.get("name", "").trim()));
|
||||
|
||||
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
|
||||
platform.viewListingID(editor.getTags().get("steamid"));
|
||||
if(editor.tags.containsKey("steamid") && builtin != null && !builtin.custom){
|
||||
platform.viewListingID(editor.tags.get("steamid"));
|
||||
return;
|
||||
}
|
||||
|
||||
Map map = save();
|
||||
|
||||
if(editor.getTags().containsKey("steamid") && map != null){
|
||||
if(editor.tags.containsKey("steamid") && map != null){
|
||||
platform.viewListing(map);
|
||||
return;
|
||||
}
|
||||
@@ -146,26 +146,26 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
if(map == null) return;
|
||||
|
||||
if(map.tags.get("description", "").length() < 4){
|
||||
ui.showErrorMessage("$editor.nodescription");
|
||||
ui.showErrorMessage("@editor.nodescription");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Structs.contains(Gamemode.all, g -> g.valid(map))){
|
||||
ui.showErrorMessage("$map.nospawn");
|
||||
ui.showErrorMessage("@map.nospawn");
|
||||
return;
|
||||
}
|
||||
|
||||
platform.publish(map);
|
||||
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
|
||||
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.tags.containsKey("steamid") ? editor.tags.get("author").equals(player.name) ? "@workshop.listing" : "@view.workshop" : "@editor.publish.workshop"));
|
||||
|
||||
menu.cont.row();
|
||||
}
|
||||
|
||||
menu.cont.button("$editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
menu.cont.button("@editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.button("$quit", Icon.exit, () -> {
|
||||
menu.cont.button("@quit", Icon.exit, () -> {
|
||||
tryExit();
|
||||
menu.hide();
|
||||
}).size(swidth * 2f + 10, 60f);
|
||||
@@ -182,7 +182,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
try{
|
||||
editor.beginEdit(map);
|
||||
}catch(Exception e){
|
||||
ui.showException("$editor.errorload", e);
|
||||
ui.showException("@editor.errorload", e);
|
||||
Log.err(e);
|
||||
}
|
||||
}));
|
||||
@@ -235,7 +235,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
state.rules = (lastSavedRules == null ? new Rules() : lastSavedRules);
|
||||
lastSavedRules = null;
|
||||
saved = false;
|
||||
editor.renderer().updateAll();
|
||||
editor.renderer.updateAll();
|
||||
}
|
||||
|
||||
private void playtest(){
|
||||
@@ -254,14 +254,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
"height", editor.height()
|
||||
));
|
||||
world.endMapLoad();
|
||||
//add entities so they update. is this really needed?
|
||||
for(Tile tile : world.tiles){
|
||||
if(tile.build != null){
|
||||
tile.build.add();
|
||||
}
|
||||
}
|
||||
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
player.clearUnit();
|
||||
Groups.unit.clear();
|
||||
Groups.build.clear();
|
||||
logic.play();
|
||||
});
|
||||
}
|
||||
@@ -269,10 +265,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
public @Nullable Map save(){
|
||||
boolean isEditor = state.rules.editor;
|
||||
state.rules.editor = false;
|
||||
String name = editor.getTags().get("name", "").trim();
|
||||
editor.getTags().put("rules", JsonIO.write(state.rules));
|
||||
editor.getTags().remove("width");
|
||||
editor.getTags().remove("height");
|
||||
String name = editor.tags.get("name", "").trim();
|
||||
editor.tags.put("rules", JsonIO.write(state.rules));
|
||||
editor.tags.remove("width");
|
||||
editor.tags.remove("height");
|
||||
|
||||
player.clearUnit();
|
||||
|
||||
@@ -280,14 +276,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
if(name.isEmpty()){
|
||||
infoDialog.show();
|
||||
Core.app.post(() -> ui.showErrorMessage("$editor.save.noname"));
|
||||
Core.app.post(() -> ui.showErrorMessage("@editor.save.noname"));
|
||||
}else{
|
||||
Map map = maps.all().find(m -> m.name().equals(name));
|
||||
if(map != null && !map.custom){
|
||||
handleSaveBuiltin(map);
|
||||
}else{
|
||||
returned = maps.saveMap(editor.getTags());
|
||||
ui.showInfoFade("$editor.saved");
|
||||
returned = maps.saveMap(editor.tags);
|
||||
ui.showInfoFade("@editor.saved");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +295,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
/** Called when a built-in map save is attempted.*/
|
||||
protected void handleSaveBuiltin(Map map){
|
||||
ui.showErrorMessage("$editor.save.overwrite");
|
||||
ui.showErrorMessage("@editor.save.overwrite");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,7 +353,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
editor.renderer().dispose();
|
||||
editor.renderer.dispose();
|
||||
}
|
||||
|
||||
public void beginEditMap(Fi file){
|
||||
@@ -368,7 +364,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
show();
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
ui.showException("$editor.errorload", e);
|
||||
ui.showException("@editor.errorload", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -521,7 +517,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
tools.row();
|
||||
|
||||
tools.table(Tex.underline, t -> t.add("$editor.teams"))
|
||||
tools.table(Tex.underline, t -> t.add("@editor.teams"))
|
||||
.colspan(3).height(40).width(size * 3f + 3f).padBottom(3);
|
||||
|
||||
tools.row();
|
||||
@@ -557,7 +553,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
}
|
||||
|
||||
t.top();
|
||||
t.add("$editor.brush");
|
||||
t.add("@editor.brush");
|
||||
t.row();
|
||||
t.add(slider).width(size * 3f - 20).padTop(4f);
|
||||
}).padTop(5).growX().top();
|
||||
@@ -576,22 +572,20 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
if(Core.input.ctrl()){
|
||||
//alt mode select
|
||||
for(int i = 0; i < view.getTool().altModes.length + 1; i++){
|
||||
if(Core.input.keyTap(KeyCode.valueOf("num" + (i + 1)))){
|
||||
view.getTool().mode = i - 1;
|
||||
for(int i = 0; i < view.getTool().altModes.length; i++){
|
||||
if(i + 1 < KeyCode.numbers.length && Core.input.keyTap(KeyCode.numbers[i + 1])){
|
||||
view.getTool().mode = i;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//tool select
|
||||
for(int i = 0; i < EditorTool.values().length; i++){
|
||||
if(Core.input.keyTap(KeyCode.valueOf("num" + (i + 1)))){
|
||||
view.setTool(EditorTool.values()[i]);
|
||||
for(EditorTool tool : EditorTool.all){
|
||||
if(Core.input.keyTap(tool.key)){
|
||||
view.setTool(tool);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(Core.input.keyTap(KeyCode.escape)){
|
||||
if(!menu.isShown()){
|
||||
menu.show();
|
||||
@@ -619,14 +613,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
for(int x = 0; x < editor.width(); x++){
|
||||
for(int y = 0; y < editor.height(); y++){
|
||||
Tile tile = editor.tile(x, y);
|
||||
if(tile.block().breakable && tile.block() instanceof Rock){
|
||||
if(tile.block().breakable && tile.block() instanceof Boulder){
|
||||
tile.setBlock(Blocks.air);
|
||||
editor.renderer().updatePoint(x, y);
|
||||
editor.renderer.updatePoint(x, y);
|
||||
}
|
||||
|
||||
if(tile.overlay() != Blocks.air && tile.overlay() != Blocks.spawn){
|
||||
tile.setOverlay(Blocks.air);
|
||||
editor.renderer().updatePoint(x, y);
|
||||
editor.renderer.updatePoint(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,11 +643,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
}
|
||||
|
||||
private void tryExit(){
|
||||
if(!saved){
|
||||
ui.showConfirm("$confirm", "$editor.unsaved", this::hide);
|
||||
}else{
|
||||
hide();
|
||||
}
|
||||
ui.showConfirm("@confirm", "@editor.unsaved", this::hide);
|
||||
}
|
||||
|
||||
private void addBlockSelection(Table table){
|
||||
|
||||
@@ -37,18 +37,18 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
private Pixmap pixmap;
|
||||
private Texture texture;
|
||||
private GenerateInput input = new GenerateInput();
|
||||
private Seq<GenerateFilter> filters = new Seq<>();
|
||||
Seq<GenerateFilter> filters = new Seq<>();
|
||||
private int scaling = mobile ? 3 : 1;
|
||||
private Table filterTable;
|
||||
|
||||
private AsyncExecutor executor = new AsyncExecutor(1);
|
||||
private AsyncResult<Void> result;
|
||||
private boolean generating;
|
||||
boolean generating;
|
||||
private GenTile returnTile = new GenTile();
|
||||
|
||||
private GenTile[][] buffer1, buffer2;
|
||||
private Cons<Seq<GenerateFilter>> applier;
|
||||
private CachedTile ctile = new CachedTile(){
|
||||
CachedTile ctile = new CachedTile(){
|
||||
//nothing.
|
||||
@Override
|
||||
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
|
||||
@@ -58,34 +58,34 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
|
||||
/** @param applied whether or not to use the applied in-game mode. */
|
||||
public MapGenerateDialog(MapEditor editor, boolean applied){
|
||||
super("$editor.generate");
|
||||
super("@editor.generate");
|
||||
this.editor = editor;
|
||||
this.applied = applied;
|
||||
|
||||
shown(this::setup);
|
||||
addCloseButton();
|
||||
if(applied){
|
||||
buttons.button("$editor.apply", () -> {
|
||||
buttons.button("@editor.apply", () -> {
|
||||
ui.loadAnd(() -> {
|
||||
apply();
|
||||
hide();
|
||||
});
|
||||
}).size(160f, 64f);
|
||||
}else{
|
||||
buttons.button("$settings.reset", () -> {
|
||||
buttons.button("@settings.reset", () -> {
|
||||
filters.set(maps.readFilters(""));
|
||||
rebuildFilters();
|
||||
update();
|
||||
}).size(160f, 64f);
|
||||
}
|
||||
buttons.button("$editor.randomize", () -> {
|
||||
buttons.button("@editor.randomize", () -> {
|
||||
for(GenerateFilter filter : filters){
|
||||
filter.randomize();
|
||||
}
|
||||
update();
|
||||
}).size(160f, 64f);
|
||||
|
||||
buttons.button("$add", Icon.add, this::showAdd).height(64f).width(140f);
|
||||
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(140f);
|
||||
|
||||
if(!applied){
|
||||
hidden(this::apply);
|
||||
@@ -144,7 +144,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
//reset undo stack as generation... messes things up
|
||||
editor.renderer().updateAll();
|
||||
editor.renderer.updateAll();
|
||||
editor.clearOp();
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
add(new Image(Styles.black8));
|
||||
add(new Image(Icon.refresh, Scaling.none));
|
||||
visible(() -> generating && !updateEditorOnChange);
|
||||
}}).grow().padRight(10);
|
||||
}}).uniformX().grow().padRight(10);
|
||||
t.pane(p -> filterTable = p.marginRight(6)).update(pane -> {
|
||||
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
|
||||
return;
|
||||
@@ -191,7 +191,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
}else{
|
||||
Core.scene.setScrollFocus(null);
|
||||
}
|
||||
}).grow().get().setScrollingDisabled(true, false);
|
||||
}).grow().uniformX().get().setScrollingDisabled(true, false);
|
||||
}).grow();
|
||||
|
||||
buffer1 = create();
|
||||
@@ -213,7 +213,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void rebuildFilters(){
|
||||
int cols = Math.max((int)(Math.max(filterTable.parent.getWidth(), Core.graphics.getWidth()/2f * 0.9f) / Scl.scl(290f)), 1);
|
||||
int cols = Math.max((int)(Core.graphics.getWidth()/2f / Scl.scl(290f)), 1);
|
||||
filterTable.clearChildren();
|
||||
filterTable.top().left();
|
||||
int i = 0;
|
||||
@@ -221,41 +221,45 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
for(GenerateFilter filter : filters){
|
||||
|
||||
//main container
|
||||
filterTable.table(Tex.button, c -> {
|
||||
filterTable.table(Tex.pane, c -> {
|
||||
c.margin(0);
|
||||
|
||||
//icons to perform actions
|
||||
c.table(t -> {
|
||||
t.top();
|
||||
t.add(filter.name()).padTop(5).color(Pal.accent).growX().left();
|
||||
c.table(Tex.whiteui, t -> {
|
||||
t.setColor(Pal.gray);
|
||||
|
||||
t.row();
|
||||
t.top().left();
|
||||
t.add(filter.name()).left().padLeft(6).width(100f).wrap();
|
||||
|
||||
t.table(b -> {
|
||||
ImageButtonStyle style = Styles.cleari;
|
||||
b.defaults().size(50f);
|
||||
b.button(Icon.refresh, style, () -> {
|
||||
filter.randomize();
|
||||
update();
|
||||
});
|
||||
t.add().growX();
|
||||
|
||||
b.button(Icon.upOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.max(0, idx - 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
b.button(Icon.downOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
b.button(Icon.trash, style, () -> {
|
||||
filters.remove(filter);
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
ImageButtonStyle style = Styles.geni;
|
||||
t.defaults().size(42f);
|
||||
|
||||
t.button(Icon.refresh, style, () -> {
|
||||
filter.randomize();
|
||||
update();
|
||||
});
|
||||
}).fillX();
|
||||
|
||||
t.button(Icon.upOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.max(0, idx - 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
t.button(Icon.downOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
t.button(Icon.cancel, style, () -> {
|
||||
filters.remove(filter);
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
}).growX();
|
||||
|
||||
c.row();
|
||||
//all the options
|
||||
c.table(f -> {
|
||||
@@ -269,20 +273,22 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
}).growX().left();
|
||||
f.row();
|
||||
}
|
||||
}).grow().left().pad(2).top();
|
||||
}).grow().left().pad(6).top();
|
||||
}).width(280f).pad(3).top().left().fillY();
|
||||
|
||||
|
||||
if(++i % cols == 0){
|
||||
filterTable.row();
|
||||
}
|
||||
}
|
||||
|
||||
if(filters.isEmpty()){
|
||||
filterTable.add("$filters.empty").wrap().width(200f);
|
||||
filterTable.add("@filters.empty").wrap().width(200f);
|
||||
}
|
||||
}
|
||||
|
||||
void showAdd(){
|
||||
BaseDialog selection = new BaseDialog("$add");
|
||||
BaseDialog selection = new BaseDialog("@add");
|
||||
selection.setFillParent(false);
|
||||
selection.cont.defaults().size(210f, 60f);
|
||||
int i = 0;
|
||||
@@ -300,7 +306,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
if(++i % 2 == 0) selection.cont.row();
|
||||
}
|
||||
|
||||
selection.cont.button("$filter.defaultores", () -> {
|
||||
selection.cont.button("@filter.defaultores", () -> {
|
||||
maps.addDefaultOres(filters);
|
||||
rebuildFilters();
|
||||
update();
|
||||
@@ -344,6 +350,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
|
||||
result = executor.submit(() -> {
|
||||
try{
|
||||
world.setGenerating(true);
|
||||
generating = true;
|
||||
|
||||
if(!filters.isEmpty()){
|
||||
@@ -396,7 +403,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
generating = false;
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
world.setGenerating(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -404,10 +411,13 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
public byte team;
|
||||
public short block, floor, ore;
|
||||
|
||||
GenTile(){
|
||||
}
|
||||
|
||||
public void set(Block floor, Block wall, Block ore, Team team){
|
||||
this.floor = floor.id;
|
||||
this.block = wall.id;
|
||||
this.ore = ore.id;
|
||||
this.ore = floor.asFloor().isLiquid ? 0 : ore.id;
|
||||
this.team = (byte)team.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.io.*;
|
||||
@@ -16,7 +16,7 @@ public class MapInfoDialog extends BaseDialog{
|
||||
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
|
||||
|
||||
public MapInfoDialog(MapEditor editor){
|
||||
super("$editor.mapinfo");
|
||||
super("@editor.mapinfo");
|
||||
this.editor = editor;
|
||||
this.waveInfo = new WaveInfoDialog(editor);
|
||||
this.generate = new MapGenerateDialog(editor, false);
|
||||
@@ -29,52 +29,52 @@ public class MapInfoDialog extends BaseDialog{
|
||||
private void setup(){
|
||||
cont.clear();
|
||||
|
||||
ObjectMap<String, String> tags = editor.getTags();
|
||||
ObjectMap<String, String> tags = editor.tags;
|
||||
|
||||
cont.pane(t -> {
|
||||
t.add("$editor.mapname").padRight(8).left();
|
||||
t.add("@editor.mapname").padRight(8).left();
|
||||
t.defaults().padTop(15);
|
||||
|
||||
TextField name = t.field(tags.get("name", ""), text -> {
|
||||
tags.put("name", text);
|
||||
}).size(400, 55f).addInputDialog(50).get();
|
||||
name.setMessageText("$unknown");
|
||||
name.setMessageText("@unknown");
|
||||
|
||||
t.row();
|
||||
t.add("$editor.description").padRight(8).left();
|
||||
t.add("@editor.description").padRight(8).left();
|
||||
|
||||
TextArea description = t.area(tags.get("description", ""), Styles.areaField, text -> {
|
||||
tags.put("description", text);
|
||||
}).size(400f, 140f).addInputDialog(1000).get();
|
||||
|
||||
t.row();
|
||||
t.add("$editor.author").padRight(8).left();
|
||||
t.add("@editor.author").padRight(8).left();
|
||||
|
||||
TextField author = t.field(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
|
||||
tags.put("author", text);
|
||||
Core.settings.put("mapAuthor", text);
|
||||
}).size(400, 55f).addInputDialog(50).get();
|
||||
author.setMessageText("$unknown");
|
||||
author.setMessageText("@unknown");
|
||||
|
||||
t.row();
|
||||
t.add("$editor.rules").padRight(8).left();
|
||||
t.button("$edit", () -> {
|
||||
t.add("@editor.rules").padRight(8).left();
|
||||
t.button("@edit", () -> {
|
||||
ruleInfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules());
|
||||
hide();
|
||||
}).left().width(200f);
|
||||
|
||||
t.row();
|
||||
t.add("$editor.waves").padRight(8).left();
|
||||
t.button("$edit", () -> {
|
||||
t.add("@editor.waves").padRight(8).left();
|
||||
t.button("@edit", () -> {
|
||||
waveInfo.show();
|
||||
hide();
|
||||
}).left().width(200f);
|
||||
|
||||
t.row();
|
||||
t.add("$editor.generation").padRight(8).left();
|
||||
t.button("$edit", () -> {
|
||||
generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
|
||||
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)));
|
||||
t.add("@editor.generation").padRight(8).left();
|
||||
t.button("@edit", () -> {
|
||||
generate.show(Vars.maps.readFilters(editor.tags.get("genfilters", "")),
|
||||
filters -> editor.tags.put("genfilters", JsonIO.write(filters)));
|
||||
hide();
|
||||
}).left().width(200f);
|
||||
|
||||
|
||||
@@ -8,17 +8,17 @@ import mindustry.maps.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
import static mindustry.Vars.maps;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapLoadDialog extends BaseDialog{
|
||||
private Map selected = null;
|
||||
|
||||
public MapLoadDialog(Cons<Map> loader){
|
||||
super("$editor.loadmap");
|
||||
super("@editor.loadmap");
|
||||
|
||||
shown(this::rebuild);
|
||||
|
||||
TextButton button = new TextButton("$load");
|
||||
TextButton button = new TextButton("@load");
|
||||
button.setDisabled(() -> selected == null);
|
||||
button.clicked(() -> {
|
||||
if(selected != null){
|
||||
@@ -28,7 +28,7 @@ public class MapLoadDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
buttons.defaults().size(200f, 50f);
|
||||
buttons.button("$cancel", this::hide);
|
||||
buttons.button("@cancel", this::hide);
|
||||
buttons.add(button);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ public class MapLoadDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
if(maps.all().size == 0){
|
||||
table.add("$maps.none").center();
|
||||
table.add("@maps.none").center();
|
||||
}else{
|
||||
cont.add("$editor.loadmap");
|
||||
cont.add("@editor.loadmap");
|
||||
}
|
||||
|
||||
cont.row();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class MapResizeDialog extends BaseDialog{
|
||||
int width, height;
|
||||
|
||||
public MapResizeDialog(MapEditor editor, Intc2 cons){
|
||||
super("$editor.resizemap");
|
||||
super("@editor.resizemap");
|
||||
shown(() -> {
|
||||
cont.clear();
|
||||
width = editor.width();
|
||||
@@ -21,7 +21,7 @@ public class MapResizeDialog extends BaseDialog{
|
||||
Table table = new Table();
|
||||
|
||||
for(boolean w : Mathf.booleans){
|
||||
table.add(w ? "$width" : "$height").padRight(8f);
|
||||
table.add(w ? "@width" : "@height").padRight(8f);
|
||||
table.defaults().height(60f).padTop(8);
|
||||
|
||||
table.field((w ? width : height) + "", TextFieldFilter.digitsOnly, value -> {
|
||||
@@ -37,8 +37,8 @@ public class MapResizeDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
buttons.defaults().size(200f, 50f);
|
||||
buttons.button("$cancel", this::hide);
|
||||
buttons.button("$ok", () -> {
|
||||
buttons.button("@cancel", this::hide);
|
||||
buttons.button("@ok", () -> {
|
||||
cons.get(width, height);
|
||||
hide();
|
||||
});
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.Core;
|
||||
import arc.graphics.Color;
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.GestureDetector;
|
||||
import arc.input.GestureDetector.GestureListener;
|
||||
import arc.input.KeyCode;
|
||||
import arc.math.Mathf;
|
||||
import arc.input.*;
|
||||
import arc.input.GestureDetector.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.Element;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.TextField;
|
||||
import arc.scene.ui.layout.Scl;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.graphics.Pal;
|
||||
import mindustry.input.Binding;
|
||||
import mindustry.ui.GridImage;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.mobile;
|
||||
import static mindustry.Vars.ui;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapView extends Element implements GestureListener{
|
||||
private MapEditor editor;
|
||||
private EditorTool tool = EditorTool.pencil;
|
||||
EditorTool tool = EditorTool.pencil;
|
||||
private float offsetx, offsety;
|
||||
private float zoom = 1f;
|
||||
private boolean grid = false;
|
||||
@@ -31,11 +29,11 @@ public class MapView extends Element implements GestureListener{
|
||||
private Rect rect = new Rect();
|
||||
private Vec2[][] brushPolygons = new Vec2[MapEditor.brushSizes.length][0];
|
||||
|
||||
private boolean drawing;
|
||||
private int lastx, lasty;
|
||||
private int startx, starty;
|
||||
private float mousex, mousey;
|
||||
private EditorTool lastTool;
|
||||
boolean drawing;
|
||||
int lastx, lasty;
|
||||
int startx, starty;
|
||||
float mousex, mousey;
|
||||
EditorTool lastTool;
|
||||
|
||||
public MapView(MapEditor editor){
|
||||
this.editor = editor;
|
||||
@@ -204,7 +202,7 @@ public class MapView extends Element implements GestureListener{
|
||||
zoom = Mathf.clamp(zoom, 0.2f, 20f);
|
||||
}
|
||||
|
||||
private Point2 project(float x, float y){
|
||||
Point2 project(float x, float y){
|
||||
float ratio = 1f / ((float)editor.width() / editor.height());
|
||||
float size = Math.min(width, height);
|
||||
float sclwidth = size * zoom;
|
||||
@@ -213,9 +211,9 @@ public class MapView extends Element implements GestureListener{
|
||||
y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.height();
|
||||
|
||||
if(editor.drawBlock.size % 2 == 0 && tool != EditorTool.eraser){
|
||||
return Tmp.g1.set((int)(x - 0.5f), (int)(y - 0.5f));
|
||||
return Tmp.p1.set((int)(x - 0.5f), (int)(y - 0.5f));
|
||||
}else{
|
||||
return Tmp.g1.set((int)x, (int)y);
|
||||
return Tmp.p1.set((int)x, (int)y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,14 +246,20 @@ public class MapView extends Element implements GestureListener{
|
||||
Draw.color(Pal.remove);
|
||||
Lines.stroke(2f);
|
||||
Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2);
|
||||
editor.renderer().draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
|
||||
editor.renderer.draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
|
||||
Draw.reset();
|
||||
|
||||
if(grid){
|
||||
Draw.color(Color.gray);
|
||||
image.setBounds(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
|
||||
image.draw();
|
||||
Draw.color();
|
||||
|
||||
Lines.stroke(3f);
|
||||
Draw.color(Pal.accent);
|
||||
Lines.line(centerx - sclwidth/2f, centery, centerx + sclwidth/2f, centery);
|
||||
Lines.line(centerx, centery - sclheight/2f, centerx, centery + sclheight/2f);
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.struct.Seq;
|
||||
import arc.struct.*;
|
||||
|
||||
public class OperationStack{
|
||||
private static final int maxSize = 10;
|
||||
|
||||
@@ -32,7 +32,6 @@ public class WaveGraph extends Table{
|
||||
|
||||
rect((x, y, width, height) -> {
|
||||
Lines.stroke(Scl.scl(3f));
|
||||
Lines.precise(true);
|
||||
|
||||
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
|
||||
Font font = Fonts.outline;
|
||||
@@ -122,7 +121,6 @@ public class WaveGraph extends Table{
|
||||
|
||||
Pools.free(lay);
|
||||
|
||||
Lines.precise(false);
|
||||
Draw.reset();
|
||||
}).pad(4).padBottom(10).grow();
|
||||
|
||||
@@ -137,7 +135,7 @@ public class WaveGraph extends Table{
|
||||
ButtonGroup<Button> group = new ButtonGroup<>();
|
||||
|
||||
for(Mode m : Mode.all){
|
||||
t.button("$wavemode." + m.name(), Styles.fullTogglet, () -> {
|
||||
t.button("@wavemode." + m.name(), Styles.fullTogglet, () -> {
|
||||
mode = m;
|
||||
}).group(group).height(35f).update(b -> b.setChecked(m == mode)).width(130f);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import static mindustry.game.SpawnGroup.*;
|
||||
|
||||
public class WaveInfoDialog extends BaseDialog{
|
||||
private int displayed = 20;
|
||||
private Seq<SpawnGroup> groups = new Seq<>();
|
||||
Seq<SpawnGroup> groups = new Seq<>();
|
||||
|
||||
private Table table;
|
||||
private int start = 0;
|
||||
@@ -32,7 +32,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
private WaveGraph graph = new WaveGraph();
|
||||
|
||||
public WaveInfoDialog(MapEditor editor){
|
||||
super("$waves.title");
|
||||
super("@waves.title");
|
||||
|
||||
shown(this::setup);
|
||||
hidden(() -> {
|
||||
@@ -48,29 +48,29 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
onResize(this::setup);
|
||||
addCloseButton();
|
||||
|
||||
buttons.button("$waves.edit", () -> {
|
||||
BaseDialog dialog = new BaseDialog("$waves.edit");
|
||||
buttons.button("@waves.edit", () -> {
|
||||
BaseDialog dialog = new BaseDialog("@waves.edit");
|
||||
dialog.addCloseButton();
|
||||
dialog.setFillParent(false);
|
||||
dialog.cont.defaults().size(210f, 64f);
|
||||
dialog.cont.button("$waves.copy", () -> {
|
||||
ui.showInfoFade("$waves.copied");
|
||||
dialog.cont.button("@waves.copy", () -> {
|
||||
ui.showInfoFade("@waves.copied");
|
||||
Core.app.setClipboardText(maps.writeWaves(groups));
|
||||
dialog.hide();
|
||||
}).disabled(b -> groups == null);
|
||||
dialog.cont.row();
|
||||
dialog.cont.button("$waves.load", () -> {
|
||||
dialog.cont.button("@waves.load", () -> {
|
||||
try{
|
||||
groups = maps.readWaves(Core.app.getClipboardText());
|
||||
buildGroups();
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
ui.showErrorMessage("$waves.invalid");
|
||||
ui.showErrorMessage("@waves.invalid");
|
||||
}
|
||||
dialog.hide();
|
||||
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
|
||||
dialog.cont.row();
|
||||
dialog.cont.button("$settings.reset", () -> ui.showConfirm("$confirm", "$settings.clear.confirm", () -> {
|
||||
dialog.cont.button("@settings.reset", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
|
||||
groups = JsonIO.copy(defaultWaves.get());
|
||||
buildGroups();
|
||||
dialog.hide();
|
||||
@@ -130,12 +130,12 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
cont.stack(new Table(Tex.clear, main -> {
|
||||
main.pane(t -> table = t).growX().growY().padRight(8f).get().setScrollingDisabled(true, false);
|
||||
main.row();
|
||||
main.button("$add", () -> {
|
||||
main.button("@add", () -> {
|
||||
if(groups == null) groups = new Seq<>();
|
||||
groups.add(new SpawnGroup(lastType));
|
||||
buildGroups();
|
||||
}).growX().height(70f);
|
||||
}), new Label("$waves.none"){{
|
||||
}), new Label("@waves.none"){{
|
||||
visible(() -> groups.isEmpty());
|
||||
this.touchable = Touchable.disabled;
|
||||
setWrap(true);
|
||||
@@ -180,7 +180,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
updateWaves();
|
||||
}
|
||||
}).width(100f);
|
||||
spawns.add("$waves.to").padLeft(4).padRight(4);
|
||||
spawns.add("@waves.to").padLeft(4).padRight(4);
|
||||
spawns.field(group.end == never ? "" : (group.end + 1) + "", TextFieldFilter.digitsOnly, text -> {
|
||||
if(Strings.canParsePositiveInt(text)){
|
||||
group.end = Strings.parseInt(text) - 1;
|
||||
@@ -193,14 +193,14 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
});
|
||||
t.row();
|
||||
t.table(p -> {
|
||||
p.add("$waves.every").padRight(4);
|
||||
p.add("@waves.every").padRight(4);
|
||||
p.field(group.spacing + "", TextFieldFilter.digitsOnly, text -> {
|
||||
if(Strings.canParsePositiveInt(text) && Strings.parseInt(text) > 0){
|
||||
group.spacing = Strings.parseInt(text);
|
||||
updateWaves();
|
||||
}
|
||||
}).width(100f);
|
||||
p.add("$waves.waves").padLeft(4);
|
||||
p.add("@waves.waves").padLeft(4);
|
||||
});
|
||||
|
||||
t.row();
|
||||
@@ -219,7 +219,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
updateWaves();
|
||||
}
|
||||
}).width(80f);
|
||||
a.add("$waves.perspawn").padLeft(4);
|
||||
a.add("@waves.perspawn").padLeft(4);
|
||||
});
|
||||
t.row();
|
||||
t.table(a -> {
|
||||
@@ -237,17 +237,17 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
updateWaves();
|
||||
}
|
||||
}).width(80f);
|
||||
a.add("$waves.shields").padLeft(4);
|
||||
a.add("@waves.shields").padLeft(4);
|
||||
});
|
||||
|
||||
t.row();
|
||||
t.check("$waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
|
||||
t.check("@waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
|
||||
}).width(340f).pad(8);
|
||||
|
||||
table.row();
|
||||
}
|
||||
}else{
|
||||
table.add("$editor.default");
|
||||
table.add("@editor.default");
|
||||
}
|
||||
|
||||
updateWaves();
|
||||
|
||||
@@ -20,6 +20,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
/** Utility class for damaging in an area. */
|
||||
public class Damage{
|
||||
private static Tile furthest;
|
||||
private static Rect rect = new Rect();
|
||||
private static Rect hitrect = new Rect();
|
||||
private static Vec2 tr = new Vec2();
|
||||
@@ -30,24 +31,26 @@ public class Damage{
|
||||
private static Unit tmpUnit;
|
||||
|
||||
/** Creates a dynamic explosion based on specified parameters. */
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
|
||||
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
|
||||
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
|
||||
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
|
||||
}
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color, boolean damage){
|
||||
if(damage){
|
||||
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
|
||||
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
|
||||
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
|
||||
}
|
||||
|
||||
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
|
||||
}
|
||||
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
|
||||
}
|
||||
|
||||
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
|
||||
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
|
||||
|
||||
for(int i = 0; i < waves; i++){
|
||||
int f = i;
|
||||
Time.run(i * 2f, () -> {
|
||||
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
|
||||
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
|
||||
});
|
||||
for(int i = 0; i < waves; i++){
|
||||
int f = i;
|
||||
Time.run(i * 2f, () -> {
|
||||
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
|
||||
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(explosiveness > 15f){
|
||||
@@ -59,7 +62,7 @@ public class Damage{
|
||||
}
|
||||
|
||||
float shake = Math.min(explosiveness / 4f + 3f, 9f);
|
||||
Effects.shake(shake, shake, x, y);
|
||||
Effect.shake(shake, shake, x, y);
|
||||
Fx.dynamicExplosion.at(x, y, radius / 8f);
|
||||
}
|
||||
|
||||
@@ -74,6 +77,28 @@ public class Damage{
|
||||
}
|
||||
}
|
||||
|
||||
public static float findLaserLength(Bullet b, float length){
|
||||
Tmp.v1.trns(b.rotation(), length);
|
||||
|
||||
furthest = null;
|
||||
|
||||
boolean found = world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
|
||||
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
|
||||
|
||||
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
|
||||
}
|
||||
|
||||
/** Collides a bullet with blocks in a laser, taking into account absorption blocks. Resulting length is stored in the bullet's fdata. */
|
||||
public static float collideLaser(Bullet b, float length, boolean large){
|
||||
float resultLength = findLaserLength(b, length);
|
||||
|
||||
collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), resultLength, large);
|
||||
|
||||
b.fdata = resultLength;
|
||||
|
||||
return resultLength;
|
||||
}
|
||||
|
||||
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
|
||||
collideLine(hitter, team, effect, x, y, angle, length, false);
|
||||
}
|
||||
@@ -83,11 +108,13 @@ public class Damage{
|
||||
* Only enemies of the specified team are damaged.
|
||||
*/
|
||||
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
|
||||
length = findLaserLength(hitter, length);
|
||||
|
||||
collidedBlocks.clear();
|
||||
tr.trns(angle, length);
|
||||
Intc2 collider = (cx, cy) -> {
|
||||
Building tile = world.build(cx, cy);
|
||||
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team() != team && tile.collide(hitter)){
|
||||
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team != team && tile.collide(hitter)){
|
||||
tile.collision(hitter);
|
||||
collidedBlocks.add(tile.pos());
|
||||
hitter.type.hit(hitter, tile.x, tile.y);
|
||||
@@ -130,13 +157,8 @@ public class Damage{
|
||||
if(!e.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround)) return;
|
||||
|
||||
e.hitbox(hitrect);
|
||||
Rect other = hitrect;
|
||||
other.y -= expand;
|
||||
other.x -= expand;
|
||||
other.width += expand * 2;
|
||||
other.height += expand * 2;
|
||||
|
||||
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, other);
|
||||
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, hitrect.grow(expand * 2));
|
||||
|
||||
if(vec != null){
|
||||
effect.at(vec.x, vec.y);
|
||||
@@ -162,7 +184,6 @@ public class Damage{
|
||||
Building tile = world.build(cx, cy);
|
||||
if(tile != null && tile.team != hitter.team){
|
||||
tmpBuilding = tile;
|
||||
//TODO return tile
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
package mindustry.entities;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Effect{
|
||||
private static final float shakeFalloff = 10000f;
|
||||
private static final EffectContainer container = new EffectContainer();
|
||||
private static int lastid = 0;
|
||||
private static final Seq<Effect> all = new Seq<>();
|
||||
|
||||
public final int id;
|
||||
public final Cons<EffectContainer> renderer;
|
||||
@@ -21,14 +32,15 @@ public class Effect{
|
||||
public float groundDuration;
|
||||
|
||||
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
|
||||
this.id = lastid++;
|
||||
this.id = all.size;
|
||||
this.lifetime = life;
|
||||
this.renderer = renderer;
|
||||
this.size = clipsize;
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public Effect(float life, Cons<EffectContainer> renderer){
|
||||
this(life, 28f, renderer);
|
||||
this(life,50f, renderer);
|
||||
}
|
||||
|
||||
public Effect ground(){
|
||||
@@ -43,42 +55,123 @@ public class Effect{
|
||||
}
|
||||
|
||||
public void at(Position pos){
|
||||
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
|
||||
create(this, pos.getX(), pos.getY(), 0, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(Position pos, float rotation){
|
||||
Effects.create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
|
||||
create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y){
|
||||
Effects.create(this, x, y, 0, Color.white, null);
|
||||
create(this, x, y, 0, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation){
|
||||
Effects.create(this, x, y, rotation, Color.white, null);
|
||||
create(this, x, y, rotation, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation, Color color){
|
||||
Effects.create(this, x, y, rotation, color, null);
|
||||
create(this, x, y, rotation, color, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, Color color){
|
||||
Effects.create(this, x, y, 0, color, null);
|
||||
create(this, x, y, 0, color, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation, Color color, Object data){
|
||||
Effects.create(this, x, y, rotation, color, data);
|
||||
create(this, x, y, rotation, color, data);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation, Object data){
|
||||
Effects.create(this, x, y, rotation, Color.white, data);
|
||||
create(this, x, y, rotation, Color.white, data);
|
||||
}
|
||||
|
||||
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
|
||||
public float render(int id, Color color, float life, float lifetime, float rotation, float x, float y, Object data){
|
||||
container.set(id, color, life, lifetime, rotation, x, y, data);
|
||||
Draw.z(ground ? Layer.debris : Layer.effect);
|
||||
Draw.reset();
|
||||
renderer.get(container);
|
||||
Draw.reset();
|
||||
|
||||
return container.lifetime;
|
||||
}
|
||||
|
||||
public static @Nullable Effect get(int id){
|
||||
return id >= all.size || id < 0 ? null : all.get(id);
|
||||
}
|
||||
|
||||
private static void shake(float intensity, float duration){
|
||||
if(!headless){
|
||||
Vars.renderer.shake(intensity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, float x, float y){
|
||||
if(Core.camera == null) return;
|
||||
|
||||
float distance = Core.camera.position.dst(x, y);
|
||||
if(distance < 1) distance = 1;
|
||||
|
||||
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, Position loc){
|
||||
shake(intensity, duration, loc.getX(), loc.getY());
|
||||
}
|
||||
|
||||
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
|
||||
if(headless || effect == Fx.none) return;
|
||||
if(Core.settings.getBool("effects")){
|
||||
Rect view = Core.camera.bounds(Tmp.r1);
|
||||
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
|
||||
|
||||
if(view.overlaps(pos)){
|
||||
EffectState entity = EffectState.create();
|
||||
entity.effect = effect;
|
||||
entity.rotation = rotation;
|
||||
entity.data = (data);
|
||||
entity.lifetime = (effect.lifetime);
|
||||
entity.set(x, y);
|
||||
entity.color.set(color);
|
||||
if(data instanceof Posc) entity.parent = ((Posc)data);
|
||||
entity.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation){
|
||||
decal(region, x, y, rotation, 3600f, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
|
||||
if(headless || region == null || !Core.atlas.isFound(region)) return;
|
||||
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
if(tile == null || tile.floor().isLiquid) return;
|
||||
|
||||
Decal decal = Decal.create();
|
||||
decal.set(x, y);
|
||||
decal.rotation(rotation);
|
||||
decal.lifetime(lifetime);
|
||||
decal.color().set(color);
|
||||
decal.region(region);
|
||||
decal.add();
|
||||
}
|
||||
|
||||
public static void scorch(float x, float y, int size){
|
||||
if(headless) return;
|
||||
|
||||
size = Mathf.clamp(size, 0, 9);
|
||||
|
||||
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void rubble(float x, float y, int blockSize){
|
||||
if(headless) return;
|
||||
|
||||
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
|
||||
public static class EffectContainer implements Scaled{
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package mindustry.entities;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Effects{
|
||||
private static final float shakeFalloff = 10000f;
|
||||
|
||||
private static void shake(float intensity, float duration){
|
||||
if(!headless){
|
||||
renderer.shake(intensity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, float x, float y){
|
||||
if(Core.camera == null) return;
|
||||
|
||||
float distance = Core.camera.position.dst(x, y);
|
||||
if(distance < 1) distance = 1;
|
||||
|
||||
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, Position loc){
|
||||
shake(intensity, duration, loc.getX(), loc.getY());
|
||||
}
|
||||
|
||||
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
|
||||
if(headless || effect == Fx.none) return;
|
||||
if(Core.settings.getBool("effects")){
|
||||
Rect view = Core.camera.bounds(Tmp.r1);
|
||||
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
|
||||
|
||||
if(view.overlaps(pos)){
|
||||
EffectState entity = EffectState.create();
|
||||
entity.effect(effect);
|
||||
entity.rotation(rotation);
|
||||
entity.data(data);
|
||||
entity.lifetime(effect.lifetime);
|
||||
entity.set(x, y);
|
||||
entity.color().set(color);
|
||||
if(data instanceof Posc) entity.parent((Posc)data);
|
||||
entity.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation){
|
||||
decal(region, x, y, rotation, 3600f, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
|
||||
if(headless || region == null || !Core.atlas.isFound(region)) return;
|
||||
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
if(tile == null || tile.floor().isLiquid) return;
|
||||
|
||||
Decal decal = Decal.create();
|
||||
decal.set(x, y);
|
||||
decal.rotation(rotation);
|
||||
decal.lifetime(lifetime);
|
||||
decal.color().set(color);
|
||||
decal.region(region);
|
||||
decal.add();
|
||||
}
|
||||
|
||||
public static void scorch(float x, float y, int size){
|
||||
if(headless) return;
|
||||
|
||||
size = Mathf.clamp(size, 0, 9);
|
||||
|
||||
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void rubble(float x, float y, int blockSize){
|
||||
if(headless) return;
|
||||
|
||||
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import mindustry.gen.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.collisions;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Represents a group of a certain type of entity.*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -57,7 +57,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
|
||||
each(Entityc::update);
|
||||
}
|
||||
|
||||
public void copy(Seq arr){
|
||||
public void copy(Seq<T> arr){
|
||||
arr.addAll(array);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
|
||||
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
|
||||
T t = map.get(id);
|
||||
if(t != null){ //remove if present in map already
|
||||
remove(t);
|
||||
t.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public class Fires{
|
||||
|
||||
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
|
||||
public static void create(Tile tile){
|
||||
if(net.client() || tile == null) return; //not clientside.
|
||||
if(net.client() || tile == null || !state.rules.fire) return; //not clientside.
|
||||
|
||||
Fire fire = map.get(tile.pos());
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class GroupDefs<G>{
|
||||
@GroupDef(value = Playerc.class, mapping = true) G player;
|
||||
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
|
||||
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
|
||||
@GroupDef(value = Buildingc.class) G tile;
|
||||
@GroupDef(value = Buildingc.class) G build;
|
||||
@GroupDef(value = Syncc.class, mapping = true) G sync;
|
||||
@GroupDef(value = Drawc.class) G draw;
|
||||
@GroupDef(value = WeatherStatec.class) G weather;
|
||||
|
||||
@@ -24,17 +24,15 @@ public class Lightning{
|
||||
|
||||
/** Create a lighting branch at a location. Use Team.derelict to damage everyone. */
|
||||
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
|
||||
createLightingInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
|
||||
createLightningInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
|
||||
}
|
||||
|
||||
/** Create a lighting branch at a location. Uses bullet parameters. */
|
||||
public static void create(Bullet bullet, Color color, float damage, float x, float y, float targetAngle, int length){
|
||||
createLightingInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
|
||||
createLightningInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
|
||||
}
|
||||
|
||||
//TODO remote method
|
||||
//@Remote(called = Loc.server, unreliable = true)
|
||||
private static void createLightingInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
|
||||
private static void createLightningInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
|
||||
random.setSeed(seed);
|
||||
hit.clear();
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public class Puddles{
|
||||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
|
||||
Fires.create(tile);
|
||||
if(Mathf.chance(0.006 * amount)){
|
||||
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
|
||||
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
|
||||
}
|
||||
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot Puddle
|
||||
if(Mathf.chance(0.5f * amount)){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.entities;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
@@ -19,8 +20,41 @@ public class Units{
|
||||
private static boolean boolResult;
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
public static void unitDeath(Unit unit){
|
||||
unit.killed();
|
||||
public static void unitCapDeath(Unit unit){
|
||||
if(unit != null){
|
||||
unit.dead = true;
|
||||
Fx.unitCapKill.at(unit);
|
||||
Core.app.post(() -> Call.unitDestroy(unit.id));
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
public static void unitDeath(int uid){
|
||||
Unit unit = Groups.unit.getByID(uid);
|
||||
|
||||
//if there's no unit don't add it later and get it stuck as a ghost
|
||||
if(netClient != null){
|
||||
netClient.addRemovedEntity(uid);
|
||||
}
|
||||
|
||||
if(unit != null){
|
||||
unit.killed();
|
||||
}
|
||||
}
|
||||
|
||||
//destroys immediately
|
||||
@Remote(called = Loc.server)
|
||||
public static void unitDestroy(int uid){
|
||||
Unit unit = Groups.unit.getByID(uid);
|
||||
|
||||
//if there's no unit don't add it later and get it stuck as a ghost
|
||||
if(netClient != null){
|
||||
netClient.addRemovedEntity(uid);
|
||||
}
|
||||
|
||||
if(unit != null){
|
||||
unit.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
@@ -36,7 +70,7 @@ public class Units{
|
||||
|
||||
public static int getCap(Team team){
|
||||
//wave team has no cap
|
||||
if((team == state.rules.waveTeam && state.rules.waves) || (state.isCampaign() && team == state.rules.waveTeam)){
|
||||
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return state.rules.unitCap + indexer.getExtraUnits(team);
|
||||
@@ -90,7 +124,7 @@ public class Units{
|
||||
|
||||
nearby(x, y, width, height, unit -> {
|
||||
if(boolResult) return;
|
||||
if(unit.isGrounded() == ground){
|
||||
if((unit.isGrounded() && !unit.type().hovering) == ground){
|
||||
unit.hitbox(hitrect);
|
||||
|
||||
if(hitrect.overlaps(x, y, width, height)){
|
||||
@@ -141,6 +175,18 @@ public class Units{
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the closest target enemy. First, units are checked, then tile entities. */
|
||||
public static Teamc bestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Building> tilePred, Sortf sort){
|
||||
if(team == Team.derelict) return null;
|
||||
|
||||
Unit unit = bestEnemy(team, x, y, range, unitPred, sort);
|
||||
if(unit != null){
|
||||
return unit;
|
||||
}else{
|
||||
return findEnemyTile(team, x, y, range, tilePred);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the closest enemy of this team. Filter by predicate. */
|
||||
public static Unit closestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate){
|
||||
if(team == Team.derelict) return null;
|
||||
@@ -161,6 +207,26 @@ public class Units{
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the closest enemy of this team using a custom comparison function. Filter by predicate. */
|
||||
public static Unit bestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate, Sortf sort){
|
||||
if(team == Team.derelict) return null;
|
||||
|
||||
result = null;
|
||||
cdist = 0f;
|
||||
|
||||
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
|
||||
if(e.dead() || !predicate.get(e) || !e.within(x, y, range)) return;
|
||||
|
||||
float cost = sort.cost(e, x, y);
|
||||
if(result == null || cost < cdist){
|
||||
result = e;
|
||||
cdist = cost;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the closest ally of this team. Filter by predicate. No range. */
|
||||
public static Unit closest(Team team, float x, float y, Boolf<Unit> predicate){
|
||||
result = null;
|
||||
@@ -242,9 +308,20 @@ public class Units{
|
||||
|
||||
/** Iterates over all units that are enemies of this team. */
|
||||
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
|
||||
for(Team enemy : state.teams.enemiesOf(team)){
|
||||
nearby(enemy, x, y, width, height, cons);
|
||||
if(team.active()){
|
||||
for(Team enemy : state.teams.enemiesOf(team)){
|
||||
nearby(enemy, x, y, width, height, cons);
|
||||
}
|
||||
}else{
|
||||
//inactive teams have no cache, check everything
|
||||
//TODO cache all teams with units OR blocks
|
||||
for(Team other : Team.all){
|
||||
if(other != team && teamIndex.count(other) > 0){
|
||||
nearby(other, x, y, width, height, cons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Iterates over all units that are enemies of this team. */
|
||||
@@ -252,4 +329,7 @@ public class Units{
|
||||
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
|
||||
}
|
||||
|
||||
public interface Sortf{
|
||||
float cost(Unit unit, float x, float y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
public class ForceFieldAbility implements Ability{
|
||||
public class ForceFieldAbility extends Ability{
|
||||
/** Shield radius. */
|
||||
public float radius = 60f;
|
||||
/** Shield regen speed in damage/tick. */
|
||||
@@ -21,21 +21,26 @@ public class ForceFieldAbility implements Ability{
|
||||
/** Cooldown after the shield is broken, in ticks. */
|
||||
public float cooldown = 60f * 5;
|
||||
|
||||
private float realRad;
|
||||
private Unit paramUnit;
|
||||
private final Cons<Shielderc> shieldConsumer = trait -> {
|
||||
if(trait.team() != paramUnit.team && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
|
||||
/** State. */
|
||||
protected float radiusScale, alpha;
|
||||
|
||||
private static float realRad;
|
||||
private static Unit paramUnit;
|
||||
private static ForceFieldAbility paramField;
|
||||
private static final Cons<Bullet> shieldConsumer = trait -> {
|
||||
if(trait.team != paramUnit.team && trait.type.absorbable && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
|
||||
trait.absorb();
|
||||
Fx.absorb.at(trait);
|
||||
|
||||
//break shield
|
||||
if(paramUnit.shield <= trait.damage()){
|
||||
paramUnit.shield -= cooldown * regen;
|
||||
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, radius, paramUnit.team.color);
|
||||
paramUnit.shield -= paramField.cooldown * paramField.regen;
|
||||
|
||||
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color);
|
||||
}
|
||||
|
||||
paramUnit.shield -= trait.damage();
|
||||
paramUnit.shieldAlpha = 1f;
|
||||
paramField.alpha = 1f;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,14 +59,17 @@ public class ForceFieldAbility implements Ability{
|
||||
unit.shield += Time.delta * regen;
|
||||
}
|
||||
|
||||
alpha = Math.max(alpha - Time.delta/10f, 0f);
|
||||
|
||||
if(unit.shield > 0){
|
||||
unit.timer2 = Mathf.lerpDelta(unit.timer2, 1f, 0.06f);
|
||||
radiusScale = Mathf.lerpDelta(radiusScale, 1f, 0.06f);
|
||||
paramUnit = unit;
|
||||
paramField = this;
|
||||
checkRadius(unit);
|
||||
|
||||
Groups.bullet.intersect(unit.x - realRad, unit.y - realRad, realRad * 2f, realRad * 2f, shieldConsumer);
|
||||
}else{
|
||||
unit.timer2 = 0f;
|
||||
radiusScale = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +80,7 @@ public class ForceFieldAbility implements Ability{
|
||||
if(unit.shield > 0){
|
||||
Draw.z(Layer.shields);
|
||||
|
||||
Draw.color(unit.team.color, Color.white, Mathf.clamp(unit.shieldAlpha));
|
||||
Draw.color(unit.team.color, Color.white, Mathf.clamp(alpha));
|
||||
|
||||
if(Core.settings.getBool("animatedshields")){
|
||||
Fill.poly(unit.x, unit.y, 6, realRad);
|
||||
@@ -88,6 +96,6 @@ public class ForceFieldAbility implements Ability{
|
||||
|
||||
private void checkRadius(Unit unit){
|
||||
//timer2 is used to store radius scale as an effect
|
||||
realRad = unit.timer2 * radius;
|
||||
realRad = radiusScale * radius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class StatusFieldAbility implements Ability{
|
||||
public @NonNull StatusEffect effect;
|
||||
public class StatusFieldAbility extends Ability{
|
||||
public StatusEffect effect;
|
||||
public float duration = 60, reload = 100, range = 20;
|
||||
public Effect applyEffect = Fx.heal;
|
||||
public Effect activeEffect = Fx.overdriveWave;
|
||||
|
||||
protected float timer;
|
||||
|
||||
StatusFieldAbility(){}
|
||||
|
||||
public StatusFieldAbility(@NonNull StatusEffect effect, float duration, float reload, float range){
|
||||
public StatusFieldAbility(StatusEffect effect, float duration, float reload, float range){
|
||||
this.duration = duration;
|
||||
this.reload = reload;
|
||||
this.range = range;
|
||||
@@ -24,9 +25,9 @@ public class StatusFieldAbility implements Ability{
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
unit.timer2 += Time.delta;
|
||||
timer += Time.delta;
|
||||
|
||||
if(unit.timer2 >= reload){
|
||||
if(timer >= reload){
|
||||
|
||||
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
|
||||
other.apply(effect, duration);
|
||||
@@ -34,7 +35,7 @@ public class StatusFieldAbility implements Ability{
|
||||
|
||||
activeEffect.at(unit);
|
||||
|
||||
unit.timer2 = 0f;
|
||||
timer = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
core/src/mindustry/entities/abilities/UnitSpawnAbility.java
Normal file
59
core/src/mindustry/entities/abilities/UnitSpawnAbility.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public class UnitSpawnAbility extends Ability{
|
||||
public UnitType type;
|
||||
public float spawnTime = 60f, spawnX, spawnY;
|
||||
public Effect spawnEffect = Fx.spawn;
|
||||
|
||||
protected float timer;
|
||||
|
||||
public UnitSpawnAbility(UnitType type, float spawnTime, float spawnX, float spawnY){
|
||||
this.type = type;
|
||||
this.spawnTime = spawnTime;
|
||||
this.spawnX = spawnX;
|
||||
this.spawnY = spawnY;
|
||||
}
|
||||
|
||||
public UnitSpawnAbility(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
timer += Time.delta;
|
||||
|
||||
if(timer >= spawnTime && Units.canCreate(unit.team, type)){
|
||||
|
||||
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
|
||||
spawnEffect.at(x, y);
|
||||
Unit u = type.create(unit.team);
|
||||
u.set(x, y);
|
||||
u.rotation = unit.rotation;
|
||||
if(!Vars.net.client()){
|
||||
u.add();
|
||||
}
|
||||
|
||||
timer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Unit unit){
|
||||
if(Units.canCreate(unit.team, type)){
|
||||
Draw.draw(Draw.z(), () -> {
|
||||
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
|
||||
Drawf.construct(x, y, type.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public abstract class BulletType extends Content{
|
||||
public float hitSize = 4;
|
||||
public float drawSize = 40f;
|
||||
public float drag = 0f;
|
||||
public boolean pierce;
|
||||
public boolean pierce, pierceBuilding;
|
||||
public Effect hitEffect, despawnEffect;
|
||||
|
||||
/** Effect created when shooting. */
|
||||
@@ -54,7 +54,7 @@ public abstract class BulletType extends Content{
|
||||
/** Status effect applied on hit. */
|
||||
public StatusEffect status = StatusEffects.none;
|
||||
/** Intensity of applied status effect in terms of duration. */
|
||||
public float statusDuration = 60 * 10f;
|
||||
public float statusDuration = 60 * 8f;
|
||||
/** Whether this bullet type collides with tiles. */
|
||||
public boolean collidesTiles = true;
|
||||
/** Whether this bullet type collides with tiles that are of the same team. */
|
||||
@@ -69,13 +69,23 @@ public abstract class BulletType extends Content{
|
||||
public boolean scaleVelocity;
|
||||
/** Whether this bullet can be hit by point defense. */
|
||||
public boolean hittable = true;
|
||||
/** Whether this bullet can be reflected. */
|
||||
public boolean reflectable = true;
|
||||
/** Whether this projectile can be absorbed by shields. */
|
||||
public boolean absorbable = true;
|
||||
/** Whether to move the bullet back depending on delta to fix some delta-time realted issues.
|
||||
* Do not change unless you know what you're doing. */
|
||||
public boolean backMove = true;
|
||||
/** Bullet range override. */
|
||||
public float range = -1f;
|
||||
|
||||
//additional effects
|
||||
|
||||
public float fragCone = 360f;
|
||||
public float fragAngle = 0f;
|
||||
public int fragBullets = 9;
|
||||
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
|
||||
public BulletType fragBullet = null;
|
||||
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f, fragLifeMin = 1f, fragLifeMax = 1f;
|
||||
public @Nullable BulletType fragBullet = null;
|
||||
public Color hitColor = Color.white;
|
||||
|
||||
public Color trailColor = Pal.missileYellowBack;
|
||||
@@ -92,14 +102,17 @@ public abstract class BulletType extends Content{
|
||||
public float homingPower = 0f;
|
||||
public float homingRange = 50f;
|
||||
|
||||
public Color lightningColor = Pal.surge;
|
||||
public int lightning;
|
||||
public int lightningLength = 5;
|
||||
public int lightningLength = 5, lightningLengthRand = 0;
|
||||
/** Use a negative value to use default bullet damage. */
|
||||
public float lightningDamage = -1;
|
||||
public float lightningCone = 360f;
|
||||
public float lightningAngle = 0f;
|
||||
|
||||
public float weaveScale = 1f;
|
||||
public float weaveMag = -1f;
|
||||
public float hitShake = 0f;
|
||||
public float hitShake = 0f, despawnShake = 0f;
|
||||
|
||||
public int puddles;
|
||||
public float puddleRange;
|
||||
@@ -123,20 +136,24 @@ public abstract class BulletType extends Content{
|
||||
|
||||
/** Returns maximum distance the bullet this bullet type has can travel. */
|
||||
public float range(){
|
||||
return speed * lifetime * (1f - drag);
|
||||
return Math.max(speed * lifetime * (1f - drag), range);
|
||||
}
|
||||
|
||||
public boolean collides(Bullet bullet, Building tile){
|
||||
return true;
|
||||
}
|
||||
|
||||
public void hitTile(Bullet b, Building tile){
|
||||
public void hitTile(Bullet b, Building tile, float initialHealth){
|
||||
if(status == StatusEffects.burning) {
|
||||
Damage.createIncend(b.x, b.y, damage/10f, (int) damage/10);
|
||||
}
|
||||
hit(b);
|
||||
}
|
||||
|
||||
public void hitEntity(Bullet b, Hitboxc other, float initialHealth){
|
||||
|
||||
}
|
||||
|
||||
public void hit(Bullet b){
|
||||
hit(b, b.x, b.y);
|
||||
}
|
||||
@@ -145,13 +162,13 @@ public abstract class BulletType extends Content{
|
||||
hitEffect.at(x, y, b.rotation(), hitColor);
|
||||
hitSound.at(b);
|
||||
|
||||
Effects.shake(hitShake, hitShake, b);
|
||||
Effect.shake(hitShake, hitShake, b);
|
||||
|
||||
if(fragBullet != null){
|
||||
for(int i = 0; i < fragBullets; i++){
|
||||
float len = Mathf.random(1f, 7f);
|
||||
float a = b.rotation() + Mathf.range(fragCone/2);
|
||||
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
|
||||
float a = b.rotation() + Mathf.range(fragCone/2) + fragAngle;
|
||||
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +192,7 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
|
||||
for(int i = 0; i < lightning; i++){
|
||||
Lightning.create(b, Pal.surge, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength);
|
||||
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, b.rotation() + Mathf.range(lightningCone/2) + lightningAngle, lightningLength + Mathf.random(lightningLengthRand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +200,8 @@ public abstract class BulletType extends Content{
|
||||
despawnEffect.at(b.x, b.y, b.rotation(), hitColor);
|
||||
hitSound.at(b);
|
||||
|
||||
Effect.shake(despawnShake, despawnShake, b);
|
||||
|
||||
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
|
||||
hit(b);
|
||||
}
|
||||
@@ -201,7 +220,7 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
|
||||
if(instantDisappear){
|
||||
b.time(lifetime);
|
||||
b.time = lifetime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +268,10 @@ public abstract class BulletType extends Content{
|
||||
return create(parent.owner(), parent.team, x, y, angle);
|
||||
}
|
||||
|
||||
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl, float lifeScale){
|
||||
return create(parent.owner(), parent.team, x, y, angle, velocityScl, lifeScale);
|
||||
}
|
||||
|
||||
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl){
|
||||
return create(parent.owner(), parent.team, x, y, angle, velocityScl);
|
||||
}
|
||||
@@ -259,7 +282,11 @@ public abstract class BulletType extends Content{
|
||||
bullet.owner = owner;
|
||||
bullet.team = team;
|
||||
bullet.vel.trns(angle, speed * velocityScl);
|
||||
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
|
||||
if(backMove){
|
||||
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
|
||||
}else{
|
||||
bullet.set(x, y);
|
||||
}
|
||||
bullet.lifetime = lifetime * lifetimeScl;
|
||||
bullet.data = data;
|
||||
bullet.drag = drag;
|
||||
@@ -267,13 +294,12 @@ public abstract class BulletType extends Content{
|
||||
bullet.damage = damage < 0 ? this.damage : damage;
|
||||
bullet.add();
|
||||
|
||||
if(keepVelocity && owner instanceof Hitboxc) bullet.vel.add(((Hitboxc)owner).deltaX(), ((Hitboxc)owner).deltaY());
|
||||
if(keepVelocity && owner instanceof Velc) bullet.vel.add(((Velc)owner).vel().x, ((Velc)owner).vel().y);
|
||||
return bullet;
|
||||
|
||||
}
|
||||
|
||||
public void createNet(Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
|
||||
Call.createBullet(this, team, x, y, damage, angle, velocityScl, lifetimeScl);
|
||||
Call.createBullet(this, team, x, y, angle, damage, velocityScl, lifetimeScl);
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
|
||||
@@ -12,10 +12,13 @@ import mindustry.graphics.*;
|
||||
public class ContinuousLaserBulletType extends BulletType{
|
||||
public float length = 220f;
|
||||
public float shake = 1f;
|
||||
public float fadeTime = 16f;
|
||||
public Color[] colors = {Color.valueOf("ec745855"), Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
|
||||
public float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
|
||||
public float[] strokes = {2f, 1.5f, 1f, 0.3f};
|
||||
public float[] lenscales = {1f, 1.12f, 1.15f, 1.17f};
|
||||
public float width = 9f, oscScl = 0.8f, oscMag = 1.5f;
|
||||
public boolean largeHit = true;
|
||||
|
||||
public ContinuousLaserBulletType(float damage){
|
||||
super(0.001f, damage);
|
||||
@@ -25,6 +28,7 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
hitSize = 4;
|
||||
drawSize = 420f;
|
||||
lifetime = 16f;
|
||||
keepVelocity = false;
|
||||
pierce = true;
|
||||
hittable = false;
|
||||
hitColor = colors[2];
|
||||
@@ -32,6 +36,8 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
incendAmount = 1;
|
||||
incendSpread = 5;
|
||||
incendChance = 0.4f;
|
||||
lightColor = Color.orange;
|
||||
absorbable = false;
|
||||
}
|
||||
|
||||
protected ContinuousLaserBulletType(){
|
||||
@@ -43,37 +49,45 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
|
||||
drawSize = Math.max(drawSize, length*2f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Bullet b){
|
||||
//TODO possible laser absorption from blocks
|
||||
|
||||
//damage every 5 ticks
|
||||
if(b.timer(1, 5f)){
|
||||
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length, true);
|
||||
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), length, largeHit);
|
||||
}
|
||||
|
||||
if(shake > 0){
|
||||
Effects.shake(shake, shake, b);
|
||||
Effect.shake(shake, shake, b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
float baseLen = length * b.fout();
|
||||
float realLength = Damage.findLaserLength(b, length);
|
||||
float fout = Mathf.clamp(b.time > b.lifetime - fadeTime ? 1f - (b.time - (lifetime - fadeTime)) / fadeTime : 1f);
|
||||
float baseLen = realLength * fout;
|
||||
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen);
|
||||
for(int s = 0; s < colors.length; s++){
|
||||
Draw.color(Tmp.c1.set(colors[s]).mul(1f + Mathf.absin(Time.time(), 1f, 0.1f)));
|
||||
for(int i = 0; i < tscales.length; i++){
|
||||
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
|
||||
Lines.stroke((9f + Mathf.absin(Time.time(), 0.8f, 1.5f)) * b.fout() * strokes[s] * tscales[i]);
|
||||
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], CapStyle.none);
|
||||
Lines.stroke((width + Mathf.absin(Time.time(), oscScl, oscMag)) * fout * strokes[s] * tscales[i]);
|
||||
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);
|
||||
|
||||
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, Color.orange, 0.7f);
|
||||
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, lightColor, 0.7f);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ public class HealBulletType extends BulletType{
|
||||
despawnEffect = Fx.hitLaser;
|
||||
collidesTeam = true;
|
||||
hittable = false;
|
||||
reflectable = false;
|
||||
}
|
||||
|
||||
public HealBulletType(){
|
||||
@@ -29,7 +30,7 @@ public class HealBulletType extends BulletType{
|
||||
|
||||
@Override
|
||||
public boolean collides(Bullet b, Building tile){
|
||||
return tile.team() != b.team || tile.healthf() < 1f;
|
||||
return tile.team != b.team || tile.healthf() < 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,11 +44,11 @@ public class HealBulletType extends BulletType{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTile(Bullet b, Building tile){
|
||||
public void hitTile(Bullet b, Building tile, float initialHealth){
|
||||
super.hit(b);
|
||||
|
||||
if(tile.team() == b.team && !(tile.block() instanceof BuildBlock)){
|
||||
Fx.healBlockFull.at(tile.x, tile.y, tile.block().size, Pal.heal);
|
||||
if(tile.team == b.team && !(tile.block instanceof ConstructBlock)){
|
||||
Fx.healBlockFull.at(tile.x, tile.y, tile.block.size, Pal.heal);
|
||||
tile.heal(healPercent / 100f * tile.maxHealth());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,17 @@ import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.world;
|
||||
|
||||
public class LaserBulletType extends BulletType{
|
||||
protected static Tile furthest;
|
||||
|
||||
protected Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
|
||||
protected Effect laserEffect = Fx.lancerLaserShootSmoke;
|
||||
protected float length = 160f;
|
||||
protected float width = 15f;
|
||||
protected float lengthFalloff = 0.5f;
|
||||
protected float sideLength = 29f, sideWidth = 0.7f;
|
||||
protected float sideAngle = 90f;
|
||||
public Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
|
||||
public Effect laserEffect = Fx.lancerLaserShootSmoke;
|
||||
public float length = 160f;
|
||||
public float width = 15f;
|
||||
public float lengthFalloff = 0.5f;
|
||||
public float sideLength = 29f, sideWidth = 0.7f;
|
||||
public float sideAngle = 90f;
|
||||
public float lightningSpacing = -1, lightningDelay = 0.1f, lightningAngleRand;
|
||||
public boolean largeHit = false;
|
||||
|
||||
public LaserBulletType(float damage){
|
||||
super(0.01f, damage);
|
||||
@@ -42,6 +39,13 @@ public class LaserBulletType extends BulletType{
|
||||
this(1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
|
||||
drawSize = Math.max(drawSize, length*2f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return length;
|
||||
@@ -49,24 +53,35 @@ public class LaserBulletType extends BulletType{
|
||||
|
||||
@Override
|
||||
public void init(Bullet b){
|
||||
Tmp.v1.trns(b.rotation(), length);
|
||||
float resultLength = Damage.collideLaser(b, length, largeHit), rot = b.rotation();
|
||||
|
||||
furthest = null;
|
||||
laserEffect.at(b.x, b.y, rot, resultLength * 0.75f);
|
||||
|
||||
world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
|
||||
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
|
||||
if(lightningSpacing > 0){
|
||||
int idx = 0;
|
||||
for(float i = 0; i <= resultLength; i += lightningSpacing){
|
||||
float cx = b.x + Angles.trnsx(rot, i),
|
||||
cy = b.y + Angles.trnsy(rot, i);
|
||||
|
||||
float resultLength = furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
|
||||
int f = idx++;
|
||||
|
||||
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), resultLength);
|
||||
if(furthest != null) b.data(resultLength);
|
||||
|
||||
laserEffect.at(b.x, b.y, b.rotation(), resultLength * 0.75f);
|
||||
for(int s : Mathf.signs){
|
||||
Time.run(f * lightningDelay, () -> {
|
||||
if(b.isAdded() && b.type == this){
|
||||
Lightning.create(b, lightningColor,
|
||||
lightningDamage < 0 ? damage : lightningDamage,
|
||||
cx, cy, rot + 90*s + Mathf.range(lightningAngleRand),
|
||||
lightningLength + Mathf.random(lightningLengthRand));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
float realLength = b.data() == null ? length : (Float)b.data();
|
||||
float realLength = b.fdata;
|
||||
|
||||
float f = Mathf.curve(b.fin(), 0f, 0.2f);
|
||||
float baseLen = realLength * f;
|
||||
@@ -74,11 +89,10 @@ public class LaserBulletType extends BulletType{
|
||||
float compound = 1f;
|
||||
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen);
|
||||
Lines.precise(true);
|
||||
for(Color color : colors){
|
||||
Draw.color(color);
|
||||
Lines.stroke((cwidth *= lengthFalloff) * b.fout());
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, CapStyle.none);
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, false);
|
||||
Tmp.v1.trns(b.rotation(), baseLen);
|
||||
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, Lines.getStroke() * 1.22f, cwidth * 2f + width / 2f, b.rotation());
|
||||
|
||||
@@ -89,7 +103,6 @@ public class LaserBulletType extends BulletType{
|
||||
|
||||
compound *= lengthFalloff;
|
||||
}
|
||||
Lines.precise(false);
|
||||
Draw.reset();
|
||||
|
||||
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);
|
||||
|
||||
@@ -13,7 +13,7 @@ import mindustry.world.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LiquidBulletType extends BulletType{
|
||||
public @NonNull Liquid liquid;
|
||||
public Liquid liquid;
|
||||
public float puddleSize = 6f;
|
||||
|
||||
public LiquidBulletType(@Nullable Liquid liquid){
|
||||
@@ -24,8 +24,9 @@ public class LiquidBulletType extends BulletType{
|
||||
this.status = liquid.effect;
|
||||
}
|
||||
|
||||
lifetime = 74f;
|
||||
statusDuration = 90f;
|
||||
ammoMultiplier = 1f;
|
||||
lifetime = 34f;
|
||||
statusDuration = 60f * 2f;
|
||||
despawnEffect = Fx.none;
|
||||
hitEffect = Fx.hitLiquid;
|
||||
smokeEffect = Fx.none;
|
||||
@@ -61,7 +62,7 @@ public class LiquidBulletType extends BulletType{
|
||||
public void draw(Bullet b){
|
||||
Draw.color(liquid.color, Color.white, b.fout() / 100f);
|
||||
|
||||
Fill.circle(b.x, b.y, 3f);
|
||||
Fill.circle(b.x, b.y, puddleSize / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +70,7 @@ public class LiquidBulletType extends BulletType{
|
||||
super.despawned(b);
|
||||
|
||||
//don't create liquids when the projectile despawns
|
||||
hitEffect.at(b.x, b.y, liquid.color);
|
||||
hitEffect.at(b.x, b.y, b.rotation(), liquid.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,7 +79,7 @@ public class LiquidBulletType extends BulletType{
|
||||
Puddles.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
|
||||
|
||||
if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){
|
||||
float intensity = 400f;
|
||||
float intensity = 400f * puddleSize/6f;
|
||||
Fires.extinguish(world.tileWorld(hitx, hity), intensity);
|
||||
for(Point2 p : Geometry.d4){
|
||||
Fires.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.graphics.Color;
|
||||
import arc.graphics.g2d.Draw;
|
||||
import arc.math.Angles;
|
||||
import arc.math.Mathf;
|
||||
import mindustry.content.Fx;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.Pal;
|
||||
import mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.blocks.distribution.MassDriver.*;
|
||||
|
||||
import static mindustry.Vars.content;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MassDriverBolt extends BulletType{
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ public class MissileBulletType extends BasicBulletType{
|
||||
height = 8f;
|
||||
hitSound = Sounds.explosion;
|
||||
trailChance = 0.2f;
|
||||
lifetime = 49f;
|
||||
}
|
||||
|
||||
public MissileBulletType(float speed, float damage){
|
||||
|
||||
70
core/src/mindustry/entities/bullet/PointBulletType.java
Normal file
70
core/src/mindustry/entities/bullet/PointBulletType.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class PointBulletType extends BulletType{
|
||||
private static float cdist = 0f;
|
||||
private static Unit result;
|
||||
|
||||
public float trailSpacing = 10f;
|
||||
|
||||
public PointBulletType(){
|
||||
scaleVelocity = true;
|
||||
lifetime = 100f;
|
||||
collides = false;
|
||||
keepVelocity = false;
|
||||
backMove = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Bullet b){
|
||||
super.init(b);
|
||||
|
||||
float px = b.x + b.lifetime * b.vel.x,
|
||||
py = b.y + b.lifetime * b.vel.y,
|
||||
rot = b.rotation();
|
||||
|
||||
Geometry.iterateLine(0f, b.x, b.y, px, py, trailSpacing, (x, y) -> {
|
||||
trailEffect.at(x, y, rot);
|
||||
});
|
||||
|
||||
b.time = b.lifetime;
|
||||
b.set(px, py);
|
||||
|
||||
//calculate hit entity
|
||||
|
||||
cdist = 0f;
|
||||
result = null;
|
||||
float range = 1f;
|
||||
|
||||
Units.nearbyEnemies(b.team, px - range, py - range, range*2f, range*2f, e -> {
|
||||
if(e.dead()) return;
|
||||
|
||||
e.hitbox(Tmp.r1);
|
||||
if(!Tmp.r1.contains(px, py)) return;
|
||||
|
||||
float dst = e.dst(px, py) - e.hitSize;
|
||||
if((result == null || dst < cdist)){
|
||||
result = e;
|
||||
cdist = dst;
|
||||
}
|
||||
});
|
||||
|
||||
if(result != null){
|
||||
b.collision(result, px, py);
|
||||
}else{
|
||||
Building build = Vars.world.buildWorld(px, py);
|
||||
if(build != null && build.team != b.team){
|
||||
build.collision(b);
|
||||
}
|
||||
}
|
||||
|
||||
b.remove();
|
||||
|
||||
b.vel.setZero();
|
||||
}
|
||||
}
|
||||
60
core/src/mindustry/entities/bullet/RailBulletType.java
Normal file
60
core/src/mindustry/entities/bullet/RailBulletType.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
28
core/src/mindustry/entities/comp/AmmoDistributeComp.java
Normal file
28
core/src/mindustry/entities/comp/AmmoDistributeComp.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.units.*;
|
||||
|
||||
@Component
|
||||
abstract class AmmoDistributeComp implements Unitc{
|
||||
@Import float x, y;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
@Import float ammo;
|
||||
|
||||
private transient float ammoCooldown;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(ammoCooldown > 0f) ammoCooldown -= Time.delta;
|
||||
|
||||
if(ammo > 0 && ammoCooldown <= 0f && ResupplyPoint.resupply(team, x, y, type.ammoResupplyRange, Math.min(type.ammoResupplyAmount, ammo), type.ammoType.color, u -> u != self())){
|
||||
ammo -= Math.min(type.ammoResupplyAmount, ammo);
|
||||
ammoCooldown = 5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,16 @@ abstract class BlockUnitComp implements Unitc{
|
||||
this.tile = tile;
|
||||
|
||||
//sets up block stats
|
||||
maxHealth(tile.block().health);
|
||||
maxHealth(tile.block.health);
|
||||
health(tile.health());
|
||||
hitSize(tile.block().size * tilesize * 0.7f);
|
||||
hitSize(tile.block.size * tilesize * 0.7f);
|
||||
set(tile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(tile != null){
|
||||
team = tile.team();
|
||||
team = tile.team;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ abstract class BlockUnitComp implements Unitc{
|
||||
public void team(Team team){
|
||||
if(tile != null && this.team != team){
|
||||
this.team = team;
|
||||
if(tile.team() != team){
|
||||
if(tile.team != team){
|
||||
tile.team(team);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.BuildBlock.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -29,13 +29,7 @@ abstract class BuilderComp implements Unitc{
|
||||
@Import float x, y, rotation;
|
||||
|
||||
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
|
||||
transient boolean updateBuilding = true;
|
||||
|
||||
@Override
|
||||
public void controller(UnitController next){
|
||||
//reset building state so AI controlled units will always start off building
|
||||
updateBuilding = true;
|
||||
}
|
||||
@SyncLocal transient boolean updateBuilding = true;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -77,20 +71,20 @@ abstract class BuilderComp implements Unitc{
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
|
||||
if(within(tile, finalPlaceDst)){
|
||||
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
|
||||
lookAt(angleTo(tile));
|
||||
}
|
||||
|
||||
if(!(tile.block() instanceof BuildBlock)){
|
||||
if(!(tile.block() instanceof ConstructBlock)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team(), current.x, current.y, current.rotation)){
|
||||
boolean hasAll = infinite || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
|
||||
|
||||
if(hasAll){
|
||||
Build.beginPlace(current.block, team(), current.x, current.y, current.rotation);
|
||||
Call.beginPlace(current.block, team(), current.x, current.y, current.rotation);
|
||||
}else{
|
||||
current.stuck = true;
|
||||
}
|
||||
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
|
||||
Build.beginBreak(team(), current.x, current.y);
|
||||
Call.beginBreak(team(), current.x, current.y);
|
||||
}else{
|
||||
plans.removeFirst();
|
||||
return;
|
||||
@@ -100,27 +94,23 @@ abstract class BuilderComp implements Unitc{
|
||||
return;
|
||||
}
|
||||
|
||||
if(tile.build instanceof BuildEntity && !current.initialized){
|
||||
if(tile.build instanceof ConstructBuild && !current.initialized){
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team(), (Builderc)this, current.breaking)));
|
||||
current.initialized = true;
|
||||
}
|
||||
|
||||
//if there is no core to build with or no build entity, stop building!
|
||||
if((core == null && !infinite) || !(tile.build instanceof BuildEntity)){
|
||||
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild)){
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise, update it.
|
||||
BuildEntity entity = tile.bc();
|
||||
ConstructBuild entity = tile.bc();
|
||||
|
||||
if(current.breaking){
|
||||
entity.deconstruct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier);
|
||||
entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier);
|
||||
}else{
|
||||
if(entity.construct(base(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.hasConfig)){
|
||||
if(current.hasConfig){
|
||||
Call.tileConfig(null, tile.build, current.config);
|
||||
}
|
||||
}
|
||||
entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type().buildSpeed * state.rules.buildSpeedMultiplier, current.config);
|
||||
}
|
||||
|
||||
current.stuck = Mathf.equal(current.progress, entity.progress);
|
||||
@@ -150,7 +140,6 @@ abstract class BuilderComp implements Unitc{
|
||||
boolean shouldSkip(BuildPlan request, @Nullable Building core){
|
||||
//requests that you have at least *started* are considered
|
||||
if(state.rules.infiniteResources || team().rules().infiniteResources || request.breaking || core == null) return false;
|
||||
//TODO these are bad criteria
|
||||
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item)) && !request.initialized);
|
||||
}
|
||||
|
||||
@@ -190,8 +179,8 @@ abstract class BuilderComp implements Unitc{
|
||||
plans.remove(replace);
|
||||
}
|
||||
Tile tile = world.tile(place.x, place.y);
|
||||
if(tile != null && tile.build instanceof BuildEntity){
|
||||
place.progress = tile.<BuildEntity>bc().progress;
|
||||
if(tile != null && tile.build instanceof ConstructBuild){
|
||||
place.progress = tile.<ConstructBuild>bc().progress;
|
||||
}
|
||||
if(tail){
|
||||
plans.addLast(place);
|
||||
|
||||
@@ -18,11 +18,13 @@ import arc.util.io.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
@@ -37,9 +39,9 @@ import static mindustry.Vars.*;
|
||||
|
||||
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
|
||||
@Component(base = true)
|
||||
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable{
|
||||
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Senseable, Controllable{
|
||||
//region vars and initialization
|
||||
static final float timeToSleep = 60f * 1;
|
||||
static final float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
|
||||
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
|
||||
static final Seq<Building> tempTileEnts = new Seq<>();
|
||||
static final Seq<Tile> tempTiles = new Seq<>();
|
||||
@@ -54,6 +56,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
transient boolean updateFlow;
|
||||
transient byte dump;
|
||||
transient int rotation;
|
||||
transient boolean enabled = true;
|
||||
transient float enabledControlTime;
|
||||
|
||||
PowerModule power;
|
||||
ItemModule items;
|
||||
@@ -72,6 +76,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public Building init(Tile tile, Team team, boolean shouldAdd, int rotation){
|
||||
if(!initialized){
|
||||
create(tile.block(), team);
|
||||
}else{
|
||||
if(block.hasPower){
|
||||
//reinit power graph
|
||||
power.graph = new PowerGraph();
|
||||
power.graph.add(self());
|
||||
}
|
||||
}
|
||||
this.rotation = rotation;
|
||||
this.tile = tile;
|
||||
@@ -84,7 +94,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
created();
|
||||
|
||||
return base();
|
||||
return self();
|
||||
}
|
||||
|
||||
/** Sets up all the necessary variables, but does not add this entity anywhere. */
|
||||
@@ -97,21 +107,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
|
||||
}
|
||||
|
||||
health(block.health);
|
||||
health = block.health;
|
||||
maxHealth(block.health);
|
||||
timer(new Interval(block.timers));
|
||||
|
||||
cons = new ConsumeModule(base());
|
||||
cons = new ConsumeModule(self());
|
||||
if(block.hasItems) items = new ItemModule();
|
||||
if(block.hasLiquids) liquids = new LiquidModule();
|
||||
if(block.hasPower){
|
||||
power = new PowerModule();
|
||||
power.graph.add(base());
|
||||
power.graph.add(self());
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
|
||||
return base();
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,8 +141,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
public final void writeBase(Writes write){
|
||||
write.f(health);
|
||||
write.b(rotation);
|
||||
write.b(rotation | 0b10000000);
|
||||
write.b(team.id);
|
||||
write.b(0); //extra padding for later use
|
||||
if(items != null) items.write(write);
|
||||
if(power != null) power.write(write);
|
||||
if(liquids != null) liquids.write(write);
|
||||
@@ -141,12 +152,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
public final void readBase(Reads read){
|
||||
health = read.f();
|
||||
rotation(read.b());
|
||||
byte rot = read.b();
|
||||
team = Team.get(read.b());
|
||||
if(items != null) items.read(read);
|
||||
if(power != null) power.read(read);
|
||||
if(liquids != null) liquids.read(read);
|
||||
if(cons != null) cons.read(read);
|
||||
|
||||
rotation = rot & 0b01111111;
|
||||
boolean legacy = true;
|
||||
if((rot & 0b10000000) != 0){
|
||||
read.b(); //padding
|
||||
legacy = false;
|
||||
}
|
||||
|
||||
if(items != null) items.read(read, legacy);
|
||||
if(power != null) power.read(read, legacy);
|
||||
if(liquids != null) liquids.read(read, legacy);
|
||||
if(cons != null) cons.read(read, legacy);
|
||||
}
|
||||
|
||||
public void writeAll(Writes write){
|
||||
@@ -176,17 +195,17 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void configure(Object value){
|
||||
//save last used config
|
||||
block.lastConfig = value;
|
||||
Call.tileConfig(player, base(), value);
|
||||
Call.tileConfig(player, self(), value);
|
||||
}
|
||||
|
||||
/** Configure from a server. */
|
||||
public void configureAny(Object value){
|
||||
Call.tileConfig(null, base(), value);
|
||||
Call.tileConfig(null, self(), value);
|
||||
}
|
||||
|
||||
/** Deselect this tile from configuration. */
|
||||
public void deselect(){
|
||||
if(!headless && control.input.frag.config.getSelectedTile() == base()){
|
||||
if(!headless && control.input.frag.config.getSelectedTile() == self()){
|
||||
control.input.frag.config.hideConfig();
|
||||
}
|
||||
}
|
||||
@@ -294,6 +313,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
|
||||
public float efficiency(){
|
||||
//disabled -> 0 efficiency
|
||||
if(!enabled) return 0;
|
||||
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
|
||||
}
|
||||
|
||||
@@ -325,10 +346,20 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//endregion
|
||||
//region handler methods
|
||||
|
||||
/** Called when this block is dropped as a payload. */
|
||||
public void dropped(){
|
||||
|
||||
}
|
||||
|
||||
/** This is for logic blocks. */
|
||||
public void handleString(Object value){
|
||||
|
||||
}
|
||||
|
||||
public void created(){}
|
||||
|
||||
|
||||
public boolean shouldConsume(){
|
||||
return true;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean productionValid(){
|
||||
@@ -341,7 +372,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Returns the amount of items this block can accept. */
|
||||
public int acceptStack(Item item, int amount, Teamc source){
|
||||
if(acceptItem(base(), item) && block.hasItems && (source == null || source.team() == team)){
|
||||
if(acceptItem(self(), item) && block.hasItems && (source == null || source.team() == team)){
|
||||
return Math.min(getMaximumAccepted(item) - items.get(item), amount);
|
||||
}else{
|
||||
return 0;
|
||||
@@ -390,12 +421,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* @param todump payload to dump.
|
||||
* @return whether the payload was moved successfully
|
||||
*/
|
||||
public boolean movePayload(@NonNull Payload todump){
|
||||
public boolean movePayload(Payload todump){
|
||||
int trns = block.size/2 + 1;
|
||||
Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
|
||||
|
||||
if(next != null && next.build != null && next.build.team() == team && next.build.acceptPayload(base(), todump)){
|
||||
next.build.handlePayload(base(), todump);
|
||||
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
|
||||
next.build.handlePayload(self(), todump);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -407,7 +438,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* @param todump payload to dump.
|
||||
* @return whether the payload was moved successfully
|
||||
*/
|
||||
public boolean dumpPayload(@NonNull Payload todump){
|
||||
public boolean dumpPayload(Payload todump){
|
||||
if(proximity.size == 0) return false;
|
||||
|
||||
int dump = this.dump;
|
||||
@@ -415,8 +446,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
|
||||
if(other.team() == team && other.acceptPayload(base(), todump)){
|
||||
other.handlePayload(base(), todump);
|
||||
if(other.team == team && other.acceptPayload(self(), todump)){
|
||||
other.handlePayload(self(), todump);
|
||||
incrementDump(proximity.size);
|
||||
return true;
|
||||
}
|
||||
@@ -449,7 +480,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(proximity.size);
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
other = other.getLiquidDestination(base(), liquid);
|
||||
other = other.getLiquidDestination(self(), liquid);
|
||||
|
||||
if(other != null && other.team == team && other.block.hasLiquids && canDumpLiquid(other, liquid) && other.liquids != null){
|
||||
float ofract = other.liquids.get(liquid) / other.block.liquidCapacity;
|
||||
@@ -468,8 +499,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void transferLiquid(Building next, float amount, Liquid liquid){
|
||||
float flow = Math.min(next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f, amount);
|
||||
|
||||
if(next.acceptLiquid(base(), liquid, flow)){
|
||||
next.handleLiquid(base(), liquid, flow);
|
||||
if(next.acceptLiquid(self(), liquid, flow)){
|
||||
next.handleLiquid(self(), liquid, flow);
|
||||
liquids.remove(liquid, flow);
|
||||
}
|
||||
}
|
||||
@@ -492,36 +523,33 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public float moveLiquid(Building next, Liquid liquid){
|
||||
if(next == null) return 0;
|
||||
|
||||
next = next.getLiquidDestination(base(), liquid);
|
||||
next = next.getLiquidDestination(self(), liquid);
|
||||
|
||||
if(next.team() == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
|
||||
if(next.team == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
|
||||
float ofract = next.liquids.get(liquid) / next.block.liquidCapacity;
|
||||
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
|
||||
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
|
||||
flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f);
|
||||
|
||||
if(next.acceptLiquid(base(), liquid, 0f)){
|
||||
float ofract = next.liquids().get(liquid) / next.block.liquidCapacity;
|
||||
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
|
||||
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
|
||||
flow = Math.min(flow, next.block.liquidCapacity - next.liquids().get(liquid) - 0.001f);
|
||||
if(flow > 0f && ofract <= fract && next.acceptLiquid(self(), liquid, flow)){
|
||||
next.handleLiquid(self(), liquid, flow);
|
||||
liquids.remove(liquid, flow);
|
||||
return flow;
|
||||
}else if(next.liquids.currentAmount() / next.block.liquidCapacity > 0.1f && fract > 0.1f){
|
||||
//TODO these are incorrect effect positions
|
||||
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
|
||||
|
||||
if(flow > 0f && ofract <= fract && next.acceptLiquid(base(), liquid, flow)){
|
||||
next.handleLiquid(base(), liquid, flow);
|
||||
liquids.remove(liquid, flow);
|
||||
return flow;
|
||||
}else if(ofract > 0.1f && fract > 0.1f){
|
||||
//TODO these are incorrect effect positions
|
||||
float fx = (x + next.x) / 2f, fy = (y + next.y) / 2f;
|
||||
|
||||
Liquid other = next.liquids().current();
|
||||
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
|
||||
damage(1 * Time.delta);
|
||||
next.damage(1 * Time.delta);
|
||||
if(Mathf.chance(0.1 * Time.delta)){
|
||||
Fx.fire.at(fx, fy);
|
||||
}
|
||||
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
|
||||
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
|
||||
if(Mathf.chance(0.2f * Time.delta)){
|
||||
Fx.steam.at(fx, fy);
|
||||
}
|
||||
Liquid other = next.liquids.current();
|
||||
if((other.flammability > 0.3f && liquid.temperature > 0.7f) || (liquid.flammability > 0.3f && other.temperature > 0.7f)){
|
||||
damage(1 * Time.delta);
|
||||
next.damage(1 * Time.delta);
|
||||
if(Mathf.chance(0.1 * Time.delta)){
|
||||
Fx.fire.at(fx, fy);
|
||||
}
|
||||
}else if((liquid.temperature > 0.7f && other.temperature < 0.55f) || (other.temperature > 0.7f && liquid.temperature < 0.55f)){
|
||||
liquids.remove(liquid, Math.min(liquids.get(liquid), 0.7f * Time.delta));
|
||||
if(Mathf.chance(0.2f * Time.delta)){
|
||||
Fx.steam.at(fx, fy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,7 +558,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public Building getLiquidDestination(Building from, Liquid liquid){
|
||||
return base();
|
||||
return self();
|
||||
}
|
||||
|
||||
public @Nullable Payload getPayload(){
|
||||
@@ -552,13 +580,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(proximity.size);
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
|
||||
other.handleItem(base(), item);
|
||||
if(other.team == team && other.acceptItem(self(), item) && canDump(other, item)){
|
||||
other.handleItem(self(), item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handleItem(base(), item);
|
||||
handleItem(self(), item);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,8 +598,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(proximity.size);
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
if(other.team() == team && other.acceptItem(base(), item) && canDump(other, item)){
|
||||
other.handleItem(base(), item);
|
||||
if(other.team == team && other.acceptItem(self(), item) && canDump(other, item)){
|
||||
other.handleItem(self(), item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -603,16 +631,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
for(int ii = 0; ii < content.items().size; ii++){
|
||||
Item item = content.item(ii);
|
||||
|
||||
if(other.team() == team && items.has(item) && other.acceptItem(base(), item) && canDump(other, item)){
|
||||
other.handleItem(base(), item);
|
||||
if(other.team == team && items.has(item) && other.acceptItem(self(), item) && canDump(other, item)){
|
||||
other.handleItem(self(), item);
|
||||
items.remove(item, 1);
|
||||
incrementDump(proximity.size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(other.team() == team && other.acceptItem(base(), todump) && canDump(other, todump)){
|
||||
other.handleItem(base(), todump);
|
||||
if(other.team == team && other.acceptItem(self(), todump) && canDump(other, todump)){
|
||||
other.handleItem(self(), todump);
|
||||
items.remove(todump, 1);
|
||||
incrementDump(proximity.size);
|
||||
return true;
|
||||
@@ -637,8 +665,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
/** Try offloading an item to a nearby container in its facing direction. Returns true if success. */
|
||||
public boolean moveForward(Item item){
|
||||
Building other = front();
|
||||
if(other != null && other.team() == team && other.acceptItem(base(), item)){
|
||||
other.handleItem(base(), item);
|
||||
if(other != null && other.team == team && other.acceptItem(self(), item)){
|
||||
other.handleItem(self(), item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -668,13 +696,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return;
|
||||
}
|
||||
|
||||
power.graph.remove(base());
|
||||
power.graph.remove(self());
|
||||
for(int i = 0; i < power.links.size; i++){
|
||||
Tile other = world.tile(power.links.get(i));
|
||||
if(other != null && other.build != null && other.build.power != null){
|
||||
other.build.power.links.removeValue(pos());
|
||||
}
|
||||
}
|
||||
power.links.clear();
|
||||
}
|
||||
|
||||
public Seq<Building> getPowerConnections(Seq<Building> out){
|
||||
@@ -715,11 +744,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void drawStatus(){
|
||||
if(block.consumes.any()){
|
||||
if(block.enableDrawStatus && block.consumes.any()){
|
||||
float brcx = tile.drawx() + (block.size * tilesize / 2f) - (tilesize / 2f);
|
||||
float brcy = tile.drawy() - (block.size * tilesize / 2f) + (tilesize / 2f);
|
||||
|
||||
Draw.z(Layer.blockOver);
|
||||
Draw.z(Layer.power + 1);
|
||||
Draw.color(Pal.gray);
|
||||
Fill.square(brcx, brcy, 2.5f, 45);
|
||||
Draw.color(cons.status().color);
|
||||
@@ -741,6 +770,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void drawSelect(){
|
||||
}
|
||||
|
||||
public void drawDisabled(){
|
||||
Draw.color(Color.scarlet);
|
||||
Draw.alpha(0.8f);
|
||||
|
||||
float size = 6f;
|
||||
Draw.rect(Icon.cancel.getRegion(), x, y, size, size);
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public void draw(){
|
||||
Draw.rect(block.region, x, y, block.rotate ? rotdeg() : 0);
|
||||
|
||||
@@ -780,10 +819,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Called after the block is placed by this client. */
|
||||
@CallSuper
|
||||
public void playerPlaced(){
|
||||
if(block.saveConfig && block.lastConfig != null){
|
||||
configure(block.lastConfig);
|
||||
}
|
||||
public void playerPlaced(Object config){
|
||||
|
||||
}
|
||||
|
||||
/** Called after the block is placed by anyone. */
|
||||
@@ -796,10 +833,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
tempTiles.clear();
|
||||
Geometry.circle(tileX(), tileY(), range, (x, y) -> {
|
||||
Building other = world.build(x, y);
|
||||
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, base()) && !PowerNode.insulated(other, base())
|
||||
&& !other.proximity().contains(this.<Building>base()) &&
|
||||
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, self()) && !PowerNode.insulated(other, self())
|
||||
&& !other.proximity().contains(this.<Building>self()) &&
|
||||
!(block.outputsPower && proximity.contains(p -> p.power != null && p.power.graph == other.power.graph))){
|
||||
tempTiles.add(other.tile());
|
||||
tempTiles.add(other.tile);
|
||||
}
|
||||
});
|
||||
tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile)));
|
||||
@@ -812,6 +849,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a block is placed over some other blocks. This seq will always have at least one item.
|
||||
* Should load some previous state, if necessary. */
|
||||
public void overwrote(Seq<Building> previous){
|
||||
|
||||
}
|
||||
|
||||
public void onRemoved(){
|
||||
}
|
||||
|
||||
@@ -824,7 +868,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
/** Called when arbitrary configuration is applied to a tile. */
|
||||
public void configured(@Nullable Player player, @Nullable Object value){
|
||||
public void configured(@Nullable Unit builder, @Nullable Object value){
|
||||
//null is of type void.class; anonymous classes use their superclass.
|
||||
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass();
|
||||
|
||||
@@ -833,8 +877,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the block is tapped.*/
|
||||
public void tapped(Player player){
|
||||
/** Called when the block is tapped by the local player. */
|
||||
public void tapped(){
|
||||
|
||||
}
|
||||
|
||||
@@ -861,7 +905,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
power += this.power.status * block.consumes.getPower().capacity;
|
||||
}
|
||||
|
||||
if(block.hasLiquids){
|
||||
if(block.hasLiquids && state.rules.damageExplosions){
|
||||
|
||||
liquids.each((liquid, amount) -> {
|
||||
float splash = Mathf.clamp(amount / 4f, 0f, 10f);
|
||||
@@ -877,9 +921,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
});
|
||||
}
|
||||
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame);
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame, state.rules.damageExplosions);
|
||||
|
||||
if(!floor().solid && !floor().isLiquid){
|
||||
Effects.rubble(x, y, block.size);
|
||||
Effect.rubble(x, y, block.size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,7 +979,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
table.row();
|
||||
table.table(this::displayConsumption).growX();
|
||||
|
||||
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow");
|
||||
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow") && block.displayFlow;
|
||||
|
||||
if(displayFlow){
|
||||
String ps = " " + StatUnit.perSecond.localized();
|
||||
@@ -972,9 +1017,21 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
if(liquids != null){
|
||||
table.row();
|
||||
table.table(l -> {
|
||||
l.left();
|
||||
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
|
||||
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
|
||||
boolean[] had = {false};
|
||||
|
||||
Runnable rebuild = () -> {
|
||||
l.clearChildren();
|
||||
l.left();
|
||||
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
|
||||
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
|
||||
};
|
||||
|
||||
l.update(() -> {
|
||||
if(!had[0] && liquids.hadFlow()){
|
||||
had[0] = true;
|
||||
rebuild.run();
|
||||
}
|
||||
});
|
||||
}).left();
|
||||
}
|
||||
}
|
||||
@@ -987,13 +1044,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
table.left();
|
||||
for(Consume cons : block.consumes.all()){
|
||||
if(cons.isOptional() && cons.isBoost()) continue;
|
||||
cons.build(base(), table);
|
||||
cons.build(self(), table);
|
||||
}
|
||||
}
|
||||
|
||||
public void displayBars(Table table){
|
||||
for(Func<Building, Bar> bar : block.bars.list()){
|
||||
table.add(bar.get(base())).growX();
|
||||
table.add(bar.get(self())).growX();
|
||||
table.row();
|
||||
}
|
||||
}
|
||||
@@ -1019,7 +1076,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* @return whether or not this block should be deselected.
|
||||
*/
|
||||
public boolean onConfigureTileTapped(Building other){
|
||||
return base() != other;
|
||||
return self() != other;
|
||||
}
|
||||
|
||||
/** Returns whether this config menu should show when the specified player taps it. */
|
||||
@@ -1060,6 +1117,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canPickup(){
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeFromProximity(){
|
||||
onProximityRemoved();
|
||||
tmpTiles.clear();
|
||||
@@ -1074,7 +1135,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
for(Building other : tmpTiles){
|
||||
other.proximity.remove(base(), true);
|
||||
other.proximity.remove(self(), true);
|
||||
other.onProximityUpdate();
|
||||
}
|
||||
}
|
||||
@@ -1090,8 +1151,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
if(other == null || !(other.tile.interactable(team))) continue;
|
||||
|
||||
//add this tile to proximity of nearby tiles
|
||||
if(!other.proximity.contains(base(), true)){
|
||||
other.proximity.add(base());
|
||||
if(!other.proximity.contains(self(), true)){
|
||||
other.proximity.add(self());
|
||||
}
|
||||
|
||||
tmpTiles.add(other);
|
||||
@@ -1130,13 +1191,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
@Replace
|
||||
@Override
|
||||
public boolean isValid(){
|
||||
return tile.build == base() && !dead();
|
||||
return tile.build == self() && !dead();
|
||||
}
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public void kill(){
|
||||
Call.tileDestroyed(base());
|
||||
Call.tileDestroyed(self());
|
||||
}
|
||||
|
||||
@Replace
|
||||
@@ -1150,10 +1211,59 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
damage /= state.rules.blockHealthMultiplier;
|
||||
}
|
||||
|
||||
Call.tileDamage(base(), health - handleDamage(damage));
|
||||
Call.tileDamage(self(), health - handleDamage(damage));
|
||||
|
||||
if(health <= 0){
|
||||
Call.tileDestroyed(base());
|
||||
Call.tileDestroyed(self());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
return switch(sensor){
|
||||
case x -> x;
|
||||
case y -> y;
|
||||
case team -> team.id;
|
||||
case health -> health;
|
||||
case maxHealth -> maxHealth();
|
||||
case efficiency -> efficiency();
|
||||
case rotation -> rotation;
|
||||
case totalItems -> items == null ? 0 : items.total();
|
||||
case totalLiquids -> liquids == null ? 0 : liquids.total();
|
||||
case totalPower -> power == null || !block.consumes.hasPower() ? 0 : power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
|
||||
case itemCapacity -> block.itemCapacity;
|
||||
case liquidCapacity -> block.liquidCapacity;
|
||||
case powerCapacity -> block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
|
||||
case powerNetIn -> power == null ? 0 : power.graph.getLastScaledPowerIn() * 60;
|
||||
case powerNetOut -> power == null ? 0 : power.graph.getLastScaledPowerOut() * 60;
|
||||
case powerNetStored -> power == null ? 0 : power.graph.getLastPowerStored();
|
||||
case powerNetCapacity -> power == null ? 0 : power.graph.getLastCapacity();
|
||||
case enabled -> enabled ? 1 : 0;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object senseObject(LAccess sensor){
|
||||
return switch(sensor){
|
||||
case type -> block;
|
||||
default -> noSensed;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(Content content){
|
||||
if(content instanceof Item && items != null) return items.get((Item)content);
|
||||
if(content instanceof Liquid && liquids != null) return liquids.get((Liquid)content);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void control(LAccess type, double p1, double p2, double p3, double p4){
|
||||
if(type == LAccess.enabled){
|
||||
enabled = !Mathf.zero((float)p1);
|
||||
enabledControlTime = timeToUncontrol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1181,15 +1291,25 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
timeScale = 1f;
|
||||
}
|
||||
|
||||
if(block.autoResetEnabled){
|
||||
enabledControlTime -= Time.delta;
|
||||
|
||||
if(enabledControlTime <= 0){
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(sound != null){
|
||||
sound.update(x, y, shouldActiveSound());
|
||||
}
|
||||
|
||||
if(block.idleSound != Sounds.none && shouldIdleSound()){
|
||||
loops.play(block.idleSound, base(), block.idleSoundVolume);
|
||||
loops.play(block.idleSound, self(), block.idleSoundVolume);
|
||||
}
|
||||
|
||||
updateTile();
|
||||
if(enabled || !block.noUpdateDisabled){
|
||||
updateTile();
|
||||
}
|
||||
|
||||
if(items != null){
|
||||
items.update(updateFlow);
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.ai.formations.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
@@ -12,16 +15,19 @@ import mindustry.gen.*;
|
||||
@Component
|
||||
abstract class CommanderComp implements Unitc{
|
||||
private static final Seq<FormationMember> members = new Seq<>();
|
||||
private static final Seq<Unit> units = new Seq<>();
|
||||
|
||||
@Import float x, y, rotation;
|
||||
|
||||
transient @Nullable Formation formation;
|
||||
transient Seq<Unit> controlling = new Seq<>();
|
||||
/** minimum speed of any unit in the formation. */
|
||||
transient float minFormationSpeed;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(formation != null){
|
||||
formation.anchor.set(x, y, rotation);
|
||||
formation.anchor.set(x, y, /*rotation*/ 0); //TODO rotation set to 0 because rotating is pointless
|
||||
formation.updateSlots();
|
||||
}
|
||||
}
|
||||
@@ -42,21 +48,51 @@ abstract class CommanderComp implements Unitc{
|
||||
clearCommand();
|
||||
}
|
||||
|
||||
void commandNearby(FormationPattern pattern){
|
||||
commandNearby(pattern, u -> true);
|
||||
}
|
||||
|
||||
void commandNearby(FormationPattern pattern, Boolf<Unit> include){
|
||||
Formation formation = new Formation(new Vec3(x, y, rotation), pattern);
|
||||
formation.slotAssignmentStrategy = new DistanceAssignmentStrategy(pattern);
|
||||
|
||||
units.clear();
|
||||
|
||||
Units.nearby(team(), x, y, 200f, u -> {
|
||||
if(u.isAI() && include.get(u) && u != self()){
|
||||
units.add(u);
|
||||
}
|
||||
});
|
||||
|
||||
units.sort(u -> u.dst2(this));
|
||||
units.truncate(type().commandLimit);
|
||||
|
||||
command(formation, units);
|
||||
}
|
||||
|
||||
void command(Formation formation, Seq<Unit> units){
|
||||
clearCommand();
|
||||
|
||||
float spacing = hitSize() * 0.65f;
|
||||
minFormationSpeed = type().speed;
|
||||
|
||||
controlling.addAll(units);
|
||||
for(Unit unit : units){
|
||||
unit.controller(new FormationAI(base(), formation));
|
||||
FormationAI ai;
|
||||
unit.controller(ai = new FormationAI(self(), formation));
|
||||
spacing = Math.max(spacing, ai.formationSize());
|
||||
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
|
||||
}
|
||||
this.formation = formation;
|
||||
|
||||
//update formation spacing based on max size
|
||||
formation.pattern.spacing = spacing;
|
||||
|
||||
members.clear();
|
||||
for(Unitc u : units){
|
||||
members.add((FormationAI)u.controller());
|
||||
}
|
||||
|
||||
|
||||
//TODO doesn't handle units that don't fit a formation
|
||||
formation.addMembers(members);
|
||||
}
|
||||
@@ -68,7 +104,7 @@ abstract class CommanderComp implements Unitc{
|
||||
void clearCommand(){
|
||||
//reset controlled units
|
||||
for(Unit unit : controlling){
|
||||
if(unit.controller().isBeingControlled(base())){
|
||||
if(unit.controller().isBeingControlled(self())){
|
||||
unit.controller(unit.type().createController());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ abstract class DecalComp implements Drawc, Timedc, Rotc, Posc{
|
||||
|
||||
@Replace
|
||||
public float clipSize(){
|
||||
return region.getWidth()*2;
|
||||
return region.width *2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.EntityCollisions.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.collisions;
|
||||
|
||||
@Component
|
||||
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
|
||||
@Import float x, y;
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public void move(float cx, float cy){
|
||||
if(isFlying()){
|
||||
x += cx;
|
||||
y += cy;
|
||||
}else{
|
||||
collisions.move(this, cx, cy);
|
||||
}
|
||||
public SolidPred solidity(){
|
||||
return isFlying() ? null : EntityCollisions::solid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import mindustry.annotations.Annotations.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.player;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
@BaseComponent
|
||||
@@ -40,7 +40,7 @@ abstract class EntityComp{
|
||||
return false;
|
||||
}
|
||||
|
||||
<T extends Entityc> T base(){
|
||||
<T extends Entityc> T self(){
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ import static mindustry.Vars.*;
|
||||
|
||||
@EntityDef(value = {Firec.class}, pooled = true)
|
||||
@Component(base = true)
|
||||
abstract class FireComp implements Timedc, Posc, Firec{
|
||||
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
|
||||
private static final float spreadChance = 0.05f, fireballChance = 0.07f;
|
||||
|
||||
@Import float time, lifetime, x, y;
|
||||
|
||||
Tile tile;
|
||||
private Block block;
|
||||
private float baseFlammability = -1, puddleFlammability;
|
||||
private transient Block block;
|
||||
private transient float baseFlammability = -1, puddleFlammability;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -97,6 +97,11 @@ abstract class FireComp implements Timedc, Posc, Firec{
|
||||
|
||||
@Override
|
||||
public void afterRead(){
|
||||
Fires.register(base());
|
||||
Fires.register(self());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSync(){
|
||||
Fires.register(self());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user