Partial 7.0 merge - API preview

This commit is contained in:
Anuken
2021-06-02 11:08:08 -04:00
parent ea75a357ca
commit 28b235ef07
531 changed files with 12356 additions and 6286 deletions

View File

@@ -47,12 +47,14 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
loadFileLogger();
platform = this;
maxTextureSize = Gl.getInt(Gl.maxTextureSize);
beginTime = Time.millis();
//debug GL information
Log.info("[GL] Version: @", graphics.getGLVersion());
Log.info("[GL] Max texture size: @", Gl.getInt(Gl.maxTextureSize));
Log.info("[GL] Max texture size: @", maxTextureSize);
Log.info("[GL] Using @ context.", gl30 != null ? "OpenGL 3" : "OpenGL 2");
if(maxTextureSize < 4096) Log.warn("[GL] Your maximum texture size is below the recommended minimum of 4096. This will cause severe performance issues.");
Log.info("[JAVA] Version: @", System.getProperty("java.version"));
Time.setDeltaProvider(() -> {
@@ -81,11 +83,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Fonts.loadDefaultFont();
//load fallback atlas if max texture size is below 4096
assets.load(new AssetDescriptor<>(Gl.getInt(Gl.maxTextureSize) >= 4096 ? "sprites/sprites.atlas" : "sprites/fallback/sprites.atlas", TextureAtlas.class)).loaded = t -> {
atlas = (TextureAtlas)t;
Fonts.mergeFontAtlas(atlas);
};
assets.load(new AssetDescriptor<>(maxTextureSize >= 4096 ? "sprites/sprites.aatls" : "sprites/fallback/sprites.aatls", TextureAtlas.class)).loaded = t -> atlas = (TextureAtlas)t;
assets.loadRun("maps", Map.class, () -> maps.loadPreviews());
Musics.load();
@@ -99,6 +97,9 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
content.createModContent();
});
assets.load(mods);
assets.loadRun("mergeUI", PixmapPacker.class, () -> {}, () -> Fonts.mergeFontAtlas(atlas));
add(logic = new Logic());
add(control = new Control());
add(renderer = new Renderer());
@@ -106,7 +107,6 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
add(netServer = new NetServer());
add(netClient = new NetClient());
assets.load(mods);
assets.load(schematics);
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> content.load());
@@ -150,6 +150,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
mods.eachClass(Mod::init);
finished = true;
Events.fire(new ClientLoadEvent());
clientLoaded = true;
super.resize(graphics.getWidth(), graphics.getHeight());
app.post(() -> app.post(() -> app.post(() -> app.post(() -> {
super.resize(graphics.getWidth(), graphics.getHeight());

View File

@@ -11,10 +11,13 @@ import arc.util.Log.*;
import mindustry.ai.*;
import mindustry.async.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.editor.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.io.*;
import mindustry.logic.*;
@@ -23,6 +26,7 @@ import mindustry.maps.*;
import mindustry.mod.*;
import mindustry.net.Net;
import mindustry.net.*;
import mindustry.service.*;
import mindustry.world.*;
import java.io.*;
@@ -42,6 +46,10 @@ public class Vars implements Loadable{
public static boolean experimental = false;
/** Name of current Steam player. */
public static String steamPlayerName = "";
/** Default accessible content types used for player-selectable icons. */
public static final ContentType[] defaultContentIcons = {ContentType.item, ContentType.liquid, ContentType.block};
/** Wall darkness radius. */
public static final int darkRadius = 4;
/** Maximum extra padding around deployment schematics. */
public static final int maxLoadoutSchematicPad = 5;
/** Maximum schematic size.*/
@@ -81,7 +89,7 @@ public class Vars implements Loadable{
/** displayed item size when ingame. */
public static final float itemSize = 5f;
/** units outside of this bound will die instantly */
public static final float finalWorldBounds = 500;
public static final float finalWorldBounds = 250;
/** range for building */
public static final float buildingRange = 220f;
/** range for moving items */
@@ -102,6 +110,8 @@ public class Vars implements Loadable{
public static final int tilesize = 8;
/** size of one tile payload (^2) */
public static final float tilePayload = tilesize * tilesize;
/** icon sizes for UI */
public static final float iconXLarge = 8*6f, iconLarge = 8*5f, iconMed = 8*4f, iconSmall = 8*3f;
/** tile used in certain situations, instead of null */
public static Tile emptyTile;
/** for map generator dialog */
@@ -131,6 +141,14 @@ public class Vars implements Loadable{
public static final int multicastPort = 20151;
/** multicast group for discovery.*/
public static final String multicastGroup = "227.2.7.7";
/** whether the graphical game client has loaded */
public static boolean clientLoaded = false;
/** max GL texture size */
public static int maxTextureSize = 2048;
/** Whether to show the core landing animation. */
public static boolean showLandAnimation = true;
/** Whether to prompt the user to confirm exiting. */
public static boolean confirmExit = true;
/** if true, UI is not drawn */
public static boolean disableUI;
/** if true, game is set up in mobile mode, even on desktop. used for debugging */
@@ -199,6 +217,8 @@ public class Vars implements Loadable{
public static AsyncCore asyncCore;
public static BaseRegistry bases;
public static GlobalConstants constants;
public static MapEditor editor;
public static GameService service = new GameService();
public static Universe universe;
public static World world;
@@ -243,6 +263,7 @@ public class Vars implements Loadable{
}
Version.init();
CacheLayer.init();
dataDirectory = settings.getDataDirectory();
screenshotDirectory = dataDirectory.child("screenshots/");
@@ -266,6 +287,7 @@ public class Vars implements Loadable{
universe = new Universe();
becontrol = new BeControl();
asyncCore = new AsyncCore();
if(!headless) editor = new MapEditor();
maps = new Maps();
spawner = new WaveSpawner();
@@ -394,7 +416,7 @@ public class Vars implements Loadable{
Log.info("NOTE: external translation bundle has been loaded.");
if(!headless){
Time.run(10f, () -> ui.showInfo("Note: You have successfully loaded an external translation bundle."));
Time.run(10f, () -> ui.showInfo("Note: You have successfully loaded an external translation bundle.\n[accent]" + handle.absolutePath()));
}
}catch(Throwable e){
//no external bundle found

View File

@@ -16,6 +16,7 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.defense.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
@@ -208,7 +209,7 @@ public class BaseAI{
}
Tile wtile = world.tile(realX, realY);
if(tile.block instanceof PayloadConveyor || tile.block instanceof PayloadAcceptor){
if(tile.block instanceof PayloadConveyor || tile.block instanceof PayloadBlock){
//near a building
for(Point2 point : Edges.getEdges(tile.block.size)){
var t = world.build(tile.x + point.x, tile.y + point.y);
@@ -288,7 +289,7 @@ public class BaseAI{
}
Tile o = world.tile(tile.x + p.x, tile.y + p.y);
if(o != null && (o.block() instanceof PayloadAcceptor || o.block() instanceof PayloadConveyor)){
if(o != null && (o.block() instanceof PayloadBlock || o.block() instanceof PayloadConveyor)){
continue outer;
}

View File

@@ -4,11 +4,9 @@ import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.EnumSet;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
@@ -25,16 +23,16 @@ import static mindustry.Vars.*;
/** Class used for indexing special target blocks for AI. */
public class BlockIndexer{
/** Size of one quadrant. */
private static final int quadrantSize = 16;
private static final int quadrantSize = 20;
private static final Rect rect = new Rect();
private static boolean returnBool = false;
/** Set of all ores that are being scanned. */
private final ObjectSet<Item> scanOres = new ObjectSet<>();
private final IntSet intSet = new IntSet();
private final ObjectSet<Item> itemSet = new ObjectSet<>();
/** Stores all ore quadtrants on the map. */
private ObjectMap<Item, TileArray> ores = new ObjectMap<>();
/** 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;
private int quadWidth, quadHeight;
/** Stores all ore quadrants on the map. Maps ID to qX to qY to a list of tiles with that ore. */
private IntSeq[][][] ores;
/** Stores all damaged tile entities by team. */
private ObjectSet<Building>[] damagedTiles = new ObjectSet[Team.all.length];
/** All ores available on this map. */
@@ -43,28 +41,27 @@ public class BlockIndexer{
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. */
private int[] unitCaps = new int[Team.all.length];
/** Maps tile positions to their last known tile index data. */
private IntMap<TileIndex> typeMap = new IntMap<>();
/** Empty set used for returning. */
private TileArray emptySet = new TileArray();
/** Array used for returning and reusing. */
private Seq<Tile> returnArray = new Seq<>();
/** Array used for returning and reusing. */
private Seq<Building> breturnArray = new Seq<>();
private Seq<Building> breturnArray = new Seq<>(Building.class);
public BlockIndexer(){
Events.on(TilePreChangeEvent.class, event -> {
removeIndex(event.tile);
});
Events.on(TileChangeEvent.class, event -> {
updateIndices(event.tile);
addIndex(event.tile);
});
Events.on(WorldLoadEvent.class, event -> {
scanOres.clear();
scanOres.addAll(Item.getAllOres());
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++){
@@ -73,12 +70,10 @@ public class BlockIndexer{
}
}
typeMap.clear();
allOres.clear();
ores = null;
//create bitset for each team type that contains each quadrant
structQuadrants = new GridBits[Team.all.length];
ores = new IntSeq[content.items().size][][];
quadWidth = Mathf.ceil(world.width() / (float)quadrantSize);
quadHeight = Mathf.ceil(world.height() / (float)quadrantSize);
for(Tile tile : world.tiles){
process(tile);
@@ -87,59 +82,83 @@ public class BlockIndexer{
notifyTileDamaged(tile.build);
}
if(tile.drop() != null) allOres.add(tile.drop());
}
var drop = tile.drop();
for(int x = 0; x < quadWidth(); x++){
for(int y = 0; y < quadHeight(); y++){
updateQuadrant(world.tile(x * quadrantSize, y * quadrantSize));
if(drop != null){
allOres.add(drop);
int qx = (tile.x / quadrantSize);
int qy = (tile.y / quadrantSize);
//add position of quadrant to list
if(tile.block() == Blocks.air){
if(ores[drop.id] == null){
ores[drop.id] = new IntSeq[quadWidth][quadHeight];
}
if(ores[drop.id][qx][qy] == null){
ores[drop.id][qx][qy] = new IntSeq(false, 16);
}
ores[drop.id][qx][qy].add(tile.pos());
}
}
}
scanOres();
});
}
public void updateIndices(Tile tile){
if(typeMap.get(tile.pos()) != null){
TileIndex index = typeMap.get(tile.pos());
for(BlockFlag flag : index.flags){
getFlagged(index.team)[flag.ordinal()].remove(tile);
public void removeIndex(Tile tile){
var team = tile.team();
if(team != Team.derelict && tile.isCenter()){
var flags = tile.block().flags;
var data = team.data();
if(flags.size() > 0){
for(BlockFlag flag : flags){
getFlagged(team)[flag.ordinal()].remove(tile);
}
}
if(index.flags.contains(BlockFlag.unitModifier)){
updateCap(index.team);
//update the unit cap when building is remove
data.unitCap -= tile.block().unitCapModifier;
//unregister building from building quadtree
if(data.buildings != null){
data.buildings.remove(tile.build);
}
}
}
public void addIndex(Tile tile){
process(tile);
updateQuadrant(tile);
var drop = tile.drop();
if(drop != null){
int qx = tile.x / quadrantSize;
int qy = tile.y / quadrantSize;
if(ores[drop.id] == null){
ores[drop.id] = new IntSeq[quadWidth][quadHeight];
}
if(ores[drop.id][qx][qy] == null){
ores[drop.id][qx][qy] = new IntSeq(false, 16);
}
int pos = tile.pos();
var seq = ores[drop.id][qx][qy];
//when the drop can be mined, record the ore position
if(tile.block() == Blocks.air && !seq.contains(pos)){
seq.add(pos);
}else{
//otherwise, it likely became blocked, remove it (even if it wasn't there)
seq.removeValue(pos);
}
}
}
private TileArray[] getFlagged(Team team){
return flagMap[team.id];
}
private GridBits structQuadrant(Team t){
if(structQuadrants[t.id] == null){
structQuadrants[t.id] = new GridBits(Mathf.ceil(world.width() / (float)quadrantSize), Mathf.ceil(world.height() / (float)quadrantSize));
}
return structQuadrants[t.id];
}
/** Updates all the structure quadrants for a newly activated team. */
public void updateTeamIndex(Team team){
if(structQuadrants == null) return;
//go through every tile... ouch
for(Tile tile : world.tiles){
if(tile.team() == team){
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
structQuadrant(team).set(quadrantX, quadrantY);
}
}
}
/** @return whether this item is present on this map. */
public boolean hasOre(Item item){
return allOres.contains(item);
@@ -181,31 +200,28 @@ public class BlockIndexer{
return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons);
}
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
intSet.clear();
public boolean eachBlock(@Nullable Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
returnBool = false;
int tx = World.toTile(wx);
int ty = World.toTile(wy);
int tileRange = (int)(range / tilesize + 1);
boolean any = false;
for(int x = -tileRange + tx; x <= tileRange + tx; x++){
for(int y = -tileRange + ty; y <= tileRange + ty; y++){
if(!Mathf.within(x * tilesize, y * tilesize, wx, wy, range)) continue;
Building other = world.build(x, y);
if(other == null) continue;
if((team == null || other.team == team) && pred.get(other) && intSet.add(other.pos())){
cons.get(other);
any = true;
if(team == null){
allBuildings(wx, wy, range, b -> {
if(pred.get(b)){
returnBool = true;
cons.get(b);
}
}
});
}else{
var buildings = team.data().buildings;
if(buildings == null) return false;
buildings.intersect(wx - range, wy - range, range*2f, range*2f, b -> {
if(b.within(wx, wy, range + b.hitSize() / 2f) && pred.get(b)){
returnBool = true;
cons.get(b);
}
});
}
return any;
return returnBool;
}
/** Get all enemy blocks with a flag. */
@@ -247,7 +263,20 @@ public class BlockIndexer{
damagedTiles[entity.team.id].add(entity);
}
public Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
public void allBuildings(float x, float y, float range, Cons<Building> cons){
for(int i = 0; i < activeTeams.size; i++){
Team team = activeTeams.items[i];
var buildings = team.data().buildings;
if(buildings == null) continue;
buildings.intersect(x - range, y - range, range*2f, range*2f, b -> {
if(b.within(x, y, range + b.hitSize()/2f)){
cons.get(b);
}
});
}
}
public Building findEnemyTile(@Nullable Team team, float x, float y, float range, Boolf<Building> pred){
for(int i = 0; i < activeTeams.size; i++){
Team enemy = activeTeams.items[i];
@@ -269,58 +298,50 @@ public class BlockIndexer{
public Building findTile(Team team, float x, float y, float range, Boolf<Building> pred, boolean usePriority){
Building closest = null;
float dst = 0;
var buildings = team.data().buildings;
if(buildings == null) return null;
for(int rx = Math.max((int)((x - range) / tilesize / quadrantSize), 0); rx <= (int)((x + range) / tilesize / quadrantSize) && rx < quadWidth(); rx++){
for(int ry = Math.max((int)((y - range) / tilesize / quadrantSize), 0); ry <= (int)((y + range) / tilesize / quadrantSize) && ry < quadHeight(); ry++){
breturnArray.clear();
buildings.intersect(rect.setCentered(x, y, range * 2f), breturnArray);
if(!getQuad(team, rx, ry)) continue;
for(int i = 0; i < breturnArray.size; i++){
var next = breturnArray.items[i];
for(int tx = rx * quadrantSize; tx < (rx + 1) * quadrantSize && tx < world.width(); tx++){
for(int ty = ry * quadrantSize; ty < (ry + 1) * quadrantSize && ty < world.height(); ty++){
Building e = world.build(tx, ty);
if(!pred.get(next) || !next.block.targetable) continue;
if(e == null || e.team != team || !pred.get(e) || !e.block.targetable || e.team == Team.derelict) continue;
float bdst = e.dst(x, y) - e.hitSize() / 2f;
if(bdst < range && (closest == null ||
//this one is closer, and it is at least of equal priority
(bdst < 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()))){
dst = bdst;
closest = e;
}
}
}
float bdst = next.dst(x, y) - next.hitSize() / 2f;
if(bdst < range && (closest == null ||
//this one is closer, and it is at least of equal priority
(bdst < dst && (!usePriority || closest.block.priority.ordinal() <= next.block.priority.ordinal())) ||
//priority is used, and new block has higher priority regardless of range
(usePriority && closest.block.priority.ordinal() < next.block.priority.ordinal()))){
dst = bdst;
closest = next;
}
}
return closest;
}
/**
* Returns a set of tiles that have ores of the specified type nearby.
* While each tile in the set is not guaranteed to have an ore directly on it,
* each tile will at least have an ore within {@link #quadrantSize} / 2 blocks of it.
* Only specific ore types are scanned. See {@link #scanOres}.
*/
public TileArray getOrePositions(Item item){
return ores.get(item, emptySet);
}
/** Find the closest ore block relative to a position. */
public Tile findClosestOre(float xp, float yp, Item item){
Tile tile = Geometry.findClosest(xp, yp, getOrePositions(item));
if(tile == null) return null;
for(int x = Math.max(0, tile.x - quadrantSize / 2); x < tile.x + quadrantSize / 2 && x < world.width(); x++){
for(int y = Math.max(0, tile.y - quadrantSize / 2); y < tile.y + quadrantSize / 2 && y < world.height(); y++){
Tile res = world.tile(x, y);
if(res.block() == Blocks.air && res.drop() == item){
return res;
if(ores[item.id] != null){
float minDst = 0f;
Tile closest = null;
for(int qx = 0; qx < quadWidth; qx++){
for(int qy = 0; qy < quadHeight; qy++){
var arr = ores[item.id][qx][qy];
if(arr != null && arr.size > 0){
Tile tile = world.tile(arr.first());
float dst = Mathf.dst2(xp, yp, tile.worldx(), tile.worldy());
if(closest == null || dst < minDst){
closest = tile;
minDst = dst;
}
}
}
}
return closest;
}
return null;
@@ -331,147 +352,36 @@ public class BlockIndexer{
return findClosestOre(unit.x, unit.y, item);
}
/** @return extra unit cap of a team. This is added onto the base value. */
public int getExtraUnits(Team team){
return unitCaps[team.id];
}
private void updateCap(Team team){
TileArray capped = getFlagged(team)[BlockFlag.unitModifier.ordinal()];
unitCaps[team.id] = 0;
for(Tile capper : capped){
unitCaps[team.id] += capper.block().unitCapModifier;
}
}
private void process(Tile tile){
if(tile.block().flags.size() > 0 && tile.team() != Team.derelict && tile.isCenter()){
TileArray[] map = getFlagged(tile.team());
var team = tile.team();
//only process entity changes with centered tiles
if(tile.isCenter() && team != Team.derelict){
var data = team.data();
if(tile.block().flags.size() > 0 && tile.isCenter()){
TileArray[] map = getFlagged(team);
for(BlockFlag flag : tile.block().flags){
for(BlockFlag flag : tile.block().flags){
TileArray arr = map[flag.ordinal()];
TileArray arr = map[flag.ordinal()];
arr.add(tile);
arr.add(tile);
map[flag.ordinal()] = arr;
}
if(tile.block().flags.contains(BlockFlag.unitModifier)){
updateCap(tile.team());
}
typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.team()));
}
if(!activeTeams.contains(tile.team())){
activeTeams.add(tile.team());
}
if(ores == null) return;
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
itemSet.clear();
Tile rounded = world.rawTile(Mathf.clamp(quadrantX * quadrantSize + quadrantSize / 2, 0, world.width() - 1), Mathf.clamp(quadrantY * quadrantSize + quadrantSize / 2, 0, world.height() - 1));
//find all items that this quadrant contains
for(int x = Math.max(0, rounded.x - quadrantSize / 2); x < rounded.x + quadrantSize / 2 && x < world.width(); x++){
for(int y = Math.max(0, rounded.y - quadrantSize / 2); y < rounded.y + quadrantSize / 2 && y < world.height(); y++){
Tile result = world.tile(x, y);
if(result == null || result.drop() == null || !scanOres.contains(result.drop()) || result.block() != Blocks.air) continue;
itemSet.add(result.drop());
}
}
//update quadrant at this position
for(Item item : scanOres){
TileArray set = ores.get(item);
//update quadrant status depending on whether the item is in it
if(!itemSet.contains(item)){
set.remove(rounded);
}else{
set.add(rounded);
}
}
}
private void updateQuadrant(Tile tile){
if(structQuadrants == null) return;
//this quadrant is now 'dirty', re-scan the whole thing
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
for(Team team : activeTeams){
GridBits bits = structQuadrant(team);
//fast-set this quadrant to 'occupied' if the tile just placed is already of this team
if(tile.team() == team && tile.build != null && tile.block().targetable){
bits.set(quadrantX, quadrantY);
continue; //no need to process futher
}
bits.set(quadrantX, quadrantY, false);
outer:
for(int x = quadrantX * quadrantSize; x < world.width() && x < (quadrantX + 1) * quadrantSize; x++){
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){
bits.set(quadrantX, quadrantY);
break outer;
}
map[flag.ordinal()] = arr;
}
}
}
}
private boolean getQuad(Team team, int quadrantX, int quadrantY){
return structQuadrant(team).get(quadrantX, quadrantY);
}
//update the unit cap when new tile is registered
data.unitCap += tile.block().unitCapModifier;
private int quadWidth(){
return Mathf.ceil(world.width() / (float)quadrantSize);
}
private int quadHeight(){
return Mathf.ceil(world.height() / (float)quadrantSize);
}
private void scanOres(){
ores = new ObjectMap<>();
//initialize ore map with empty sets
for(Item item : scanOres){
ores.put(item, new TileArray());
}
for(Tile tile : world.tiles){
int qx = (tile.x / quadrantSize);
int qy = (tile.y / quadrantSize);
//add position of quadrant to list when an ore is found
if(tile.drop() != null && scanOres.contains(tile.drop()) && tile.block() == Blocks.air){
ores.get(tile.drop()).add(world.tile(
//make sure to clamp quadrant middle position, since it might go off bounds
Mathf.clamp(qx * quadrantSize + quadrantSize / 2, 0, world.width() - 1),
Mathf.clamp(qy * quadrantSize + quadrantSize / 2, 0, world.height() - 1)));
if(!activeTeams.contains(team)){
activeTeams.add(team);
}
}
}
private static class TileIndex{
public final EnumSet<BlockFlag> flags;
public final Team team;
public TileIndex(EnumSet<BlockFlag> flags, Team team){
this.flags = flags;
this.team = team;
//insert the new tile into the quadtree for targeting
if(data.buildings == null){
data.buildings = new QuadTree<>(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
}
data.buildings.insert(tile.build);
}
}

View File

@@ -70,13 +70,15 @@ public class BuilderAI extends AIController{
for(Player player : Groups.player){
if(player.isBuilder() && player.unit().activelyBuilding() && player.unit().buildPlan().samePos(req) && player.unit().buildPlan().breaking){
unit.plans.removeFirst();
//remove from list of plans
unit.team.data().blocks.remove(p -> p.x == req.x && p.y == req.y);
return;
}
}
}
boolean valid =
(req.tile() != null && req.tile().build instanceof ConstructBuild cons && cons.cblock == req.block) ||
(req.tile() != null && req.tile().build instanceof ConstructBuild cons && cons.current == req.block) ||
(req.breaking ?
Build.validBreak(unit.team(), req.x, req.y) :
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation));

View File

@@ -33,7 +33,12 @@ public class FormationAI extends AIController implements FormationMember{
}
if(unit.type.canBoost){
unit.elevation = Mathf.approachDelta(unit.elevation, unit.onSolid() ? 1f : leader.type.canBoost ? leader.elevation : 0f, unit.type.riseSpeed);
unit.elevation = Mathf.approachDelta(unit.elevation,
unit.onSolid() ? 1f : //definitely cannot land
unit.isFlying() && !unit.canLand() ? unit.elevation : //try to maintain altitude
leader.type.canBoost ? leader.elevation : //follow leader
0f,
unit.type.riseSpeed);
}
unit.controlWeapons(true, leader.isShooting);
@@ -85,7 +90,7 @@ public class FormationAI extends AIController implements FormationMember{
@Override
public float formationSize(){
return unit.hitSize * 1.1f;
return unit.hitSize * 1.3f;
}
@Override

View File

@@ -8,8 +8,6 @@ import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import java.util.*;
import static mindustry.Vars.*;
public class GroundAI extends AIController{
@@ -19,16 +17,16 @@ public class GroundAI extends AIController{
Building core = unit.closestEnemyCore();
if(core != null && unit.within(core, unit.range() / 1.1f + core.block.size * tilesize / 2f)){
if(core != null && unit.within(core, unit.range() / 1.3f + core.block.size * tilesize / 2f)){
target = core;
for(int i = 0; i < targets.length; i++){
if(unit.mounts[i].weapon.bullet.collidesGround){
targets[i] = core;
for(var mount : unit.mounts){
if(mount.weapon.controllable && mount.weapon.bullet.collidesGround){
mount.target = core;
}
}
}
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
if((core == null || !unit.within(core, unit.type.range * 0.5f)) && command() == UnitCommand.attack){
boolean move = true;
if(state.rules.waves && unit.team == state.rules.defaultTeam){
@@ -47,7 +45,7 @@ public class GroundAI extends AIController{
}
}
if(unit.type.canBoost && !unit.onSolid()){
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, unit.type.riseSpeed);
}

View File

@@ -0,0 +1,81 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class HugAI extends AIController{
@Override
public void updateMovement(){
Building core = unit.closestEnemyCore();
if(core != null && unit.within(core, unit.range() / 1.1f + core.block.size * tilesize / 2f)){
target = core;
for(var mount : unit.mounts){
if(mount.weapon.controllable && mount.weapon.bullet.collidesGround){
mount.target = core;
}
}
}
if(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;
}
//raycast for target
if(target != null && unit.within(target, unit.type.range) && !Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
for(Point2 p : Geometry.d4c){
if(!unit.canPass(x + p.x, y + p.y)){
return true;
}
}
return false;
})){
if(unit.within(target, (unit.hitSize + (target instanceof Sized s ? s.hitSize() : 1f)) * 0.6f)){
//circle target
unit.moveAt(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
}else{
//move toward target in a straight line
unit.moveAt(vec.set(target).sub(unit).limit(unit.speed()));
}
}else if(move){
pathfind(Pathfinder.fieldCore);
}
}
if(command() == UnitCommand.rally){
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
}
}
if(unit.type.canBoost && unit.elevation > 0.001f && !unit.onSolid()){
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, unit.type.riseSpeed);
}
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting){
if(unit.type.hasWeapons()){
unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
}
}

View File

@@ -98,7 +98,7 @@ public class LogicAI extends AIController{
}
if(unit.type.canBoost && !unit.type.flying){
unit.elevation = Mathf.approachDelta(unit.elevation, Mathf.num(boost || unit.onSolid()), 0.08f);
unit.elevation = Mathf.approachDelta(unit.elevation, Mathf.num(boost || unit.onSolid() || (unit.isFlying() && !unit.canLand())), unit.type.riseSpeed);
}
//look where moving if there's nothing to aim at

View File

@@ -31,7 +31,7 @@ public class MinerAI extends AIController{
//core full of the target item, do nothing
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
unit.clearItem();
unit.mineTile(null);
unit.mineTile =null;
return;
}
@@ -46,7 +46,7 @@ public class MinerAI extends AIController{
if(ore != null){
moveTo(ore, unit.type.miningRange / 2f, 20f);
if(unit.within(ore, unit.type.miningRange)){
if(ore.block() == Blocks.air && unit.within(ore, unit.type.miningRange)){
unit.mineTile = ore;
}

View File

@@ -9,8 +9,11 @@ import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class SuicideAI extends GroundAI{
static boolean blockedByBlock;
@@ -29,6 +32,10 @@ public class SuicideAI extends GroundAI{
boolean rotate = false, shoot = false, moveToTarget = false;
if(target == null){
target = core;
}
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){
rotate = true;
shoot = unit.within(target, unit.type.weapons.first().bullet.range() +
@@ -39,7 +46,7 @@ public class SuicideAI extends GroundAI{
}
//do not move toward walls or transport blocks
if(!(target instanceof Building build && (
if(!(target instanceof Building build && !(build.block instanceof CoreBlock) && (
build.block.group == BlockGroup.walls ||
build.block.group == BlockGroup.liquids ||
build.block.group == BlockGroup.transportation
@@ -81,8 +88,20 @@ public class SuicideAI extends GroundAI{
if(target != null && !unit.within(target, 70f)){
pathfind(Pathfinder.fieldRally);
}
}else if(command() == UnitCommand.attack && core != null){
pathfind(Pathfinder.fieldCore);
}else if(command() == UnitCommand.attack){
boolean move = true;
//stop moving toward the drop zone if applicable
if(core == null && state.rules.waves && unit.team == state.rules.defaultTeam){
Tile spawner = getClosestSpawner();
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)){
move = false;
}
}
if(move){
pathfind(Pathfinder.fieldCore);
}
}
if(unit.moving()) unit.lookAt(unit.vel().angle());

View File

@@ -10,19 +10,14 @@ import static mindustry.Vars.*;
public class AsyncCore{
//all processes to be executed each frame
private final Seq<AsyncProcess> processes = Seq.with(
public final Seq<AsyncProcess> processes = Seq.with(
new PhysicsProcess()
);
//futures to be awaited
private final Seq<Future<?>> futures = new Seq<>();
private final ExecutorService executor = Executors.newFixedThreadPool(processes.size, r -> {
Thread thread = new Thread(r, "AsyncLogic-Thread");
thread.setDaemon(true);
thread.setUncaughtExceptionHandler((t, e) -> Core.app.post(() -> { throw new RuntimeException(e); }));
return thread;
});
private ExecutorService executor;
public AsyncCore(){
Events.on(WorldLoadEvent.class, e -> {
@@ -49,6 +44,16 @@ public class AsyncCore{
futures.clear();
//init executor with size of potentially-modified process list
if(executor == null){
executor = Executors.newFixedThreadPool(processes.size, r -> {
Thread thread = new Thread(r, "AsyncLogic-Thread");
thread.setDaemon(true);
thread.setUncaughtExceptionHandler((t, e) -> Core.app.post(() -> { throw new RuntimeException(e); }));
return thread;
});
}
//submit all tasks
for(AsyncProcess p : processes){
if(p.shouldProcess()){

View File

@@ -134,6 +134,8 @@ public class SoundControl{
}
}
Core.audio.setPaused(Core.audio.soundBus.id, state.isPaused());
if(state.isMenu()){
silenced = false;
if(ui.planet.isShown()){

View File

@@ -20,6 +20,7 @@ import mindustry.world.blocks.experimental.*;
import mindustry.world.blocks.legacy.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
@@ -36,7 +37,8 @@ public class Blocks implements ContentList{
//environment
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater, space,
dacite, stoneWall, dirtWall, sporeWall, iceWall, daciteWall, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
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, boulder, snowBoulder, basaltBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, basalt, magmarock, hotrock, snowWall, saltWall,
darkPanel1, darkPanel2, darkPanel3, darkPanel4, darkPanel5, darkPanel6, darkMetal,
@@ -50,7 +52,7 @@ public class Blocks implements ContentList{
melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge,
//sandbox
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, illuminator,
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, payloadVoid, payloadSource, illuminator,
//defense
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
@@ -59,7 +61,8 @@ public class Blocks implements ContentList{
//transport
conveyor, titaniumConveyor, plastaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router,
overflowGate, underflowGate, massDriver, payloadConveyor, payloadRouter,
overflowGate, underflowGate, massDriver,
duct, ductRouter, ductBridge,
//liquid
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
@@ -81,16 +84,20 @@ public class Blocks implements ContentList{
commandCenter,
groundFactory, airFactory, navalFactory,
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
repairPoint, resupplyPoint,
repairPoint, repairTurret, resupplyPoint,
//payloads
payloadConveyor, payloadRouter, payloadPropulsionTower,
//logic
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, largeLogicDisplay, logicDisplay, memoryCell, memoryBank,
//campaign
launchPad, launchPadLarge, interplanetaryAccelerator,
launchPad, interplanetaryAccelerator,
//misc experimental
blockForge, blockLoader, blockUnloader;
blockForge, blockLoader, blockUnloader
;
@Override
public void load(){
@@ -361,32 +368,37 @@ public class Blocks implements ContentList{
whiteTree = new TreeBlock("white-tree");
sporeCluster = new Boulder("spore-cluster"){{
sporeCluster = new Prop("spore-cluster"){{
variants = 3;
}};
boulder = new Boulder("boulder"){{
//glowBlob = new Prop("glowblob"){{
// variants = 1;
//}};
boulder = new Prop("boulder"){{
variants = 2;
stone.asFloor().decoration = this;
}};
snowBoulder = new Boulder("snow-boulder"){{
snowBoulder = new Prop("snow-boulder"){{
variants = 2;
snow.asFloor().decoration = ice.asFloor().decoration = iceSnow.asFloor().decoration = salt.asFloor().decoration = this;
}};
shaleBoulder = new Boulder("shale-boulder"){{
shaleBoulder = new Prop("shale-boulder"){{
variants = 2;
}};
sandBoulder = new Boulder("sand-boulder"){{
sandBoulder = new Prop("sand-boulder"){{
variants = 2;
}};
daciteBoulder = new Boulder("dacite-boulder"){{
daciteBoulder = new Prop("dacite-boulder"){{
variants = 2;
}};
basaltBoulder = new Boulder("basalt-boulder"){{
basaltBoulder = new Prop("basalt-boulder"){{
variants = 2;
}};
@@ -491,6 +503,7 @@ public class Blocks implements ContentList{
craftEffect = Fx.pulverizeMedium;
outputItem = new ItemStack(Items.graphite, 2);
craftTime = 30f;
itemCapacity = 20;
size = 3;
hasItems = true;
hasLiquids = true;
@@ -501,7 +514,7 @@ public class Blocks implements ContentList{
consumes.liquid(Liquids.water, 0.1f);
}};
siliconSmelter = new GenericSmelter("silicon-smelter"){{
siliconSmelter = new GenericCrafter("silicon-smelter"){{
requirements(Category.crafting, with(Items.copper, 30, Items.lead, 25));
craftEffect = Fx.smeltsmoke;
outputItem = new ItemStack(Items.silicon, 1);
@@ -509,13 +522,13 @@ public class Blocks implements ContentList{
size = 2;
hasPower = true;
hasLiquids = false;
flameColor = Color.valueOf("ffef99");
drawer = new DrawSmelter(Color.valueOf("ffef99"));
consumes.items(with(Items.coal, 1, Items.sand, 2));
consumes.power(0.50f);
}};
siliconCrucible = new AttributeSmelter("silicon-crucible"){{
siliconCrucible = new AttributeCrafter("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, 8);
@@ -523,22 +536,22 @@ public class Blocks implements ContentList{
size = 3;
hasPower = true;
hasLiquids = false;
flameColor = Color.valueOf("ffef99");
itemCapacity = 30;
boostScale = 0.15f;
drawer = new DrawSmelter(Color.valueOf("ffef99"));
consumes.items(with(Items.coal, 4, Items.sand, 6, Items.pyratite, 1));
consumes.power(4f);
}};
kiln = new GenericSmelter("kiln"){{
kiln = new GenericCrafter("kiln"){{
requirements(Category.crafting, with(Items.copper, 60, Items.graphite, 30, Items.lead, 30));
craftEffect = Fx.smeltsmoke;
outputItem = new ItemStack(Items.metaglass, 1);
craftTime = 30f;
size = 2;
hasPower = hasItems = true;
flameColor = Color.valueOf("ffc099");
drawer = new DrawSmelter(Color.valueOf("ffc099"));
consumes.items(with(Items.lead, 1, Items.sand, 1));
consumes.power(0.60f);
@@ -579,7 +592,7 @@ public class Blocks implements ContentList{
itemCapacity = 20;
}};
surgeSmelter = new GenericSmelter("alloy-smelter"){{
surgeSmelter = new GenericCrafter("alloy-smelter"){{
requirements(Category.crafting, with(Items.silicon, 80, Items.lead, 80, Items.thorium, 70));
craftEffect = Fx.smeltsmoke;
outputItem = new ItemStack(Items.surgeAlloy, 1);
@@ -587,6 +600,7 @@ public class Blocks implements ContentList{
size = 3;
hasPower = true;
itemCapacity = 20;
drawer = new DrawSmelter();
consumes.power(4f);
consumes.items(with(Items.copper, 3, Items.lead, 4, Items.titanium, 2, Items.silicon, 3));
@@ -610,9 +624,8 @@ public class Blocks implements ContentList{
consumes.liquid(Liquids.water, 0.2f);
}};
pyratiteMixer = new GenericSmelter("pyratite-mixer"){{
pyratiteMixer = new GenericCrafter("pyratite-mixer"){{
requirements(Category.crafting, with(Items.copper, 50, Items.lead, 25));
flameColor = Color.clear;
hasItems = true;
hasPower = true;
outputItem = new ItemStack(Items.pyratite, 1);
@@ -757,7 +770,7 @@ public class Blocks implements ContentList{
plastaniumWall = new Wall("plastanium-wall"){{
requirements(Category.defense, with(Items.plastanium, 5, Items.metaglass, 2));
health = 130 * wallHealthMultiplier;
health = 125 * wallHealthMultiplier;
insulated = true;
absorbLasers = true;
schematicPriority = 10;
@@ -765,7 +778,7 @@ public class Blocks implements ContentList{
plastaniumWallLarge = new Wall("plastanium-wall-large"){{
requirements(Category.defense, ItemStack.mult(plastaniumWall.requirements, 4));
health = 130 * wallHealthMultiplier * 4;
health = 125 * wallHealthMultiplier * 4;
size = 2;
insulated = true;
absorbLasers = true;
@@ -972,7 +985,6 @@ public class Blocks implements ContentList{
phaseConveyor = new ItemBridge("phase-conveyor"){{
requirements(Category.distribution, with(Items.phaseFabric, 5, Items.silicon, 7, Items.lead, 10, Items.graphite, 10));
range = 12;
canOverdrive = false;
hasPower = true;
consumes.power(0.30f);
}};
@@ -1018,14 +1030,20 @@ public class Blocks implements ContentList{
consumes.power(1.75f);
}};
payloadConveyor = new PayloadConveyor("payload-conveyor"){{
requirements(Category.distribution, with(Items.graphite, 10, Items.copper, 20));
canOverdrive = false;
//special transport blocks
duct = new Duct("duct"){{
requirements(Category.distribution, BuildVisibility.debugOnly, with(Items.graphite, 5, Items.copper, 5));
speed = 5f;
}};
payloadRouter = new PayloadRouter("payload-router"){{
requirements(Category.distribution, with(Items.graphite, 15, Items.copper, 20));
canOverdrive = false;
ductRouter = new DuctRouter("duct-router"){{
requirements(Category.distribution, BuildVisibility.debugOnly, with(Items.graphite, 10, Items.copper, 5));
speed = 5f;
}};
ductBridge = new DuctBridge("duct-bridge"){{
requirements(Category.distribution, BuildVisibility.debugOnly, with(Items.graphite, 20, Items.copper, 15));
}};
//endregion
@@ -1294,21 +1312,30 @@ public class Blocks implements ContentList{
liquidCapacity = 30f;
rotateSpeed = 1.4f;
attribute = Attribute.water;
envRequired |= Env.groundWater;
consumes.power(1.5f);
}};
cultivator = new Cultivator("cultivator"){{
cultivator = new AttributeCrafter("cultivator"){{
requirements(Category.production, with(Items.copper, 25, Items.lead, 25, Items.silicon, 10));
outputItem = new ItemStack(Items.sporePod, 1);
craftTime = 140;
craftTime = 100;
size = 2;
hasLiquids = true;
hasPower = true;
hasItems = true;
consumes.power(0.9f);
consumes.liquid(Liquids.water, 0.2f);
craftEffect = Fx.none;
envRequired |= Env.spores;
attribute = Attribute.spores;
legacyReadWarmup = true;
drawer = new DrawCultivator();
maxBoost = 2f;
consumes.power(80f / 60f);
consumes.liquid(Liquids.water, 20f / 60f);
}};
oilExtractor = new Fracker("oil-extractor"){{
@@ -1353,7 +1380,7 @@ public class Blocks implements ContentList{
size = 4;
unitCapModifier = 16;
researchCostMultiplier = 0.04f;
researchCostMultiplier = 0.07f;
}};
coreNucleus = new CoreBlock("core-nucleus"){{
@@ -1365,24 +1392,26 @@ public class Blocks implements ContentList{
size = 5;
unitCapModifier = 24;
researchCostMultiplier = 0.06f;
researchCostMultiplier = 0.11f;
}};
vault = new StorageBlock("vault"){{
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
health = size * size * 55;
}};
container = new StorageBlock("container"){{
requirements(Category.effect, with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
health = size * size * 55;
}};
unloader = new Unloader("unloader"){{
requirements(Category.effect, with(Items.titanium, 25, Items.silicon, 30));
speed = 6f;
speed = 60f / 11f;
group = BlockGroup.transportation;
}};
@@ -1403,12 +1432,14 @@ public class Blocks implements ContentList{
alternate = true;
reloadTime = 20f;
restitution = 0.03f;
range = 100;
range = 110;
shootCone = 15f;
ammoUseEffect = Fx.casing1;
health = 250;
inaccuracy = 2f;
rotateSpeed = 10f;
limitRange();
}};
scatter = new ItemTurret("scatter"){{
@@ -1419,7 +1450,7 @@ public class Blocks implements ContentList{
Items.metaglass, Bullets.flakGlass
);
reloadTime = 18f;
range = 160f;
range = 220f;
size = 2;
burstSpacing = 5f;
shots = 2;
@@ -1432,6 +1463,8 @@ public class Blocks implements ContentList{
health = 200 * size * size;
shootSound = Sounds.shootSnap;
limitRange(2);
}};
scorch = new ItemTurret("scorch"){{
@@ -1461,11 +1494,12 @@ public class Blocks implements ContentList{
targetAir = false;
reloadTime = 60f;
recoilAmount = 2f;
range = 230f;
range = 235f;
inaccuracy = 1f;
shootCone = 10f;
health = 260;
shootSound = Sounds.bang;
limitRange(0f);
}};
wave = new LiquidTurret("wave"){{
@@ -1478,7 +1512,7 @@ public class Blocks implements ContentList{
);
size = 2;
recoilAmount = 0f;
reloadTime = 2f;
reloadTime = 3f;
inaccuracy = 5f;
shootCone = 50f;
liquidCapacity = 10f;
@@ -1512,12 +1546,12 @@ public class Blocks implements ContentList{
shootType = new LaserBulletType(140){{
colors = new Color[]{Pal.lancerLaser.cpy().a(0.4f), Pal.lancerLaser, Color.white};
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
hitSize = 4;
lifetime = 16f;
drawSize = 400f;
collidesAir = false;
length = 173f;
ammoMultiplier = 1f;
}};
}};
@@ -1527,6 +1561,7 @@ public class Blocks implements ContentList{
damage = 20;
lightningLength = 25;
collidesAir = false;
ammoMultiplier = 1f;
}};
reloadTime = 35f;
shootCone = 40f;
@@ -1547,9 +1582,9 @@ public class Blocks implements ContentList{
hasPower = true;
size = 2;
force = 8f;
scaledForce = 7f;
range = 230f;
force = 12f;
scaledForce = 6f;
range = 240f;
damage = 0.3f;
health = 160 * size * size;
rotateSpeed = 10;
@@ -1568,15 +1603,17 @@ public class Blocks implements ContentList{
shots = 4;
burstSpacing = 5;
inaccuracy = 10f;
range = 200f;
range = 210f;
xRand = 6f;
size = 2;
health = 300 * size * size;
shootSound = Sounds.missile;
limitRange(2f);
}};
salvo = new ItemTurret("salvo"){{
requirements(Category.turret, with(Items.copper, 100, Items.graphite, 90, Items.titanium, 60));
requirements(Category.turret, with(Items.copper, 100, Items.graphite, 80, Items.titanium, 50));
ammo(
Items.copper, Bullets.standardCopper,
Items.graphite, Bullets.standardDense,
@@ -1586,7 +1623,7 @@ public class Blocks implements ContentList{
);
size = 2;
range = 150f;
range = 180f;
reloadTime = 38f;
restitution = 0.03f;
ammoEjectBack = 3f;
@@ -1598,6 +1635,8 @@ public class Blocks implements ContentList{
ammoUseEffect = Fx.casing2;
health = 240 * size * size;
shootSound = Sounds.shootBig;
limitRange();
}};
segment = new PointDefenseTurret("segment"){{
@@ -1610,7 +1649,7 @@ public class Blocks implements ContentList{
size = 2;
shootLength = 5f;
bulletDamage = 30f;
reloadTime = 9f;
reloadTime = 8f;
}};
tsunami = new LiquidTurret("tsunami"){{
@@ -1622,7 +1661,7 @@ public class Blocks implements ContentList{
Liquids.oil, Bullets.heavyOilShot
);
size = 3;
reloadTime = 2f;
reloadTime = 3f;
shots = 2;
velocityInaccuracy = 0.1f;
inaccuracy = 4f;
@@ -1721,6 +1760,7 @@ public class Blocks implements ContentList{
shootSound = Sounds.shootSnap;
health = 145 * size * size;
limitRange();
}};
foreshadow = new ItemTurret("foreshadow"){{
@@ -1736,7 +1776,7 @@ public class Blocks implements ContentList{
despawnEffect = Fx.instBomb;
trailSpacing = 20f;
damage = 1350;
buildingDamageMultiplier = 0.3f;
buildingDamageMultiplier = 0.25f;
speed = brange;
hitShake = 6f;
ammoMultiplier = 1f;
@@ -1744,7 +1784,7 @@ public class Blocks implements ContentList{
);
maxAmmo = 40;
ammoPerShot = 4;
ammoPerShot = 5;
rotateSpeed = 2f;
reloadTime = 200f;
ammoUseEffect = Fx.casing3Double;
@@ -1773,11 +1813,11 @@ public class Blocks implements ContentList{
Items.pyratite, Bullets.standardIncendiaryBig,
Items.thorium, Bullets.standardThoriumBig
);
reloadTime = 6f;
reloadTime = 7f;
coolantMultiplier = 0.5f;
restitution = 0.1f;
ammoUseEffect = Fx.casing3;
range = 200f;
range = 260f;
inaccuracy = 3f;
recoilAmount = 3f;
spread = 8f;
@@ -1790,6 +1830,8 @@ public class Blocks implements ContentList{
health = 160 * size * size;
coolantUsage = 1f;
limitRange();
}};
meltdown = new LaserTurret("meltdown"){{
@@ -1799,7 +1841,7 @@ public class Blocks implements ContentList{
recoilAmount = 4f;
size = 4;
shootShake = 2f;
range = 190f;
range = 195f;
reloadTime = 90f;
firingMoveFract = 0.5f;
shootDuration = 220f;
@@ -1817,6 +1859,7 @@ public class Blocks implements ContentList{
incendChance = 0.4f;
incendSpread = 5f;
incendAmount = 1;
ammoMultiplier = 1f;
}};
health = 200 * size * size;
@@ -1856,7 +1899,8 @@ public class Blocks implements ContentList{
navalFactory = new UnitFactory("naval-factory"){{
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
plans = Seq.with(
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35))
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35)),
new UnitPlan(UnitTypes.retusa, 60f * 60f, with(Items.silicon, 15, Items.metaglass, 25, Items.titanium, 20))
);
size = 3;
consumes.power(1.2f);
@@ -1878,7 +1922,8 @@ public class Blocks implements ContentList{
new UnitType[]{UnitTypes.crawler, UnitTypes.atrax},
new UnitType[]{UnitTypes.flare, UnitTypes.horizon},
new UnitType[]{UnitTypes.mono, UnitTypes.poly},
new UnitType[]{UnitTypes.risso, UnitTypes.minke}
new UnitType[]{UnitTypes.risso, UnitTypes.minke},
new UnitType[]{UnitTypes.retusa, UnitTypes.oxynoe}
);
}};
@@ -1944,10 +1989,26 @@ public class Blocks implements ContentList{
}};
repairPoint = new RepairPoint("repair-point"){{
requirements(Category.units, with(Items.lead, 15, Items.copper, 15, Items.silicon, 15));
requirements(Category.units, with(Items.lead, 20, Items.copper, 20, Items.silicon, 15));
repairSpeed = 0.5f;
repairRadius = 65f;
beamWidth = 0.73f;
powerUse = 1f;
pulseRadius = 5f;
}};
repairTurret = new RepairPoint("repair-turret"){{
requirements(Category.units, with(Items.silicon, 70, Items.thorium, 60, Items.plastanium, 60));
size = 2;
length = 6f;
repairSpeed = 5f;
repairRadius = 140f;
powerUse = 5f;
beamWidth = 1.1f;
pulseRadius = 6.1f;
coolantUse = 0.15f;
coolantMultiplier = 1.7f;
acceptCoolant = true;
}};
resupplyPoint = new ResupplyPoint("resupply-point"){{
@@ -1961,6 +2022,28 @@ public class Blocks implements ContentList{
consumes.item(Items.copper, 1);
}};
//endregion
//region payloads
payloadConveyor = new PayloadConveyor("payload-conveyor"){{
requirements(Category.units, with(Items.graphite, 10, Items.copper, 20));
canOverdrive = false;
}};
payloadRouter = new PayloadRouter("payload-router"){{
requirements(Category.units, with(Items.graphite, 15, Items.copper, 20));
canOverdrive = false;
}};
payloadPropulsionTower = new PayloadMassDriver("payload-propulsion-tower"){{
requirements(Category.units, with(Items.thorium, 300, Items.silicon, 200, Items.plastanium, 200, Items.phaseFabric, 50));
size = 5;
reloadTime = 150f;
chargeTime = 100f;
range = 300f;
consumes.power(10f);
}};
//endregion
//region sandbox
@@ -1995,10 +2078,21 @@ public class Blocks implements ContentList{
alwaysUnlocked = true;
}};
payloadVoid = new PayloadVoid("payload-void"){{
requirements(Category.units, BuildVisibility.sandboxOnly, with());
size = 5;
}};
payloadSource = new PayloadSource("payload-source"){{
requirements(Category.units, BuildVisibility.sandboxOnly, with());
size = 5;
}};
//TODO move
illuminator = new LightBlock("illuminator"){{
requirements(Category.effect, BuildVisibility.lightingOnly, with(Items.graphite, 12, Items.silicon, 8));
brightness = 0.75f;
radius = 120f;
radius = 140f;
consumes.power(0.05f);
}};
@@ -2027,16 +2121,6 @@ public class Blocks implements ContentList{
consumes.power(4f);
}};
//TODO remove
launchPadLarge = new LaunchPad("launch-pad-large"){{
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 200, Items.silicon, 150, Items.lead, 250, Items.plastanium, 75));
size = 4;
itemCapacity = 300;
launchTime = 60f * 35;
hasPower = true;
consumes.power(6f);
}};
interplanetaryAccelerator = new Accelerator("interplanetary-accelerator"){{
requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 16000, Items.silicon, 11000, Items.thorium, 13000, Items.titanium, 12000, Items.surgeAlloy, 6000, Items.phaseFabric, 5000));
researchCostMultiplier = 0.1f;
@@ -2059,7 +2143,7 @@ public class Blocks implements ContentList{
}};
microProcessor = new LogicBlock("micro-processor"){{
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 30));
requirements(Category.logic, with(Items.copper, 90, Items.lead, 50, Items.silicon, 50));
instructionsPerTick = 2;
@@ -2067,7 +2151,7 @@ public class Blocks implements ContentList{
}};
logicProcessor = new LogicBlock("logic-processor"){{
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 60, Items.graphite, 60, Items.thorium, 50));
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 80, Items.graphite, 60, Items.thorium, 50));
instructionsPerTick = 8;
@@ -2077,7 +2161,7 @@ public class Blocks implements ContentList{
}};
hyperProcessor = new LogicBlock("hyper-processor"){{
requirements(Category.logic, with(Items.lead, 450, Items.silicon, 130, Items.thorium, 75, Items.surgeAlloy, 50));
requirements(Category.logic, with(Items.lead, 450, Items.silicon, 150, Items.thorium, 75, Items.surgeAlloy, 50));
consumes.liquid(Liquids.cryofluid, 0.08f);
hasLiquids = true;
@@ -2122,21 +2206,21 @@ public class Blocks implements ContentList{
//region experimental
blockForge = new BlockForge("block-forge"){{
requirements(Category.crafting, BuildVisibility.debugOnly, with(Items.thorium, 100));
requirements(Category.units, BuildVisibility.debugOnly, with(Items.thorium, 100));
hasPower = true;
consumes.power(2f);
size = 3;
}};
blockLoader = new BlockLoader("block-loader"){{
requirements(Category.distribution, BuildVisibility.debugOnly, with(Items.thorium, 100));
requirements(Category.units, BuildVisibility.debugOnly, with(Items.thorium, 100));
hasPower = true;
consumes.power(2f);
size = 3;
}};
blockUnloader = new BlockUnloader("block-unloader"){{
requirements(Category.distribution, BuildVisibility.debugOnly, with(Items.thorium, 100));
requirements(Category.units, BuildVisibility.debugOnly, with(Items.thorium, 100));
hasPower = true;
consumes.power(2f);
size = 3;

View File

@@ -9,7 +9,6 @@ import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -53,8 +52,7 @@ 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){};
JsonIO.copy(damageLightning, damageLightningGround);
damageLightningGround = damageLightning.copy();
damageLightningGround.collidesAir = false;
artilleryDense = new ArtilleryBulletType(3f, 20, "shell"){{
@@ -262,7 +260,7 @@ public class Bullets implements ContentList{
drag = -0.01f;
splashDamageRadius = 30f;
splashDamage = 30f * 1.5f;
ammoMultiplier = 4f;
ammoMultiplier = 5f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
@@ -281,6 +279,7 @@ public class Bullets implements ContentList{
splashDamageRadius = 20f;
splashDamage = 20f * 1.5f;
makeFire = true;
ammoMultiplier = 5f;
hitEffect = Fx.blastExplosion;
status = StatusEffects.burning;
}};
@@ -291,9 +290,10 @@ public class Bullets implements ContentList{
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 25f;
splashDamage = 25f * 1.5f;
splashDamage = 25f * 1.4f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
ammoMultiplier = 4f;
lightningDamage = 10;
lightning = 2;
lightningLength = 10;
@@ -346,12 +346,14 @@ public class Bullets implements ContentList{
}};
standardDenseBig = new BasicBulletType(7f, 55, "bullet"){{
hitSize = 5;
width = 15f;
height = 21f;
shootEffect = Fx.shootBig;
}};
standardThoriumBig = new BasicBulletType(8f, 80, "bullet"){{
hitSize = 5;
width = 16f;
height = 23f;
shootEffect = Fx.shootBig;
@@ -361,6 +363,7 @@ public class Bullets implements ContentList{
}};
standardIncendiaryBig = new BasicBulletType(7f, 60, "bullet"){{
hitSize = 5;
width = 16f;
height = 21f;
frontColor = Pal.lightishOrange;

View File

@@ -12,7 +12,6 @@ import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import static arc.graphics.g2d.Draw.rect;
import static arc.graphics.g2d.Draw.*;
@@ -21,16 +20,29 @@ import static arc.math.Angles.*;
import static mindustry.Vars.*;
public class Fx{
private static final Rand rand = new Rand();
private static final Vec2 v = new Vec2();
public static final Effect
none = new Effect(0, 0f, e -> {}),
trailFade = new Effect(400f, e -> {
if(!(e.data instanceof Trail trail)) return;
//lifetime is how many frames it takes to fade out the trail
e.lifetime = trail.length * 1.4f;
trail.shorten();
trail.drawCap(e.color, e.rotation);
trail.draw(e.color, e.rotation);
}),
unitSpawn = new Effect(30f, e -> {
if(!(e.data instanceof UnitType unit)) return;
float scl = 1f + e.fout() * 2f;
TextureRegion region = unit.icon(Cicon.full);
TextureRegion region = unit.fullIcon;
alpha(e.fout());
mixcol(Color.white, e.fin());
@@ -41,9 +53,7 @@ public class Fx{
alpha(e.fin());
rect(region, e.x, e.y,
region.width * Draw.scl * scl, region.height * Draw.scl * scl, e.rotation - 90);
rect(region, e.x, e.y, region.width * Draw.scl * scl, region.height * Draw.scl * scl, e.rotation - 90);
}),
unitCapKill = new Effect(80f, e -> {
@@ -62,7 +72,7 @@ public class Fx{
mixcol(Pal.accent, 1f);
alpha(e.fout());
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type.icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
rect(block ? ((BlockUnitc)select).tile().block.fullIcon : select.type.fullIcon, select.x, select.y, block ? 0f : select.rotation - 90f);
alpha(1f);
Lines.stroke(e.fslope());
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
@@ -80,7 +90,7 @@ public class Fx{
Draw.scl *= scl;
mixcol(Pal.accent, 1f);
rect(select.type.icon(Cicon.full), select.x, select.y, select.rotation - 90f);
rect(select.type.fullIcon, select.x, select.y, select.rotation - 90f);
reset();
Draw.scl = p;
@@ -125,10 +135,10 @@ public class Fx{
Position pos = e.data();
Draw.color(e.color);
Draw.alpha(e.fout());
Draw.color(e.color, e.fout());
Lines.stroke(1.5f);
Lines.line(e.x, e.y, pos.getX(), pos.getY());
Drawf.light(null, e.x, e.y, pos.getX(), pos.getY(), 20f, e.color, 0.6f * e.fout());
}),
pointHit = new Effect(8f, e -> {
@@ -258,6 +268,14 @@ public class Fx{
Fill.circle(e.x + trnsx(ang, len), e.y + trnsy(ang, len), 2f * e.fout());
}),
breakProp = new Effect(23, e -> {
float scl = Math.max(e.rotation, 1);
color(Tmp.c1.set(e.color).mul(1.1f));
randLenVectors(e.id, 6, 19f * e.finpow() * scl, (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * 3.5f * scl + 0.3f);
});
}).layer(Layer.debris),
unitDrop = new Effect(30, e -> {
color(Pal.lightishGray);
randLenVectors(e.id, 9, 3 + 20f * e.finpow(), (x, y) -> {
@@ -300,7 +318,8 @@ public class Fx{
greenBomb = new Effect(40f, 100f, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 4f + e.finpow() * 65f);
float circleRad = 4f + e.finpow() * 65f;
Lines.circle(e.x, e.y, circleRad);
color(Pal.heal);
for(int i = 0; i < 4; i++){
@@ -311,6 +330,8 @@ public class Fx{
for(int i = 0; i < 4; i++){
Drawf.tri(e.x, e.y, 3f, 35f * e.fout(), i*90);
}
Drawf.light(e.x, e.y, circleRad * 1.6f, Pal.heal, e.fout());
}),
greenLaserCharge = new Effect(80f, 100f, e -> {
@@ -322,11 +343,13 @@ public class Fx{
randLenVectors(e.id, 20, 40f * e.fout(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fin() * 5f);
Drawf.light(e.x + x, e.y + y, e.fin() * 15f, Pal.heal, 0.7f);
});
color();
Fill.circle(e.x, e.y, e.fin() * 10);
Drawf.light(e.x, e.y, e.fin() * 20f, Pal.heal, 0.7f);
}),
greenLaserChargeSmall = new Effect(40f, 100f, e -> {
@@ -335,6 +358,13 @@ public class Fx{
Lines.circle(e.x, e.y, e.fout() * 50f);
}),
greenCloud = new Effect(80f, e -> {
color(Pal.heal);
randLenVectors(e.id, e.fin(), 7, 9f, (x, y, fin, fout) -> {
Fill.circle(e.x + x, e.y + y, 5f * fout);
});
}),
healWaveDynamic = new Effect(22, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
@@ -353,14 +383,20 @@ public class Fx{
Lines.circle(e.x, e.y, 2f + e.finpow() * 7f);
}),
healDenamic = new Effect(11, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 2f + e.finpow() * e.rotation);
}),
shieldWave = new Effect(22, e -> {
color(Pal.shield);
color(e.color, 0.7f);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 4f + e.finpow() * 60f);
}),
shieldApply = new Effect(11, e -> {
color(Pal.shield);
color(e.color, 0.7f);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 2f + e.finpow() * 7f);
}),
@@ -379,6 +415,8 @@ public class Fx{
float ang = Mathf.angle(x, y);
lineAngle(e.x + x, e.y + y, ang, e.fout() * 3 + 1f);
});
Drawf.light(e.x, e.y, 20f, Pal.lightOrange, 0.6f * e.fout());
}),
hitFuse = new Effect(14, e -> {
@@ -417,6 +455,16 @@ public class Fx{
});
}),
hitFlamePlasma = new Effect(14, e -> {
color(Color.white, Pal.heal, e.fin());
stroke(0.5f + e.fout());
randLenVectors(e.id, 2, e.fin() * 15f, e.rotation, 50f, (x, y) -> {
float ang = Mathf.angle(x, y);
lineAngle(e.x + x, e.y + y, ang, e.fout() * 3 + 1f);
});
}),
hitLiquid = new Effect(16, e -> {
color(e.color);
@@ -435,6 +483,16 @@ public class Fx{
});
}),
hitEmpSpark = new Effect(40, e -> {
color(Pal.heal);
stroke(e.fout() * 1.6f);
randLenVectors(e.id, 18, e.finpow() * 27f, e.rotation, 360f, (x, y) -> {
float ang = Mathf.angle(x, y);
lineAngle(e.x + x, e.y + y, ang, e.fout() * 6 + 1f);
});
}),
hitLancer = new Effect(12, e -> {
color(Color.white);
stroke(e.fout() * 1.5f);
@@ -488,6 +546,8 @@ public class Fx{
for(int i = 0; i < 4; i++){
Drawf.tri(e.x, e.y, 3f, 30f * e.fout(), i*90 + 45);
}
Drawf.light(e.x, e.y, 150f, Pal.bulletYellowBack, 0.9f * e.fout());
}),
instTrail = new Effect(30, e -> {
@@ -501,6 +561,8 @@ public class Fx{
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);
}
Drawf.light(e.x, e.y, 60f, Pal.bulletYellowBack, 0.6f * e.fout());
}),
instShoot = new Effect(24f, e -> {
@@ -516,6 +578,8 @@ public class Fx{
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);
}
Drawf.light(e.x, e.y, 180f, Pal.bulletYellowBack, 0.9f * e.fout());
}),
instHit = new Effect(20f, 200f, e -> {
@@ -552,6 +616,8 @@ public class Fx{
color(Color.white, Pal.heal, e.fin());
stroke(0.5f + e.fout());
Lines.circle(e.x, e.y, e.fin() * 5f);
Drawf.light(e.x, e.y, 23f, Pal.heal, e.fout() * 0.7f);
}),
hitYellowLaser = new Effect(8, e -> {
@@ -571,6 +637,12 @@ public class Fx{
}),
airBubble = new Effect(100f, e -> {
randLenVectors(e.id, 1, e.fin() * 12f, (x, y) -> {
rect(renderer.bubbles[Math.min((int)(renderer.bubbles.length * Mathf.curveMargin(e.fin(), 0.11f, 0.06f)), renderer.bubbles.length - 1)], e.x + x, e.y + y);
});
}).layer(Layer.flyingUnitLow + 1),
flakExplosion = new Effect(20, e -> {
color(Pal.bulletYellow);
@@ -591,6 +663,8 @@ public class Fx{
randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
});
Drawf.light(e.x, e.y, 50f, Pal.lighterOrange, 0.8f * e.fout());
}),
plasticExplosion = new Effect(24, e -> {
@@ -613,6 +687,8 @@ public class Fx{
randLenVectors(e.id + 1, 4, 1f + 25f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
});
Drawf.light(e.x, e.y, 50f, Pal.plastaniumBack, 0.8f * e.fout());
}),
plasticExplosionFlak = new Effect(28, e -> {
@@ -657,6 +733,8 @@ public class Fx{
randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
});
Drawf.light(e.x, e.y, 45f, Pal.missileYellowBack, 0.8f * e.fout());
}),
sapExplosion = new Effect(25, e -> {
@@ -679,6 +757,8 @@ public class Fx{
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);
});
Drawf.light(e.x, e.y, 90f, Pal.sapBulletBack, 0.8f * e.fout());
}),
massiveExplosion = new Effect(30, e -> {
@@ -701,6 +781,8 @@ public class Fx{
randLenVectors(e.id + 1, 6, 1f + 29f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 4f);
});
Drawf.light(e.x, e.y, 50f, Pal.missileYellowBack, 0.8f * e.fout());
}),
artilleryTrail = new Effect(50, e -> {
@@ -716,7 +798,7 @@ public class Fx{
missileTrail = new Effect(50, e -> {
color(e.color);
Fill.circle(e.x, e.y, e.rotation * e.fout());
}),
}).layer(Layer.bullet - 0.001f), //below bullets
absorb = new Effect(12, e -> {
color(Pal.accent);
@@ -757,6 +839,8 @@ public class Fx{
randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
});
Drawf.light(e.x, e.y, 60f, Pal.bulletYellowBack, 0.7f * e.fout());
}),
burning = new Effect(35f, e -> {
@@ -767,6 +851,12 @@ public class Fx{
});
}),
fireRemove = new Effect(70f, e -> {
if(Fire.regions[0] == null) return;
alpha(e.fout());
rect(Fire.regions[((int)(e.rotation + e.fin() * Fire.frames)) % Fire.frames], e.x, e.y);
}),
fire = new Effect(50f, e -> {
color(Pal.lightFlame, Pal.darkFlame, e.fin());
@@ -849,6 +939,14 @@ public class Fx{
});
}),
electrified = new Effect(40f, e -> {
color(Pal.heal);
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
Fill.square(e.x + x, e.y + y, e.fslope() * 1.1f, 45f);
});
}),
sporeSlowed = new Effect(40f, e -> {
color(Pal.spore);
@@ -881,7 +979,7 @@ public class Fx{
float length = 20f * e.finpow();
float size = 7f * e.fout();
rect(((Item)e.data).icon(Cicon.medium), e.x + trnsx(e.rotation, length), e.y + trnsy(e.rotation, length), size, size);
rect(((Item)e.data).fullIcon, e.x + trnsx(e.rotation, length), e.y + trnsy(e.rotation, length), size, size);
}),
shockwave = new Effect(10f, 80f, e -> {
@@ -935,26 +1033,82 @@ public class Fx{
});
}),
dynamicExplosion = new Effect(30, 100f, e -> {
float intensity = e.rotation;
e.scaled(5 + intensity * 2, i -> {
stroke(3.1f * i.fout());
Lines.circle(e.x, e.y, (3f + i.fin() * 14f) * intensity);
});
dynamicExplosion = new Effect(30, 500f, b -> {
float intensity = b.rotation;
float baseLifetime = 26f + intensity * 15f;
b.lifetime = 43f + intensity * 35f;
color(Color.gray);
//TODO awful borders with linear filtering here
alpha(0.9f);
for(int i = 0; i < 4; i++){
rand.setSeed(b.id*2 + i);
float lenScl = rand.random(0.4f, 1f);
int fi = i;
b.scaled(b.lifetime * lenScl, e -> {
randLenVectors(e.id + fi - 1, e.fin(Interp.pow10Out), (int)(3f * intensity), 14f * intensity, (x, y, in, out) -> {
float fout = e.fout(Interp.pow5Out) * rand.random(0.5f, 1f);
Fill.circle(e.x + x, e.y + y, fout * ((2f + intensity) * 1.8f));
});
});
}
randLenVectors(e.id, e.finpow(), (int)(6 * intensity), 21f * intensity, (x, y, in, out) -> {
Fill.circle(e.x + x, e.y + y, out * (2f + intensity) * 3 + 0.5f);
Fill.circle(e.x + x / 2f, e.y + y / 2f, out * (intensity) * 3);
b.scaled(baseLifetime, e -> {
e.scaled(5 + intensity * 2.5f, i -> {
stroke((3.1f + intensity/5f) * i.fout());
Lines.circle(e.x, e.y, (3f + i.fin() * 14f) * intensity);
Drawf.light(e.x, e.y, i.fin() * 14f * 2f * intensity, Color.white, 0.9f * e.fout());
});
color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
stroke((1.7f * e.fout()) * (1f + (intensity - 1f) / 2f));
Draw.z(Layer.effect + 0.001f);
randLenVectors(e.id + 1, e.finpow() + 0.001f, (int)(9 * intensity), 40f * intensity, (x, y, in, out) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + out * 4 * (3f + intensity));
Drawf.light(e.x + x, e.y + y, (out * 4 * (3f + intensity)) * 3.5f, Draw.getColor(), 0.8f);
});
});
}),
color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
stroke((1.7f * e.fout()) * (1f + (intensity - 1f) / 2f));
reactorExplosion = new Effect(30, 500f, b -> {
float intensity = 6.8f;
float baseLifetime = 25f + intensity * 11f;
b.lifetime = 50f + intensity * 65f;
randLenVectors(e.id + 1, e.finpow(), (int)(9 * intensity), 40f * intensity, (x, y, in, out) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + out * 4 * (3f + intensity));
color(Pal.reactorPurple2);
alpha(0.7f);
for(int i = 0; i < 4; i++){
rand.setSeed(b.id*2 + i);
float lenScl = rand.random(0.4f, 1f);
int fi = i;
b.scaled(b.lifetime * lenScl, e -> {
randLenVectors(e.id + fi - 1, e.fin(Interp.pow10Out), (int)(2.9f * intensity), 22f * intensity, (x, y, in, out) -> {
float fout = e.fout(Interp.pow5Out) * rand.random(0.5f, 1f);
float rad = fout * ((2f + intensity) * 2.35f);
Fill.circle(e.x + x, e.y + y, rad);
Drawf.light(e.x + x, e.y + y, rad * 2.5f, Pal.reactorPurple, 0.5f);
});
});
}
b.scaled(baseLifetime, e -> {
Draw.color();
e.scaled(5 + intensity * 2f, i -> {
stroke((3.1f + intensity/5f) * i.fout());
Lines.circle(e.x, e.y, (3f + i.fin() * 14f) * intensity);
Drawf.light(e.x, e.y, i.fin() * 14f * 2f * intensity, Color.white, 0.9f * e.fout());
});
color(Pal.lighterOrange, Pal.reactorPurple, e.fin());
stroke((2f * e.fout()));
Draw.z(Layer.effect + 0.001f);
randLenVectors(e.id + 1, e.finpow() + 0.001f, (int)(8 * intensity), 28f * intensity, (x, y, in, out) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + out * 4 * (4f + intensity));
Drawf.light(e.x + x, e.y + y, (out * 4 * (3f + intensity)) * 3.5f, Draw.getColor(), 0.8f);
});
});
}),
@@ -1047,6 +1201,19 @@ public class Fx{
});
}),
shootPayloadDriver = new Effect(30f, e -> {
color(Pal.accent);
Lines.stroke(0.5f + 0.5f*e.fout());
float spread = 9f;
rand.setSeed(e.id);
for(int i = 0; i < 20; i++){
float ang = e.rotation + rand.range(17f);
v.trns(ang, rand.random(e.fin() * 55f));
Lines.lineAngle(e.x + v.x + rand.range(spread), e.y + v.y + rand.range(spread), ang, e.fout() * 5f * rand.random(1f) + 1f);
}
}),
shootSmallFlame = new Effect(32f, 80f, e -> {
color(Pal.lightFlame, Pal.darkFlame, Color.gray, e.fin());
@@ -1188,6 +1355,8 @@ public class Fx{
for(int i : Mathf.signs){
Drawf.tri(e.x, e.y, 10f * e.fout(), 24f, e.rotation + 90 + 90f * i);
}
Drawf.light(e.x, e.y, 60f * e.fout(), Pal.orangeSpark, 0.5f);
}),
railHit = new Effect(18f, 200f, e -> {
@@ -1478,30 +1647,44 @@ public class Fx{
Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 8);
}),
mineSmall = new Effect(30, e -> {
color(e.color, Color.lightGray, e.fin());
randLenVectors(e.id, 3, e.fin() * 5f, (x, y) -> {
Fill.square(e.x + x, e.y + y, e.fout() + 0.5f, 45);
});
}),
mine = new Effect(20, e -> {
color(e.color, Color.lightGray, e.fin());
randLenVectors(e.id, 6, 3f + e.fin() * 6f, (x, y) -> {
color(e.color, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, e.fout() * 2f, 45);
});
}),
mineBig = new Effect(30, e -> {
color(e.color, Color.lightGray, e.fin());
randLenVectors(e.id, 6, 4f + e.fin() * 8f, (x, y) -> {
color(e.color, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, e.fout() * 2f + 0.2f, 45);
});
}),
mineHuge = new Effect(40, e -> {
color(e.color, Color.lightGray, e.fin());
randLenVectors(e.id, 8, 5f + e.fin() * 10f, (x, y) -> {
color(e.color, Color.lightGray, e.fin());
Fill.square(e.x + x, e.y + y, e.fout() * 2f + 0.5f, 45);
});
}),
payloadReceive = new Effect(30, e -> {
color(Color.white, Pal.accent, e.fin());
randLenVectors(e.id, 12, 7f + e.fin() * 13f, (x, y) -> {
Fill.square(e.x + x, e.y + y, e.fout() * 2.1f + 0.5f, 45);
});
}),
smelt = new Effect(20, e -> {
color(Color.white, e.color, e.fin());
randLenVectors(e.id, 6, 2f + e.fin() * 5f, (x, y) -> {
color(Color.white, e.color, e.fin());
Fill.square(e.x + x, e.y + y, 0.5f + e.fout() * 2f, 45);
});
}),
@@ -1635,7 +1818,7 @@ public class Fx{
float radius = unit.hitSize() * 1.3f;
e.scaled(16f, c -> {
color(Pal.shield);
color(e.color, 0.9f);
stroke(c.fout() * 2f + 0.1f);
randLenVectors(e.id, (int)(radius * 1.2f), radius/2f + c.finpow() * radius*1.25f, (x, y) -> {
@@ -1643,11 +1826,85 @@ public class Fx{
});
});
color(Pal.shield, e.fout());
color(e.color, e.fout() * 0.9f);
stroke(e.fout());
Lines.circle(e.x, e.y, radius);
}),
chainLightning = new Effect(20f, 300f, e -> {
if(!(e.data instanceof Position p)) return;
float tx = p.getX(), ty = p.getY(), dst = Mathf.dst(e.x, e.y, tx, ty);
Tmp.v1.set(p).sub(e.x, e.y).nor();
float normx = Tmp.v1.x, normy = Tmp.v1.y;
float range = 6f;
int links = Mathf.ceil(dst / range);
float spacing = dst / links;
Lines.stroke(2.5f * e.fout());
Draw.color(Color.white, e.color, e.fin());
Lines.beginLine();
Lines.linePoint(e.x, e.y);
rand.setSeed(e.id);
for(int i = 0; i < links; i++){
float nx, ny;
if(i == links - 1){
nx = tx;
ny = ty;
}else{
float len = (i + 1) * spacing;
Tmp.v1.setToRandomDirection(rand).scl(range/2f);
nx = e.x + normx * len + Tmp.v1.x;
ny = e.y + normy * len + Tmp.v1.y;
}
Lines.linePoint(nx, ny);
}
Lines.endLine();
}).followParent(false),
chainEmp = new Effect(30f, 300f, e -> {
if(!(e.data instanceof Position p)) return;
float tx = p.getX(), ty = p.getY(), dst = Mathf.dst(e.x, e.y, tx, ty);
Tmp.v1.set(p).sub(e.x, e.y).nor();
float normx = Tmp.v1.x, normy = Tmp.v1.y;
float range = 6f;
int links = Mathf.ceil(dst / range);
float spacing = dst / links;
Lines.stroke(4f * e.fout());
Draw.color(Color.white, e.color, e.fin());
Lines.beginLine();
Lines.linePoint(e.x, e.y);
rand.setSeed(e.id);
for(int i = 0; i < links; i++){
float nx, ny;
if(i == links - 1){
nx = tx;
ny = ty;
}else{
float len = (i + 1) * spacing;
Tmp.v1.setToRandomDirection(rand).scl(range/2f);
nx = e.x + normx * len + Tmp.v1.x;
ny = e.y + normy * len + Tmp.v1.y;
}
Lines.linePoint(nx, ny);
}
Lines.endLine();
}).followParent(false),
coreLand = new Effect(120f, e -> {
});
}

View File

@@ -5,8 +5,9 @@ import mindustry.ctype.*;
import mindustry.type.*;
public class Items implements ContentList{
public static Item scrap, copper, lead, graphite, coal, titanium, thorium, silicon, plastanium, phaseFabric, surgeAlloy,
sporePod, sand, blastCompound, pyratite, metaglass;
public static Item
scrap, copper, lead, graphite, coal, titanium, thorium, silicon, plastanium,
phaseFabric, surgeAlloy, sporePod, sand, blastCompound, pyratite, metaglass;
@Override
public void load(){

View File

@@ -24,7 +24,7 @@ public class Liquids implements ContentList{
}};
oil = new Liquid("oil", Color.valueOf("313131")){{
viscosity = 0.7f;
viscosity = 0.75f;
flammability = 1.2f;
explosiveness = 1.2f;
heatCapacity = 0.7f;

View File

@@ -9,7 +9,6 @@ import mindustry.type.*;
public class Planets implements ContentList{
public static Planet
sun,
//tantros,
serpulo;
@Override
@@ -18,8 +17,6 @@ public class Planets implements ContentList{
bloom = true;
accessible = false;
//lightColor = Color.valueOf("f4ee8e");
meshLoader = () -> new SunMesh(
this, 4,
5, 0.3, 1.7, 1.2, 1,
@@ -33,15 +30,6 @@ public class Planets implements ContentList{
);
}};
/*tantros = new Planet("tantros", sun, 2, 0.8f){{
generator = new TantrosPlanetGenerator();
meshLoader = () -> new HexMesh(this, 4);
atmosphereColor = Color.valueOf("3db899");
startSector = 10;
atmosphereRadIn = -0.01f;
atmosphereRadOut = 0.3f;
}};*/
serpulo = new Planet("serpulo", sun, 3, 1){{
generator = new SerpuloPlanetGenerator();
meshLoader = () -> new HexMesh(this, 6);
@@ -49,6 +37,7 @@ public class Planets implements ContentList{
atmosphereRadIn = 0.02f;
atmosphereRadOut = 0.3f;
startSector = 15;
alwaysUnlocked = true;
}};
}
}

View File

@@ -14,12 +14,14 @@ public class SectorPresets implements ContentList{
@Override
public void load(){
//region serpulo
groundZero = new SectorPreset("groundZero", serpulo, 15){{
alwaysUnlocked = true;
addStartingItems = true;
captureWave = 10;
difficulty = 1;
startWaveTimeMultiplier = 3f;
}};
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
@@ -95,5 +97,7 @@ public class SectorPresets implements ContentList{
planetaryTerminal = new SectorPreset("planetaryTerminal", serpulo, 93){{
difficulty = 10;
}};
//endregion
}
}

View File

@@ -12,7 +12,7 @@ import mindustry.graphics.*;
import static mindustry.Vars.*;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed, disarmed;
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed, disarmed, electrified;
@Override
public void load(){
@@ -27,10 +27,10 @@ public class StatusEffects implements ContentList{
init(() -> {
opposite(wet, freezing);
affinity(tarred, ((unit, time, newTime, result) -> {
affinity(tarred, ((unit, result, time) -> {
unit.damagePierce(transitionDamage);
Fx.burning.at(unit.x + Mathf.range(unit.bounds() / 2f), unit.y + Mathf.range(unit.bounds() / 2f));
result.set(burning, Math.min(time + newTime, 300f));
result.set(burning, Math.min(time + result.time, 300f));
}));
});
}};
@@ -45,7 +45,7 @@ public class StatusEffects implements ContentList{
init(() -> {
opposite(melting, burning);
affinity(blasted, ((unit, time, newTime, result) -> {
affinity(blasted, ((unit, result, time) -> {
unit.damagePierce(transitionDamage);
result.set(freezing, time);
}));
@@ -70,14 +70,14 @@ public class StatusEffects implements ContentList{
transitionDamage = 14;
init(() -> {
affinity(shocked, ((unit, time, newTime, result) -> {
affinity(shocked, ((unit, result, time) -> {
unit.damagePierce(transitionDamage);
if(unit.team == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
result.set(wet, time);
}));
opposite(burning);
opposite(burning, melting);
});
}};
@@ -86,6 +86,7 @@ public class StatusEffects implements ContentList{
speedMultiplier = 0.94f;
effect = Fx.muddy;
effectChance = 0.09f;
show = false;
}};
melting = new StatusEffect("melting"){{
@@ -97,10 +98,10 @@ public class StatusEffects implements ContentList{
init(() -> {
opposite(wet, freezing);
affinity(tarred, ((unit, time, newTime, result) -> {
affinity(tarred, ((unit, result, time) -> {
unit.damagePierce(8f);
Fx.burning.at(unit.x + Mathf.range(unit.bounds() / 2f), unit.y + Mathf.range(unit.bounds() / 2f));
result.set(melting, Math.min(time + newTime, 200f));
result.set(melting, Math.min(time + result.time, 200f));
}));
});
}};
@@ -113,6 +114,14 @@ public class StatusEffects implements ContentList{
effectChance = 0.1f;
}};
electrified = new StatusEffect("electrified"){{
color = Pal.heal;
speedMultiplier = 0.7f;
reloadMultiplier = 0.6f;
effect = Fx.electrified;
effectChance = 0.1f;
}};
sporeSlowed = new StatusEffect("spore-slowed"){{
color = Pal.spore;
speedMultiplier = 0.8f;
@@ -126,8 +135,8 @@ public class StatusEffects implements ContentList{
effect = Fx.oily;
init(() -> {
affinity(melting, ((unit, time, newTime, result) -> result.set(melting, newTime + time)));
affinity(burning, ((unit, time, newTime, result) -> result.set(burning, newTime + time)));
affinity(melting, ((unit, result, time) -> result.set(melting, result.time + time)));
affinity(burning, ((unit, result, time) -> result.set(burning, result.time + time)));
});
}};

View File

@@ -445,7 +445,6 @@ public class TechTree implements ContentList{
node(ruinousShores, Seq.with(
new SectorComplete(craters),
new Research(graphitePress),
new Research(combustionGenerator),
new Research(kiln),
new Research(mechanicalPump)
), () -> {
@@ -549,8 +548,7 @@ public class TechTree implements ContentList{
node(fungalPass, Seq.with(
new SectorComplete(stainedMountains),
new Research(groundFactory),
new Research(door),
new Research(siliconSmelter)
new Research(door)
), () -> {
node(nuclearComplex, Seq.with(
new SectorComplete(fungalPass),

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,8 @@ public class Weathers implements ContentList{
snow,
sandstorm,
sporestorm,
fog;
fog,
suspendParticles;
@Override
public void load(){
@@ -102,5 +103,19 @@ public class Weathers implements ContentList{
attrs.set(Attribute.water, 0.05f);
opacityMultiplier = 0.47f;
}};
suspendParticles = new ParticleWeather("suspend-particles"){{
color = noiseColor = Color.valueOf("a7c1fa");
particleRegion = "particle";
statusGround = false;
useWindVector = true;
sizeMax = 4f;
sizeMin = 1.4f;
minAlpha = 0.5f;
maxAlpha = 1f;
density = 10000f;
baseSpeed = 0.03f;
}};
}
}

View File

@@ -104,6 +104,7 @@ public class ContentLoader{
/** Calls Content#load() on everything. Use only after all modules have been created on the client.*/
public void load(){
initialize(Content::loadIcon);
initialize(Content::load);
}
@@ -132,9 +133,9 @@ public class ContentLoader{
/** Loads block colors. */
public void loadColors(){
Pixmap pixmap = new Pixmap(files.internal("sprites/block_colors.png"));
for(int i = 0; i < pixmap.getWidth(); i++){
for(int i = 0; i < pixmap.width; i++){
if(blocks().size > i){
int color = pixmap.getPixel(i, 0);
int color = pixmap.get(i, 0);
if(color == 0 || color == 255) continue;
@@ -290,6 +291,10 @@ public class ContentLoader{
return getBy(ContentType.unit);
}
public UnitType unit(int id){
return getByID(ContentType.unit, id);
}
public Seq<Planet> planets(){
return getBy(ContentType.planet);
}

View File

@@ -28,7 +28,6 @@ import mindustry.maps.Map;
import mindustry.maps.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
@@ -123,6 +122,10 @@ public class Control implements ApplicationListener, Loadable{
//add player when world loads regardless
Events.on(WorldLoadEvent.class, e -> {
player.add();
//make player admin on any load when hosting
if(net.active() && net.server()){
player.admin = true;
}
});
//autohost for pvp maps
@@ -130,7 +133,7 @@ public class Control implements ApplicationListener, Loadable{
if(state.rules.pvp && !net.active()){
try{
net.host(port);
player.admin(true);
player.admin = true;
}catch(IOException e){
ui.showException("@server.error", e);
state.set(State.menu);
@@ -148,7 +151,7 @@ public class Control implements ApplicationListener, Loadable{
if(e.content instanceof SectorPreset){
for(TechNode node : TechTree.all){
if(!node.content.unlocked() && node.objectives.contains(o -> o instanceof SectorComplete sec && sec.preset == e.content) && !node.objectives.contains(o -> !o.complete())){
ui.hudfrag.showToast(new TextureRegionDrawable(node.content.icon(Cicon.large)), bundle.get("available"));
ui.hudfrag.showToast(new TextureRegionDrawable(node.content.uiIcon), iconLarge, bundle.get("available"));
}
}
}
@@ -192,33 +195,36 @@ public class Control implements ApplicationListener, Loadable{
});
Events.run(Trigger.newGame, () -> {
Building core = player.closestCore();
Building core = player.bestCore();
if(core == null) return;
//TODO this sounds pretty bad due to conflict
if(settings.getInt("musicvol") > 0){
Musics.land.stop();
Musics.land.play();
Musics.land.setVolume(settings.getInt("musicvol") / 100f);
}
app.post(() -> ui.hudfrag.showLand());
renderer.zoomIn(Fx.coreLand.lifetime);
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
camera.position.set(core);
player.set(core);
Time.run(Fx.coreLand.lifetime, () -> {
Fx.launch.at(core);
Effect.shake(5f, 5f, core);
if(state.isCampaign()){
ui.announce("[accent]" + state.rules.sector.name() + "\n" +
(state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " +
state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5);
if(showLandAnimation){
//TODO this sounds pretty bad due to conflict
if(settings.getInt("musicvol") > 0){
Musics.land.stop();
Musics.land.play();
Musics.land.setVolume(settings.getInt("musicvol") / 100f);
}
});
app.post(() -> ui.hudfrag.showLand());
renderer.zoomIn(Fx.coreLand.lifetime);
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
Time.run(Fx.coreLand.lifetime, () -> {
Fx.launch.at(core);
Effect.shake(5f, 5f, core);
if(state.isCampaign()){
ui.announce("[accent]" + state.rules.sector.name() + "\n" +
(state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " +
state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5);
}
});
}
});
}
@@ -345,7 +351,7 @@ public class Control implements ApplicationListener, Loadable{
//reset wave so things are more fair
state.wave = 1;
//set up default wave time
state.wavetime = state.rules.waveSpacing * 2f;
state.wavetime = state.rules.waveSpacing * (sector.preset == null ? 2f : sector.preset.startWaveTimeMultiplier);
//reset captured state
sector.info.wasCaptured = false;
//re-enable waves

View File

@@ -17,7 +17,9 @@ public class GameState{
/** Wave countdown in ticks. */
public float wavetime;
/** Whether the game is in game over state. */
public boolean gameOver = false, serverPaused = false, wasTimeout;
public boolean gameOver = false, serverPaused = false;
/** Server ticks/second. Only valid in multiplayer. */
public int serverTps = -1;
/** Map that is currently being played on. */
public Map map = emptyMap;
/** The current game rules. */
@@ -33,8 +35,9 @@ public class GameState{
/** Current game state. */
private State state = State.menu;
@Nullable
public Unit boss(){
return teams.boss;
return teams.bosses.firstOpt();
}
public void set(State astate){

View File

@@ -14,6 +14,7 @@ import mindustry.maps.*;
import mindustry.type.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
@@ -34,8 +35,8 @@ public class Logic implements ApplicationListener{
Events.on(BlockDestroyEvent.class, event -> {
//blocks that get broken are appended to the team's broken block queue
Tile tile = event.tile;
//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;
//skip null entities or un-rebuildables, for obvious reasons
if(tile.build == null || !tile.block().rebuildable) return;
tile.build.addPlan(true);
});
@@ -174,10 +175,11 @@ public class Logic implements ApplicationListener{
if(!state.isCampaign()){
for(TeamData team : state.teams.getActive()){
if(team.hasCore()){
Building entity = team.core();
CoreBuild entity = team.core();
entity.items.clear();
for(ItemStack stack : state.rules.loadout){
entity.items.add(stack.item, stack.amount);
//make sure to cap storage
entity.items.add(stack.item, Math.min(stack.amount, entity.storageCapacity - entity.items.get(stack.item)));
}
}
}
@@ -406,6 +408,7 @@ public class Logic implements ApplicationListener{
//apply weather attributes
state.envAttrs.clear();
state.envAttrs.add(state.rules.attributes);
Groups.weather.each(w -> state.envAttrs.add(w.weather.attrs, w.opacity));
Groups.update();

View File

@@ -16,9 +16,9 @@ import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.Net.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
@@ -32,9 +32,9 @@ import java.util.zip.*;
import static mindustry.Vars.*;
public class NetClient implements ApplicationListener{
private static final float dataTimeout = 60 * 18;
private static final float playerSyncTime = 2;
public static final float viewScale = 2f;
private static final float dataTimeout = 60 * 20;
private static final float playerSyncTime = 5;
private static final Reads dataReads = new Reads(null);
private long ping;
private Interval timer = new Interval(5);
@@ -62,7 +62,7 @@ public class NetClient implements ApplicationListener{
net.handleClient(Connect.class, packet -> {
Log.info("Connecting to server: @", packet.addressTCP);
player.admin(false);
player.admin = false;
reset();
@@ -92,7 +92,7 @@ public class NetClient implements ApplicationListener{
c.mods = mods.getModStrings();
c.mobile = mobile;
c.versionType = Version.type;
c.color = player.color().rgba();
c.color = player.color.rgba();
c.usid = getUsid(packet.addressTCP);
c.uuid = platform.getUUID();
@@ -103,7 +103,7 @@ public class NetClient implements ApplicationListener{
return;
}
net.send(c, SendMode.tcp);
net.send(c, true);
});
net.handleClient(Disconnect.class, packet -> {
@@ -112,19 +112,19 @@ public class NetClient implements ApplicationListener{
connecting = false;
logic.reset();
platform.updateRPC();
player.name(Core.settings.getString("name"));
player.color().set(Core.settings.getInt("color-0"));
player.name = Core.settings.getString("name");
player.color.set(Core.settings.getInt("color-0"));
if(quiet) return;
Time.runTask(3f, ui.loadfrag::hide);
if(packet.reason != null){
switch(packet.reason){
case "closed" -> ui.showSmall("@disconnect", "@disconnect.closed");
case "timeout" -> ui.showSmall("@disconnect", "@disconnect.timeout");
case "error" -> ui.showSmall("@disconnect", "@disconnect.error");
}
ui.showSmall(switch(packet.reason){
case "closed" -> "@disconnect.closed";
case "timeout" -> "@disconnect.timeout";
default -> "@disconnect.error";
}, "@disconnect.closed");
}else{
ui.showErrorMessage("@disconnect");
}
@@ -136,10 +136,6 @@ public class NetClient implements ApplicationListener{
finishConnecting();
});
net.handleClient(InvokePacket.class, packet -> {
RemoteReadClient.readPacket(packet.reader(), packet.type);
});
}
public void addPacketHandler(String type, Cons<String> handler){
@@ -428,9 +424,9 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void entitySnapshot(short amount, short dataLen, byte[] data){
public static void entitySnapshot(short amount, byte[] data){
try{
netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen));
netClient.byteStream.setBytes(data);
DataInputStream input = netClient.dataStream;
for(int j = 0; j < amount; j++){
@@ -474,9 +470,9 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.both, priority = PacketPriority.low, unreliable = true)
public static void blockSnapshot(short amount, short dataLen, byte[] data){
public static void blockSnapshot(short amount, byte[] data){
try{
netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen));
netClient.byteStream.setBytes(data);
DataInputStream input = netClient.dataStream;
for(int i = 0; i < amount; i++){
@@ -499,7 +495,7 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, boolean gameOver, int timeData, short coreDataLen, byte[] coreData){
public static void stateSnapshot(float waveTime, int wave, int enemies, boolean paused, boolean gameOver, int timeData, byte tps, byte[] coreData){
try{
if(wave > state.wave){
state.wave = wave;
@@ -511,21 +507,22 @@ public class NetClient implements ApplicationListener{
state.wave = wave;
state.enemies = enemies;
state.serverPaused = paused;
state.serverTps = tps & 0xff;
universe.updateNetSeconds(timeData);
netClient.byteStream.setBytes(net.decompressSnapshot(coreData, coreDataLen));
netClient.byteStream.setBytes(coreData);
DataInputStream input = netClient.dataStream;
dataReads.input = input;
int cores = input.readInt();
for(int i = 0; i < cores; i++){
int pos = input.readInt();
Tile tile = world.tile(pos);
if(tile != null && tile.build != null){
tile.build.items.read(Reads.get(input));
int teams = input.readUnsignedByte();
for(int i = 0; i < teams; i++){
int team = input.readUnsignedByte();
TeamData data = state.teams.get(Team.all[team]);
if(data.cores.any()){
data.cores.first().items.read(dataReads);
}else{
new ItemModule().read(Reads.get(input));
new ItemModule().read(dataReads);
}
}
@@ -665,7 +662,7 @@ public class NetClient implements ApplicationListener{
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale
Core.camera.width, Core.camera.height
);
}

View File

@@ -23,7 +23,6 @@ import mindustry.net.*;
import mindustry.net.Administration.*;
import mindustry.net.Packets.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.io.*;
import java.net.*;
@@ -38,8 +37,8 @@ public class NetServer implements ApplicationListener{
private static final int maxSnapshotSize = 800, timerBlockSync = 0, serverSyncTime = 200;
private static final float blockSyncTime = 60 * 6;
private static final FloatBuffer fbuffer = FloatBuffer.allocate(20);
private static final Writes dataWrites = new Writes(null);
private static final Vec2 vector = new Vec2();
private static final Rect viewport = new Rect();
/** If a player goes away of their server-side coordinates by this distance, they get teleported back. */
private static final float correctDist = tilesize * 14f;
@@ -101,6 +100,8 @@ public class NetServer implements ApplicationListener{
packet.uuid = con.address.substring("steam:".length());
}
Events.fire(new ConnectPacketEvent(con, packet));
con.connectTime = Time.millis();
String uuid = packet.uuid;
@@ -109,8 +110,7 @@ public class NetServer implements ApplicationListener{
crc.update(buuid, 0, 8);
ByteBuffer buff = ByteBuffer.allocate(8);
buff.put(buuid, 8, 8);
buff.position(0);
if(crc.getValue() != buff.getLong()){
if(crc.getValue() != buff.getLong(0)){
con.kick(KickReason.clientOutdated);
return;
}
@@ -254,22 +254,6 @@ public class NetServer implements ApplicationListener{
Events.fire(new PlayerConnect(player));
});
net.handleServer(InvokePacket.class, (con, packet) -> {
if(con.player == null || con.kicked) return;
try{
RemoteReadServer.readPacket(packet.reader(), packet.type, con.player);
}catch(ValidateException e){
debug("Validation failed for '@': @", e.player, e.getMessage());
}catch(RuntimeException e){
if(e.getCause() instanceof ValidateException v){
debug("Validation failed for '@': @", v.player, v.getMessage());
}else{
throw e;
}
}
});
registerCommands();
}
@@ -659,7 +643,7 @@ public class NetServer implements ApplicationListener{
if(!player.dead()){
Unit unit = player.unit();
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
long elapsed = Math.min(Time.timeSinceMillis(con.lastReceivedClientTime), 1500);
float maxSpeed = unit.realSpeed();
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.2f;
@@ -847,8 +831,7 @@ public class NetServer implements ApplicationListener{
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
byte[] stateBytes = syncStream.toByteArray();
Call.blockSnapshot(sent, (short)stateBytes.length, net.compressSnapshot(stateBytes));
Call.blockSnapshot(sent, syncStream.toByteArray());
sent = 0;
syncStream.reset();
}
@@ -856,31 +839,30 @@ public class NetServer implements ApplicationListener{
if(sent > 0){
dataStream.close();
byte[] stateBytes = syncStream.toByteArray();
Call.blockSnapshot(sent, (short)stateBytes.length, net.compressSnapshot(stateBytes));
Call.blockSnapshot(sent, syncStream.toByteArray());
}
}
public void writeEntitySnapshot(Player player) throws IOException{
byte tps = (byte)Math.min(Core.graphics.getFramesPerSecond(), 255);
syncStream.reset();
int sum = state.teams.present.sum(t -> t.cores.size);
int activeTeams = (byte)state.teams.present.count(t -> t.cores.size > 0);
dataStream.writeInt(sum);
dataStream.writeByte(activeTeams);
dataWrites.output = dataStream;
//block data isn't important, just send the items for each team, they're synced across cores
for(TeamData data : state.teams.present){
for(CoreBuild entity : data.cores){
dataStream.writeInt(entity.tile.pos());
entity.items.write(Writes.get(dataStream));
if(data.cores.size > 0){
dataStream.writeByte(data.team.id);
data.cores.first().items.write(dataWrites);
}
}
dataStream.close();
byte[] stateBytes = syncStream.toByteArray();
//write basic state data.
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);
Call.stateSnapshot(player.con, state.wavetime, state.wave, state.enemies, state.serverPaused, state.gameOver, universe.seconds(), tps, syncStream.toByteArray());
syncStream.reset();
@@ -896,8 +878,7 @@ public class NetServer implements ApplicationListener{
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.entitySnapshot(player.con, (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray());
sent = 0;
syncStream.reset();
}
@@ -906,8 +887,7 @@ public class NetServer implements ApplicationListener{
if(sent > 0){
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.entitySnapshot(player.con, (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
Call.entitySnapshot(player.con, (short)sent, syncStream.toByteArray());
}
}

View File

@@ -21,8 +21,8 @@ import static mindustry.Vars.*;
public interface Platform{
/** Dynamically creates a class loader for a jar file. */
default ClassLoader loadJar(Fi jar, String mainClass) throws Exception{
return new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, getClass().getClassLoader());
default ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
return new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, parent);
}
/** Steam: Update lobby visibility.*/
@@ -59,6 +59,15 @@ public interface Platform{
}
default Context getScriptContext(){
ContextFactory.getGlobalSetter().setContextFactoryGlobal(new ContextFactory(){
@Override
protected Context makeContext(){
Context ctx = super.makeContext();
ctx.setClassShutter(Scripts::allowClass);
return ctx;
}
});
Context c = Context.enter();
c.setOptimizationLevel(9);
return c;

View File

@@ -3,17 +3,19 @@ package mindustry.core;
import arc.*;
import arc.files.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.async.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.g3d.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static arc.Core.*;
@@ -35,6 +37,8 @@ public class Renderer implements ApplicationListener{
public boolean animateShields, drawWeather = true, drawStatus;
/** minZoom = zooming out, maxZoom = zooming in */
public float minZoom = 1.5f, maxZoom = 6f;
public Seq<EnvRenderer> envRenderers = new Seq<>();
public TextureRegion[] bubbles = new TextureRegion[16], splashes = new TextureRegion[12];
private @Nullable CoreBuild landCore;
private Color clearColor = new Color(0f, 0f, 0f, 1f);
@@ -51,6 +55,10 @@ public class Renderer implements ApplicationListener{
shaketime = Math.max(shaketime, duration);
}
public void addEnvRenderer(int mask, Runnable render){
envRenderers.add(new EnvRenderer(mask, render));
}
@Override
public void init(){
planets = new PlanetRenderer();
@@ -59,9 +67,18 @@ public class Renderer implements ApplicationListener{
setupBloom();
}
Events.on(WorldLoadEvent.class, e -> {
Events.run(Trigger.newGame, () -> {
landCore = player.bestCore();
});
EnvRenderers.init();
for(int i = 0; i < bubbles.length; i++) bubbles[i] = atlas.find("bubble-" + i);
for(int i = 0; i < splashes.length; i++) splashes[i] = atlas.find("splash-" + i);
assets.load("sprites/clouds.png", Texture.class).loaded = t -> {
((Texture)t).setWrap(TextureWrap.repeat);
((Texture)t).setFilter(TextureFilter.linear);
};
}
@Override
@@ -77,7 +94,9 @@ public class Renderer implements ApplicationListener{
drawStatus = Core.settings.getBool("blockstatus");
if(landTime > 0){
landTime -= Time.delta;
if(!state.isPaused()){
landTime -= Time.delta;
}
landscale = Interp.pow5In.apply(minZoomScl, Scl.scl(4f), 1f - landTime / Fx.coreLand.lifetime);
camerascale = landscale;
weatherAlpha = 0f;
@@ -204,6 +223,13 @@ public class Renderer implements ApplicationListener{
Draw.drawRange(Layer.blockBuilding, () -> Draw.shader(Shaders.blockbuild, true), Draw::shader);
//render all matching environments
for(var renderer : envRenderers){
if((renderer.env & state.rules.environment) == renderer.env){
renderer.renderer.run();
}
}
if(state.rules.lighting){
Draw.draw(Layer.light, lights::draw);
}
@@ -214,8 +240,8 @@ public class Renderer implements ApplicationListener{
if(bloom != null){
bloom.resize(graphics.getWidth() / 4, graphics.getHeight() / 4);
Draw.draw(Layer.bullet - 0.01f, bloom::capture);
Draw.draw(Layer.effect + 0.01f, bloom::render);
Draw.draw(Layer.bullet - 0.02f, bloom::capture);
Draw.draw(Layer.effect + 0.02f, bloom::render);
}
Draw.draw(Layer.plans, overlays::drawBottom);
@@ -252,24 +278,38 @@ public class Renderer implements ApplicationListener{
private void drawLanding(){
CoreBuild entity = landCore == null ? player.bestCore() : landCore;
//var clouds = assets.get("sprites/clouds.png", Texture.class);
if(landTime > 0 && entity != null){
float fract = landTime / Fx.coreLand.lifetime;
float fout = landTime / Fx.coreLand.lifetime;
TextureRegion reg = entity.block.icon(Cicon.full);
//TODO clouds
/*
float scaling = 10000f;
float sscl = 1f + fout*1.5f;
float offset = -0.38f;
Tmp.tr1.set(clouds);
Tmp.tr1.set((camera.position.x - camera.width/2f * sscl) / scaling, (camera.position.y - camera.height/2f * sscl) / scaling, (camera.position.x + camera.width/2f * sscl) / scaling, (camera.position.y + camera.height/2f * sscl) / scaling);
Draw.alpha(Mathf.slope(Mathf.clamp(((1f - fout) + offset)/(1f + offset))));
Draw.mixcol(Pal.spore, 0.5f);
Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height);
Draw.reset();*/
TextureRegion reg = entity.block.fullIcon;
float scl = Scl.scl(4f) / camerascale;
float s = reg.width * Draw.scl * scl * 4f * fract;
float s = reg.width * Draw.scl * scl * 4f * fout;
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.x, entity.y, s, s);
Angles.randLenVectors(1, (1f- fract), 100, 1000f * scl * (1f-fract), (x, y, fin, fout) -> {
Lines.stroke(scl * fin);
Lines.lineAngle(entity.x + x, entity.y + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
Angles.randLenVectors(1, (1f- fout), 100, 1000f * scl * (1f-fout), (x, y, ffin, ffout) -> {
Lines.stroke(scl * ffin);
Lines.lineAngle(entity.x + x, entity.y + y, Mathf.angle(x, y), (ffin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.x, entity.y, reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fract * 135f);
Draw.mixcol(Color.white, fout);
Draw.rect(reg, entity.x, entity.y, reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fout * 135f);
Draw.reset();
}
@@ -330,25 +370,39 @@ public class Renderer implements ApplicationListener{
camera.position.y = h / 2f + tilesize / 2f;
buffer.begin();
draw();
Draw.flush();
byte[] lines = ScreenUtils.getFrameBufferPixels(0, 0, w, h, true);
buffer.end();
disableUI = false;
camera.width = vpW;
camera.height = vpH;
camera.position.set(px, py);
buffer.begin();
byte[] lines = ScreenUtils.getFrameBufferPixels(0, 0, w, h, true);
for(int i = 0; i < lines.length; i += 4){
lines[i + 3] = (byte)255;
}
buffer.end();
Pixmap fullPixmap = new Pixmap(w, h, Pixmap.Format.rgba8888);
Buffers.copy(lines, 0, fullPixmap.getPixels(), lines.length);
Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png");
PixmapIO.writePNG(file, fullPixmap);
fullPixmap.dispose();
ui.showInfoFade(Core.bundle.format("screenshot", file.toString()));
drawWeather = true;
buffer.dispose();
Threads.thread(() -> {
for(int i = 0; i < lines.length; i += 4){
lines[i + 3] = (byte)255;
}
Pixmap fullPixmap = new Pixmap(w, h);
Buffers.copy(lines, 0, fullPixmap.getPixels(), lines.length);
Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png");
PixmapIO.writePng(file, fullPixmap);
fullPixmap.dispose();
app.post(() -> ui.showInfoFade(Core.bundle.format("screenshot", file.toString())));
});
}
public static class EnvRenderer{
/** Environment bitmask; must match env exactly when and-ed. */
public final int env;
/** Rendering callback. */
public final Runnable renderer;
public EnvRenderer(int env, Runnable renderer){
this.env = env;
this.renderer = renderer;
}
}
}

View File

@@ -33,6 +33,8 @@ import static arc.scene.actions.Actions.*;
import static mindustry.Vars.*;
public class UI implements ApplicationListener, Loadable{
private static String billions, millions, thousands;
public static PixmapPacker packer;
public MenuFragment menufrag;
@@ -56,7 +58,7 @@ public class UI implements ApplicationListener, Loadable{
public HostDialog host;
public PausedDialog paused;
public SettingsMenuDialog settings;
public ControlsDialog controls;
public KeybindDialog controls;
public MapEditorDialog editor;
public LanguageDialog language;
public BansDialog bans;
@@ -152,6 +154,10 @@ public class UI implements ApplicationListener, Loadable{
@Override
public void init(){
billions = Core.bundle.get("unit.billions");
millions = Core.bundle.get("unit.millions");
thousands = Core.bundle.get("unit.thousands");
menuGroup = new WidgetGroup();
hudGroup = new WidgetGroup();
@@ -166,7 +172,7 @@ public class UI implements ApplicationListener, Loadable{
picker = new ColorPicker();
editor = new MapEditorDialog();
controls = new ControlsDialog();
controls = new KeybindDialog();
restart = new GameOverDialog();
join = new JoinDialog();
discord = new DiscordDialog();
@@ -347,17 +353,11 @@ public class UI implements ApplicationListener, Loadable{
}
public void showInfo(String info){
showInfo(info, () -> {});
}
public void showInfo(String info, Runnable listener){
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
buttons.button("@ok", () -> {
hide();
listener.run();
}).size(110, 50).pad(4);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
keyDown(KeyCode.enter, this::hide);
closeOnBack();
}}.show();
}
@@ -458,6 +458,10 @@ public class UI implements ApplicationListener, Loadable{
}}.show();
}
public void showConfirm(String text, Runnable confirmed){
showConfirm("@confirm", text, null, confirmed);
}
public void showConfirm(String title, String text, Runnable confirmed){
showConfirm(title, text, null, confirmed);
}
@@ -535,6 +539,13 @@ public class UI implements ApplicationListener, Loadable{
dialog.show();
}
public static String formatTime(float ticks){
int time = (int)(ticks / 60);
if(time < 60) return "0:" + (time < 10 ? "0" : "") + time;
int mod = time % 60;
return (time / 60) + ":" + (mod < 10 ? "0" : "") + mod;
}
public static String formatAmount(long number){
//prevent overflow
if(number == Long.MIN_VALUE) number ++;
@@ -542,13 +553,13 @@ public class UI implements ApplicationListener, Loadable{
long mag = Math.abs(number);
String sign = number < 0 ? "-" : "";
if(mag >= 1_000_000_000){
return sign + Strings.fixed(mag / 1_000_000_000f, 1) + "[gray]" + Core.bundle.get("unit.billions") + "[]";
return sign + Strings.fixed(mag / 1_000_000_000f, 1) + "[gray]" + billions+ "[]";
}else if(mag >= 1_000_000){
return sign + Strings.fixed(mag / 1_000_000f, 1) + "[gray]" + Core.bundle.get("unit.millions") + "[]";
return sign + Strings.fixed(mag / 1_000_000f, 1) + "[gray]" +millions + "[]";
}else if(mag >= 10_000){
return number / 1000 + "[gray]" + Core.bundle.get("unit.thousands") + "[]";
return number / 1000 + "[gray]" + thousands + "[]";
}else if(mag >= 1000){
return sign + Strings.fixed(mag / 1000f, 1) + "[gray]" + Core.bundle.get("unit.thousands") + "[]";
return sign + Strings.fixed(mag / 1000f, 1) + "[gray]" + thousands + "[]";
}else{
return number + "";
}

View File

@@ -271,13 +271,13 @@ public class World{
private void setSectorRules(Sector sector){
state.map = new Map(StringMap.of("name", sector.preset == null ? sector.planet.localizedName + "; Sector " + sector.id : sector.preset.localizedName));
state.rules.sector = sector;
state.rules.weather.clear();
//apply weather based on terrain
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
sector.planet.generator.addWeather(sector, state.rules);
ObjectSet<UnlockableContent> content = new ObjectSet<>();
//TODO duplicate code?
for(Tile tile : world.tiles){
if(world.getDarkness(tile.x, tile.y) >= 3){
continue;
@@ -287,47 +287,6 @@ public class World{
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()){
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];
for(int i = 0; i < entries.size; i++){
floors[i] = entries.get(i).key;
}
//TODO bad code
boolean hasSnow = floors[0].name.contains("ice") || floors[0].name.contains("snow");
boolean hasRain = !hasSnow && content.contains(Liquids.water) && !floors[0].name.contains("sand");
boolean hasDesert = !hasSnow && !hasRain && floors[0] == Blocks.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));
state.rules.weather.add(new WeatherEntry(Weathers.fog));
}
if(hasDesert){
state.rules.weather.add(new WeatherEntry(Weathers.sandstorm));
}
if(hasSpores){
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
}
sector.info.resources = content.asArray();
@@ -393,12 +352,6 @@ public class World{
if(invalidMap) Core.app.post(() -> state.set(State.menu));
}
public void notifyChanged(Tile tile){
if(!generating){
Core.app.post(() -> Events.fire(new TileChangeEvent(tile)));
}
}
public void raycastEachWorld(float x0, float y0, float x1, float y1, Raycaster cons){
raycastEach(toTile(x0), toTile(y0), toTile(x1), toTile(y1), cons);
}
@@ -464,7 +417,7 @@ public class World{
byte[] dark = new byte[tiles.width * tiles.height];
byte[] writeBuffer = new byte[tiles.width * tiles.height];
byte darkIterations = 4;
byte darkIterations = darkRadius;
for(int i = 0; i < dark.length; i++){
Tile tile = tiles.geti(i);
@@ -498,7 +451,7 @@ public class World{
tile.data = dark[idx];
}
if(dark[idx] == 4){
if(dark[idx] == darkRadius){
boolean full = true;
for(Point2 p : Geometry.d4){
int px = p.x + tile.x, py = p.y + tile.y;
@@ -509,11 +462,28 @@ public class World{
}
}
if(full) tile.data = 5;
if(full) tile.data = darkRadius + 1;
}
}
}
public byte getWallDarkness(Tile tile){
if(tile.isDarkened()){
int minDst = darkRadius + 1;
for(int cx = tile.x - darkRadius; cx <= tile.x + darkRadius; cx++){
for(int cy = tile.y - darkRadius; cy <= tile.y + darkRadius; cy++){
if(tiles.in(cx, cy) && !rawTile(cx, cy).isDarkened()){
minDst = Math.min(minDst, Math.abs(cx - tile.x) + Math.abs(cy - tile.y));
}
}
}
return (byte)Math.max((minDst - 1), 0);
}
return 0;
}
//TODO optimize; this is very slow and called too often!
public float getDarkness(int x, int y){
int edgeBlend = 2;

View File

@@ -7,7 +7,7 @@ import mindustry.mod.Mods.*;
/** Base class for a content type that is loaded in {@link mindustry.core.ContentLoader}. */
public abstract class Content implements Comparable<Content>, Disposable{
public final short id;
public short id;
/** Info on which mod this content was loaded from. */
public ModContentInfo minfo = new ModContentInfo();
@@ -31,6 +31,9 @@ public abstract class Content implements Comparable<Content>, Disposable{
*/
public void load(){}
/** Called right after load(). */
public void loadIcon(){}
/** @return whether an error occurred during mod loading. */
public boolean hasErrored(){
return minfo.error != null;

View File

@@ -3,7 +3,6 @@ package mindustry.ctype;
import arc.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
@@ -30,8 +29,10 @@ public abstract class UnlockableContent extends MappableContent{
public boolean inlineDescription = true;
/** Special logic icon ID. */
public int iconId = 0;
/** Icons by Cicon ID.*/
protected TextureRegion[] cicons = new TextureRegion[Cicon.all.length];
/** Icon of the content to use in UI. */
public TextureRegion uiIcon;
/** Icon of the full content. Unscaled.*/
public TextureRegion fullIcon;
/** Unlock state. Loaded from settings. Do not modify outside of the constructor. */
protected boolean unlocked;
@@ -44,11 +45,29 @@ public abstract class UnlockableContent extends MappableContent{
this.unlocked = Core.settings != null && Core.settings.getBool(this.name + "-unlocked", false);
}
@Override
public void loadIcon(){
fullIcon =
Core.atlas.find(getContentType().name() + "-" + name + "-full",
Core.atlas.find(name + "-full",
Core.atlas.find(name,
Core.atlas.find(getContentType().name() + "-" + name,
Core.atlas.find(name + "1")))));
uiIcon = Core.atlas.find(getContentType().name() + "-" + name + "-ui", fullIcon);
}
/** @return the tech node for this content. may be null. */
public @Nullable TechNode node(){
return TechTree.get(this);
}
/** Use fullIcon / uiIcon instead! This will be removed. */
@Deprecated
public TextureRegion icon(Cicon icon){
return icon == Cicon.full ? fullIcon : uiIcon;
}
public String displayDescription(){
return minfo.mod == null ? description : description + "\n" + Core.bundle.format("mod.display", minfo.mod.meta.displayName());
}
@@ -65,7 +84,10 @@ public abstract class UnlockableContent extends MappableContent{
public void setStats(){
}
/** Generate any special icons for this content. Called asynchronously.*/
/**
* Generate any special icons for this content. Called synchronously.
* No regions are loaded at this point; grab pixmaps from the packer.
* */
@CallSuper
public void createIcons(MultiPacker packer){
@@ -80,23 +102,8 @@ public abstract class UnlockableContent extends MappableContent{
return Fonts.getUnicodeStr(name);
}
/** Returns a specific content icon, or the region {contentType}-{name} if not found.*/
public TextureRegion icon(Cicon icon){
if(cicons[icon.ordinal()] == null){
cicons[icon.ordinal()] =
Core.atlas.find(getContentType().name() + "-" + name + "-" + icon.name(),
Core.atlas.find(getContentType().name() + "-" + name + "-full",
Core.atlas.find(name + "-" + icon.name(),
Core.atlas.find(name + "-full",
Core.atlas.find(name,
Core.atlas.find(getContentType().name() + "-" + name,
Core.atlas.find(name + "1")))))));
}
return cicons[icon.ordinal()];
}
public Cicon prefDatabaseIcon(){
return Cicon.xlarge;
public boolean hasEmoji(){
return Fonts.hasUnicodeStr(name);
}
/** Iterates through any implicit dependencies of this content.
@@ -105,11 +112,6 @@ public abstract class UnlockableContent extends MappableContent{
}
/** This should show all necessary info about this content in the specified table. */
public void display(Table table){
}
/** Called when this content is unlocked. Use this to unlock other related content. */
public void onUnlock(){
}
@@ -144,7 +146,7 @@ public abstract class UnlockableContent extends MappableContent{
}
public boolean unlocked(){
if(net != null && net.client()) return unlocked || alwaysUnlocked || state.rules.researched.contains(name);
if(net != null && net.client()) return alwaysUnlocked || state.rules.researched.contains(name);
return unlocked || alwaysUnlocked;
}

View File

@@ -10,13 +10,8 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
public class DrawOperation{
private MapEditor editor;
private LongSeq array = new LongSeq();
public DrawOperation(MapEditor editor){
this.editor = editor;
}
public boolean isEmpty(){
return array.isEmpty();
}

View File

@@ -77,7 +77,7 @@ public class EditorTile extends Tile{
op(OpType.team, (byte)getTeamID());
super.setTeam(team);
getLinkedTiles(t -> ui.editor.editor.renderer.updatePoint(t.x, t.y));
getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
}
@Override
@@ -140,14 +140,14 @@ public class EditorTile extends Tile{
}
private void update(){
ui.editor.editor.renderer.updatePoint(x, y);
editor.renderer.updatePoint(x, y);
}
private boolean skip(){
return state.isGame() || ui.editor.editor.isLoading();
return state.isGame() || editor.isLoading();
}
private void op(OpType type, short value){
ui.editor.editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value));
editor.addTileOp(TileOp.get(x, y, (byte)type.ordinal(), value));
}
}

View File

@@ -9,11 +9,12 @@ import arc.util.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public enum EditorTool{
zoom(KeyCode.v),
pick(KeyCode.i){
public void touched(MapEditor editor, int x, int y){
public void touched(int x, int y){
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
Tile tile = editor.tile(x, y);
@@ -23,7 +24,7 @@ public enum EditorTool{
line(KeyCode.l, "replace", "orthogonal"){
@Override
public void touchedLine(MapEditor editor, int x1, int y1, int x2, int y2){
public void touchedLine(int x1, int y1, int x2, int y2){
//straight
if(mode == 1){
if(Math.abs(x2 - x1) > Math.abs(y2 - y1)){
@@ -51,7 +52,7 @@ public enum EditorTool{
}
@Override
public void touched(MapEditor editor, int x, int y){
public void touched(int x, int y){
if(mode == -1){
//normal mode
editor.drawBlocks(x, y);
@@ -75,7 +76,7 @@ public enum EditorTool{
}
@Override
public void touched(MapEditor editor, int x, int y){
public void touched(int x, int y){
editor.drawCircle(x, y, tile -> {
if(mode == -1){
//erase block
@@ -95,13 +96,13 @@ public enum EditorTool{
IntSeq stack = new IntSeq();
@Override
public void touched(MapEditor editor, int x, int y){
public void touched(int x, int y){
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
Tile tile = editor.tile(x, y);
if(editor.drawBlock.isMultiblock()){
//don't fill multiblocks, thanks
pencil.touched(editor, x, y);
pencil.touched(x, y);
return;
}
@@ -133,19 +134,19 @@ public enum EditorTool{
}
//replace only when the mode is 0 using the specified functions
fill(editor, x, y, mode == 0, tester, setter);
fill(x, y, mode == 0, tester, setter);
}else if(mode == 1){ //mode 1 is team fill
//only fill synthetic blocks, it's meaningless otherwise
if(tile.synthetic()){
Team dest = tile.team();
if(dest == editor.drawTeam) return;
fill(editor, x, y, false, t -> t.getTeamID() == dest.id && t.synthetic(), t -> t.setTeam(editor.drawTeam));
fill(x, y, false, t -> t.getTeamID() == dest.id && t.synthetic(), t -> t.setTeam(editor.drawTeam));
}
}
}
void fill(MapEditor editor, int x, int y, boolean replace, Boolf<Tile> tester, Cons<Tile> filler){
void fill(int x, int y, boolean replace, Boolf<Tile> tester, Cons<Tile> filler){
int width = editor.width(), height = editor.height();
if(replace){
@@ -215,7 +216,7 @@ public enum EditorTool{
}
@Override
public void touched(MapEditor editor, int x, int y){
public void touched(int x, int y){
//floor spray
if(editor.drawBlock.isFloor()){
@@ -263,7 +264,7 @@ public enum EditorTool{
this.key = code;
}
public void touched(MapEditor editor, int x, int y){}
public void touched(int x, int y){}
public void touchedLine(MapEditor editor, int x1, int y1, int x2, int y2){}
public void touchedLine(int x1, int y1, int x2, int y2){}
}

View File

@@ -21,7 +21,7 @@ public class MapEditor{
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20};
public StringMap tags = new StringMap();
public MapRenderer renderer = new MapRenderer(this);
public MapRenderer renderer = new MapRenderer();
private final Context context = new Context();
private OperationStack stack = new OperationStack();
@@ -62,7 +62,7 @@ public class MapEditor{
public void beginEdit(Pixmap pixmap){
reset();
createTiles(pixmap.getWidth(), pixmap.getHeight());
createTiles(pixmap.width, pixmap.height);
load(() -> MapIO.readImage(pixmap, tiles()));
renderer.resize(width(), height());
}
@@ -330,7 +330,7 @@ public class MapEditor{
public void addTileOp(long data){
if(loading) return;
if(currentOp == null) currentOp = new DrawOperation(this);
if(currentOp == null) currentOp = new DrawOperation();
currentOp.addOperation(data);
renderer.updatePoint(TileOp.x(data), TileOp.y(data));

View File

@@ -33,8 +33,6 @@ import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class MapEditorDialog extends Dialog implements Disposable{
public final MapEditor editor;
private MapView view;
private MapInfoDialog infoDialog;
private MapLoadDialog loadDialog;
@@ -53,10 +51,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
background(Styles.black);
editor = new MapEditor();
view = new MapView(editor);
infoDialog = new MapInfoDialog(editor);
generateDialog = new MapGenerateDialog(editor, true);
view = new MapView();
infoDialog = new MapInfoDialog();
generateDialog = new MapGenerateDialog(true);
menu = new BaseDialog("@menu");
menu.addCloseButton();
@@ -120,7 +117,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
"@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);
file.writePng(out);
out.dispose();
})));
});
@@ -173,7 +170,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.hide();
}).size(swidth * 2f + 10, 60f);
resizeDialog = new MapResizeDialog(editor, (x, y) -> {
resizeDialog = new MapResizeDialog((x, y) -> {
if(!(editor.width() == x && editor.height() == y)){
ui.loadAnd(() -> {
editor.resize(x, y);
@@ -639,7 +636,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
for(int x = 0; x < editor.width(); x++){
for(int y = 0; y < editor.height(); y++){
Tile tile = editor.tile(x, y);
if(tile.block().breakable && tile.block() instanceof Boulder){
if(tile.block().breakable && tile.block() instanceof Prop){
tile.setBlock(Blocks.air);
editor.renderer.updatePoint(x, y);
}
@@ -714,7 +711,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
int i = 0;
for(Block block : blocksOut){
TextureRegion region = block.icon(Cicon.medium);
TextureRegion region = block.uiIcon;
if(!Core.atlas.isFound(region) || !block.inEditor
|| block.buildVisibility == BuildVisibility.debugOnly

View File

@@ -26,28 +26,27 @@ import static mindustry.Vars.*;
@SuppressWarnings("unchecked")
public class MapGenerateDialog extends BaseDialog{
private final Prov<GenerateFilter>[] filterTypes = new Prov[]{
final Prov<GenerateFilter>[] filterTypes = new Prov[]{
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new,
EnemySpawnFilter::new, SpawnPathFilter::new
};
private final MapEditor editor;
private final boolean applied;
final boolean applied;
private Pixmap pixmap;
private Texture texture;
private GenerateInput input = new GenerateInput();
Pixmap pixmap;
Texture texture;
GenerateInput input = new GenerateInput();
Seq<GenerateFilter> filters = new Seq<>();
private int scaling = mobile ? 3 : 1;
private Table filterTable;
int scaling = mobile ? 3 : 1;
Table filterTable;
private AsyncExecutor executor = new AsyncExecutor(1);
private AsyncResult<Void> result;
AsyncExecutor executor = new AsyncExecutor(1);
AsyncResult<Void> result;
boolean generating;
private long[] buffer1, buffer2;
private Cons<Seq<GenerateFilter>> applier;
long[] buffer1, buffer2;
Cons<Seq<GenerateFilter>> applier;
CachedTile ctile = new CachedTile(){
//nothing.
@Override
@@ -62,35 +61,79 @@ public class MapGenerateDialog extends BaseDialog{
};
/** @param applied whether or not to use the applied in-game mode. */
public MapGenerateDialog(MapEditor editor, boolean applied){
public MapGenerateDialog(boolean applied){
super("@editor.generate");
this.editor = editor;
this.applied = applied;
shown(this::setup);
addCloseButton();
addCloseListener();
var style = Styles.cleart;
buttons.defaults().size(180f, 64f).pad(2f);
buttons.button("@back", Icon.left, this::hide);
if(applied){
buttons.button("@editor.apply", Icon.ok, () -> {
ui.loadAnd(() -> {
apply();
hide();
});
}).size(160f, 64f);
}else{
buttons.button("@settings.reset", () -> {
filters.set(maps.readFilters(""));
rebuildFilters();
update();
}).size(160f, 64f);
});
}
buttons.button("@editor.randomize", Icon.refresh, () -> {
for(GenerateFilter filter : filters){
filter.randomize();
}
update();
}).size(160f, 64f);
});
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(150f);
buttons.button("@edit", Icon.edit, () -> {
BaseDialog dialog = new BaseDialog("@editor.export");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, in -> {
in.defaults().size(280f, 60f).left();
in.button("@waves.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(JsonIO.write(filters));
}).marginLeft(12f).row();
in.button("@waves.load", Icon.download, style, () -> {
dialog.hide();
try{
filters.set(JsonIO.read(Seq.class, Core.app.getClipboardText()));
rebuildFilters();
update();
}catch(Throwable e){
ui.showException(e);
}
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null).row();
in.button("@clear", Icon.none, style, () -> {
dialog.hide();
filters.clear();
rebuildFilters();
update();
}).marginLeft(12f).row();
if(!applied){
in.button("@settings.reset", Icon.refresh, style, () -> {
dialog.hide();
filters.set(maps.readFilters(""));
rebuildFilters();
update();
}).marginLeft(12f).row();
}
});
});
dialog.addCloseButton();
dialog.show();
});
buttons.button("@add", Icon.add, this::showAdd);
if(!applied){
hidden(this::apply);
@@ -173,7 +216,7 @@ public class MapGenerateDialog extends BaseDialog{
@Override
public void draw(){
super.draw();
for(GenerateFilter filter : filters){
for(var filter : filters){
filter.draw(this);
}
}
@@ -214,7 +257,7 @@ public class MapGenerateDialog extends BaseDialog{
filterTable.top().left();
int i = 0;
for(GenerateFilter filter : filters){
for(var filter : filters){
//main container
filterTable.table(Tex.pane, c -> {
@@ -284,31 +327,33 @@ public class MapGenerateDialog extends BaseDialog{
}
void showAdd(){
BaseDialog selection = new BaseDialog("@add");
var selection = new BaseDialog("@add");
selection.cont.pane(p -> {
p.background(Tex.button);
p.marginRight(14);
p.defaults().size(210f, 60f);
p.defaults().size(195f, 56f);
int i = 0;
for(Prov<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
for(var gen : filterTypes){
var filter = gen.get();
var icon = filter.icon();
if((filter.isPost() && applied)) continue;
if(filter.isPost() && applied) continue;
p.button(filter.name(), () -> {
p.button((icon == '\0' ? "" : icon + " ") + filter.name(), Styles.cleart, () -> {
filters.add(filter);
rebuildFilters();
update();
selection.hide();
});
if(++i % 2 == 0) p.row();
}).with(Table::left).get().getLabelCell().growX().left().padLeft(5).labelAlign(Align.left);
if(++i % 3 == 0) p.row();
}
p.button("@filter.defaultores", () -> {
p.button(Iconc.refresh + " " + Core.bundle.get("filter.defaultores"), Styles.cleart, () -> {
maps.addDefaultOres(filters);
rebuildFilters();
update();
selection.hide();
});
}).with(Table::left).get().getLabelCell().growX().left().padLeft(5).labelAlign(Align.left);
}).get().setScrollingDisabled(true, false);
selection.addCloseButton();
@@ -350,25 +395,25 @@ public class MapGenerateDialog extends BaseDialog{
return;
}
Seq<GenerateFilter> copy = new Seq<>(filters);
var copy = filters.copy();
result = executor.submit(() -> {
try{
int w = pixmap.getWidth();
int w = pixmap.width;
world.setGenerating(true);
generating = true;
if(!filters.isEmpty()){
//write to buffer1 for reading
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
for(int px = 0; px < pixmap.width; px++){
for(int py = 0; py < pixmap.height; py++){
buffer1[px + py*w] = pack(editor.tile(px * scaling, py * scaling));
}
}
}
for(GenerateFilter filter : copy){
input.begin(filter, editor.width(), editor.height(), (x, y) -> unpack(buffer1[Mathf.clamp(x / scaling, 0, pixmap.getWidth()-1) + w* Mathf.clamp(y / scaling, 0, pixmap.getHeight()-1)]));
for(var filter : copy){
input.begin(filter, editor.width(), editor.height(), (x, y) -> unpack(buffer1[Mathf.clamp(x / scaling, 0, pixmap.width -1) + w* Mathf.clamp(y / scaling, 0, pixmap.height -1)]));
//read from buffer1 and write to buffer2
pixmap.each((px, py) -> {
@@ -382,8 +427,8 @@ public class MapGenerateDialog extends BaseDialog{
pixmap.each((px, py) -> buffer1[px + py*w] = buffer2[px + py*w]);
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
for(int px = 0; px < pixmap.width; px++){
for(int py = 0; py < pixmap.height; py++){
int color;
//get result from buffer1 if there's filters left, otherwise get from editor directly
if(filters.isEmpty()){
@@ -393,7 +438,7 @@ public class MapGenerateDialog extends BaseDialog{
long tile = buffer1[px + py*w];
color = MapIO.colorFor(content.block(PackTile.block(tile)), content.block(PackTile.floor(tile)), content.block(PackTile.overlay(tile)), Team.derelict);
}
pixmap.draw(px, pixmap.getHeight() - 1 - py, color);
pixmap.set(px, pixmap.height - 1 - py, color);
}
}

View File

@@ -9,17 +9,17 @@ import mindustry.io.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class MapInfoDialog extends BaseDialog{
private final MapEditor editor;
private final WaveInfoDialog waveInfo;
private final MapGenerateDialog generate;
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
public MapInfoDialog(MapEditor editor){
public MapInfoDialog(){
super("@editor.mapinfo");
this.editor = editor;
this.waveInfo = new WaveInfoDialog(editor);
this.generate = new MapGenerateDialog(editor, false);
this.waveInfo = new WaveInfoDialog();
this.generate = new MapGenerateDialog(false);
addCloseButton();

View File

@@ -18,14 +18,7 @@ public class MapRenderer implements Disposable{
private IndexedRenderer[][] chunks;
private IntSet updates = new IntSet();
private IntSet delayedUpdates = new IntSet();
private MapEditor editor;
private int width, height;
private Texture texture;
public MapRenderer(MapEditor editor){
this.editor = editor;
this.texture = Core.atlas.find("clear-editor").texture;
}
public void resize(int width, int height){
updates.clear();
@@ -64,6 +57,8 @@ public class MapRenderer implements Disposable{
return;
}
var texture = Core.atlas.find("clear-editor").texture;
for(int x = 0; x < chunks.length; x++){
for(int y = 0; y < chunks[0].length; y++){
IndexedRenderer mesh = chunks[x][y];

View File

@@ -6,13 +6,14 @@ import arc.scene.ui.TextField.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class MapResizeDialog extends BaseDialog{
public static int minSize = 50, maxSize = 500, increment = 50;
int width, height;
public MapResizeDialog(MapEditor editor, Intc2 cons){
public MapResizeDialog(Intc2 cons){
super("@editor.resizemap");
shown(() -> {
cont.clear();

View File

@@ -19,7 +19,6 @@ import mindustry.ui.*;
import static mindustry.Vars.*;
public class MapView extends Element implements GestureListener{
private MapEditor editor;
EditorTool tool = EditorTool.pencil;
private float offsetx, offsety;
private float zoom = 1f;
@@ -35,8 +34,7 @@ public class MapView extends Element implements GestureListener{
float mousex, mousey;
EditorTool lastTool;
public MapView(MapEditor editor){
this.editor = editor;
public MapView(){
for(int i = 0; i < MapEditor.brushSizes.length; i++){
float size = MapEditor.brushSizes[i];
@@ -92,7 +90,7 @@ public class MapView extends Element implements GestureListener{
lasty = p.y;
startx = p.x;
starty = p.y;
tool.touched(editor, p.x, p.y);
tool.touched(p.x, p.y);
firstTouch.set(p);
if(tool.edit){
@@ -115,7 +113,7 @@ public class MapView extends Element implements GestureListener{
if(tool == EditorTool.line){
ui.editor.resetSaved();
tool.touchedLine(editor, startx, starty, p.x, p.y);
tool.touchedLine(startx, starty, p.x, p.y);
}
editor.flushOp();
@@ -136,7 +134,7 @@ public class MapView extends Element implements GestureListener{
if(drawing && tool.draggable && !(p.x == lastx && p.y == lasty)){
ui.editor.resetSaved();
Bresenham2.line(lastx, lasty, p.x, p.y, (cx, cy) -> tool.touched(editor, cx, cy));
Bresenham2.line(lastx, lasty, p.x, p.y, (cx, cy) -> tool.touched(cx, cy));
}
if(tool == EditorTool.line && tool.mode == 1){

View File

@@ -177,7 +177,7 @@ public class WaveGraph extends Table{
t.button(b -> {
Color tcolor = color(type).cpy();
b.image().size(32f).update(i -> i.setColor(b.isChecked() ? Tmp.c1.set(tcolor).mul(0.5f) : tcolor)).get().act(1);
b.image(type.icon(Cicon.medium)).size(32f).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
b.image(type.uiIcon).size(32f).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
b.margin(0f);
}, Styles.fullTogglet, () -> {
if(!hidden.add(type)){

View File

@@ -30,7 +30,7 @@ public class WaveInfoDialog extends BaseDialog{
private float updateTimer, updatePeriod = 1f;
private WaveGraph graph = new WaveGraph();
public WaveInfoDialog(MapEditor editor){
public WaveInfoDialog(){
super("@waves.title");
shown(this::setup);
@@ -160,7 +160,7 @@ public class WaveInfoDialog extends BaseDialog{
t.margin(0).defaults().pad(3).padLeft(5f).growX().left();
t.button(b -> {
b.left();
b.image(group.type.icon(Cicon.medium)).size(32f).padRight(3).scaling(Scaling.fit);
b.image(group.type.uiIcon).size(32f).padRight(3).scaling(Scaling.fit);
b.add(group.type.localizedName).color(Pal.accent);
b.add().growX();
@@ -263,7 +263,7 @@ public class WaveInfoDialog extends BaseDialog{
if(type.isHidden()) continue;
p.button(t -> {
t.left();
t.image(type.icon(Cicon.medium)).size(8 * 4).scaling(Scaling.fit).padRight(2f);
t.image(type.uiIcon).size(8 * 4).scaling(Scaling.fit).padRight(2f);
t.add(type.localizedName);
}, () -> {
lastType = type;

View File

@@ -83,8 +83,17 @@ public class Damage{
}
}
public static @Nullable Building findAbsorber(Team team, float x1, float y1, float x2, float y2){
tmpBuilding = null;
boolean found = world.raycast(World.toTile(x1), World.toTile(y1), World.toTile(x2), World.toTile(y2),
(x, y) -> (tmpBuilding = world.build(x, y)) != null && tmpBuilding.team != team && tmpBuilding.block.absorbLasers);
return found ? tmpBuilding : null;
}
public static float findLaserLength(Bullet b, float length){
Tmp.v1.trns(b.rotation(), length);
Tmp.v1.trnsExact(b.rotation(), length);
furthest = null;
@@ -125,7 +134,7 @@ public class Damage{
if(laser) length = findLaserLength(hitter, length);
collidedBlocks.clear();
tr.trns(angle, length);
tr.trnsExact(angle, length);
Intc2 collider = (cx, cy) -> {
Building tile = world.build(cx, cy);
@@ -378,7 +387,7 @@ public class Damage{
//this needs to be compensated
if(in != null && in.team != team && in.block.size > 1 && in.health > damage){
//deal the damage of an entire side, to be equivalent with maximum 'standard' damage
in.damage(damage * Math.min((in.block.size), baseRadius * 0.45f));
in.damage(team, damage * Math.min((in.block.size), baseRadius * 0.4f));
//no need to continue with the explosion
return;
}
@@ -435,7 +444,7 @@ public class Damage{
int cx = Point2.x(e.key), cy = Point2.y(e.key);
var build = world.build(cx, cy);
if(build != null){
build.damage(e.value);
build.damage(team, e.value);
}
}
});

View File

@@ -29,6 +29,8 @@ public class Effect{
public float lifetime = 50f;
/** Clip size. */
public float clip;
/** If true, parent unit is data are followed. */
public boolean followParent;
public float layer = Layer.effect;
public float layerDuration;
@@ -53,6 +55,11 @@ public class Effect{
public void init(){}
public Effect followParent(boolean follow){
followParent = follow;
return this;
}
public Effect layer(float l){
layer = l;
return this;
@@ -148,11 +155,11 @@ public class Effect{
EffectState entity = EffectState.create();
entity.effect = effect;
entity.rotation = rotation;
entity.data = (data);
entity.lifetime = (effect.lifetime);
entity.data = data;
entity.lifetime = effect.lifetime;
entity.set(x, y);
entity.color.set(color);
if(data instanceof Posc) entity.parent = ((Posc)data);
if(effect.followParent && data instanceof Posc) entity.parent = ((Posc)data);
entity.add();
}
}

View File

@@ -1,5 +1,7 @@
package mindustry.entities;
public interface Sized{
import arc.math.geom.*;
public interface Sized extends Position{
float hitSize();
}

View File

@@ -4,6 +4,7 @@ import arc.*;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.*;
@@ -20,6 +21,7 @@ public class Units{
private static Unit result;
private static float cdist;
private static boolean boolResult;
private static int intResult;
@Remote(called = Loc.server)
public static void unitCapDeath(Unit unit){
@@ -75,7 +77,7 @@ public class Units{
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
return Integer.MAX_VALUE;
}
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + indexer.getExtraUnits(team) : state.rules.unitCap);
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + team.data().unitCap : state.rules.unitCap);
}
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
@@ -138,23 +140,28 @@ public class Units{
return boolResult;
}
/** Returns the neareset damaged tile. */
/** Returns the nearest damaged tile. */
public static Building findDamagedTile(Team team, float x, float y){
return Geometry.findClosest(x, y, indexer.getDamaged(team));
}
/** Returns the neareset ally tile in a range. */
/** Returns the nearest ally tile in a range. */
public static Building findAllyTile(Team team, float x, float y, float range, Boolf<Building> pred){
return indexer.findTile(team, x, y, range, pred);
}
/** Returns the neareset enemy tile in a range. */
/** Returns the nearest enemy tile in a range. */
public static Building findEnemyTile(Team team, float x, float y, float range, Boolf<Building> pred){
if(team == Team.derelict) return null;
return indexer.findEnemyTile(team, x, y, range, pred);
}
/** Iterates through all buildings in a range. */
public static void nearbyBuildings(float x, float y, float range, Cons<Building> cons){
indexer.allBuildings(x, y, range, cons);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static Teamc closestTarget(Team team, float x, float y, float range){
return closestTarget(team, x, y, range, Unit::isValid);
@@ -199,7 +206,7 @@ public class Units{
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.dead() || !predicate.get(e) || e.team == Team.derelict) return;
float dst2 = e.dst2(x, y);
float dst2 = e.dst2(x, y) - (e.hitSize * e.hitSize);
if(dst2 < range*range && (result == null || dst2 < cdist)){
result = e;
cdist = dst2;
@@ -302,13 +309,40 @@ public class Units{
return result;
}
/** @return whether any units exist in this square (centered) */
public static int count(float x, float y, float size, Boolf<Unit> filter){
return count(x - size/2f, y - size/2f, size, size, filter);
}
/** @return whether any units exist in this rectangle */
public static int count(float x, float y, float width, float height, Boolf<Unit> filter){
intResult = 0;
Groups.unit.intersect(x, y, width, height, v -> {
if(filter.get(v)){
intResult ++;
}
});
return intResult;
}
/** @return whether any units exist in this rectangle */
public static boolean any(float x, float y, float width, float height, Boolf<Unit> filter){
return count(x, y, width, height, filter) > 0;
}
/** Iterates over all units in a rectangle. */
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
team.data().tree().intersect(x, y, width, height, cons);
public static void nearby(@Nullable Team team, float x, float y, float width, float height, Cons<Unit> cons){
if(team != null){
team.data().tree().intersect(x, y, width, height, cons);
}else{
for(var other : state.teams.getActive()){
other.tree().intersect(x, y, width, height, cons);
}
}
}
/** Iterates over all units in a circle around this position. */
public static void nearby(Team team, float x, float y, float radius, Cons<Unit> cons){
public static void nearby(@Nullable Team team, float x, float y, float radius, Cons<Unit> cons){
nearby(team, x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.within(x, y, radius + unit.hitSize/2f)){
cons.get(unit);
@@ -336,6 +370,15 @@ public class Units{
}
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float radius, Cons<Unit> cons){
nearbyEnemies(team, x - radius, y - radius, radius * 2f, radius * 2f, u -> {
if(u.within(x, y, radius + u.hitSize/2f)){
cons.get(u);
}
});
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, Rect rect, Cons<Unit> cons){
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);

View File

@@ -0,0 +1,145 @@
package mindustry.entities.abilities;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
public class EnergyFieldAbility extends Ability{
private static final Seq<Healthc> all = new Seq<>();
public float damage = 1, repair = 20f, reload = 100, range = 60;
public Effect healEffect = Fx.heal, hitEffect = Fx.hitLaserBlast, damageEffect = Fx.chainLightning;
public StatusEffect status = StatusEffects.electrified;
public float statusDuration = 60f * 6f;
public float x, y;
public boolean hitBuildings = true;
public int maxTargets = 25;
public float healPercent = 3f;
public float layer = Layer.bullet - 0.001f, blinkScl = 20f;
public float effectRadius = 5f, sectorRad = 0.14f, rotateSpeed = 0.5f;
public int sectors = 5;
public Color color = Pal.heal;
protected float timer, curStroke;
protected boolean anyNearby = false;
EnergyFieldAbility(){}
public EnergyFieldAbility(float damage, float reload, float range){
this.damage = damage;
this.reload = reload;
this.range = range;
}
@Override
public String localized(){
return Core.bundle.format("ability.energyfield", damage, range / Vars.tilesize, maxTargets);
}
@Override
public void draw(Unit unit){
super.draw(unit);
Draw.z(layer);
Draw.color(color);
Tmp.v1.trns(unit.rotation - 90, x, y).add(unit.x, unit.y);
float rx = Tmp.v1.x, ry = Tmp.v1.y;
float orbRadius = effectRadius * (1f + Mathf.absin(blinkScl, 0.1f));
Fill.circle(rx, ry, orbRadius);
Draw.color();
Fill.circle(rx, ry, orbRadius / 2f);
Lines.stroke((0.7f + Mathf.absin(blinkScl, 0.7f)), color);
for(int i = 0; i < sectors; i++){
float rot = unit.rotation + i * 360f/sectors - Time.time * rotateSpeed;
Lines.swirl(rx, ry, orbRadius + 3f, sectorRad, rot);
}
Lines.stroke(Lines.getStroke() * curStroke);
if(curStroke > 0){
for(int i = 0; i < sectors; i++){
float rot = unit.rotation + i * 360f/sectors + Time.time * rotateSpeed;
Lines.swirl(rx, ry, range, sectorRad, rot);
}
}
Drawf.light(rx, ry, range * 1.5f, color, curStroke * 0.8f);
Draw.reset();
}
@Override
public void update(Unit unit){
curStroke = Mathf.lerpDelta(curStroke, anyNearby ? 1 : 0, 0.09f);
if((timer += Time.delta) >= reload){
Tmp.v1.trns(unit.rotation - 90, x, y).add(unit.x, unit.y);
float rx = Tmp.v1.x, ry = Tmp.v1.y;
anyNearby = false;
all.clear();
Units.nearby(null, rx, ry, range, other -> {
if(other != unit){
all.add(other);
}
});
if(hitBuildings){
Units.nearbyBuildings(rx, ry, range, all::add);
}
all.sort(h -> h.dst2(rx, ry));
int len = Math.min(all.size, maxTargets);
for(int i = 0; i < len; i++){
Healthc other = all.get(i);
//lightning gets absorbed by plastanium
var absorber = Damage.findAbsorber(unit.team, rx, ry, other.getX(), other.getY());
if(absorber != null){
other = absorber;
}
if(((Teamc)other).team() == unit.team){
if(other.damaged()){
anyNearby = true;
other.heal(healPercent / 100f * other.maxHealth());
healEffect.at(other);
damageEffect.at(rx, ry, 0f, color, other);
hitEffect.at(rx, ry, unit.angleTo(other), color);
if(other instanceof Building b){
Fx.healBlockFull.at(b.x, b.y, b.block.size, color);
}
}
}else{
anyNearby = true;
other.damage(damage);
if(other instanceof Statusc s){
s.apply(status, statusDuration);
}
hitEffect.at(other.x(), other.y(), unit.angleTo(other), color);
damageEffect.at(rx, ry, 0f, color, other);
hitEffect.at(rx, ry, unit.angleTo(other), color);
}
}
timer = 0f;
}
}
}

View File

@@ -33,13 +33,13 @@ public class ShieldRegenFieldAbility extends Ability{
if(other.shield < max){
other.shield = Math.max(other.shield + amount, max);
other.shieldAlpha = 1f; //TODO may not be necessary
applyEffect.at(unit);
applyEffect.at(unit.x, unit.y, unit.team.color);
applied = true;
}
});
if(applied){
activeEffect.at(unit);
activeEffect.at(unit.x, unit.y, unit.team.color);
}
timer = 0f;

View File

@@ -1,5 +1,6 @@
package mindustry.entities.abilities;
import arc.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -23,6 +24,11 @@ public class StatusFieldAbility extends Ability{
this.effect = effect;
}
@Override
public String localized(){
return Core.bundle.format("ability.statusfield", effect.emoji());
}
@Override
public void update(Unit unit){
timer += Time.delta;

View File

@@ -54,7 +54,7 @@ public class UnitSpawnAbility extends Ability{
if(Units.canCreate(unit.team, this.unit)){
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, this.unit.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
Drawf.construct(x, y, this.unit.fullIcon, unit.rotation - 90, timer / spawnTime, 1f, timer);
});
}
}

View File

@@ -18,6 +18,24 @@ public class ArtilleryBulletType extends BasicBulletType{
hitSound = Sounds.explosion;
shootEffect = Fx.shootBig;
trailEffect = Fx.artilleryTrail;
//default settings:
shrinkX = 0.15f;
shrinkY = 0.63f;
//for trail:
/*
trailLength = 27;
trailWidth = 3.5f;
trailEffect = Fx.none;
trailColor = Pal.bulletYellowBack;
trailInterp = Interp.slope;
shrinkX = 0.8f;
shrinkY = 0.3f;
*/
}
public ArtilleryBulletType(float speed, float damage){
@@ -39,15 +57,13 @@ public class ArtilleryBulletType extends BasicBulletType{
@Override
public void draw(Bullet b){
float baseScale = 0.7f;
float scale = (baseScale + b.fslope() * (1f - baseScale));
float height = this.height * ((1f - shrinkY) + shrinkY * b.fout());
drawTrail(b);
float xscale = (1f - shrinkX + b.fslope() * (shrinkX)), yscale = (1f - shrinkY + b.fslope() * (shrinkY)), rot = b.rotation();
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, width * scale, height * scale, b.rotation() - 90);
Draw.rect(backRegion, b.x, b.y, width * xscale, height * yscale, rot - 90);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, width * scale, height * scale, b.rotation() - 90);
Draw.rect(frontRegion, b.x, b.y, width * xscale, height * yscale, rot - 90);
Draw.color();
}
}

View File

@@ -42,6 +42,7 @@ public class BasicBulletType extends BulletType{
@Override
public void draw(Bullet b){
super.draw(b);
float height = this.height * ((1f - shrinkY) + shrinkY * b.fout());
float width = this.width * ((1f - shrinkX) + shrinkX * b.fout());
float offset = -90 + (spin != 0 ? Mathf.randomSeed(b.id, 360f) + b.time * spin : 0f);

View File

@@ -1,39 +1,60 @@
package mindustry.entities.bullet;
import arc.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.defense.Wall.*;
import static mindustry.Vars.*;
public abstract class BulletType extends Content{
public class BulletType extends Content implements Cloneable{
/** Lifetime in ticks. */
public float lifetime = 40f;
public float speed;
public float damage;
/** Speed in units/tick. */
public float speed = 1f;
/** Direct damage dealt on hit. */
public float damage = 1f;
/** Hitbox size. */
public float hitSize = 4;
/** Clipping hitbox. */
public float drawSize = 40f;
/** Drag as fraction of velocity. */
public float drag = 0f;
public boolean pierce, pierceBuilding;
/** Whether to pierce units. */
public boolean pierce;
/** Whether to pierce buildings. */
public boolean pierceBuilding;
/** Maximum # of pierced objects. */
public int pierceCap = -1;
public Effect hitEffect, despawnEffect;
/** Z layer to drawn on. */
public float layer = Layer.bullet;
/** Effect shown on direct hit. */
public Effect hitEffect = Fx.hitBulletSmall;
/** Effect shown when bullet despawns. */
public Effect despawnEffect = Fx.hitBulletSmall;
/** Effect created when shooting. */
public Effect shootEffect = Fx.shootSmall;
/** Extra smoke effect created when shooting. */
public Effect smokeEffect = Fx.shootSmallSmoke;
/** Sound made when hitting something or getting removed.*/
public Sound hitSound = Sounds.none;
/** Sound made when hitting something or getting removed.*/
public Sound despawnSound = Sounds.none;
/** Pitch of the sound made when hitting something*/
public float hitSoundPitch = 1;
/** Volume of the sound made when hitting something*/
@@ -80,15 +101,17 @@ public abstract class BulletType extends Content{
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.
/** Whether to move the bullet back depending on delta to fix some delta-time related issues.
* Do not change unless you know what you're doing. */
public boolean backMove = true;
/** Bullet range override. */
public float maxRange = -1f;
/** % of block health healed **/
public float healPercent = 0f;
/** whether to make fire on impact */
/** Whether to make fire on impact */
public boolean makeFire = false;
/** Whether to create hit effects on despawn. Forced to true if this bullet has any special effects like splash damage. */
public boolean despawnHit = false;
//additional effects
@@ -101,8 +124,14 @@ public abstract class BulletType extends Content{
public Color trailColor = Pal.missileYellowBack;
public float trailChance = -0.0001f;
public float trailInterval = 0f;
public Effect trailEffect = Fx.missileTrail;
public float trailParam = 2f;
public boolean trailRotation = false;
public Interp trailInterp = Interp.one;
/** Any value <= 0 disables the trail. */
public int trailLength = -1;
public float trailWidth = 2f;
/** Use a negative value to disable splash damage. */
public float splashDamageRadius = -1f;
@@ -134,19 +163,27 @@ public abstract class BulletType extends Content{
public float puddleAmount = 5f;
public Liquid puddleLiquid = Liquids.water;
public float lightRadius = 16f;
public float lightRadius = -1f;
public float lightOpacity = 0.3f;
public Color lightColor = Pal.powerLight;
public BulletType(float speed, float damage){
this.speed = speed;
this.damage = damage;
hitEffect = Fx.hitBulletSmall;
despawnEffect = Fx.hitBulletSmall;
}
public BulletType(){
this(1f, 1f);
}
public BulletType copy(){
try{
BulletType copy = (BulletType)clone();
copy.id = (short)Vars.content.getBy(getContentType()).size;
Vars.content.handleContent(copy);
return copy;
}catch(Exception e){
throw new RuntimeException("death to checked exceptions", e);
}
}
/** @return estimated damage per shot. this can be very inaccurate. */
@@ -181,13 +218,13 @@ public abstract class BulletType extends Content{
if(healPercent > 0f && build.team == b.team && !(build.block instanceof ConstructBlock)){
Fx.healBlockFull.at(build.x, build.y, build.block.size, Pal.heal);
build.heal(healPercent / 100f * build.maxHealth());
build.heal(healPercent / 100f * build.maxHealth);
}else if(build.team != b.team && direct){
hit(b);
}
}
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
public void hitEntity(Bullet b, Hitboxc entity, float health){
if(entity instanceof Healthc h){
h.damage(b.damage);
}
@@ -198,6 +235,11 @@ public abstract class BulletType extends Content{
unit.impulse(Tmp.v3);
unit.apply(status, statusDuration);
}
//for achievements
if(b.owner instanceof WallBuild && player != null && b.team == player.team() && entity instanceof Unit unit && unit.dead){
Events.fire(Trigger.phaseDeflectHit);
}
}
public void hit(Bullet b){
@@ -205,7 +247,6 @@ public abstract class BulletType extends Content{
}
public void hit(Bullet b, float x, float y){
b.hit = true;
hitEffect.at(x, y, b.rotation(), hitColor);
hitSound.at(x, y, hitSoundPitch, hitSoundVolume);
@@ -226,7 +267,7 @@ public abstract class BulletType extends Content{
}
}
if(Mathf.chance(incendChance)){
if(incendChance > 0 && Mathf.chance(incendChance)){
Damage.createIncend(x, y, incendSpread, incendAmount);
}
@@ -245,9 +286,7 @@ public abstract class BulletType extends Content{
}
if(makeFire){
indexer.eachBlock(null, x, y, splashDamageRadius, other -> other.team != b.team, other -> {
Fires.create(other.tile);
});
indexer.eachBlock(null, x, y, splashDamageRadius, other -> other.team != b.team, other -> Fires.create(other.tile));
}
}
@@ -256,21 +295,40 @@ public abstract class BulletType extends Content{
}
}
/** Called when the bullet reaches the end of its lifetime of is destroyed by something external. */
public void despawned(Bullet b){
if(despawnHit){
hit(b);
}
despawnEffect.at(b.x, b.y, b.rotation(), hitColor);
hitSound.at(b);
despawnSound.at(b);
Effect.shake(despawnShake, despawnShake, b);
}
if(!b.hit && (fragBullet != null || splashDamageRadius > 0 || lightning > 0)){
hit(b);
/** Called when the bullet is removed for any reason. */
public void removed(Bullet b){
if(trailLength > 0 && b.trail != null && b.trail.size() > 0){
Fx.trailFade.at(b.x, b.y, trailWidth, trailColor, b.trail.copy());
}
}
public void draw(Bullet b){
drawTrail(b);
}
public void drawTrail(Bullet b){
if(trailLength > 0 && b.trail != null){
//draw below bullets? TODO
float z = Draw.z();
Draw.z(z - 0.0001f);
b.trail.draw(trailColor, trailWidth);
Draw.z(z);
}
}
public void drawLight(Bullet b){
if(lightOpacity <= 0f || lightRadius <= 0f) return;
Drawf.light(b.team, b, lightRadius, lightColor, lightOpacity);
}
@@ -283,11 +341,37 @@ public abstract class BulletType extends Content{
if(instantDisappear){
b.time = lifetime;
}
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
despawnHit = true;
}
if(lightRadius == -1){
lightRadius = Math.max(18, hitSize * 5f);
}
drawSize = Math.max(drawSize, trailLength * speed * 2f);
}
public void update(Bullet b){
if(!headless && trailLength > 0){
if(b.trail == null){
b.trail = new Trail(trailLength);
}
b.trail.length = trailLength;
b.trail.update(b.x, b.y, trailInterp.apply(b.fin()));
}
if(homingPower > 0.0001f && b.time >= homingDelay){
Teamc target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> e.checkTarget(collidesAir, collidesGround), t -> collidesGround);
Teamc target;
//home in on allies if possible
if(healPercent > 0){
target = Units.closestTarget(null, b.x, b.y, homingRange,
e -> e.checkTarget(collidesAir, collidesGround) && e.team != b.team,
t -> collidesGround && (t.team != b.team || t.damaged()));
}else{
target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> e.checkTarget(collidesAir, collidesGround), t -> collidesGround);
}
if(target != null){
b.vel.setAngle(Angles.moveToward(b.rotation(), b.angleTo(target), homingPower * Time.delta * 50f));
}
@@ -299,7 +383,13 @@ public abstract class BulletType extends Content{
if(trailChance > 0){
if(Mathf.chanceDelta(trailChance)){
trailEffect.at(b.x, b.y, trailParam, trailColor);
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
}
}
if(trailInterval > 0f){
if(b.timer(0, trailInterval)){
trailEffect.at(b.x, b.y, trailRotation ? b.rotation() : trailParam, trailColor);
}
}
}
@@ -366,6 +456,10 @@ public abstract class BulletType extends Content{
bullet.drag = drag;
bullet.hitSize = hitSize;
bullet.damage = (damage < 0 ? this.damage : damage) * bullet.damageMultiplier();
//reset trail
if(bullet.trail != null){
bullet.trail.clear();
}
bullet.add();
if(keepVelocity && owner instanceof Velc v) bullet.vel.add(v.vel().x, v.vel().y);

View File

@@ -0,0 +1,70 @@
package mindustry.entities.bullet;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class EmpBulletType extends BasicBulletType{
public float radius = 100f;
public float timeIncrease = 2.5f, timeDuration = 60f * 10f;
public float powerDamageScl = 2f, powerSclDecrease = 0.2f;
public Effect hitPowerEffect = Fx.hitEmpSpark, chainEffect = Fx.chainEmp, applyEffect = Fx.heal;
public boolean hitUnits = true;
public float unitDamageScl = 0.7f;
@Override
public void hit(Bullet b, float x, float y){
super.hit(b, x, y);
if(!b.absorbed){
Vars.indexer.allBuildings(x, y, radius, other -> {
if(other.team == b.team){
if(other.block.hasPower && other.block.canOverdrive && other.timeScale < timeIncrease){
if(timeIncrease >= other.timeScale){
other.timeScale = Math.max(other.timeScale, timeIncrease);
}
other.timeScaleDuration = Math.max(other.timeScaleDuration, timeDuration);
chainEffect.at(x, y, 0, hitColor, other);
applyEffect.at(other, other.block.size * 7f);
}
if(other.block.hasPower && other.damaged()){
other.heal(healPercent / 100f * other.maxHealth());
Fx.healBlockFull.at(other.x, other.y, other.block.size, hitColor);
applyEffect.at(other, other.block.size * 7f);
}
}else if(other.power != null){
var absorber = Damage.findAbsorber(b.team, x, y, other.x, other.y);
if(absorber != null){
other = absorber;
}
if(other.power != null && other.power.graph.getLastPowerProduced() > 0f){
other.timeScale = Math.min(other.timeScale, powerSclDecrease);
other.timeScaleDuration = timeDuration;
other.damage(damage * powerDamageScl);
hitPowerEffect.at(other.x, other.y, b.angleTo(other), hitColor);
chainEffect.at(x, y, 0, hitColor, other);
}
}
});
if(hitUnits){
Units.nearbyEnemies(b.team, x, y, radius, other -> {
if(other.team != b.team){
var absorber = Damage.findAbsorber(b.team, x, y, other.x, other.y);
if(absorber != null){
return;
}
hitPowerEffect.at(other.x, other.y, b.angleTo(other), hitColor);
chainEffect.at(x, y, 0, hitColor, other);
other.damage(damage * unitDamageScl);
other.apply(status, statusDuration);
}
});
}
}
}
}

View File

@@ -6,7 +6,7 @@ import mindustry.entities.*;
import mindustry.gen.*;
public class FlakBulletType extends BasicBulletType{
public float explodeRange = 30f;
public float explodeRange = 30f, explodeDelay = 5f;
public FlakBulletType(float speed, float damage){
super(speed, damage, "shell");
@@ -25,17 +25,21 @@ public class FlakBulletType extends BasicBulletType{
@Override
public void update(Bullet b){
super.update(b);
if(b.data() instanceof Integer) return;
//don't check for targets if primed to explode
if(b.fdata < 0f) return;
if(b.timer(2, 6)){
Units.nearbyEnemies(b.team, Tmp.r1.setSize(explodeRange * 2f).setCenter(b.x, b.y), unit -> {
if(b.data() instanceof Float || !unit.checkTarget(collidesAir, collidesGround)) return;
//fadata < 0 means it's primed to explode
if(b.fdata < 0f || !unit.checkTarget(collidesAir, collidesGround)) return;
if(unit.dst(b) < explodeRange){
b.data(0);
Time.run(5f, () -> {
if(b.data() instanceof Integer){
b.time(b.lifetime());
if(unit.within(b, explodeRange)){
//mark as primed
b.fdata = -1f;
Time.run(explodeDelay, () -> {
//explode
if(b.fdata < 0){
b.time = b.lifetime;
}
});
}

View File

@@ -3,6 +3,7 @@ package mindustry.entities.bullet;
import arc.graphics.g2d.*;
import mindustry.gen.*;
import mindustry.content.*;
import mindustry.graphics.*;
public class LaserBoltBulletType extends BasicBulletType{
public float width = 2f, height = 7f;
@@ -15,6 +16,8 @@ public class LaserBoltBulletType extends BasicBulletType{
despawnEffect = Fx.hitLaser;
hittable = false;
reflectable = false;
lightColor = Pal.heal;
lightOpacity = 0.6f;
}
public LaserBoltBulletType(){
@@ -23,6 +26,7 @@ public class LaserBoltBulletType extends BasicBulletType{
@Override
public void draw(Bullet b){
super.draw(b);
Draw.color(backColor);
Lines.stroke(width);
Lines.lineAngleCenter(b.x, b.y, b.rotation(), height);

View File

@@ -63,9 +63,10 @@ public class LiquidBulletType extends BulletType{
@Override
public void draw(Bullet b){
super.draw(b);
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, orbSize);
Draw.reset();
}
@Override

View File

@@ -69,9 +69,9 @@ public class RailBulletType extends BulletType{
}
@Override
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
handle(b, entity, initialHealth);
super.hitEntity(b, entity, initialHealth);
public void hitEntity(Bullet b, Hitboxc entity, float health){
handle(b, entity, health);
super.hitEntity(b, entity, health);
}
@Override

View File

@@ -25,6 +25,8 @@ public class SapBulletType extends BulletType{
hittable = false;
hitEffect = Fx.hitLiquid;
status = StatusEffects.sapped;
lightColor = Pal.sap;
lightOpacity = 0.6f;
statusDuration = 60f * 3f;
impact = true;
}
@@ -40,7 +42,7 @@ public class SapBulletType extends BulletType{
Draw.reset();
Drawf.light(b.team, b.x, b.y, Tmp.v1.x, 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, lightOpacity);
}
}

View File

@@ -29,6 +29,7 @@ public class ShrapnelBulletType extends BulletType{
pierce = true;
hittable = false;
absorbable = false;
lightOpacity = 0.6f;
}
@Override
@@ -52,11 +53,11 @@ public class ShrapnelBulletType extends BulletType{
@Override
public void draw(Bullet b){
float realLength = b.fdata;
float realLength = b.fdata, rot = b.rotation();
Draw.color(fromColor, toColor, b.fin());
for(int i = 0; i < (int)(serrations * realLength / length); i++){
Tmp.v1.trns(b.rotation(), i * serrationSpacing);
Tmp.v1.trns(rot, i * serrationSpacing);
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);
@@ -64,5 +65,7 @@ public class ShrapnelBulletType extends BulletType{
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();
Drawf.light(b.team, b.x, b.y, b.x + Angles.trnsx(rot, realLength), b.y + Angles.trnsy(rot, realLength), width * 2.5f * b.fout(), toColor, lightOpacity);
}
}

View File

@@ -4,7 +4,6 @@ import arc.graphics.g2d.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
@@ -34,7 +33,7 @@ abstract class BlockUnitComp implements Unitc{
@Replace
@Override
public TextureRegion icon(){
return tile.block.icon(Cicon.full);
return tile.block.fullIcon;
}
@Override

View File

@@ -9,7 +9,7 @@ import static mindustry.Vars.*;
@Component
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
static final float warpDst = 180f;
static final float warpDst = 40f;
@Import float x, y;
@Import Vec2 vel;
@@ -17,11 +17,16 @@ abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
@Override
public void update(){
if(!net.client() || isLocal()){
float dx = 0f, dy = 0f;
//repel unit out of bounds
if(x < 0) vel.x += (-x/warpDst);
if(y < 0) vel.y += (-y/warpDst);
if(x > world.unitWidth()) vel.x -= (x - world.unitWidth())/warpDst;
if(y > world.unitHeight()) vel.y -= (y - world.unitHeight())/warpDst;
if(x < 0) dx += (-x/warpDst);
if(y < 0) dy += (-y/warpDst);
if(x > world.unitWidth()) dx -= (x - world.unitWidth())/warpDst;
if(y > world.unitHeight()) dy -= (y - world.unitHeight())/warpDst;
velAddNet(dx, dy);
}
//clamp position if not flying

View File

@@ -20,6 +20,7 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.util.*;
@@ -69,7 +70,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
}
}
Building core = core();
var core = core();
//nothing to build.
if(buildPlan() == null) return;
@@ -109,7 +110,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
plans.removeFirst();
return;
}
}else if((tile.team() != team && tile.team() != Team.derelict) || (!current.breaking && (cb.cblock != current.block || cb.tile != current.tile()))){
}else if((tile.team() != team && tile.team() != Team.derelict) || (!current.breaking && (cb.current != current.block || cb.tile != current.tile()))){
plans.removeFirst();
return;
}

View File

@@ -48,8 +48,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//region vars and initialization
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<>();
static final Seq<Building> tempBuilds = new Seq<>();
static int sleepingEntities = 0;
@Import float x, y, health, maxHealth;
@@ -57,7 +56,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient Tile tile;
transient Block block;
transient Seq<Building> proximity = new Seq<>(8);
transient Seq<Building> proximity = new Seq<>(6);
transient boolean updateFlow;
transient byte cdump;
transient int rotation;
@@ -71,6 +70,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
ConsumeModule cons;
private transient float timeScale = 1f, timeScaleDuration;
private transient float dumpAccum;
private transient @Nullable SoundLoop sound;
@@ -213,8 +213,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(self() instanceof ConstructBuild entity){
//update block to reflect the fact that something was being constructed
if(entity.cblock != null && entity.cblock.synthetic() && entity.wasConstructing){
block = entity.cblock;
if(entity.current != null && entity.current.synthetic() && entity.wasConstructing){
block = entity.current;
overrideConfig = entity.lastConfig;
}else{
//otherwise this was a deconstruction that was interrupted, don't want to rebuild that
@@ -401,6 +401,29 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
//endregion
//region handler methods
/** @return whether the player can select (but not actually control) this building. */
public boolean canControlSelect(Player player){
return false;
}
/** Called when a player control-selects this building - not called for ControlBlock subclasses. */
public void onControlSelect(Player player){
}
public void acceptPlayerPayload(Player player, Cons<Payload> grabber){
Fx.spawn.at(player);
var unit = player.unit();
player.clearUnit();
//player.deathTimer = Player.deathDelay + 1f; //for instant respawn
unit.remove();
grabber.get(new UnitPayload(unit));
Fx.unitDrop.at(unit);
if(Vars.net.client()){
Vars.netClient.clearRemovedEntity(unit.id);
}
}
public boolean canUnload(){
return block.unloadable;
}
@@ -467,10 +490,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void onProximityUpdate(){
noSleep();
}
public boolean acceptPayload(Building source, Payload payload){
return false;
}
@@ -688,13 +707,27 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(Vars.state.rules.sector != null && team == state.rules.defaultTeam) Vars.state.rules.sector.info.handleProduction(item, amount);
}
/** Try dumping any item near the */
/** Dumps any item with an accumulator. May dump multiple times per frame. Use with care. */
public void dumpAccumulate(){
dumpAccumulate(null);
}
/** Dumps any item with an accumulator. May dump multiple times per frame. Use with care. */
public void dumpAccumulate(Item item){
dumpAccum += delta();
while(dumpAccum >= 1f){
dump(item);
dumpAccum -=1f;
}
}
/** Try dumping any item near the building. */
public boolean dump(){
return dump(null);
}
/**
* Try dumping a specific item near the
* Try dumping a specific item near the building.
* @param todump Item to dump. Can be null to dump anything.
*/
public boolean dump(Item todump){
@@ -753,20 +786,27 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return false;
}
/** Called shortly before this building is removed. */
public void onProximityRemoved(){
if(power != null){
powerGraphRemoved();
}
}
/** in overrides, this does the exact same thing as onProximityUpdate, use that instead */
/** Called after this building is created in the world. May be called multiple times, or when adjacent buildings change. */
public void onProximityAdded(){
if(block.hasPower) updatePowerGraph();
if(power != null){
updatePowerGraph();
}
}
/** Called when anything adjacent to this building is placed/removed, including itself. */
public void onProximityUpdate(){
noSleep();
}
public void updatePowerGraph(){
for(Building other : getPowerConnections(tempTileEnts)){
for(Building other : getPowerConnections(tempBuilds)){
if(other.power != null){
other.power.graph.addGraph(power.graph);
}
@@ -970,7 +1010,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
/** Called when the block is destroyed. */
/** Called *after* the tile has been removed. */
public void afterDestroyed(){
}
/** Called when the block is destroyed. The tile is still intact at this stage. */
public void onDestroyed(){
float explosiveness = block.baseExplosiveness;
float flammability = 0f;
@@ -1022,7 +1067,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public TextureRegion getDisplayIcon(){
return block.icon(Cicon.medium);
return block.uiIcon;
}
@Override
@@ -1063,7 +1108,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
l.left();
for(Item item : content.items()){
if(items.hasFlowItem(item)){
l.image(item.icon(Cicon.small)).padRight(3f);
l.image(item.uiIcon).padRight(3f);
l.label(() -> items.getFlowRate(item) < 0 ? "..." : Strings.fixed(items.getFlowRate(item), 1) + ps).color(Color.lightGray);
l.row();
}
@@ -1090,7 +1135,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
Runnable rebuild = () -> {
l.clearChildren();
l.left();
l.image(() -> liquids.current().icon(Cicon.small)).padRight(3f);
l.image(() -> liquids.current().uiIcon).padRight(3f);
l.label(() -> liquids.getFlowRate() < 0 ? "..." : Strings.fixed(liquids.getFlowRate(), 2) + ps).color(Color.lightGray);
};
@@ -1189,11 +1234,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Handle a bullet collision.
* @return whether the bullet should be removed. */
public boolean collision(Bullet other){
damage(other.damage() * other.type().buildingDamageMultiplier);
damage(other.team, other.damage() * other.type().buildingDamageMultiplier);
return true;
}
/** Used to handle damage from splash damage for certain types of blocks. */
public void damage(@Nullable Team source, float damage){
damage(damage);
}
/** Changes this building's team in a safe manner. */
public void changeTeam(Team next){
indexer.removeIndex(tile);
this.team = next;
indexer.addIndex(tile);
}
public boolean canPickup(){
return true;
}
@@ -1366,11 +1423,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
@Override
public void control(LAccess type, Object p1, double p2, double p3, double p4){
//don't execute configure instructions that copy logic building configures; this can cause extreme lag
if(type == LAccess.configure && block.logicConfigurable && !(p1 instanceof LogicBuild)){
if(type == LAccess.config && block.logicConfigurable && !(p1 instanceof LogicBuild)){
//change config only if it's new
if(senseObject(LAccess.config) != p1){
configured(null, p1);
}
configured(null, p1);
}
}
@@ -1388,6 +1443,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
onDestroyed();
tile.remove();
remove();
afterDestroyed();
}
@Final
@@ -1409,7 +1465,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
if(team == Team.derelict){
if(team == Team.derelict || !block.supportsEnv(state.rules.environment)){
enabled = false;
}

View File

@@ -1,6 +1,5 @@
package mindustry.entities.comp;
import arc.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -10,12 +9,10 @@ import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.*;
import mindustry.entities.bullet.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.blocks.defense.Wall.*;
import static mindustry.Vars.*;
@@ -31,6 +28,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
BulletType type;
float fdata;
transient boolean absorbed, hit;
transient @Nullable Trail trail;
@Override
public void getCollisions(Cons<QuadTree> consumer){
@@ -42,11 +40,6 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}
}
@Override
public void drawBullets(){
type.draw(self());
}
@Override
public void add(){
type.init(self());
@@ -54,13 +47,17 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Override
public void remove(){
type.despawned(self());
//'despawned' only counts when the bullet is killed externally or reaches the end of life
if(!hit){
type.despawned(self());
}
type.removed(self());
collided.clear();
}
@Override
public float damageMultiplier(){
if(owner instanceof Unit) return ((Unit)owner).damageMultiplier() * state.rules.unitDamageMultiplier;
if(owner instanceof Unit u) return u.damageMultiplier() * state.rules.unitDamageMultiplier;
if(owner instanceof Building) return state.rules.blockDamageMultiplier;
return 1f;
@@ -80,8 +77,8 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Replace
@Override
public boolean collides(Hitboxc other){
return type.collides && (other instanceof Teamc && ((Teamc)other).team() != team)
&& !(other instanceof Flyingc && !((Flyingc)other).checkTarget(type.collidesAir, type.collidesGround))
return type.collides && (other instanceof Teamc t && t.team() != team)
&& !(other instanceof Flyingc f && !f.checkTarget(type.collidesAir, type.collidesGround))
&& !(type.pierce && collided.contains(other.id())); //prevent multiple collisions
}
@@ -89,24 +86,16 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(self(), x, y);
float health = 0f;
if(other instanceof Healthc h){
health = h.health();
}
//must be last.
if(!type.pierce){
hit = true;
remove();
}else{
collided.add(other.id());
}
type.hitEntity(self(), other, health);
if(owner instanceof WallBuild && player != null && team == player.team() && other instanceof Unit unit && unit.dead){
Events.fire(Trigger.phaseDeflectHit);
}
type.hitEntity(self(), other, other instanceof Healthc h ? h.health() : 0f);
}
@Override
@@ -130,6 +119,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
if(remove || type.collidesTeam){
if(!type.pierceBuilding){
hit = true;
remove();
}else{
collided.add(tile.id);
@@ -146,13 +136,14 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}
if(type.pierceCap != -1 && collided.size >= type.pierceCap){
hit = true;
remove();
}
}
@Override
public void draw(){
Draw.z(Layer.bullet);
Draw.z(type.layer);
type.draw(self());
type.drawLight(self());

View File

@@ -83,7 +83,7 @@ abstract class CommanderComp implements Entityc, Posc{
clearCommand();
units.shuffle();
float spacing = hitSize * 0.8f;
float spacing = hitSize * 0.9f;
minFormationSpeed = type.speed;
controlling.addAll(units);

View File

@@ -40,6 +40,7 @@ abstract class EntityComp{
return false;
}
/** Replaced with `this` after code generation. */
<T extends Entityc> T self(){
return (T)this;
}
@@ -48,11 +49,6 @@ abstract class EntityComp{
return (T)this;
}
<T> T with(Cons<T> cons){
cons.get((T)this);
return (T)this;
}
@InternalImpl
abstract int classId();

View File

@@ -1,5 +1,7 @@
package mindustry.entities.comp;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
@@ -9,6 +11,7 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -16,24 +19,30 @@ import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component(base = true)
abstract class FireComp implements Timedc, Posc, Firec, Syncc{
private static final float spreadChance = 0.04f, fireballChance = 0.06f;
abstract class FireComp implements Timedc, Posc, Syncc, Drawc{
public static final int frames = 40, duration = 90;
private static final float spreadDelay = 22f, fireballDelay = 40f,
ticksPerFrame = (float)duration / frames, warmupDuration = 20f, damageDelay = 40f, tileDamage = 1.8f, unitDamage = 3f;
public static final TextureRegion[] regions = new TextureRegion[frames];
@Import float time, lifetime, x, y;
Tile tile;
private transient Block block;
private transient float baseFlammability = -1, puddleFlammability;
private transient float
baseFlammability = -1, puddleFlammability, damageTimer = Mathf.random(40f),
spreadTimer = Mathf.random(spreadDelay), fireballTimer = Mathf.random(fireballDelay),
warmup = 0f,
animation = Mathf.random(frames);
@Override
public void update(){
if(Mathf.chance(0.09 * Time.delta)){
Fx.fire.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.05 * Time.delta)){
Fx.fireSmoke.at(x + Mathf.range(4f), y + Mathf.range(4f));
}
animation += Time.delta / ticksPerFrame;
warmup += Time.delta;
animation %= frames;
if(!headless){
control.sound.loop(Sounds.fire, this, 0.07f);
@@ -55,46 +64,71 @@ abstract class FireComp implements Timedc, Posc, Firec, Syncc{
Building entity = tile.build;
boolean damage = entity != null;
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.getFlammability();
block = tile.block();
}
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Time.delta * 8;
}
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.build == null ? 0 : tile.getFlammability();
block = tile.block();
}
if(damage){
lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Time.delta;
}
if(flammability > 1f && Mathf.chance(spreadChance * Time.delta * Mathf.clamp(flammability / 5f, 0.3f, 2f))){
if(flammability > 1f && (spreadTimer += Time.delta * Mathf.clamp(flammability / 5f, 0.3f, 2f)) >= spreadDelay){
spreadTimer = 0f;
Point2 p = Geometry.d4[Mathf.random(3)];
Tile other = world.tile(tile.x + p.x, tile.y + p.y);
Fires.create(other);
if(Mathf.chance(fireballChance * Time.delta * Mathf.clamp(flammability / 10f))){
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1, 1);
}
}
if(Mathf.chance(0.025 * Time.delta)){
if(flammability > 0 && (fireballTimer += Time.delta * Mathf.clamp(flammability / 10f, 0f, 0.5f)) >= fireballDelay){
fireballTimer = 0f;
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1, 1);
}
//apply damage to nearby units & building
if((damageTimer += Time.delta) >= damageDelay){
damageTimer = 0f;
Puddlec p = Puddles.get(tile);
puddleFlammability = p != null ? p.getFlammability() / 3f : 0;
if(damage){
entity.damage(1.6f);
entity.damage(tileDamage);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, unitDamage,
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.apply(StatusEffects.burning, 60 * 5));
}
}
@Override
public void draw(){
if(regions[0] == null){
for(int i = 0; i < frames; i++){
regions[i] = Core.atlas.find("fire" + i);
}
}
Draw.alpha(Mathf.clamp(warmup / warmupDuration));
Draw.z(Layer.effect);
Draw.rect(regions[(int)animation], x, y);
Draw.reset();
}
@Replace
@Override
public float clipSize(){
return 25;
}
@Override
public void remove(){
Fx.fireRemove.at(x, y, animation);
Fires.remove(tile);
}

View File

@@ -43,7 +43,7 @@ abstract class LaunchCoreComp implements Drawc, Timedc{
Draw.z(Layer.weather - 1);
TextureRegion region = block.icon(Cicon.full);
TextureRegion region = block.fullIcon;
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
Draw.alpha(alpha);

View File

@@ -83,8 +83,8 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
Tile on = tileOn();
//clear removed state of unit so it can be synced
if(Vars.net.client() && payload instanceof UnitPayload){
Vars.netClient.clearRemovedEntity(((UnitPayload)payload).unit.id);
if(Vars.net.client() && payload instanceof UnitPayload u){
Vars.netClient.clearRemovedEntity(u.unit.id);
}
//drop off payload on an acceptor if possible
@@ -106,7 +106,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
Unit u = payload.unit;
//can't drop ground units
if(!u.canPass(tileX(), tileY())){
if(!u.canPass(tileX(), tileY()) || Units.count(x, y, u.physicSize(), o -> o.isGrounded()) > 1){
return false;
}
@@ -159,7 +159,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
}
for(Payload p : payloads){
table.image(p.icon(Cicon.small)).size(itemSize).padRight(pad);
table.image(p.icon()).size(itemSize).padRight(pad);
}
}
}

View File

@@ -11,7 +11,7 @@ import mindustry.gen.*;
* Has mass.*/
@Component
abstract class PhysicsComp implements Velc, Hitboxc, Flyingc{
@Import float hitSize;
@Import float hitSize, x, y;
@Import Vec2 vel;
transient PhysicRef physref;

View File

@@ -19,7 +19,6 @@ import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
@@ -33,31 +32,31 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
@Import float x, y;
@ReadOnly Unit unit = Nulls.unit;
transient private Unit lastReadUnit = Nulls.unit;
transient @Nullable NetConnection con;
@ReadOnly Team team = Team.sharded;
@SyncLocal boolean typing, shooting, boosting;
boolean admin;
@SyncLocal float mouseX, mouseY;
String name = "noname";
boolean admin;
String name = "frog";
Color color = new Color();
//locale should not be synced.
transient String locale = "en";
transient float deathTimer;
transient String lastText = "";
transient float textFadeTime;
transient private Unit lastReadUnit = Nulls.unit;
public boolean isBuilder(){
return unit.canBuild();
}
public @Nullable CoreBuild closestCore(){
public @Nullable
CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable CoreBuild core(){
public @Nullable
CoreBuild core(){
return team.core();
}
@@ -69,7 +68,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
public TextureRegion icon(){
//display default icon for dead players
if(dead()) return core() == null ? UnitTypes.alpha.icon(Cicon.full) : ((CoreBlock)core().block).unitType.icon(Cicon.full);
if(dead()) return core() == null ? UnitTypes.alpha.fullIcon : ((CoreBlock)core().block).unitType.fullIcon;
return unit.icon();
}
@@ -133,8 +132,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
//update some basic state to sync things
if(unit.type.canBoost){
Tile tile = unit.tileOn();
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, unit.type.riseSpeed);
unit.elevation = Mathf.approachDelta(unit.elevation, unit.onSolid() || boosting || (unit.isFlying() && !unit.canLand()) ? 1f : 0f, unit.type.riseSpeed);
}
}else if((core = bestCore()) != null){
//have a small delay before death to prevent the camera from jumping around too quickly
@@ -266,9 +264,9 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
layout.setText(font, text, Color.white, width, Align.bottom, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(unit.x, unit.y + textHeight + layout.height - layout.height/2f, layout.width + 2, layout.height + 3);
font.draw(text, unit.x - width/2f, unit.y + textHeight + layout.height, width, Align.center, true);
Draw.color(0f, 0f, 0f, 0.3f * (textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime));
Fill.rect(unit.x, unit.y + textHeight + layout.height - layout.height / 2f, layout.width + 2, layout.height + 3);
font.draw(text, unit.x - width / 2f, unit.y + textHeight + layout.height, width, Align.center, true);
}
Draw.reset();
@@ -294,7 +292,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
sendMessage(text, from, NetClient.colorizeName(from.id(), from.name));
}
void sendMessage(String text, Player from, String fromName){
void sendMessage(String text, Player from, String fromName){
if(isLocal()){
if(ui != null){
ui.chatfrag.addMessage(text, fromName);

View File

@@ -21,10 +21,10 @@ import static mindustry.entities.Puddles.*;
@Component(base = true)
abstract class PuddleComp implements Posc, Puddlec, Drawc{
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rect rect = new Rect(), rect2 = new Rect();
private static int seeds;
@Import int id;
@Import float x, y;
transient float accepting, updateTime, lastRipple;
@@ -92,13 +92,13 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
public void draw(){
Draw.z(Layer.debris - 1);
seeds = id();
seeds = id;
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 25f;
Draw.color(tmp.set(liquid.color).shiftValue(-0.05f));
Draw.color(Tmp.c1.set(liquid.color).shiftValue(-0.05f));
Fill.circle(x + Mathf.sin(Time.time + seeds * 532, sscl, smag), y + Mathf.sin(Time.time + seeds * 53, sscl, smag), f * 8f);
Angles.randLenVectors(id(), 3, f * 6f, (ex, ey) -> {
Fill.circle(x + ex + Mathf.sin(Time.time + seeds * 532, sscl, smag),

View File

@@ -3,6 +3,7 @@ package mindustry.entities.comp;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -11,10 +12,11 @@ import static mindustry.Vars.*;
abstract class ShieldComp implements Healthc, Posc{
@Import float health, hitTime, x, y, healthMultiplier;
@Import boolean dead;
@Import Team team;
/** Absorbs health damage. */
float shield;
/** Substracts an amount from damage. */
/** Subtracts an amount from damage. */
float armor;
/** Shield opacity. */
transient float shieldAlpha = 0f;
@@ -60,7 +62,7 @@ abstract class ShieldComp implements Healthc, Posc{
}
if(hadShields && shield <= 0.0001f){
Fx.unitShieldBreak.at(x, y, 0, this);
Fx.unitShieldBreak.at(x, y, 0, team.color, this);
}
}
}

View File

@@ -19,8 +19,9 @@ abstract class StatusComp implements Posc, Flyingc{
private Seq<StatusEntry> statuses = new Seq<>();
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1, buildSpeedMultiplier = 1, dragMultiplier = 1;
@ReadOnly transient boolean disarmed = false;
//these are considered read-only
transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1, buildSpeedMultiplier = 1, dragMultiplier = 1;
transient boolean disarmed = false;
@Import UnitType type;
@@ -46,15 +47,8 @@ abstract class StatusComp implements Posc, Flyingc{
if(entry.effect == effect){
entry.time = Math.max(entry.time, duration);
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
StatusEntry.tmp.effect = entry.effect;
entry.effect.getTransition(self(), effect, entry.time, duration, StatusEntry.tmp);
entry.time = StatusEntry.tmp.time;
if(StatusEntry.tmp.effect != entry.effect){
entry.effect = StatusEntry.tmp.effect;
}
}else if(entry.effect.applyTransition(self(), effect, entry, duration)){ //find reaction
//TODO effect may react with multiple other effects
//stop looking when one is found
return;
}
@@ -69,6 +63,11 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
float getDuration(StatusEffect effect){
var entry = statuses.find(e -> e.effect == effect);
return entry == null ? 0 : entry.time;
}
void clearStatuses(){
statuses.clear();
}
@@ -149,6 +148,10 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
public Bits statusBits(){
return applied;
}
public void draw(){
for(StatusEntry e : statuses){
e.effect.draw(self());

View File

@@ -4,6 +4,7 @@ import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
@@ -18,17 +19,17 @@ abstract class TeamComp implements Posc{
}
@Nullable
public Building core(){
public CoreBuild core(){
return team.core();
}
@Nullable
public Building closestCore(){
public CoreBuild closestCore(){
return state.teams.closestCore(x, y, team);
}
@Nullable
public Building closestEnemyCore(){
public CoreBuild closestEnemyCore(){
return state.teams.closestEnemyCore(x, y, team);
}
}

View File

@@ -46,8 +46,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
double flag;
transient Seq<Ability> abilities = new Seq<>(0);
transient float healTime;
private transient float resupplyTime = Mathf.random(10f);
private transient boolean wasPlayer;
private transient float lastHealth;
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
@@ -67,6 +69,16 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
lookAt(x, y);
}
/** @return approx. square size of the physical hitbox for physics */
public float physicSize(){
return hitSize * 0.7f;
}
/** @return whether there is solid, un-occupied ground under this unit. */
public boolean canLand(){
return !onSolid() && Units.count(x, y, physicSize(), f -> f != self() && f.isGrounded()) == 0;
}
public boolean inRange(Position other){
return within(other, type.range);
}
@@ -98,7 +110,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return angleTo(buildPlan());
}else if(mineTile != null){
return angleTo(mineTile);
}else if(moving()){
}else if(moving() && type.omniMovement){
return vel().angle();
}
return rotation;
@@ -114,7 +126,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(isBuilding()){
return state.rules.infiniteResources ? Float.MAX_VALUE : Math.max(type.clipSize, type.region.width) + buildingRange + tilesize*4f;
}
return Math.max(type.region.width * 2f, type.clipSize);
return type.clipSize;
}
@Override
@@ -146,7 +158,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
controller instanceof FormationAI ? ctrlFormation :
0;
case commanded -> controller instanceof FormationAI && isValid() ? 1 : 0;
case payloadCount -> self() instanceof Payloadc pay ? pay.payloads().size : 0;
case payloadCount -> ((Object)this) instanceof Payloadc pay ? pay.payloads().size : 0;
case size -> hitSize / tilesize;
default -> Float.NaN;
};
@@ -159,7 +171,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case name -> controller instanceof Player p ? p.name : null;
case firstItem -> stack().amount == 0 ? null : item();
case controller -> !isValid() ? null : controller instanceof LogicAI log ? log.controller : controller instanceof FormationAI form ? form.leader : this;
case payloadType -> self() instanceof Payloadc pay ?
case payloadType -> ((Object)this) instanceof Payloadc pay ?
(pay.payloads().isEmpty() ? null :
pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type :
pay.payloads().peek() instanceof BuildPayload p2 ? p2.block() : null) : null;
@@ -313,6 +325,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
type.update(self());
if(health > lastHealth && lastHealth > 0 && healTime <= -1f){
healTime = 1f;
}
healTime -= Time.delta / 20f;
lastHealth = health;
//check if environment is unsupported
if(!type.supportsEnv(state.rules.environment) && !dead){
Call.unitCapDeath(self());
team.data().updateCount(type, -1);
}
if(state.rules.unitAmmo && ammo < type.ammoCapacity - 0.0001f){
resupplyTime += Time.delta;
@@ -336,7 +360,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
float relativeSize = state.rules.dropZoneRadius + hitSize/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(within(spawn.worldx(), spawn.worldy(), relativeSize)){
vel().add(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta));
velAddNet(Tmp.v1.set(this).sub(spawn.worldx(), spawn.worldy()).setLength(0.1f + 1f - dst(spawn) / relativeSize).scl(0.45f * Time.delta));
}
}
}
@@ -367,7 +391,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
elevation -= type.fallSpeed * Time.delta;
if(isGrounded()){
destroy();
Call.unitDestroy(id);
}
}
@@ -414,7 +438,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return a preview icon for this unit. */
public TextureRegion icon(){
return type.icon(Cicon.full);
return type.fullIcon;
}
/** Actually destroys the unit, removing it and creating explosions. **/

View File

@@ -1,5 +1,6 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@@ -20,7 +21,11 @@ abstract class VelComp implements Posc{
@MethodPriority(-1)
@Override
public void update(){
float px = x, py = y;
move(vel.x * Time.delta, vel.y * Time.delta);
if(Mathf.equal(px, x)) vel.x = 0;
if(Mathf.equal(py, y)) vel.y = 0;
vel.scl(Math.max(1f - drag * Time.delta, 0));
}
@@ -45,6 +50,10 @@ abstract class VelComp implements Posc{
return !vel.isZero(0.01f);
}
void move(Vec2 v){
move(v.x, v.y);
}
void move(float cx, float cy){
SolidPred check = solidity();
@@ -55,4 +64,20 @@ abstract class VelComp implements Posc{
y += cy;
}
}
void velAddNet(Vec2 v){
vel.add(v);
if(isRemote()){
x += v.x;
y += v.y;
}
}
void velAddNet(float vx, float vy){
vel.add(vx, vy);
if(isRemote()){
x += vx;
y += vy;
}
}
}

View File

@@ -1,28 +1,18 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.audio.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Import float x, y, rotation, reloadMultiplier;
@Import float x, y;
@Import boolean disarmed;
@Import Vec2 vel;
@Import UnitType type;
/** temporary weapon sequence number */
static int sequenceNum = 0;
/** weapon mount array, never null */
@SyncLocal WeaponMount[] mounts = {};
@ReadOnly transient boolean isRotate;
@@ -43,7 +33,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size];
for(int i = 0; i < mounts.length; i++){
mounts[i] = new WeaponMount(def.weapons.get(i));
mounts[i] = def.weapons.get(i).mountType.get(def.weapons.get(i));
}
}
@@ -53,8 +43,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
void controlWeapons(boolean rotate, boolean shoot){
for(WeaponMount mount : mounts){
mount.rotate = rotate;
mount.shoot = shoot;
if(mount.weapon.controllable){
mount.rotate = rotate;
mount.shoot = shoot;
}
}
isRotate = rotate;
isShooting = shoot;
@@ -73,8 +65,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
y = Tmp.v1.y + this.y;
for(WeaponMount mount : mounts){
mount.aimX = x;
mount.aimY = y;
if(mount.weapon.controllable){
mount.aimX = x;
mount.aimY = y;
}
}
aimX = x;
@@ -88,7 +82,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Override
public void remove(){
for(WeaponMount mount : mounts){
if(mount.bullet != null){
if(mount.bullet != null && mount.bullet.owner == self()){
mount.bullet.time = mount.bullet.lifetime - 10f;
mount.bullet = null;
}
@@ -102,136 +96,8 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
/** Update shooting and rotation for this unit. */
@Override
public void update(){
boolean can = canShoot();
for(WeaponMount mount : mounts){
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
float weaponRotation = this.rotation - 90 + (weapon.rotate ? mount.rotation : 0);
float mountX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
mountY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
//update continuous state
if(weapon.continuous && mount.bullet != null){
if(!mount.bullet.isAdded() || mount.bullet.time >= mount.bullet.lifetime || mount.bullet.type != weapon.bullet){
mount.bullet = null;
}else{
mount.bullet.rotation(weaponRotation + 90);
mount.bullet.set(shootX, shootY);
mount.reload = weapon.reload;
vel.add(Tmp.v1.trns(rotation + 180f, mount.bullet.type.recoil));
if(weapon.shootSound != Sounds.none && !headless){
if(mount.sound == null) mount.sound = new SoundLoop(weapon.shootSound, 1f);
mount.sound.update(x, y, true);
}
}
}else{
//heat decreases when not firing
mount.heat = Math.max(mount.heat - Time.delta * reloadMultiplier / mount.weapon.cooldownTime, 0);
if(mount.sound != null){
mount.sound.update(x, y, false);
}
}
//flip weapon shoot side for alternating weapons at half reload
if(weapon.otherSide != -1 && weapon.alternate && mount.side == weapon.flipSprite &&
mount.reload + Time.delta * reloadMultiplier > weapon.reload/2f && mount.reload <= weapon.reload/2f){
mounts[weapon.otherSide].side = !mounts[weapon.otherSide].side;
mount.side = !mount.side;
}
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
float axisX = this.x + Angles.trnsx(this.rotation - 90, weapon.x, weapon.y),
axisY = this.y + Angles.trnsy(this.rotation - 90, weapon.x, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - this.rotation;
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
}else if(!weapon.rotate){
mount.rotation = 0;
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
}
//shoot if applicable
if(mount.shoot && //must be shooting
can && //must be able to shoot
(ammo > 0 || !state.rules.unitAmmo || team().rules().infiniteAmmo) && //check ammo
(!weapon.alternate || mount.side == weapon.flipSprite) &&
//TODO checking for velocity this way isn't entirely correct
(vel.len() >= mount.weapon.minShootVelocity || (net.active() && !isLocal())) && //check velocity requirements
mount.reload <= 0.0001f && //reload has to be 0
Angles.within(weapon.rotate ? mount.rotation : this.rotation, mount.targetRotation, mount.weapon.shootCone) //has to be within the cone
){
shoot(mount, shootX, shootY, mount.aimX, mount.aimY, mountX, mountY, shootAngle, Mathf.sign(weapon.x));
mount.reload = weapon.reload;
ammo--;
if(ammo < 0) ammo = 0;
}
mount.weapon.update(self(), mount);
}
}
private void shoot(WeaponMount mount, float x, float y, float aimX, float aimY, float mountX, float mountY, float rotation, int side){
Weapon weapon = mount.weapon;
float baseX = this.x, baseY = this.y;
boolean delay = weapon.firstShotDelay + weapon.shotDelay > 0f;
(delay ? weapon.chargeSound : weapon.continuous ? Sounds.none : weapon.shootSound).at(x, y, Mathf.random(weapon.soundPitchMin, weapon.soundPitchMax));
BulletType ammo = weapon.bullet;
float lifeScl = ammo.scaleVelocity ? Mathf.clamp(Mathf.dst(x, y, aimX, aimY) / ammo.range()) : 1f;
sequenceNum = 0;
if(delay){
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> {
Time.run(sequenceNum * weapon.shotDelay + weapon.firstShotDelay, () -> {
if(!isAdded()) return;
mount.bullet = bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy), lifeScl);
});
sequenceNum++;
});
}else{
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> mount.bullet = bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy), lifeScl));
}
boolean parentize = ammo.keepVelocity;
if(delay){
Time.run(weapon.firstShotDelay, () -> {
if(!isAdded()) return;
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
if(!weapon.continuous){
weapon.shootSound.at(x, y, Mathf.random(weapon.soundPitchMin, weapon.soundPitchMax));
}
});
}else{
vel.add(Tmp.v1.trns(rotation + 180f, ammo.recoil));
Effect.shake(weapon.shake, weapon.shake, x, y);
mount.heat = 1f;
}
weapon.ejectEffect.at(mountX, mountY, rotation * side);
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);
apply(weapon.shootStatus, weapon.shootStatusDuration);
}
private Bullet bullet(Weapon weapon, float x, float y, float angle, float lifescl){
float xr = Mathf.range(weapon.xRand);
return weapon.bullet.create(this, team(),
x + Angles.trnsx(angle, 0, xr),
y + Angles.trnsy(angle, 0, xr),
angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd), lifescl);
}
}

View File

@@ -0,0 +1,47 @@
package mindustry.entities.effect;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.entities.*;
import mindustry.graphics.*;
import static arc.graphics.g2d.Draw.*;
import static arc.graphics.g2d.Lines.*;
import static arc.math.Angles.*;
public class ExplosionEffect extends Effect{
public Color waveColor = Pal.missileYellow, smokeColor = Color.gray, sparkColor = Pal.missileYellowBack;
public float waveLife = 6f, waveStroke = 3f, waveRad = 15f, waveRadBase = 2f, sparkStroke = 1f, sparkRad = 23f, sparkLen = 3f, smokeSize = 4f, smokeSizeBase = 0.5f, smokeRad = 23f;
public int smokes = 5, sparks = 4;
public ExplosionEffect(){
clip = 100f;
lifetime = 22;
renderer = e -> {
color(waveColor);
e.scaled(waveLife, i -> {
stroke(waveStroke * i.fout());
Lines.circle(e.x, e.y, waveRadBase + i.fin() * waveRad);
});
color(smokeColor);
if(smokeSize > 0){
randLenVectors(e.id, smokes, 2f + smokeRad * e.finpow(), (x, y) -> {
Fill.circle(e.x + x, e.y + y, e.fout() * smokeSize + smokeSizeBase);
});
}
color(sparkColor);
stroke(e.fout() * sparkStroke);
randLenVectors(e.id + 1, sparks, 1f + sparkRad * e.finpow(), (x, y) -> {
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * sparkLen);
Drawf.light(e.x + x, e.y + y, e.fout() * sparkLen * 4f, sparkColor, 0.7f);
});
};
}
}

View File

@@ -6,17 +6,28 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.graphics.*;
/** The most essential effect class. Can create particles in various shapes. */
public class ParticleEffect extends Effect{
public Color colorFrom = Color.white.cpy(), colorTo = Color.white.cpy();
public int particles = 6;
public float cone = 180f, length = 20f, baseLength = 0f;
/** Particle size/length/radius interpolation. */
public Interp interp = Interp.linear;
public float offsetX, offsetY;
public float lightScl = 2f, lightOpacity = 0.6f;
public @Nullable Color lightColor;
//region only
/** Spin in degrees per tick. */
public float spin = 0f;
/** Controls the initial and final sprite sizes. */
public float sizeFrom = 2f, sizeTo = 0f;
/** Rotation offset. */
public float offset = 0;
/** Sprite to draw. */
public String region = "circle";
//line only
@@ -37,19 +48,23 @@ public class ParticleEffect extends Effect{
float rawfin = e.fin();
float fin = e.fin(interp);
float rad = interp.apply(sizeFrom, sizeTo, rawfin) * 2;
float ox = e.x + Angles.trnsx(e.rotation, offsetX, offsetY), oy = e.y + Angles.trnsy(e.rotation, offsetX, offsetY);
Draw.color(colorFrom, colorTo, fin);
Color lightColor = this.lightColor == null ? Draw.getColor() : this.lightColor;
if(line){
Lines.stroke(interp.apply(strokeFrom, strokeTo, rawfin));
float len = interp.apply(lenFrom, lenTo, rawfin);
Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> {
Lines.lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), len);
Lines.lineAngle(ox + x, oy + y, Mathf.angle(x, y), len);
Drawf.light(ox + x, oy + y, len * lightScl, lightColor, lightOpacity);
});
}else{
Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> {
Draw.rect(tex, e.x + x, e.y + y, rad, rad, e.rotation + offset);
Draw.rect(tex, ox + x, oy + y, rad, rad, e.rotation + offset + e.time * spin);
Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity);
});
}
}

View File

@@ -3,16 +3,21 @@ package mindustry.entities.effect;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.entities.*;
import mindustry.graphics.*;
/** Effect that renders a basic shockwave. */
public class WaveEffect extends Effect{
public Color colorFrom = Color.white.cpy(), colorTo = Color.white.cpy();
public float sizeFrom = 0f, sizeTo = 100f;
public @Nullable Color lightColor;
public float sizeFrom = 0f, sizeTo = 100f, lightScl = 3f, lightOpacity = 0.8f;
public int sides = -1;
public float rotation = 0f;
public float strokeFrom = 2f, strokeTo = 0f;
public Interp interp = Interp.linear;
public Interp lightInterp = Interp.reverse;
public float offsetX, offsetY;
@Override
public void init(){
@@ -23,11 +28,14 @@ public class WaveEffect extends Effect{
public void render(EffectContainer e){
float fin = e.fin();
float ifin = e.fin(interp);
float ox = e.x + Angles.trnsx(e.rotation, offsetX, offsetY), oy = e.y + Angles.trnsy(e.rotation, offsetX, offsetY);
Draw.color(colorFrom, colorTo, ifin);
Lines.stroke(interp.apply(strokeFrom, strokeTo, fin));
float rad = interp.apply(sizeFrom, sizeTo, fin);
Lines.poly(e.x, e.y, sides <= 0 ? Lines.circleVertices(rad) : sides, rad, rotation + e.rotation);
Lines.poly(ox, oy, sides <= 0 ? Lines.circleVertices(rad) : sides, rad, rotation + e.rotation);
Drawf.light(ox, oy, rad * lightScl, lightColor == null ? Draw.getColor() : lightColor, lightOpacity * e.fin(lightInterp));
}
}

View File

@@ -23,8 +23,6 @@ public class AIController implements UnitController{
/** main target that is being faced */
protected Teamc target;
/** targets for each weapon */
protected Teamc[] targets = {};
{
timer.reset(0, Mathf.random(40f));
@@ -94,8 +92,6 @@ public class AIController implements UnitController{
}
protected void updateWeapons(){
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
float rotation = unit.rotation - 90;
boolean ret = retarget();
@@ -109,39 +105,39 @@ public class AIController implements UnitController{
unit.isShooting = false;
for(int i = 0; i < targets.length; i++){
WeaponMount mount = unit.mounts[i];
for(var mount : unit.mounts){
Weapon weapon = mount.weapon;
//let uncontrollable weapons do their own thing
if(!weapon.controllable) continue;
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
if(unit.type.singleTarget){
targets[i] = target;
mount.target = target;
}else{
if(ret){
targets[i] = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
mount.target = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
}
if(checkTarget(targets[i], mountX, mountY, weapon.bullet.range())){
targets[i] = null;
if(checkTarget(mount.target, mountX, mountY, weapon.bullet.range())){
mount.target = null;
}
}
boolean shoot = false;
if(targets[i] != null){
shoot = targets[i].within(mountX, mountY, weapon.bullet.range()) && shouldShoot();
if(mount.target != null){
shoot = mount.target.within(mountX, mountY, weapon.bullet.range() + (mount.target instanceof Sized s ? s.hitSize()/2f : 0f)) && shouldShoot();
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
Vec2 to = Predict.intercept(unit, mount.target, weapon.bullet.speed);
mount.aimX = to.x;
mount.aimY = to.y;
}
mount.shoot = shoot;
mount.rotate = shoot;
unit.isShooting |= (mount.shoot = mount.rotate = shoot);
unit.isShooting |= shoot;
if(shoot){
unit.aimX = mount.aimX;
unit.aimY = mount.aimY;

View File

@@ -3,8 +3,6 @@ package mindustry.entities.units;
import mindustry.type.*;
public class StatusEntry{
public static final StatusEntry tmp = new StatusEntry();
public StatusEffect effect;
public float time;

View File

@@ -28,6 +28,10 @@ public class WeaponMount{
public @Nullable Bullet bullet;
/** sound loop for continuous weapons */
public @Nullable SoundLoop sound;
/** current target; used for autonomous weapons and AI */
public @Nullable Teamc target;
/** retarget counter */
public float retarget = 0f;
public WeaponMount(Weapon weapon){
this.weapon = weapon;

View File

@@ -6,6 +6,7 @@ import mindustry.ctype.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -242,11 +243,31 @@ public class EventType{
}
}
public static class TileChangeEvent{
public final Tile tile;
/**
* Called *before* a tile has changed.
* WARNING! This event is special: its instance is reused! Do not cache or use with a timer.
* Do not modify any tiles inside listeners that use this tile.
* */
public static class TilePreChangeEvent{
public Tile tile;
public TileChangeEvent(Tile tile){
public TilePreChangeEvent set(Tile tile){
this.tile = tile;
return this;
}
}
/**
* Called *after* a tile has changed.
* WARNING! This event is special: its instance is reused! Do not cache or use with a timer.
* Do not modify any tiles inside listeners that use this tile.
* */
public static class TileChangeEvent{
public Tile tile;
public TileChangeEvent set(Tile tile){
this.tile = tile;
return this;
}
}
@@ -392,6 +413,17 @@ public class EventType{
}
}
/** Called when a player sends a connection packet. */
public static class ConnectPacketEvent{
public final NetConnection connection;
public final ConnectPacket packet;
public ConnectPacketEvent(NetConnection connection, ConnectPacket packet){
this.connection = connection;
this.packet = packet;
}
}
/** Called after connecting; when a player receives world data and is ready to play.*/
public static class PlayerJoin{
public final Player player;

View File

@@ -2,6 +2,7 @@ package mindustry.game;
import arc.*;
import arc.func.*;
import arc.util.*;
import mindustry.maps.*;
import static mindustry.Vars.*;
@@ -22,7 +23,7 @@ public enum Gamemode{
rules.waves = true;
rules.waveTimer = true;
rules.waveSpacing /= 2f;
rules.waveSpacing = 60f * Time.toMinutes;
rules.teams.get(rules.waveTeam).infiniteResources = true;
}, map -> map.teams.contains(state.rules.waveTeam.id)),
pvp(rules -> {

View File

@@ -10,6 +10,8 @@ import mindustry.io.*;
import mindustry.type.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
/**
* Defines current rules on how the game should function.
@@ -34,6 +36,8 @@ public class Rules{
public boolean editor = false;
/** Whether a gameover can happen at all. Set this to false to implement custom gameover conditions. */
public boolean canGameOver = true;
/** Whether cores change teams when they are destroyed. */
public boolean coreCapture = false;
/** Whether reactors can explode and damage other blocks. */
public boolean reactorExplosions = true;
/** Whether schematics are allowed. */
@@ -72,6 +76,10 @@ public class Rules{
public int winWave = 0;
/** Base unit cap. Can still be increased by blocks. */
public int unitCap = 0;
/** Environmental flags that dictate visuals & how blocks function. */
public int environment = Env.terrestrial | Env.spores | Env.groundOil | Env.groundWater;
/** Attributes of the environment. */
public Attributes attributes = new Attributes();
/** Sector for saves that have them. */
public @Nullable Sector sector;
/** Spawn layout. */

View File

@@ -214,7 +214,7 @@ public class Saves{
}
previewExecutor.submit(() -> {
try{
previewFile().writePNG(renderer.minimap.getPixmap());
previewFile().writePng(renderer.minimap.getPixmap());
requestedPreview = false;
}catch(Throwable t){
Log.err(t);

View File

@@ -15,6 +15,9 @@ import static mindustry.Vars.*;
public class Schematic implements Publishable, Comparable<Schematic>{
public final Seq<Stile> tiles;
/** These are used for the schematic tag UI. */
public Seq<String> labels = new Seq<>();
/** Internal meta tags. */
public StringMap tags;
public int width, height;
public @Nullable Fi file;
@@ -29,7 +32,7 @@ public class Schematic implements Publishable, Comparable<Schematic>{
}
public float powerProduction(){
return tiles.sumf(s -> s.block instanceof PowerGenerator ? ((PowerGenerator)s.block).powerProduction : 0f);
return tiles.sumf(s -> s.block instanceof PowerGenerator p ? p.powerProduction : 0f);
}
public float powerConsumption(){

View File

@@ -26,7 +26,6 @@ import mindustry.input.*;
import mindustry.input.Placement.*;
import mindustry.io.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.legacy.*;
@@ -128,8 +127,6 @@ public class Schematics implements Loadable{
Log.err(e);
ui.showException(e);
}
}
private @Nullable Schematic loadFile(Fi file){
@@ -173,7 +170,7 @@ public class Schematics implements Loadable{
Draw.flush();
buffer.begin();
Pixmap pixmap = ScreenUtils.getFrameBufferPixmap(0, 0, buffer.getWidth(), buffer.getHeight());
file.writePNG(pixmap);
file.writePng(pixmap);
buffer.end();
}
@@ -349,7 +346,7 @@ public class Schematics implements Loadable{
for(int cx = x; cx <= x2; cx++){
for(int cy = y; cy <= y2; cy++){
Building linked = world.build(cx, cy);
Block realBlock = linked == null ? null : linked instanceof ConstructBuild cons ? cons.cblock : linked.block;
Block realBlock = linked == null ? null : linked instanceof ConstructBuild cons ? cons.current : linked.block;
if(linked != null && realBlock != null && (realBlock.isVisible() || realBlock instanceof CoreBlock)){
int top = realBlock.size/2;
@@ -378,7 +375,7 @@ public class Schematics implements Loadable{
for(int cx = ox; cx <= ox2; cx++){
for(int cy = oy; cy <= oy2; cy++){
Building tile = world.build(cx, cy);
Block realBlock = tile == null ? null : tile instanceof ConstructBuild cons ? cons.cblock : tile.block;
Block realBlock = tile == null ? null : tile instanceof ConstructBuild cons ? cons.current : tile.block;
if(tile != null && !counted.contains(tile.pos()) && realBlock != null
&& (realBlock.isVisible() || realBlock instanceof CoreBlock)){
@@ -424,7 +421,7 @@ public class Schematics implements Loadable{
Seq<Tile> seq = new Seq<>();
if(coreTile == null) throw new IllegalArgumentException("Loadout schematic has no core tile!");
int ox = x - coreTile.x, oy = y - coreTile.y;
schem.tiles.each(st -> {
schem.tiles.copy().sort(s -> -s.block.schematicPriority).each(st -> {
Tile tile = world.tile(st.x + ox, st.y + oy);
if(tile == null) return;
@@ -498,11 +495,19 @@ public class Schematics implements Loadable{
short width = stream.readShort(), height = stream.readShort();
StringMap map = new StringMap();
byte tags = stream.readByte();
int tags = stream.readUnsignedByte();
for(int i = 0; i < tags; i++){
map.put(stream.readUTF(), stream.readUTF());
}
String[] labels = null;
//try to read the categories, but skip if it fails
try{
labels = JsonIO.read(String[].class, map.get("labels", "[]"));
}catch(Exception ignored){
}
IntMap<Block> blocks = new IntMap<>();
byte length = stream.readByte();
for(int i = 0; i < length; i++){
@@ -523,7 +528,9 @@ public class Schematics implements Loadable{
}
}
return new Schematic(tiles, map, width, height);
Schematic out = new Schematic(tiles, map, width, height);
if(labels != null) out.labels.addAll(labels);
return out;
}
}
@@ -540,6 +547,8 @@ public class Schematics implements Loadable{
stream.writeShort(schematic.width);
stream.writeShort(schematic.height);
schematic.tags.put("labels", JsonIO.write(schematic.labels.toArray(String.class)));
stream.writeByte(schematic.tags.size);
for(ObjectMap.Entry<String, String> e : schematic.tags.entries()){
stream.writeUTF(e.key);

View File

@@ -9,6 +9,7 @@ import mindustry.maps.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import mindustry.world.meta.*;
import mindustry.world.modules.*;
import java.util.*;
@@ -69,6 +70,8 @@ public class SectorInfo{
public @Nullable String name;
/** Displayed icon. */
public @Nullable String icon;
/** Displayed icon, as content. */
public @Nullable UnlockableContent contentIcon;
/** Version of generated waves. When it doesn't match, new waves are generated. */
public int waveVersion = -1;
/** Whether this sector was indicated to the player or not. */
@@ -191,6 +194,13 @@ public class SectorInfo{
stat.mean = Math.min(stat.mean, rawProduction.get(item, ExportStat::new).mean);
});
var pads = indexer.getAllied(state.rules.defaultTeam, BlockFlag.launchPad);
//disable export when launch pads are disabled, or there aren't any active ones
if(pads.size() == 0 || !Seq.with(pads).contains(t -> t.build.consValid())){
export.clear();
}
if(state.rules.sector != null){
state.rules.sector.saveInfo();
}

View File

@@ -26,8 +26,8 @@ public class Teams{
public Seq<TeamData> active = new Seq<>();
/** Teams with block or unit presence. */
public Seq<TeamData> present = new Seq<>(TeamData.class);
/** Current boss unit. */
public @Nullable Unit boss;
/** Current boss units. */
public Seq<Unit> bosses = new Seq<>();
public Teams(){
active.add(get(Team.crux));
@@ -117,7 +117,6 @@ public class Teams{
if(data.active() && !active.contains(data)){
active.add(data);
updateEnemies();
indexer.updateTeamIndex(data.team);
}
}
@@ -145,7 +144,7 @@ public class Teams{
public void updateTeamStats(){
present.clear();
boss = null;
bosses.clear();
for(Team team : Team.all){
TeamData data = team.data();
@@ -172,16 +171,17 @@ public class Teams{
}
//update presence flag.
Groups.build.each( b -> b.team.data().presentFlag = true);
Groups.build.each(b -> b.team.data().presentFlag = true);
for(Unit unit : Groups.unit){
if(unit.type == null) continue;
TeamData data = unit.team.data();
data.tree().insert(unit);
data.units.add(unit);
data.presentFlag = true;
if(unit.team == state.rules.waveTeam && unit.isBoss()){
boss = unit;
bosses.add(unit);
}
if(data.unitsByType == null || data.unitsByType.length <= unit.type.id){
@@ -241,12 +241,17 @@ public class Teams{
/** Target items to mine. */
public Seq<Item> mineItems = Seq.with(Items.copper, Items.lead, Items.titanium, Items.thorium);
/** Quadtree for all buildings of this team. Null if not active. */
@Nullable
public QuadTree<Building> buildings;
/** Current unit cap. Do not modify externally. */
public int unitCap;
/** Total unit count. */
public int unitCount;
/** Counts for each type of unit. Do not access directly. */
@Nullable
public int[] typeCounts;
/** Quadtree for units of this type. Do not access directly. */
/** Quadtree for units of this team. Do not access directly. */
@Nullable
public QuadTree<Unit> tree;
/** Units of this team. Updated each frame. */
@@ -328,5 +333,16 @@ public class Teams{
this.block = block;
this.config = config;
}
@Override
public String toString(){
return "BlockPlan{" +
"x=" + x +
", y=" + y +
", rotation=" + rotation +
", block=" + block +
", config=" + config +
'}';
}
}
}

View File

@@ -7,6 +7,7 @@ import arc.util.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.game.SectorInfo.*;
import mindustry.gen.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.world.blocks.storage.*;
@@ -236,6 +237,10 @@ public class Universe{
state.rules.winWave = waveMax;
state.rules.waves = true;
state.rules.attackMode = false;
//update rules in multiplayer
if(net.server()){
Call.setRules(state.rules);
}
}else{
sector.info.winWave = waveMax;
sector.info.waves = true;

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