Partial 7.0 merge - API preview
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
81
core/src/mindustry/ai/types/HugAI.java
Normal file
81
core/src/mindustry/ai/types/HugAI.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -> {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
}};
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 + "";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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){}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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)){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package mindustry.entities;
|
||||
|
||||
public interface Sized{
|
||||
import arc.math.geom.*;
|
||||
|
||||
public interface Sized extends Position{
|
||||
float hitSize();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
145
core/src/mindustry/entities/abilities/EnergyFieldAbility.java
Normal file
145
core/src/mindustry/entities/abilities/EnergyFieldAbility.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
70
core/src/mindustry/entities/bullet/EmpBulletType.java
Normal file
70
core/src/mindustry/entities/bullet/EmpBulletType.java
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. **/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
47
core/src/mindustry/entities/effect/ExplosionEffect.java
Normal file
47
core/src/mindustry/entities/effect/ExplosionEffect.java
Normal 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);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user