Merge branch '6.0' into crater

# Conflicts:
#	core/assets/icons/icons.properties
#	core/assets/sprites/block_colors.png
#	core/assets/sprites/sprites.atlas
#	core/assets/sprites/sprites.png
#	core/assets/sprites/sprites3.png
#	core/assets/sprites/sprites5.png
#	core/src/mindustry/content/Blocks.java
#	core/src/mindustry/ui/fragments/PlayerListFragment.java
#	core/src/mindustry/world/BlockStorage.java
This commit is contained in:
Patrick 'Quezler' Mounier
2020-04-16 12:39:52 +02:00
610 changed files with 27167 additions and 25697 deletions

View File

@@ -92,10 +92,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
assets.load(mods);
assets.load(schematics);
assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init();
content.load();
});
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> content.load());
}
@Override

View File

@@ -13,18 +13,15 @@ import arc.util.io.*;
import mindustry.ai.*;
import mindustry.core.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.maps.*;
import mindustry.maps.Map;
import mindustry.mod.*;
import mindustry.net.Net;
import mindustry.net.*;
import mindustry.world.blocks.defense.ForceProjector.*;
import java.io.*;
import java.nio.charset.*;
@@ -77,6 +74,10 @@ public class Vars implements Loadable{
public static final float worldBounds = 100f;
/** units outside of this bound will simply die instantly */
public static final float finalWorldBounds = worldBounds + 500;
/** mining range for manual miners */
public static final float miningRange = 70f;
/** range for building */
public static final float buildingRange = 220f;
/** ticks spent out of bound until self destruct. */
public static final float boundsCountdown = 60 * 7;
/** for map generator dialog */
@@ -142,6 +143,8 @@ public class Vars implements Loadable{
public static Fi schematicDirectory;
/** data subdirectory used for bleeding edge build versions */
public static Fi bebuildDirectory;
/** empty map, indicates no current map */
public static Map emptyMap;
/** map file extension */
public static final String mapExtension = "msav";
/** save file extension */
@@ -152,7 +155,7 @@ public class Vars implements Loadable{
/** list of all locales that can be switched to */
public static Locale[] locales;
public static FileTree tree;
public static FileTree tree = new FileTree();
public static Net net;
public static ContentLoader content;
public static GameState state;
@@ -165,6 +168,7 @@ public class Vars implements Loadable{
public static Schematics schematics = new Schematics();
public static BeControl becontrol;
public static Universe universe;
public static World world;
public static Maps maps;
public static WaveSpawner spawner;
@@ -178,18 +182,7 @@ public class Vars implements Loadable{
public static NetServer netServer;
public static NetClient netClient;
public static Entities entities;
public static EntityGroup<Player> playerGroup;
public static EntityGroup<TileEntity> tileGroup;
public static EntityGroup<Bullet> bulletGroup;
public static EntityGroup<EffectEntity> effectGroup;
public static EntityGroup<DrawTrait> groundEffectGroup;
public static EntityGroup<ShieldEntity> shieldGroup;
public static EntityGroup<Puddle> puddleGroup;
public static EntityGroup<Fire> fireGroup;
public static EntityGroup<BaseUnit> unitGroup;
public static Player player;
public static Playerc player;
@Override
public void loadAsync(){
@@ -199,6 +192,7 @@ public class Vars implements Loadable{
public static void init(){
Serialization.init();
Groups.init();
DefaultSerializers.typeMappings.put("mindustry.type.ContentType", "mindustry.ctype.ContentType");
if(loadLocales){
@@ -219,47 +213,6 @@ public class Vars implements Loadable{
Version.init();
if(tree == null) tree = new FileTree();
if(mods == null) mods = new Mods();
content = new ContentLoader();
loops = new LoopControl();
defaultWaves = new DefaultWaves();
collisions = new EntityCollisions();
world = new World();
becontrol = new BeControl();
maps = new Maps();
spawner = new WaveSpawner();
indexer = new BlockIndexer();
pathfinder = new Pathfinder();
entities = new Entities();
playerGroup = entities.add(Player.class).enableMapping();
tileGroup = entities.add(TileEntity.class, false);
bulletGroup = entities.add(Bullet.class).enableMapping();
effectGroup = entities.add(EffectEntity.class, false);
groundEffectGroup = entities.add(DrawTrait.class, false);
puddleGroup = entities.add(Puddle.class).enableMapping();
shieldGroup = entities.add(ShieldEntity.class, false);
fireGroup = entities.add(Fire.class).enableMapping();
unitGroup = entities.add(BaseUnit.class).enableMapping();
for(EntityGroup<?> group : entities.all()){
group.setRemoveListener(entity -> {
if(entity instanceof SyncTrait && net.client()){
netClient.addRemovedEntity((entity).getID());
}
});
}
state = new GameState();
data = new GlobalData();
mobile = Core.app.getType() == ApplicationType.Android || Core.app.getType() == ApplicationType.iOS || testMobile;
ios = Core.app.getType() == ApplicationType.iOS;
android = Core.app.getType() == ApplicationType.Android;
dataDirectory = Core.settings.getDataDirectory();
screenshotDirectory = dataDirectory.child("screenshots/");
customMapDirectory = dataDirectory.child("maps/");
@@ -269,6 +222,30 @@ public class Vars implements Loadable{
modDirectory = dataDirectory.child("mods/");
schematicDirectory = dataDirectory.child("schematics/");
bebuildDirectory = dataDirectory.child("be_builds/");
emptyMap = new Map(new StringMap());
if(tree == null) tree = new FileTree();
if(mods == null) mods = new Mods();
content = new ContentLoader();
loops = new LoopControl();
defaultWaves = new DefaultWaves();
collisions = new EntityCollisions();
world = new World();
universe = new Universe();
becontrol = new BeControl();
maps = new Maps();
spawner = new WaveSpawner();
indexer = new BlockIndexer();
pathfinder = new Pathfinder();
state = new GameState();
data = new GlobalData();
mobile = Core.app.getType() == ApplicationType.Android || Core.app.getType() == ApplicationType.iOS || testMobile;
ios = Core.app.getType() == ApplicationType.iOS;
android = Core.app.getType() == ApplicationType.Android;
modDirectory.mkdirs();

View File

@@ -4,17 +4,20 @@ 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.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import java.util.*;
import static mindustry.Vars.*;
/** Class used for indexing special target blocks for AI. */
@@ -25,24 +28,24 @@ public class BlockIndexer{
/** 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, ObjectSet<Tile>> ores = new ObjectMap<>();
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;
/** Stores all damaged tile entities by team. */
private ObjectSet<Tile>[] damagedTiles = new ObjectSet[Team.all().length];
private TileArray[] damagedTiles = new TileArray[Team.all().length];
/**All ores available on this map.*/
private ObjectSet<Item> allOres = new ObjectSet<>();
/**Stores teams that are present here as tiles.*/
private ObjectSet<Team> activeTeams = new ObjectSet<>();
private Array<Team> activeTeams = new Array<>();
/** Maps teams to a map of flagged tiles by type. */
private ObjectSet<Tile>[][] flagMap = new ObjectSet[Team.all().length][BlockFlag.all.length];
private TileArray[][] flagMap = new TileArray[Team.all().length][BlockFlag.all.length];
/** Maps tile positions to their last known tile index data. */
private IntMap<TileIndex> typeMap = new IntMap<>();
/** Empty set used for returning. */
private ObjectSet<Tile> emptySet = new ObjectSet<>();
private TileArray emptySet = new TileArray();
/** Array used for returning and reusing. */
private Array<Tile> returnArray = new Array<>();
@@ -61,12 +64,12 @@ public class BlockIndexer{
Events.on(WorldLoadEvent.class, event -> {
scanOres.clear();
scanOres.addAll(Item.getAllOres());
damagedTiles = new ObjectSet[Team.all().length];
flagMap = new ObjectSet[Team.all().length][BlockFlag.all.length];
damagedTiles = new TileArray[Team.all().length];
flagMap = new TileArray[Team.all().length][BlockFlag.all.length];
for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
flagMap[i][j] = new ObjectSet<>();
flagMap[i][j] = new TileArray();
}
}
@@ -77,18 +80,14 @@ public class BlockIndexer{
//create bitset for each team type that contains each quadrant
structQuadrants = new GridBits[Team.all().length];
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.tile(x, y);
for(Tile tile : world.tiles){
process(tile);
process(tile);
if(tile.entity != null && tile.entity.damaged()){
notifyTileDamaged(tile.entity);
}
if(tile.drop() != null) allOres.add(tile.drop());
if(tile.entity != null && tile.entity.damaged()){
notifyTileDamaged(tile.entity);
}
if(tile.drop() != null) allOres.add(tile.drop());
}
for(int x = 0; x < quadWidth(); x++){
@@ -101,7 +100,7 @@ public class BlockIndexer{
});
}
private ObjectSet<Tile>[] getFlagged(Team team){
private TileArray[] getFlagged(Team team){
return flagMap[team.id];
}
@@ -118,14 +117,11 @@ public class BlockIndexer{
if(structQuadrants == null) return;
//go through every tile... ouch
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.tile(x, y);
if(tile.getTeam() == team){
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
structQuadrant(team).set(quadrantX, quadrantY);
}
for(Tile tile : world.tiles){
if(tile.team() == team){
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
structQuadrant(team).set(quadrantX, quadrantY);
}
}
}
@@ -136,16 +132,16 @@ public class BlockIndexer{
}
/** Returns all damaged tiles by team. */
public ObjectSet<Tile> getDamaged(Team team){
public TileArray getDamaged(Team team){
returnArray.clear();
if(damagedTiles[team.id] == null){
damagedTiles[team.id] = new ObjectSet<>();
damagedTiles[team.id] = new TileArray();
}
ObjectSet<Tile> set = damagedTiles[team.id];
TileArray set = damagedTiles[team.id];
for(Tile tile : set){
if((tile.entity == null || tile.entity.getTeam() != team || !tile.entity.damaged()) || tile.block() instanceof BuildBlock){
if((tile.entity == null || tile.entity.team() != team || !tile.entity.damaged()) || tile.block() instanceof BuildBlock){
returnArray.add(tile);
}
}
@@ -158,16 +154,49 @@ public class BlockIndexer{
}
/** Get all allied blocks with a flag. */
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
public TileArray getAllied(Team team, BlockFlag type){
return flagMap[team.id][type.ordinal()];
}
public boolean eachBlock(Teamc team, float range, Boolf<Tilec> pred, Cons<Tilec> cons){
return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons);
}
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Tilec> pred, Cons<Tilec> cons){
intSet.clear();
int tx = world.toTile(wx);
int ty = world.toTile(wy);
int tileRange = (int)(range / tilesize + 1);
intSet.clear();
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;
Tilec other = world.ent(x, y);
if(other == null) continue;
if(other.team() == team && !intSet.contains(other.pos()) && pred.get(other)){
cons.get(other);
any = true;
intSet.add(other.pos());
}
}
}
return any;
}
/** Get all enemy blocks with a flag. */
public Array<Tile> getEnemy(Team team, BlockFlag type){
returnArray.clear();
for(Team enemy : team.enemies()){
if(state.teams.isActive(enemy)){
ObjectSet<Tile> set = getFlagged(enemy)[type.ordinal()];
TileArray set = getFlagged(enemy)[type.ordinal()];
if(set != null){
for(Tile tile : set){
returnArray.add(tile);
@@ -178,20 +207,20 @@ public class BlockIndexer{
return returnArray;
}
public void notifyTileDamaged(TileEntity entity){
if(damagedTiles[(int)entity.getTeam().id] == null){
damagedTiles[(int)entity.getTeam().id] = new ObjectSet<>();
public void notifyTileDamaged(Tilec entity){
if(damagedTiles[(int)entity.team().id] == null){
damagedTiles[(int)entity.team().id] = new TileArray();
}
ObjectSet<Tile> set = damagedTiles[(int)entity.getTeam().id];
set.add(entity.tile);
TileArray set = damagedTiles[(int)entity.team().id];
set.add(entity.tile());
}
public TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public Tilec findEnemyTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
for(Team enemy : activeTeams){
if(!team.isEnemy(enemy)) continue;
TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
Tilec entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
return entity;
}
@@ -200,12 +229,12 @@ public class BlockIndexer{
return null;
}
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public Tilec findTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
return findTile(team, x, y, range, pred, false);
}
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred, boolean usePriority){
TileEntity closest = null;
public Tilec findTile(Team team, float x, float y, float range, Boolf<Tilec> pred, boolean usePriority){
Tilec closest = null;
float dst = 0;
float range2 = range*range;
@@ -216,21 +245,19 @@ public class BlockIndexer{
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++){
Tile other = world.ltile(tx, ty);
Tilec e = world.ent(tx, ty);
if(other == null) continue;
if(e == null) continue;
if(other.entity == null || other.getTeam() != team || !pred.get(other) || !other.block().targetable)
if(e.team() != team || !pred.get(e) || !e.block().targetable)
continue;
TileEntity e = other.entity;
float ndst = Mathf.dst2(x, y, e.x, e.y);
float ndst = e.dst2(x, y);
if(ndst < range2 && (closest == null ||
//this one is closer, and it is at least of equal priority
(ndst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
(ndst < dst && (!usePriority || closest.block().priority.ordinal() <= e.block().priority.ordinal())) ||
//priority is used, and new block has higher priority regardless of range
(usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
(usePriority && closest.block().priority.ordinal() < e.block().priority.ordinal()))){
dst = ndst;
closest = e;
}
@@ -248,7 +275,7 @@ public class BlockIndexer{
* 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 ObjectSet<Tile> getOrePositions(Item item){
public TileArray getOrePositions(Item item){
return ores.get(item, emptySet);
}
@@ -271,20 +298,22 @@ public class BlockIndexer{
}
private void process(Tile tile){
if(tile.block().flags.size() > 0 && tile.getTeam() != Team.derelict){
ObjectSet<Tile>[] map = getFlagged(tile.getTeam());
if(tile.block().flags.size() > 0 && tile.team() != Team.derelict){
TileArray[] map = getFlagged(tile.team());
for(BlockFlag flag : tile.block().flags){
ObjectSet<Tile> arr = map[flag.ordinal()];
TileArray arr = map[flag.ordinal()];
arr.add(tile);
map[flag.ordinal()] = arr;
}
typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.getTeam()));
typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.team()));
}
if(!activeTeams.contains(tile.team())){
activeTeams.add(tile.team());
}
activeTeams.add(tile.getTeam());
if(ores == null) return;
@@ -306,7 +335,7 @@ public class BlockIndexer{
//update quadrant at this position
for(Item item : scanOres){
ObjectSet<Tile> set = ores.get(item);
TileArray set = ores.get(item);
//update quadrant status depending on whether the item is in it
if(!itemSet.contains(item)){
@@ -328,7 +357,7 @@ public class BlockIndexer{
GridBits bits = structQuadrant(team);
//fast-set this quadrant to 'occupied' if the tile just placed is already of this team
if(tile.getTeam() == team && tile.entity != null && tile.block().targetable){
if(tile.team() == team && tile.entity != null && tile.block().targetable){
bits.set(quadrantX, quadrantY);
continue; //no need to process futher
}
@@ -338,9 +367,9 @@ public class BlockIndexer{
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++){
Tile result = world.ltile(x, y);
Tilec result = world.ent(x, y);
//when a targetable block is found, mark this quadrant as occupied and stop searching
if(result.entity != null && result.getTeam() == team){
if(result!= null && result.team() == team){
bits.set(quadrantX, quadrantY);
break outer;
}
@@ -366,23 +395,19 @@ public class BlockIndexer{
//initialize ore map with empty sets
for(Item item : scanOres){
ores.put(item, new ObjectSet<>());
ores.put(item, new TileArray());
}
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
int qx = (x / quadrantSize);
int qy = (y / quadrantSize);
for(Tile tile : world.tiles){
int qx = (tile.x / quadrantSize);
int qy = (tile.y / quadrantSize);
Tile tile = world.tile(x, y);
//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)));
}
//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)));
}
}
}
@@ -396,4 +421,34 @@ public class BlockIndexer{
this.team = team;
}
}
public static class TileArray implements Iterable<Tile>{
private Array<Tile> tiles = new Array<>(false, 16);
private IntSet contained = new IntSet();
public void add(Tile tile){
if(contained.add(tile.pos())){
tiles.add(tile);
}
}
public void remove(Tile tile){
if(contained.remove(tile.pos())){
tiles.remove(tile);
}
}
public int size(){
return tiles.size;
}
public Tile first(){
return tiles.first();
}
@Override
public Iterator<Tile> iterator(){
return tiles.iterator();
}
}
}

View File

@@ -0,0 +1,50 @@
package mindustry.ai;
//new indexer implementation, uses quadtrees
public class NewBlockIndexer{
/*
public ObjectSet<Tile> getOrePositions(Item item){
}
public Tile findClosestOre(float xp, float yp, Item item){
}
public ObjectSet<Tile> getDamaged(Team team){
}
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
}
public boolean eachBlock(Teamc team, float range, Boolf<Tilec> pred, Cons<Tilec> cons){
return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons);
}
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Tilec> pred, Cons<Tilec> cons){
}
public Array<Tile> getEnemy(Team team, BlockFlag type){
}
public void notifyTileDamaged(Tilec entity){
}
public Tilec findEnemyTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
}
public Tilec findTile(Team team, float x, float y, float range, Boolf<Tilec> pred, boolean usePriority){
}
public Tilec findTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
return findTile(team, x, y, range, pred, false);
}*/
}

View File

@@ -1,13 +1,13 @@
package mindustry.ai;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.func.*;
import arc.math.geom.*;
import arc.util.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.async.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -45,10 +45,8 @@ public class Pathfinder implements Runnable{
created = new GridBits(Team.all().length, PathTarget.all.length);
list = new Array<>();
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
tiles[x][y] = packTile(world.rawTile(x, y));
}
for(Tile tile : world.tiles){
tiles[tile.x][tile.y] = packTile(tile);
}
//special preset which may help speed things up; this is optional
@@ -218,7 +216,7 @@ public class Pathfinder implements Runnable{
//add targets
for(int i = 0; i < path.targets.size; i++){
int pos = path.targets.get(i);
int tx = Pos.x(pos), ty = Pos.y(pos);
int tx = Point2.x(pos), ty = Point2.y(pos);
path.weights[tx][ty] = 0;
path.searches[tx][ty] = (short)path.search;
@@ -255,7 +253,7 @@ public class Pathfinder implements Runnable{
//add targets
for(int i = 0; i < path.targets.size; i++){
int pos = path.targets.get(i);
path.weights[Pos.x(pos)][Pos.y(pos)] = 0;
path.weights[Point2.x(pos)][Point2.y(pos)] = 0;
path.frontier.addFirst(pos);
}
@@ -285,7 +283,7 @@ public class Pathfinder implements Runnable{
if(other != null && (path.weights[dx][dy] > cost + other.cost || path.searches[dx][dy] < path.search) && passable(dx, dy, path.team)){
if(other.cost < 0) throw new IllegalArgumentException("Tile cost cannot be negative! " + other);
path.frontier.addFirst(Pos.get(dx, dy));
path.frontier.addFirst(Point2.pack(dx, dy));
path.weights[dx][dy] = cost + other.cost;
path.searches[dx][dy] = (short)path.search;
}
@@ -303,7 +301,7 @@ public class Pathfinder implements Runnable{
//spawn points are also enemies.
if(state.rules.waves && team == state.rules.defaultTeam){
for(Tile other : spawner.getGroundSpawns()){
for(Tile other : spawner.getSpawns()){
out.add(other.pos());
}
}

View File

@@ -1,28 +1,23 @@
package mindustry.ai;
import arc.Events;
import arc.struct.Array;
import arc.func.Floatc2;
import arc.math.Angles;
import arc.math.Mathf;
import arc.util.Time;
import arc.util.Tmp;
import mindustry.content.Blocks;
import mindustry.content.Fx;
import mindustry.entities.Damage;
import mindustry.entities.Effects;
import mindustry.entities.type.*;
import mindustry.game.EventType.WorldLoadEvent;
import mindustry.game.SpawnGroup;
import mindustry.world.Tile;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class WaveSpawner{
private static final float margin = 40f, coreMargin = tilesize * 3; //how far away from the edge flying units spawn
private Array<FlyerSpawn> flySpawns = new Array<>();
private Array<Tile> groundSpawns = new Array<>();
private Array<Tile> spawns = new Array<>();
private boolean spawning = false;
public WaveSpawner(){
@@ -30,16 +25,16 @@ public class WaveSpawner{
}
public int countSpawns(){
return groundSpawns.size;
return spawns.size;
}
public Array<Tile> getGroundSpawns(){
return groundSpawns;
public Array<Tile> getSpawns(){
return spawns;
}
/** @return true if the player is near a ground spawn point. */
public boolean playerNear(){
return groundSpawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.getTeam() != state.rules.waveTeam);
return !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x(), player.y()) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
}
public void spawnEnemies(){
@@ -53,7 +48,7 @@ public class WaveSpawner{
eachFlyerSpawn((spawnX, spawnY) -> {
for(int i = 0; i < spawned; i++){
BaseUnit unit = group.createUnit(state.rules.waveTeam);
Unitc unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
unit.add();
}
@@ -66,9 +61,8 @@ public class WaveSpawner{
for(int i = 0; i < spawned; i++){
Tmp.v1.rnd(spread);
BaseUnit unit = group.createUnit(state.rules.waveTeam);
Unitc unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
Time.run(Math.min(i * 5, 60 * 2), () -> spawnEffect(unit));
}
});
@@ -77,7 +71,7 @@ public class WaveSpawner{
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
if(doShockwave){
Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawnX, spawnY, state.rules.dropZoneRadius));
Time.run(20f, () -> Fx.spawnShockwave.at(spawnX, spawnY, state.rules.dropZoneRadius));
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
}
});
@@ -86,30 +80,32 @@ public class WaveSpawner{
}
private void eachGroundSpawn(SpawnConsumer cons){
for(Tile spawn : groundSpawns){
for(Tile spawn : spawns){
cons.accept(spawn.worldx(), spawn.worldy(), true);
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
TileEntity firstCore = state.teams.playerCores().first();
for(TileEntity core : state.rules.waveTeam.cores()){
Tmp.v1.set(firstCore).sub(core.x, core.y).limit(coreMargin + core.block.size*tilesize);
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
Tilec firstCore = state.teams.playerCores().first();
for(Tilec core : state.rules.waveTeam.cores()){
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block().size*tilesize);
cons.accept(core.x() + Tmp.v1.x, core.y() + Tmp.v1.y, false);
}
}
}
private void eachFlyerSpawn(Floatc2 cons){
for(FlyerSpawn spawn : flySpawns){
float trns = (world.width() + world.height()) * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(spawn.angle, trns), -margin, world.width() * tilesize + margin);
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(spawn.angle, trns), -margin, world.height() * tilesize + margin);
for(Tile tile : spawns){
float angle = Angles.angle(tile.x, tile.y, world.width()/2, world.height()/2);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(angle, trns), -margin, world.height() * tilesize + margin);
cons.get(spawnX, spawnY);
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam)){
for(TileEntity core : state.teams.get(state.rules.waveTeam).cores){
cons.get(core.x, core.y);
for(Tilec core : state.teams.get(state.rules.waveTeam).cores){
cons.get(core.x(), core.y());
}
}
}
@@ -119,41 +115,24 @@ public class WaveSpawner{
}
private void reset(){
spawns.clear();
flySpawns.clear();
groundSpawns.clear();
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
if(world.tile(x, y).overlay() == Blocks.spawn){
addSpawns(x, y);
}
for(Tile tile : world.tiles){
if(tile.overlay() == Blocks.spawn){
spawns.add(tile);
}
}
}
private void addSpawns(int x, int y){
groundSpawns.add(world.tile(x, y));
FlyerSpawn fspawn = new FlyerSpawn();
fspawn.angle = Angles.angle(world.width() / 2f, world.height() / 2f, x, y);
flySpawns.add(fspawn);
}
private void spawnEffect(BaseUnit unit){
Effects.effect(Fx.unitSpawn, unit.x, unit.y, 0f, unit);
private void spawnEffect(Unitc unit){
Fx.unitSpawn.at(unit.x(), unit.y(), 0f, unit);
Time.run(30f, () -> {
unit.add();
Effects.effect(Fx.spawn, unit);
Fx.spawn.at(unit);
});
}
private interface SpawnConsumer{
void accept(float x, float y, boolean shockwave);
}
private class FlyerSpawn{
float angle;
}
}

View File

@@ -0,0 +1,102 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.world.meta.*;
public class FlyingAI extends AIController{
@Override
public void update(){
unit.rotation(unit.vel().angle());
if(unit.isFlying()){
unit.wobble();
}
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y())){
target = null;
}
if(retarget()){
targetClosest();
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
}
boolean shoot = false;
if(target != null){
attack(80f);
shoot = unit.inRange(target);
if(shoot && unit.type().hasWeapons()){
Vec2 to = Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed);
unit.aim(to);
}
}else{
target = unit.closestCore();
moveTo(Vars.state.rules.dropZoneRadius + 120f);
}
unit.controlWeapons(shoot, shoot);
}
protected void circle(float circleLength){
circle(circleLength, unit.type().speed);
}
protected void circle(float circleLength, float speed){
if(target == null) return;
vec.set(target).sub(unit);
if(vec.len() < circleLength){
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
}
vec.setLength(speed * Time.delta());
unit.moveAt(vec);
}
protected void moveTo(float circleLength){
if(target == null) return;
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta() * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
protected void attack(float circleLength){
vec.set(target).sub(unit);
float ang = unit.angleTo(target);
float diff = Angles.angleDist(ang, unit.rotation());
if(diff > 100f && vec.len() < circleLength){
vec.setAngle(unit.vel().angle());
}else{
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
}
vec.setLength(unit.type().speed * Time.delta());
unit.moveAt(vec);
}
}

View File

@@ -0,0 +1,95 @@
package mindustry.ai.types;
import arc.util.*;
import mindustry.ai.Pathfinder.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.pathfinder;
public class GroundAI extends AIController{
@Override
public void update(){
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
target = null;
//TODO this is hacky, cleanup
if(unit instanceof Legsc){
unit.lookAt(((Legsc)unit).baseRotation());
}
}
if(retarget()){
targetClosest();
}
Tilec core = unit.closestEnemyCore();
if(core == null) return;
float dst = unit.dst(core);
if(dst < unit.range() / 1.1f){
target = core;
}
if(dst > unit.range() * 0.5f){
moveToCore(PathTarget.enemyCores);
}
boolean rotate = false, shoot = false;
if(!Units.invalidateTarget(target, unit, unit.range())){
rotate = true;
shoot = unit.within(target, unit.range());
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
}
unit.controlWeapons(rotate, shoot);
}
protected void moveToCore(PathTarget path){
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path);
if(tile == targetTile) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed * Time.delta()));
}
protected void moveAwayFromCore(){
Team enemy = null;
for(Team team : unit.team().enemies()){
if(team.active()){
enemy = team;
break;
}
}
if(enemy == null){
for(Team team : unit.team().enemies()){
enemy = team;
break;
}
}
if(enemy == null) return;
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, enemy, PathTarget.enemyCores);
Tilec core = unit.closestCore();
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed * Time.delta()));
}
}

View File

@@ -0,0 +1,31 @@
package mindustry.ai.types;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
public class MimicAI extends AIController{
public @Nullable Unitc control;
public MimicAI(@Nullable Unitc control){
this.control = control;
}
public MimicAI(){
}
@Override
public void update(){
if(control != null){
unit.controlWeapons(control.isRotate(), control.isShooting());
//TODO this isn't accurate
unit.moveAt(Tmp.v1.set(control.vel()).limit(unit.type().speed));
if(control.isShooting()){
unit.aimLook(control.aimX(), control.aimY());
}else{
unit.lookAt(unit.vel().angle());
}
}
}
}

View File

@@ -1,7 +1,6 @@
package mindustry.content;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -10,7 +9,6 @@ import mindustry.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -19,6 +17,8 @@ import mindustry.world.blocks.*;
import mindustry.world.blocks.defense.*;
import mindustry.world.blocks.defense.turrets.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.legacy.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.power.*;
@@ -28,13 +28,12 @@ import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.units.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.modules.*;
public class Blocks implements ContentList{
public static Block
//environment
air, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, sandBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
@@ -57,7 +56,8 @@ public class Blocks implements ContentList{
scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet
//transport
conveyor, titaniumConveyor, plastaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router, overflowGate, underflowGate, massDriver,
conveyor, titaniumConveyor, plastaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router,
overflowGate, underflowGate, massDriver, massConveyor,
//liquid
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
@@ -76,11 +76,9 @@ public class Blocks implements ContentList{
duo, scatter, scorch, hail, arc, wave, lancer, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown,
//units
commandCenter, draugFactory, spiritFactory, phantomFactory, wraithFactory, ghoulFactory, revenantFactory, daggerFactory, crawlerFactory, titanFactory,
fortressFactory, repairPoint,
groundFactory, repairPoint
//upgrades
dartPad, deltaPad, tauPad, omegaPad, javelinPad, tridentPad, glaivePad;
;
@Override
public void load(){
@@ -92,7 +90,7 @@ public class Blocks implements ContentList{
hasShadow = false;
}
public void draw(Tile tile){}
public void drawBase(Tile tile){}
public void load(){}
public void init(){}
public boolean isHidden(){
@@ -107,23 +105,16 @@ public class Blocks implements ContentList{
}
};
//create special blockpart variants
for(int dx = 0; dx < BlockPart.maxSize; dx++){
for(int dy = 0; dy < BlockPart.maxSize; dy++){
int fx = dx - BlockPart.maxSize/2, fy = dy - BlockPart.maxSize/2;
if(fx != 0 || fy != 0){
new BlockPart(fx, fy);
}
}
}
spawn = new OverlayFloor("spawn"){
{
variants = 0;
}
public void draw(Tile tile){}
@Override
public void drawBase(Tile tile){}
};
cliff = new Cliff("cliff");
//Registers build blocks
//no reference is needed here since they can be looked up by name later
for(int i = 1; i <= BuildBlock.maxSize; i++){
@@ -139,6 +130,7 @@ public class Blocks implements ContentList{
statusDuration = 120f;
drownTime = 140f;
cacheLayer = CacheLayer.water;
albedo = 0.5f;
}};
water = new Floor("water"){{
@@ -149,6 +141,7 @@ public class Blocks implements ContentList{
liquidDrop = Liquids.water;
isLiquid = true;
cacheLayer = CacheLayer.water;
albedo = 0.5f;
}};
taintedWater = new Floor("tainted-water"){{
@@ -160,36 +153,25 @@ public class Blocks implements ContentList{
liquidDrop = Liquids.water;
isLiquid = true;
cacheLayer = CacheLayer.water;
albedo = 0.5f;
}};
darksandTaintedWater = new Floor("darksand-tainted-water"){{
darksandTaintedWater = new ShallowLiquid("darksand-tainted-water"){{
speedMultiplier = 0.75f;
variants = 0;
status = StatusEffects.wet;
statusDuration = 60f;
liquidDrop = Liquids.water;
isLiquid = true;
cacheLayer = CacheLayer.water;
albedo = 0.5f;
}};
sandWater = new Floor("sand-water"){{
sandWater = new ShallowLiquid("sand-water"){{
speedMultiplier = 0.8f;
variants = 0;
status = StatusEffects.wet;
statusDuration = 50f;
liquidDrop = Liquids.water;
isLiquid = true;
cacheLayer = CacheLayer.water;
albedo = 0.5f;
}};
darksandWater = new Floor("darksand-water"){{
darksandWater = new ShallowLiquid("darksand-water"){{
speedMultiplier = 0.8f;
variants = 0;
status = StatusEffects.wet;
statusDuration = 50f;
liquidDrop = Liquids.water;
isLiquid = true;
cacheLayer = CacheLayer.water;
albedo = 0.5f;
}};
tar = new Floor("tar"){{
@@ -203,6 +185,17 @@ public class Blocks implements ContentList{
cacheLayer = CacheLayer.tar;
}};
slag = new Floor("slag"){{
drownTime = 150f;
status = StatusEffects.melting;
statusDuration = 240f;
speedMultiplier = 0.19f;
variants = 0;
liquidDrop = Liquids.slag;
isLiquid = true;
cacheLayer = CacheLayer.slag;
}};
stone = new Floor("stone"){{
}};
@@ -217,16 +210,18 @@ public class Blocks implements ContentList{
}};
ignarock = new Floor("ignarock"){{
attributes.set(Attribute.water, -0.1f);
}};
hotrock = new Floor("hotrock"){{
attributes.set(Attribute.heat, 0.5f);
attributes.set(Attribute.water, -0.2f);
blendGroup = ignarock;
}};
magmarock = new Floor("magmarock"){{
attributes.set(Attribute.heat, 0.75f);
attributes.set(Attribute.water, -0.5f);
updateEffect = Fx.magmasmoke;
blendGroup = ignarock;
}};
@@ -241,6 +236,10 @@ public class Blocks implements ContentList{
playerUnmineable = true;
}};
((ShallowLiquid)darksandTaintedWater).set(Blocks.taintedWater, Blocks.darksand);
((ShallowLiquid)sandWater).set(Blocks.water, Blocks.sand);
((ShallowLiquid)darksandWater).set(Blocks.water, Blocks.darksand);
holostone = new Floor("holostone"){{
}};
@@ -251,6 +250,7 @@ public class Blocks implements ContentList{
salt = new Floor("salt"){{
variants = 0;
attributes.set(Attribute.water, -0.2f);
}};
snow = new Floor("snow"){{
@@ -258,13 +258,13 @@ public class Blocks implements ContentList{
}};
ice = new Floor("ice"){{
//TODO fix drag/speed
dragMultiplier = 1f;
speedMultiplier = 1f;
dragMultiplier = 0.35f;
speedMultiplier = 0.9f;
attributes.set(Attribute.water, 0.4f);
}};
iceSnow = new Floor("ice-snow"){{
dragMultiplier = 0.6f;
variants = 3;
attributes.set(Attribute.water, 0.3f);
}};
@@ -292,6 +292,7 @@ public class Blocks implements ContentList{
icerocks = new StaticWall("icerocks"){{
variants = 2;
iceSnow.asFloor().wall = this;
}};
snowrocks = new StaticWall("snowrocks"){{
@@ -355,11 +356,13 @@ public class Blocks implements ContentList{
moss = new Floor("moss"){{
variants = 3;
attributes.set(Attribute.spores, 0.15f);
wall = sporePine;
}};
sporeMoss = new Floor("spore-moss"){{
variants = 3;
attributes.set(Attribute.spores, 0.3f);
wall = sporerocks;
}};
metalFloor = new Floor("metal-floor"){{
@@ -506,13 +509,10 @@ public class Blocks implements ContentList{
int topRegion = reg("-top");
drawer = tile -> {
Draw.rect(region, tile.drawx(), tile.drawy());
GenericCrafterEntity entity = tile.ent();
drawer = entity -> {
Draw.rect(region, entity.x(), entity.y());
Draw.alpha(Mathf.absin(entity.totalProgress, 3f, 0.9f) * entity.warmup);
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy());
Draw.rect(reg(topRegion), entity.x(), entity.y());
Draw.reset();
};
}};
@@ -533,24 +533,22 @@ public class Blocks implements ContentList{
drawIcons = () -> new TextureRegion[]{Core.atlas.find(name + "-bottom"), Core.atlas.find(name), Core.atlas.find(name + "-weave")};
drawer = tile -> {
GenericCrafterEntity entity = tile.ent();
Draw.rect(reg(bottomRegion), tile.drawx(), tile.drawy());
Draw.rect(reg(weaveRegion), tile.drawx(), tile.drawy(), entity.totalProgress);
drawer = entity -> {
Draw.rect(reg(bottomRegion), entity.x(), entity.y());
Draw.rect(reg(weaveRegion), entity.x(), entity.y(), entity.totalProgress);
Draw.color(Pal.accent);
Draw.alpha(entity.warmup);
Lines.lineAngleCenter(
tile.drawx() + Mathf.sin(entity.totalProgress, 6f, Vars.tilesize / 3f * size),
tile.drawy(),
entity.x() + Mathf.sin(entity.totalProgress, 6f, Vars.tilesize / 3f * size),
entity.y(),
90,
size * Vars.tilesize / 2f);
Draw.reset();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.rect(region, entity.x(), entity.y());
};
}};
@@ -586,21 +584,19 @@ public class Blocks implements ContentList{
drawIcons = () -> new TextureRegion[]{Core.atlas.find(name + "-bottom"), Core.atlas.find(name + "-top")};
drawer = tile -> {
LiquidModule mod = tile.entity.liquids;
drawer = entity -> {
int rotation = rotate ? entity.rotation() * 90 : 0;
int rotation = rotate ? tile.rotation() * 90 : 0;
Draw.rect(reg(bottomRegion), entity.x(), entity.y(), rotation);
Draw.rect(reg(bottomRegion), tile.drawx(), tile.drawy(), rotation);
if(mod.total() > 0.001f){
if(entity.liquids().total() > 0.001f){
Draw.color(outputLiquid.liquid.color);
Draw.alpha(mod.get(outputLiquid.liquid) / liquidCapacity);
Draw.rect(reg(liquidRegion), tile.drawx(), tile.drawy(), rotation);
Draw.alpha(entity.liquids().get(outputLiquid.liquid) / liquidCapacity);
Draw.rect(reg(liquidRegion), entity.x(), entity.y(), rotation);
Draw.color();
}
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy(), rotation);
Draw.rect(reg(topRegion), entity.x(), entity.y(), rotation);
};
}};
@@ -678,15 +674,13 @@ public class Blocks implements ContentList{
int topRegion = reg("-top");
drawIcons = () -> new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-top")};
drawer = tile -> {
GenericCrafterEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.rect(reg(frameRegions[(int)Mathf.absin(entity.totalProgress, 5f, 2.999f)]), tile.drawx(), tile.drawy());
Draw.color(Color.clear, tile.entity.liquids.current().color, tile.entity.liquids.total() / liquidCapacity);
Draw.rect(reg(liquidRegion), tile.drawx(), tile.drawy());
drawer = entity -> {
Draw.rect(region, entity.x(), entity.y());
Draw.rect(reg(frameRegions[(int)Mathf.absin(entity.totalProgress, 5f, 2.999f)]), entity.x(), entity.y());
Draw.color(Color.clear, entity.liquids().current().color, entity.liquids().total() / liquidCapacity);
Draw.rect(reg(liquidRegion), entity.x(), entity.y());
Draw.color();
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy());
Draw.rect(reg(topRegion), entity.x(), entity.y());
};
}};
@@ -705,11 +699,9 @@ public class Blocks implements ContentList{
drawIcons = () -> new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-rotator")};
drawer = tile -> {
GenericCrafterEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.rect(reg(rotatorRegion), tile.drawx(), tile.drawy(), entity.totalProgress * 2f);
drawer = entity -> {
Draw.rect(region, entity.x(), entity.y());
Draw.rect(reg(rotatorRegion), entity.x(), entity.y(), entity.totalProgress * 2f);
};
}};
@@ -990,6 +982,10 @@ public class Blocks implements ContentList{
consumes.power(1.75f);
}};
massConveyor = new MassConveyor("mass-conveyor"){{
requirements(Category.distribution, ItemStack.with(Items.copper, 1));
}};
//endregion
//region liquid
@@ -1040,7 +1036,7 @@ public class Blocks implements ContentList{
liquidCapacity = 20f;
}};
liquidTank = new LiquidTank("liquid-tank"){{
liquidTank = new LiquidRouter("liquid-tank"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 25, Items.metaglass, 25));
size = 3;
liquidCapacity = 1500f;
@@ -1297,13 +1293,13 @@ public class Blocks implements ContentList{
size = 5;
}};
vault = new Vault("vault"){{
vault = new StorageBlock("vault"){{
requirements(Category.effect, ItemStack.with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
}};
container = new Vault("container"){{
container = new StorageBlock("container"){{
requirements(Category.effect, ItemStack.with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
@@ -1336,7 +1332,7 @@ public class Blocks implements ContentList{
//endregion
//region turrets
duo = new DoubleTurret("duo"){{
duo = new ItemTurret("duo"){{
requirements(Category.turret, ItemStack.with(Items.copper, 35), true);
ammo(
Items.copper, Bullets.standardCopper,
@@ -1344,7 +1340,11 @@ public class Blocks implements ContentList{
Items.pyratite, Bullets.standardIncendiary,
Items.silicon, Bullets.standardHoming
);
reload = 20f;
spread = 4f;
shots = 2;
alternate = true;
reloadTime = 20f;
restitution = 0.03f;
range = 100;
shootCone = 15f;
@@ -1361,14 +1361,14 @@ public class Blocks implements ContentList{
Items.lead, Bullets.flakLead,
Items.metaglass, Bullets.flakGlass
);
reload = 18f;
reloadTime = 18f;
range = 170f;
size = 2;
burstSpacing = 5f;
shots = 2;
targetGround = false;
recoil = 2f;
recoilAmount = 2f;
rotatespeed = 15f;
inaccuracy = 17f;
shootCone = 35f;
@@ -1383,8 +1383,8 @@ public class Blocks implements ContentList{
Items.coal, Bullets.basicFlame,
Items.pyratite, Bullets.pyraFlame
);
recoil = 0f;
reload = 5f;
recoilAmount = 0f;
reloadTime = 5f;
coolantMultiplier = 2f;
range = 60f;
shootCone = 50f;
@@ -1401,8 +1401,8 @@ public class Blocks implements ContentList{
Items.silicon, Bullets.artilleryHoming,
Items.pyratite, Bullets.artilleryIncendiary
);
reload = 60f;
recoil = 2f;
reloadTime = 60f;
recoilAmount = 2f;
range = 230f;
inaccuracy = 1f;
shootCone = 10f;
@@ -1419,8 +1419,8 @@ public class Blocks implements ContentList{
Liquids.oil, Bullets.oilShot
);
size = 2;
recoil = 0f;
reload = 2f;
recoilAmount = 0f;
reloadTime = 2f;
inaccuracy = 5f;
shootCone = 50f;
shootEffect = Fx.shootLiquid;
@@ -1436,8 +1436,8 @@ public class Blocks implements ContentList{
chargeMaxDelay = 30f;
chargeEffects = 7;
shootType = Bullets.lancerLaser;
recoil = 2f;
reload = 90f;
recoilAmount = 2f;
reloadTime = 90f;
cooldown = 0.03f;
powerUse = 2.5f;
shootShake = 2f;
@@ -1455,7 +1455,7 @@ public class Blocks implements ContentList{
arc = new PowerTurret("arc"){{
requirements(Category.turret, ItemStack.with(Items.copper, 35, Items.lead, 50));
shootType = Bullets.arc;
reload = 35f;
reloadTime = 35f;
shootCone = 40f;
rotatespeed = 8f;
powerUse = 1.5f;
@@ -1463,7 +1463,7 @@ public class Blocks implements ContentList{
range = 90f;
shootEffect = Fx.lightningShoot;
heatColor = Color.red;
recoil = 1f;
recoilAmount = 1f;
size = 1;
health = 260;
shootSound = Sounds.spark;
@@ -1476,7 +1476,7 @@ public class Blocks implements ContentList{
Items.pyratite, Bullets.missileIncendiary,
Items.surgealloy, Bullets.missileSurge
);
reload = 40f;
reloadTime = 40f;
shots = 4;
burstSpacing = 5;
inaccuracy = 10f;
@@ -1499,11 +1499,11 @@ public class Blocks implements ContentList{
size = 2;
range = 150f;
reload = 38f;
reloadTime = 38f;
restitution = 0.03f;
ammoEjectBack = 3f;
cooldown = 0.03f;
recoil = 3f;
recoilAmount = 3f;
shootShake = 2f;
burstSpacing = 3f;
shots = 4;
@@ -1515,10 +1515,10 @@ public class Blocks implements ContentList{
fuse = new ItemTurret("fuse"){{
requirements(Category.turret, ItemStack.with(Items.copper, 225, Items.graphite, 225, Items.thorium, 100));
reload = 35f;
reloadTime = 35f;
shootShake = 4f;
range = 90f;
recoil = 5f;
recoilAmount = 5f;
shots = 3;
spread = 20f;
restitution = 0.1f;
@@ -1541,25 +1541,25 @@ public class Blocks implements ContentList{
}
@Override
public void init(mindustry.entities.type.Bullet b){
public void init(Bulletc b){
for(int i = 0; i < rays; i++){
Damage.collideLine(b, b.getTeam(), hitEffect, b.x, b.y, b.rot(), rayLength - Math.abs(i - (rays / 2)) * 20f);
Damage.collideLine(b, b.team(), hitEffect, b.x(), b.y(), b.rotation(), rayLength - Math.abs(i - (rays / 2)) * 20f);
}
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
super.draw(b);
Draw.color(Color.white, Pal.lancerLaser, b.fin());
//Draw.alpha(b.fout());
for(int i = 0; i < 7; i++){
Tmp.v1.trns(b.rot(), i * 8f);
Tmp.v1.trns(b.rotation(), i * 8f);
float sl = Mathf.clamp(b.fout() - 0.5f) * (80f - i * 10);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, 4f, sl, b.rot() + 90);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, 4f, sl, b.rot() - 90);
Drawf.tri(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, 4f, sl, b.rotation() + 90);
Drawf.tri(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, 4f, sl, b.rotation() - 90);
}
Drawf.tri(b.x, b.y, 20f * b.fout(), (rayLength + 50), b.rot());
Drawf.tri(b.x, b.y, 20f * b.fout(), 10f, b.rot() + 180f);
Drawf.tri(b.x(), b.y(), 20f * b.fout(), (rayLength + 50), b.rotation());
Drawf.tri(b.x(), b.y(), 20f * b.fout(), 10f, b.rotation() + 180f);
Draw.reset();
}
});
@@ -1577,13 +1577,13 @@ public class Blocks implements ContentList{
size = 3;
shots = 4;
inaccuracy = 12f;
reload = 60f;
reloadTime = 60f;
ammoEjectBack = 5f;
ammoUseEffect = Fx.shellEjectBig;
cooldown = 0.03f;
velocityInaccuracy = 0.2f;
restitution = 0.02f;
recoil = 6f;
recoilAmount = 6f;
shootShake = 2f;
range = 290f;
@@ -1600,10 +1600,10 @@ public class Blocks implements ContentList{
Items.surgealloy, Bullets.flakSurge
);
xRand = 4f;
reload = 6f;
reloadTime = 6f;
range = 200f;
size = 3;
recoil = 3f;
recoilAmount = 3f;
rotatespeed = 10f;
inaccuracy = 10f;
shootCone = 30f;
@@ -1612,22 +1612,22 @@ public class Blocks implements ContentList{
health = 145 * size * size;
}};
spectre = new DoubleTurret("spectre"){{
spectre = new ItemTurret("spectre"){{
requirements(Category.turret, ItemStack.with(Items.copper, 350, Items.graphite, 300, Items.surgealloy, 250, Items.plastanium, 175, Items.thorium, 250));
ammo(
Items.graphite, Bullets.standardDenseBig,
Items.pyratite, Bullets.standardIncendiaryBig,
Items.thorium, Bullets.standardThoriumBig
);
reload = 6f;
reloadTime = 6f;
coolantMultiplier = 0.5f;
restitution = 0.1f;
ammoUseEffect = Fx.shellEjectBig;
range = 200f;
inaccuracy = 3f;
recoil = 3f;
xRand = 3f;
shotWidth = 4f;
recoilAmount = 3f;
spread = 8f;
alternate = true;
shootShake = 2f;
shots = 2;
size = 4;
@@ -1643,11 +1643,11 @@ public class Blocks implements ContentList{
shootType = Bullets.meltdownLaser;
shootEffect = Fx.shootBigSmoke2;
shootCone = 40f;
recoil = 4f;
recoilAmount = 4f;
size = 4;
shootShake = 2f;
range = 190f;
reload = 80f;
reloadTime = 80f;
firingMoveFract = 0.5f;
shootDuration = 220f;
powerUse = 14f;
@@ -1662,106 +1662,16 @@ public class Blocks implements ContentList{
//endregion
//region units
draugFactory = new UnitFactory("draug-factory"){{
//for testing only.
groundFactory = new UnitFactory("ground-factory"){{
requirements(Category.units, ItemStack.with(Items.copper, 30, Items.lead, 70));
unitType = UnitTypes.draug;
produceTime = 2500;
size = 2;
maxSpawn = 1;
consumes.power(1.2f);
consumes.items();
}};
spiritFactory = new UnitFactory("spirit-factory"){{
requirements(Category.units, ItemStack.with(Items.metaglass, 45, Items.lead, 55, Items.silicon, 45));
unitType = UnitTypes.spirit;
produceTime = 4000;
size = 2;
maxSpawn = 1;
consumes.power(1.2f);
consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30));
}};
phantomFactory = new UnitFactory("phantom-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 50, Items.thorium, 60, Items.lead, 65, Items.silicon, 105));
unitType = UnitTypes.phantom;
produceTime = 4400;
size = 2;
maxSpawn = 1;
consumes.power(2.5f);
consumes.items(new ItemStack(Items.silicon, 50), new ItemStack(Items.lead, 30), new ItemStack(Items.titanium, 20));
}};
commandCenter = new CommandCenter("command-center"){{
requirements(Category.units, ItemStack.with(Items.copper, 200, Items.lead, 250, Items.silicon, 250, Items.graphite, 100));
flags = EnumSet.of(BlockFlag.rally, BlockFlag.comandCenter);
size = 2;
health = size * size * 55;
}};
wraithFactory = new UnitFactory("wraith-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 30, Items.lead, 40, Items.silicon, 45));
unitType = UnitTypes.wraith;
produceTime = 700;
size = 2;
consumes.power(0.5f);
consumes.items(new ItemStack(Items.silicon, 10), new ItemStack(Items.titanium, 5));
}};
ghoulFactory = new UnitFactory("ghoul-factory"){{
requirements(Category.units, ItemStack.with(Items.titanium, 75, Items.lead, 65, Items.silicon, 110));
unitType = UnitTypes.ghoul;
produceTime = 1150;
plans = new UnitPlan[]{
new UnitPlan(UnitTypes.dagger, 60f, ItemStack.with(Items.silicon, 10)),
new UnitPlan(UnitTypes.wraith, 60f, ItemStack.with(Items.silicon, 10)),
};
size = 3;
consumes.power(1.2f);
consumes.items(new ItemStack(Items.silicon, 15), new ItemStack(Items.titanium, 10));
}};
revenantFactory = new UnitFactory("revenant-factory"){{
requirements(Category.units, ItemStack.with(Items.plastanium, 50, Items.titanium, 150, Items.lead, 150, Items.silicon, 200));
unitType = UnitTypes.revenant;
produceTime = 2000;
size = 4;
consumes.power(3f);
consumes.items(new ItemStack(Items.silicon, 40), new ItemStack(Items.titanium, 30));
}};
daggerFactory = new UnitFactory("dagger-factory"){{
requirements(Category.units, ItemStack.with(Items.lead, 55, Items.silicon, 35));
unitType = UnitTypes.dagger;
produceTime = 850;
size = 2;
consumes.power(0.5f);
consumes.items(new ItemStack(Items.silicon, 6));
}};
crawlerFactory = new UnitFactory("crawler-factory"){{
requirements(Category.units, ItemStack.with(Items.lead, 45, Items.silicon, 30));
unitType = UnitTypes.crawler;
produceTime = 300;
size = 2;
maxSpawn = 6;
consumes.power(0.5f);
consumes.items(new ItemStack(Items.coal, 10));
}};
titanFactory = new UnitFactory("titan-factory"){{
requirements(Category.units, ItemStack.with(Items.graphite, 50, Items.lead, 50, Items.silicon, 45));
unitType = UnitTypes.titan;
produceTime = 1050;
size = 3;
consumes.power(0.60f);
consumes.items(new ItemStack(Items.silicon, 12));
}};
fortressFactory = new UnitFactory("fortress-factory"){{
requirements(Category.units, ItemStack.with(Items.thorium, 40, Items.lead, 110, Items.silicon, 75));
unitType = UnitTypes.fortress;
produceTime = 2000;
size = 3;
maxSpawn = 3;
consumes.power(1.4f);
consumes.items(new ItemStack(Items.silicon, 20), new ItemStack(Items.graphite, 10));
consumes.items(new ItemStack(Items.silicon, 10));
}};
repairPoint = new RepairPoint("repair-point"){{
@@ -1771,58 +1681,6 @@ public class Blocks implements ContentList{
powerUse = 1f;
}};
//endregion
//region upgrades
dartPad = new MechPad("dart-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 100, Items.graphite, 50, Items.copper, 75));
mech = Mechs.alpha;
size = 2;
consumes.power(0.5f);
}};
deltaPad = new MechPad("delta-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 175, Items.titanium, 175, Items.copper, 200, Items.silicon, 225, Items.thorium, 150));
mech = Mechs.delta;
size = 2;
consumes.power(0.7f);
}};
tauPad = new MechPad("tau-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 125, Items.titanium, 125, Items.copper, 125, Items.silicon, 125));
mech = Mechs.tau;
size = 2;
consumes.power(1f);
}};
omegaPad = new MechPad("omega-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 225, Items.graphite, 275, Items.silicon, 325, Items.thorium, 300, Items.surgealloy, 120));
mech = Mechs.omega;
size = 3;
consumes.power(1.2f);
}};
javelinPad = new MechPad("javelin-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 175, Items.silicon, 225, Items.titanium, 250, Items.plastanium, 200, Items.phasefabric, 100));
mech = Mechs.javelin;
size = 2;
consumes.power(0.8f);
}};
tridentPad = new MechPad("trident-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 125, Items.copper, 125, Items.silicon, 125, Items.titanium, 150, Items.plastanium, 100));
mech = Mechs.trident;
size = 2;
consumes.power(1f);
}};
glaivePad = new MechPad("glaive-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 225, Items.silicon, 325, Items.titanium, 350, Items.plastanium, 300, Items.surgealloy, 100));
mech = Mechs.glaive;
size = 3;
consumes.power(1.2f);
}};
//endregion
//region sandbox
@@ -1867,6 +1725,14 @@ public class Blocks implements ContentList{
consumes.power(0.05f);
}};
//endregion
//region legacy
//looked up by name, no ref needed
new LegacyMechPad("legacy-mech-pad");
new LegacyUnitFactory("legacy-unit-factory");
new LegacyCommandCenter("legacy-command-center");
//endregion
}
}

View File

@@ -4,11 +4,10 @@ import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.ctype.ContentList;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
@@ -384,7 +383,7 @@ public class Bullets implements ContentList{
}};
damageLightning = new BulletType(0.0001f, 0f){{
lifetime = Lightning.lifetime;
lifetime = Fx.lightning.lifetime;
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
status = StatusEffects.shocked;
@@ -410,32 +409,32 @@ public class Bullets implements ContentList{
}
@Override
public void init(Bullet b){
b.velocity().setLength(0.6f + Mathf.random(2f));
public void init(Bulletc b){
b.vel().setLength(0.6f + Mathf.random(2f));
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.gray, b.fin());
Fill.circle(b.x, b.y, 3f * b.fout());
Fill.circle(b.x(), b.y(), 3f * b.fout());
Draw.reset();
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
if(Mathf.chance(0.04 * Time.delta())){
Tile tile = world.tileWorld(b.x, b.y);
Tile tile = world.tileWorld(b.x(), b.y());
if(tile != null){
Fire.create(tile);
Fires.create(tile);
}
}
if(Mathf.chance(0.1 * Time.delta())){
Effects.effect(Fx.fireballsmoke, b.x, b.y);
Fx.fireballsmoke.at(b.x(), b.y());
}
if(Mathf.chance(0.1 * Time.delta())){
Effects.effect(Fx.ballfire, b.x, b.y);
Fx.ballfire.at(b.x(), b.y());
}
}
};
@@ -452,6 +451,7 @@ public class Bullets implements ContentList{
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;
status = StatusEffects.burning;
keepVelocity = false;
}
@Override
@@ -460,7 +460,7 @@ public class Bullets implements ContentList{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
}
};
@@ -479,50 +479,17 @@ public class Bullets implements ContentList{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
}
};
lancerLaser = new BulletType(0.001f, 140){
Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
float[] lenscales = {1f, 1.1f, 1.13f, 1.14f};
float length = 160f;
{
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
hitSize = 4;
lifetime = 16f;
pierce = true;
}
@Override
public float range(){
return length;
}
@Override
public void init(Bullet b){
Damage.collideLine(b, b.getTeam(), hitEffect, b.x, b.y, b.rot(), length);
}
@Override
public void draw(Bullet b){
float f = Mathf.curve(b.fin(), 0f, 0.2f);
float baseLen = length * f;
Lines.lineAngle(b.x, b.y, b.rot(), baseLen);
for(int s = 0; s < 3; s++){
Draw.color(colors[s]);
for(int i = 0; i < tscales.length; i++){
Lines.stroke(7f * b.fout() * (s == 0 ? 1.5f : s == 1 ? 1f : 0.3f) * tscales[i]);
Lines.lineAngle(b.x, b.y, b.rot(), baseLen * lenscales[i]);
}
}
Draw.reset();
}
};
lancerLaser = new LaserBulletType(140){{
colors = new Color[]{Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
hitSize = 4;
lifetime = 16f;
}};
meltdownLaser = new BulletType(0.001f, 70){
Color tmpColor = new Color();
@@ -542,32 +509,32 @@ public class Bullets implements ContentList{
}
@Override
public void update(Bullet b){
if(b.timer.get(1, 5f)){
Damage.collideLine(b, b.getTeam(), hitEffect, b.x, b.y, b.rot(), length, true);
public void update(Bulletc b){
if(b.timer(1, 5f)){
Damage.collideLine(b, b.team(), hitEffect, b.x(), b.y(), b.rotation(), length, true);
}
Effects.shake(1f, 1f, b.x, b.y);
Effects.shake(1f, 1f, b.x(), b.y());
}
@Override
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hitEffect, colors[2], hitx, hity);
public void hit(Bulletc b, float hitx, float hity){
hitEffect.at(hitx, hity, colors[2]);
if(Mathf.chance(0.4)){
Fire.create(world.tileWorld(hitx + Mathf.range(5f), hity + Mathf.range(5f)));
Fires.create(world.tileWorld(hitx + Mathf.range(5f), hity + Mathf.range(5f)));
}
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
float baseLen = (length) * b.fout();
Lines.lineAngle(b.x, b.y, b.rot(), baseLen);
Lines.lineAngle(b.x(), b.y(), b.rotation(), baseLen);
for(int s = 0; s < colors.length; s++){
Draw.color(tmpColor.set(colors[s]).mul(1f + Mathf.absin(Time.time(), 1f, 0.1f)));
for(int i = 0; i < tscales.length; i++){
Tmp.v1.trns(b.rot() + 180f, (lenscales[i] - 1f) * 35f);
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
Lines.stroke((9f + Mathf.absin(Time.time(), 0.8f, 1.5f)) * b.fout() * strokes[s] * tscales[i]);
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rot(), baseLen * lenscales[i], CapStyle.none);
Lines.lineAngle(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], CapStyle.none);
}
}
Draw.reset();
@@ -613,31 +580,20 @@ public class Bullets implements ContentList{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
}
@Override
public void init(Bullet b){
Lightning.create(b.getTeam(), Pal.lancerLaser, damage * (b.getOwner() instanceof Player ? state.rules.playerDamageMultiplier : 1f), b.x, b.y, b.rot(), 30);
public void init(Bulletc b){
//TODO owners are never players...
Lightning.create(b.team(), Pal.lancerLaser, damage * (b.owner() instanceof Playerc ? state.rules.playerDamageMultiplier : 1f), b.x(), b.y(), b.rotation(), 30);
}
};
arc = new BulletType(0.001f, 21){
{
lifetime = 1;
despawnEffect = Fx.none;
hitEffect = Fx.hitLancer;
}
@Override
public void draw(Bullet b){
}
@Override
public void init(Bullet b){
Lightning.create(b.getTeam(), Pal.lancerLaser, damage, b.x, b.y, b.rot(), 25);
}
};
arc = new LightningBulletType(){{
damage = 21;
lightningLength = 25;
}};
driverBolt = new MassDriverBolt();
@@ -678,12 +634,12 @@ public class Bullets implements ContentList{
}
@Override
public void hit(Bullet b, float x, float y){
public void hit(Bulletc b, float x, float y){
super.hit(b, x, y);
for(int i = 0; i < 3; i++){
Tile tile = world.tileWorld(x + Mathf.range(8f), y + Mathf.range(8f));
Puddle.deposit(tile, Liquids.oil, 5f);
Puddles.deposit(tile, Liquids.oil, 5f);
}
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,378 +0,0 @@
package mindustry.content;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import mindustry.ctype.ContentList;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
public class Mechs implements ContentList{
public static Mech alpha, delta, tau, omega, dart, javelin, trident, glaive;
public static Mech starter;
@Override
public void load(){
alpha = new Mech("alpha-mech", false){
{
drillPower = 1;
mineSpeed = 1.5f;
mass = 1.2f;
speed = 0.5f;
itemCapacity = 40;
boostSpeed = 0.95f;
buildPower = 1.2f;
engineColor = Color.valueOf("ffd37f");
health = 250f;
weapon = new Weapon("blaster"){{
length = 1.5f;
reload = 14f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardMechSmall;
}};
}
@Override
public void updateAlt(Player player){
player.healBy(Time.delta() * 0.09f);
}
};
delta = new Mech("delta-mech", false){
float cooldown = 120;
{
drillPower = -1;
speed = 0.75f;
boostSpeed = 0.95f;
itemCapacity = 15;
mass = 0.9f;
health = 150f;
buildPower = 0.9f;
weaponOffsetX = -1;
weaponOffsetY = -1;
engineColor = Color.valueOf("d3ddff");
weapon = new Weapon("shockgun"){{
shake = 2f;
length = 1f;
reload = 55f;
shotDelay = 3f;
alternate = true;
shots = 2;
inaccuracy = 0f;
ejectEffect = Fx.none;
bullet = Bullets.lightning;
shootSound = Sounds.spark;
}};
}
@Override
public void onLand(Player player){
if(player.timer.get(Player.timerAbility, cooldown)){
Effects.shake(1f, 1f, player);
Effects.effect(Fx.landShock, player);
for(int i = 0; i < 8; i++){
Time.run(Mathf.random(8f), () -> Lightning.create(player.getTeam(), Pal.lancerLaser, 17f * Vars.state.rules.playerDamageMultiplier, player.x, player.y, Mathf.random(360f), 14));
}
}
}
};
tau = new Mech("tau-mech", false){
float healRange = 60f;
float healAmount = 10f;
float healReload = 160f;
boolean wasHealed;
{
drillPower = 4;
mineSpeed = 3f;
itemCapacity = 70;
weaponOffsetY = -1;
weaponOffsetX = 1;
mass = 1.75f;
speed = 0.44f;
drag = 0.35f;
boostSpeed = 0.8f;
canHeal = true;
health = 200f;
buildPower = 1.6f;
engineColor = Pal.heal;
weapon = new Weapon("heal-blaster"){{
length = 1.5f;
reload = 24f;
alternate = false;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBullet;
shootSound = Sounds.pew;
}};
}
@Override
public void updateAlt(Player player){
if(player.timer.get(Player.timerAbility, healReload)){
wasHealed = false;
Units.nearby(player.getTeam(), player.x, player.y, healRange, unit -> {
if(unit.health < unit.maxHealth()){
Effects.effect(Fx.heal, unit);
wasHealed = true;
}
unit.healBy(healAmount);
});
if(wasHealed){
Effects.effect(Fx.healWave, player);
}
}
}
};
omega = new Mech("omega-mech", false){
protected TextureRegion armorRegion;
{
drillPower = 2;
mineSpeed = 1.5f;
itemCapacity = 80;
speed = 0.36f;
boostSpeed = 0.6f;
mass = 4f;
shake = 4f;
weaponOffsetX = 1;
weaponOffsetY = 0;
engineColor = Color.valueOf("feb380");
health = 350f;
buildPower = 1.5f;
weapon = new Weapon("swarmer"){{
length = 1.5f;
recoil = 4f;
reload = 38f;
shots = 4;
spacing = 8f;
inaccuracy = 8f;
alternate = true;
ejectEffect = Fx.none;
shake = 3f;
bullet = Bullets.missileSwarm;
shootSound = Sounds.shootBig;
}};
}
@Override
public float getRotationAlpha(Player player){
return 0.6f - player.shootHeat * 0.3f;
}
@Override
public float spreadX(Player player){
return player.shootHeat * 2f;
}
@Override
public void load(){
super.load();
armorRegion = Core.atlas.find(name + "-armor");
}
@Override
public void updateAlt(Player player){
float scl = 1f - player.shootHeat / 2f*Time.delta();
player.velocity().scl(scl);
}
@Override
public float getExtraArmor(Player player){
return player.shootHeat * 30f;
}
@Override
public void draw(Player player){
if(player.shootHeat <= 0.01f) return;
Shaders.build.progress = player.shootHeat;
Shaders.build.region = armorRegion;
Shaders.build.time = Time.time() / 10f;
Shaders.build.color.set(Pal.accent).a = player.shootHeat;
Draw.shader(Shaders.build);
Draw.rect(armorRegion, player.x, player.y, player.rotation);
Draw.shader();
}
};
dart = new Mech("dart-ship", true){
{
drillPower = 1;
mineSpeed = 3f;
speed = 0.5f;
drag = 0.09f;
health = 200f;
weaponOffsetX = -1;
weaponOffsetY = -1;
engineColor = Pal.lightTrail;
cellTrnsY = 1f;
buildPower = 1.1f;
weapon = new Weapon("blaster"){{
length = 1.5f;
reload = 15f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}};
}
@Override
public boolean alwaysUnlocked(){
return true;
}
};
javelin = new Mech("javelin-ship", true){
float minV = 3.6f;
float maxV = 6f;
TextureRegion shield;
{
drillPower = -1;
speed = 0.11f;
drag = 0.01f;
mass = 2f;
health = 170f;
engineColor = Color.valueOf("d3ddff");
cellTrnsY = 1f;
weapon = new Weapon("missiles"){{
length = 1.5f;
reload = 70f;
shots = 4;
inaccuracy = 2f;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 0.2f;
spacing = 1f;
bullet = Bullets.missileJavelin;
shootSound = Sounds.missile;
}};
}
@Override
public void load(){
super.load();
shield = Core.atlas.find(name + "-shield");
}
@Override
public float getRotationAlpha(Player player){
return 0.5f;
}
@Override
public void updateAlt(Player player){
float scl = scld(player);
if(Mathf.chance(Time.delta() * (0.15 * scl))){
Effects.effect(Fx.hitLancer, Pal.lancerLaser, player.x, player.y);
Lightning.create(player.getTeam(), Pal.lancerLaser, 10f * Vars.state.rules.playerDamageMultiplier,
player.x + player.velocity().x, player.y + player.velocity().y, player.rotation, 14);
}
}
@Override
public void draw(Player player){
float scl = scld(player);
if(scl < 0.01f) return;
Draw.color(Pal.lancerLaser);
Draw.alpha(scl / 2f);
Draw.blend(Blending.additive);
Draw.rect(shield, player.x + Mathf.range(scl / 2f), player.y + Mathf.range(scl / 2f), player.rotation - 90);
Draw.blend();
}
float scld(Player player){
return Mathf.clamp((player.velocity().len() - minV) / (maxV - minV));
}
};
trident = new Mech("trident-ship", true){
{
drillPower = 2;
speed = 0.15f;
drag = 0.034f;
mass = 2.5f;
turnCursor = false;
health = 250f;
itemCapacity = 30;
engineColor = Color.valueOf("84f491");
cellTrnsY = 1f;
buildPower = 2.5f;
weapon = new Weapon("bomber"){{
length = 0f;
width = 2f;
reload = 25f;
shots = 2;
shotDelay = 1f;
shots = 8;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 1f;
inaccuracy = 20f;
ignoreRotation = true;
bullet = new BombBulletType(16f, 25f, "shell"){{
bulletWidth = 10f;
bulletHeight = 14f;
hitEffect = Fx.flakExplosion;
shootEffect = Fx.none;
smokeEffect = Fx.none;
shootSound = Sounds.artillery;
}};
}};
}
@Override
public boolean canShoot(Player player){
return player.velocity().len() > 1.2f;
}
};
glaive = new Mech("glaive-ship", true){
{
drillPower = 4;
mineSpeed = 1.3f;
speed = 0.32f;
drag = 0.06f;
mass = 3f;
health = 240f;
itemCapacity = 60;
engineColor = Color.valueOf("feb380");
cellTrnsY = 1f;
buildPower = 1.2f;
weapon = new Weapon("bomber"){{
length = 1.5f;
reload = 13f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardGlaive;
shootSound = Sounds.shootSnap;
}};
}
};
starter = dart;
}
}

View File

@@ -0,0 +1,45 @@
package mindustry.content;
import arc.graphics.*;
import mindustry.ctype.*;
import mindustry.graphics.g3d.*;
import mindustry.maps.planet.*;
import mindustry.type.*;
public class Planets implements ContentList{
public static Planet
sun,
starter;
@Override
public void load(){
sun = new Planet("sun", null, 0, 2){{
bloom = true;
//lightColor = Color.valueOf("f4ee8e");
meshLoader = () -> new SunMesh(this, 3){{
setColors(
1.1f,
Color.valueOf("ff7a38"),
Color.valueOf("ff9638"),
Color.valueOf("ffc64c"),
Color.valueOf("ffc64c"),
Color.valueOf("ffe371"),
Color.valueOf("f4ee8e")
);
scale = 1f;
speed = 1000f;
falloff = 0.3f;
octaves = 4;
spread = 1.2f;
magnitude = 0f;
}};
}};
starter = new Planet("TODO", sun, 3, 1){{
generator = new TODOPlanetGenerator();
meshLoader = () -> new HexMesh(this, 6);
atmosphereColor = Color.valueOf("3c1b8f");
}};
}
}

View File

@@ -1,8 +1,8 @@
package mindustry.content;
import arc.*;
import arc.graphics.*;
import arc.math.Mathf;
import mindustry.entities.Effects;
import mindustry.ctype.ContentList;
import mindustry.game.EventType.*;
import mindustry.type.StatusEffect;
@@ -24,7 +24,7 @@ public class StatusEffects implements ContentList{
opposite(wet,freezing);
trans(tarred, ((unit, time, newTime, result) -> {
unit.damage(1f);
Effects.effect(Fx.burning, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f));
Fx.burning.at(unit.x() + Mathf.range(unit.bounds() / 2f), unit.y() + Mathf.range(unit.bounds() / 2f));
result.set(this, Math.min(time + newTime, 300f));
}));
});
@@ -41,13 +41,14 @@ public class StatusEffects implements ContentList{
}};
wet = new StatusEffect("wet"){{
color = Color.royal;
speedMultiplier = 0.9f;
effect = Fx.wet;
init(() -> {
trans(shocked, ((unit, time, newTime, result) -> {
unit.damage(20f);
if(unit.getTeam() == state.rules.waveTeam){
if(unit.team() == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
result.set(this, time);

View File

@@ -280,6 +280,7 @@ public class TechTree implements ContentList{
});
});
/*
node(draugFactory, () -> {
node(spiritFactory, () -> {
node(phantomFactory);
@@ -305,6 +306,7 @@ public class TechTree implements ContentList{
});
});
/*
node(dartPad, () -> {
node(deltaPad, () -> {
@@ -320,7 +322,7 @@ public class TechTree implements ContentList{
});
});
});
});
});*/
});
});
});
@@ -347,6 +349,7 @@ public class TechTree implements ContentList{
public static class TechNode{
static TechNode context;
public TechNode parent;
public final Block block;
public final ItemStack[] requirements;
public final Array<TechNode> children = new Array<>();
@@ -356,6 +359,7 @@ public class TechTree implements ContentList{
ccontext.children.add(this);
}
this.parent = ccontext;
this.block = block;
this.requirements = requirements;

View File

@@ -1,18 +0,0 @@
package mindustry.content;
import mindustry.entities.effect.Fire;
import mindustry.entities.effect.Puddle;
import mindustry.entities.type.Player;
import mindustry.ctype.ContentList;
import mindustry.type.TypeID;
public class TypeIDs implements ContentList{
public static TypeID fire, puddle, player;
@Override
public void load(){
fire = new TypeID("fire", Fire::new);
puddle = new TypeID("puddle", Puddle::new);
player = new TypeID("player", Player::new);
}
}

View File

@@ -1,95 +1,154 @@
package mindustry.content;
import arc.struct.*;
import mindustry.annotations.Annotations.*;
import mindustry.ctype.*;
import mindustry.entities.bullet.*;
import mindustry.entities.type.base.*;
import mindustry.gen.*;
import mindustry.type.*;
public class UnitTypes implements ContentList{
//TODO reimplement - DO NOT USE
public static UnitType
draug, spirit, phantom,
wraith, ghoul, revenant, lich, reaper,
dagger, crawler, titan, fortress, eruptor, chaosArray, eradicator;
ghoul, revenant, lich,
crawler, titan, fortress, eruptor, chaosArray, eradicator;
public static @EntityDef({Unitc.class, Legsc.class}) UnitType dagger;
public static @EntityDef({Unitc.class, WaterMovec.class}) UnitType vanguard;
public static @EntityDef({Unitc.class, Minerc.class}) UnitType draug;
public static @EntityDef({Unitc.class}) UnitType wraith;
public static @EntityDef({Unitc.class}) UnitType reaper;
public static @EntityDef({Unitc.class}) UnitType spirit;
public static @EntityDef({Unitc.class, Builderc.class}) UnitType phantom;
//TODO remove
public static UnitType alpha, delta, tau, omega, dart, javelin, trident, glaive;
public static UnitType starter;
@Override
public void load(){
draug = new UnitType("draug", MinerDrone::new){{
flying = true;
drag = 0.01f;
speed = 0.3f;
maxVelocity = 1.2f;
range = 50f;
health = 80;
minePower = 0.9f;
engineSize = 1.8f;
engineOffset = 5.7f;
weapon = new Weapon("you have incurred my wrath. prepare to die."){{
bullet = Bullets.lancerLaser;
}};
dagger = new UnitType("dagger"){{
speed = 0.5f;
drag = 0.3f;
hitsize = 8f;
mass = 1.75f;
health = 130;
weapons.add(new Weapon("chain-blaster"){{
reload = 14f;
x = 4f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}});
}};
spirit = new UnitType("spirit", RepairDrone::new){{
wraith = new UnitType("wraith"){{
speed = 3f;
accel = 0.08f;
drag = 0f;
mass = 1.5f;
flying = true;
drag = 0.01f;
speed = 0.42f;
maxVelocity = 1.6f;
health = 75;
engineOffset = 5.5f;
range = 140f;
weapons.add(new Weapon(){{
y = 1.5f;
reload = 28f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
shootSound = Sounds.shoot;
}});
}};
reaper = new UnitType("reaper"){{
speed = 1f;
accel = 0.08f;
drag = 0f;
mass = 2f;
flying = true;
health = 75000;
engineOffset = 40;
engineSize = 7.3f;
weapons.add(new Weapon(){{
y = 1.5f;
reload = 28f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
shootSound = Sounds.shoot;
}});
}};
vanguard = new UnitType("vanguard"){{
speed = 1.3f;
drag = 0.1f;
hitsize = 8f;
mass = 1.75f;
health = 130;
immunities = ObjectSet.with(StatusEffects.wet);
weapons.add(new Weapon("chain-blaster"){{
reload = 10f;
x = 1.25f;
alternate = true;
rotate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}});
}};
draug = new UnitType("draug"){{
flying = true;
drag = 0.05f;
speed = 2f;
range = 50f;
accel = 0.2f;
health = 80;
mineSpeed = 0.9f;
engineSize = 1.8f;
engineOffset = 5.7f;
drillTier = 1;
}};
spirit = new UnitType("spirit"){{
flying = true;
drag = 0.05f;
accel = 0.2f;
speed = 2f;
range = 50f;
health = 100;
engineSize = 1.8f;
engineOffset = 5.7f;
weapon = new Weapon(){{
length = 1.5f;
weapons.add(new Weapon(){{
y = 1.5f;
reload = 40f;
width = 0.5f;
x = 0.5f;
alternate = true;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBulletBig;
shootSound = Sounds.pew;
}};
}});
}};
phantom = new UnitType("phantom", BuilderDrone::new){{
phantom = new UnitType("phantom"){{
flying = true;
drag = 0.01f;
drag = 0.05f;
mass = 2f;
speed = 0.45f;
maxVelocity = 1.9f;
speed = 4f;
rotateSpeed = 12f;
accel = 0.3f;
range = 70f;
itemCapacity = 70;
health = 400;
buildPower = 0.4f;
buildSpeed = 0.4f;
engineOffset = 6.5f;
toMine = ObjectSet.with(Items.lead, Items.copper, Items.titanium);
weapon = new Weapon(){{
length = 1.5f;
reload = 20f;
width = 0.5f;
alternate = true;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBullet;
}};
}};
dagger = new UnitType("dagger", GroundUnit::new){{
maxVelocity = 1.1f;
speed = 0.2f;
drag = 0.4f;
hitsize = 8f;
mass = 1.75f;
health = 130;
weapon = new Weapon("chain-blaster"){{
length = 1.5f;
reload = 28f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}};
}};
/*
crawler = new UnitType("crawler", GroundUnit::new){{
maxVelocity = 1.27f;
speed = 0.285f;
@@ -97,7 +156,7 @@ public class UnitTypes implements ContentList{
hitsize = 8f;
mass = 1.75f;
health = 120;
weapon = new Weapon(){{
weapons.add(new Weapon(){{
reload = 12f;
ejectEffect = Fx.none;
shootSound = Sounds.explosion;
@@ -110,7 +169,7 @@ public class UnitTypes implements ContentList{
splashDamage = 30f;
killShooter = true;
}};
}};
}});
}};
titan = new UnitType("titan", GroundUnit::new){{
@@ -123,7 +182,7 @@ public class UnitTypes implements ContentList{
rotatespeed = 0.1f;
health = 460;
immunities.add(StatusEffects.burning);
weapon = new Weapon("flamethrower"){{
weapons.add(new Weapon("flamethrower"){{
shootSound = Sounds.flame;
length = 1f;
reload = 14f;
@@ -131,7 +190,7 @@ public class UnitTypes implements ContentList{
recoil = 1f;
ejectEffect = Fx.none;
bullet = Bullets.basicFlame;
}};
}});
}};
fortress = new UnitType("fortress", GroundUnit::new){{
@@ -143,7 +202,7 @@ public class UnitTypes implements ContentList{
rotatespeed = 0.06f;
targetAir = false;
health = 750;
weapon = new Weapon("artillery"){{
weapons.add(new Weapon("artillery"){{
length = 1f;
reload = 60f;
width = 10f;
@@ -153,7 +212,7 @@ public class UnitTypes implements ContentList{
ejectEffect = Fx.shellEjectMedium;
bullet = Bullets.artilleryUnit;
shootSound = Sounds.artillery;
}};
}});
}};
eruptor = new UnitType("eruptor", GroundUnit::new){{
@@ -166,7 +225,7 @@ public class UnitTypes implements ContentList{
targetAir = false;
health = 600;
immunities = ObjectSet.with(StatusEffects.burning, StatusEffects.melting);
weapon = new Weapon("eruption"){{
weapons.add(new Weapon("eruption"){{
length = 3f;
reload = 10f;
alternate = true;
@@ -175,7 +234,7 @@ public class UnitTypes implements ContentList{
recoil = 1f;
width = 7f;
shootSound = Sounds.flame;
}};
}});
}};
chaosArray = new UnitType("chaos-array", GroundUnit::new){{
@@ -186,7 +245,7 @@ public class UnitTypes implements ContentList{
hitsize = 20f;
rotatespeed = 0.06f;
health = 3000;
weapon = new Weapon("chaos"){{
weapons.add(new Weapon("chaos"){{
length = 8f;
reload = 50f;
width = 17f;
@@ -199,7 +258,7 @@ public class UnitTypes implements ContentList{
ejectEffect = Fx.shellEjectMedium;
bullet = Bullets.flakSurge;
shootSound = Sounds.shootBig;
}};
}});
}};
eradicator = new UnitType("eradicator", GroundUnit::new){{
@@ -210,7 +269,7 @@ public class UnitTypes implements ContentList{
hitsize = 20f;
rotatespeed = 0.06f;
health = 9000;
weapon = new Weapon("eradication"){{
weapons.add(new Weapon("eradication"){{
length = 13f;
reload = 30f;
width = 22f;
@@ -224,7 +283,7 @@ public class UnitTypes implements ContentList{
ejectEffect = Fx.shellEjectMedium;
bullet = Bullets.standardThoriumBig;
shootSound = Sounds.shootBig;
}};
}});
}};
wraith = new UnitType("wraith", FlyingUnit::new){{
@@ -236,14 +295,14 @@ public class UnitTypes implements ContentList{
health = 75;
engineOffset = 5.5f;
range = 140f;
weapon = new Weapon(){{
weapons.add(new Weapon(){{
length = 1.5f;
reload = 28f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
shootSound = Sounds.shoot;
}};
}});
}};
ghoul = new UnitType("ghoul", FlyingUnit::new){{
@@ -256,7 +315,7 @@ public class UnitTypes implements ContentList{
targetAir = false;
engineOffset = 7.8f;
range = 140f;
weapon = new Weapon(){{
weapons.add(new Weapon(){{
length = 0f;
width = 2f;
reload = 12f;
@@ -267,7 +326,7 @@ public class UnitTypes implements ContentList{
ignoreRotation = true;
bullet = Bullets.bombExplosive;
shootSound = Sounds.none;
}};
}});
}};
revenant = new UnitType("revenant", HoverUnit::new){{
@@ -280,13 +339,13 @@ public class UnitTypes implements ContentList{
range = 80f;
shootCone = 40f;
flying = true;
rotateWeapon = true;
//rotateWeapons = true;
engineOffset = 12f;
engineSize = 3f;
rotatespeed = 0.01f;
attackLength = 90f;
baseRotateSpeed = 0.06f;
weapon = new Weapon("revenant-missiles"){{
weapons.add(new Weapon("revenant-missiles"){{
length = 3f;
reload = 70f;
width = 10f;
@@ -298,7 +357,7 @@ public class UnitTypes implements ContentList{
spacing = 1f;
shootSound = Sounds.missile;
bullet = Bullets.missileRevenant;
}};
}});
}};
lich = new UnitType("lich", HoverUnit::new){{
@@ -311,13 +370,13 @@ public class UnitTypes implements ContentList{
range = 80f;
shootCone = 20f;
flying = true;
rotateWeapon = true;
//rotateWeapons = true;
engineOffset = 21;
engineSize = 5.3f;
rotatespeed = 0.01f;
attackLength = 90f;
baseRotateSpeed = 0.04f;
weapon = new Weapon("lich-missiles"){{
weapons.add(new Weapon("lich-missiles"){{
length = 4f;
reload = 160f;
width = 22f;
@@ -331,7 +390,7 @@ public class UnitTypes implements ContentList{
spacing = 1f;
bullet = Bullets.missileRevenant;
shootSound = Sounds.artillery;
}};
}});
}};
reaper = new UnitType("reaper", HoverUnit::new){{
@@ -344,12 +403,12 @@ public class UnitTypes implements ContentList{
range = 80f;
shootCone = 30f;
flying = true;
rotateWeapon = true;
//rotateWeapons = true;
engineOffset = 40;
engineSize = 7.3f;
rotatespeed = 0.01f;
baseRotateSpeed = 0.04f;
weapon = new Weapon("reaper-gun"){{
weapons.add(new Weapon("reaper-gun"){{
length = 3f;
reload = 10f;
width = 32f;
@@ -373,7 +432,442 @@ public class UnitTypes implements ContentList{
}
};
shootSound = Sounds.shootBig;
}};
}});
}};
/*
vanguard = new UnitType("vanguard-ship"){
float healRange = 60f;
float healReload = 200f;
float healPercent = 10f;
{
flying = true;
drillTier = 1;
mineSpeed = 4f;
speed = 0.49f;
drag = 0.09f;
health = 200f;
weaponOffsetX = -1;
engineSize = 2.3f;
weaponOffsetY = -1;
engineColor = Pal.lightTrail;
cellTrnsY = 1f;
buildSpeed = 1.2f;
weapons.add(new Weapon("vanguard-blaster"){{
length = 1.5f;
reload = 30f;
alternate = true;
inaccuracy = 6f;
velocityRnd = 0.1f;
ejectEffect = Fx.none;
bullet = new HealBulletType(){{
healPercent = 3f;
backColor = engineColor;
homingPower = 20f;
bulletHeight = 4f;
bulletWidth = 1.5f;
damage = 3f;
speed = 4f;
lifetime = 40f;
shootEffect = Fx.shootHealYellow;
smokeEffect = hitEffect = despawnEffect = Fx.hitYellowLaser;
}});
}};
}
@Override
public boolean alwaysUnlocked(){
return true;
}
@Override
public void update(Playerc player){
if(player.timer.get(Playerc.timerAbility, healReload)){
if(indexer.eachBlock(player, healRange, other -> other.entity.damaged(), other -> {
other.entity.heal(other.entity.maxHealth() * healPercent / 100f);
Fx.healBlockFull.at(other.drawx(), other.drawy(), other.block().size, Pal.heal);
})){
Fx.healWave.at(player);
}
}
}
};
alpha = new UnitType("alpha-mech", false){
{
drillTier = -1;
speed = 0.5f;
boostSpeed = 0.95f;
itemCapacity = 15;
mass = 0.9f;
health = 150f;
buildSpeed = 0.9f;
weaponOffsetX = 1;
weaponOffsetY = -1;
engineColor = Pal.heal;
weapons.add(new Weapon("shockgun"){{
shake = 2f;
length = 0.5f;
reload = 70f;
alternate = true;
recoil = 4f;
width = 5f;
shootSound = Sounds.laser;
bullet = new LaserBulletType(){{
damage = 20f;
recoil = 1f;
sideAngle = 45f;
sideWidth = 1f;
sideLength = 70f;
colors = new Color[]{Pal.heal.cpy().a(0.4f), Pal.heal, Color.white};
}});
}};
}
@Override
public void update(Playerc player){
player.heal(Time.delta() * 0.09f);
}
};
delta = new UnitType("delta-mech", false){
{
drillPower = 1;
mineSpeed = 1.5f;
mass = 1.2f;
speed = 0.5f;
itemCapacity = 40;
boostSpeed = 0.95f;
buildSpeed = 1.2f;
engineColor = Color.valueOf("ffd37f");
health = 250f;
weaponOffsetX = 4f;
weapons.add(new Weapon("flamethrower"){{
length = 1.5f;
reload = 30f;
width = 4f;
alternate = true;
shots = 3;
inaccuracy = 40f;
shootSound = Sounds.spark;
bullet = new LightningBulletType(){{
damage = 5;
lightningLength = 10;
lightningColor = Pal.lightFlame;
}});
}};
}
};
tau = new UnitType("tau-mech", false){
float healRange = 60f;
float healAmount = 10f;
float healReload = 160f;
boolean wasHealed;
{
drillPower = 4;
mineSpeed = 3f;
itemCapacity = 70;
weaponOffsetY = -1;
weaponOffsetX = 1;
mass = 1.75f;
speed = 0.44f;
drag = 0.35f;
boostSpeed = 0.8f;
canHeal = true;
health = 200f;
buildSpeed = 1.6f;
engineColor = Pal.heal;
weapons.add(new Weapon("heal-blaster"){{
length = 1.5f;
reload = 24f;
alternate = false;
ejectEffect = Fx.none;
recoil = 2f;
bullet = Bullets.healBullet;
shootSound = Sounds.pew;
}};
}
@Override
public void update(Playerc player){
if(player.timer.get(Playerc.timerAbility, healReload)){
wasHealed = false;
Units.nearby(player.team(), player.x, player.y, healRange, unit -> {
if(unit.health < unit.maxHealth()){
Fx.heal.at(unit);
wasHealed = true;
}
unit.heal(healAmount);
});
if(wasHealed){
Fx.healWave.at(player);
}
}
}
};
omega = new UnitType("omega-mech", false){
protected TextureRegion armorRegion;
{
drillPower = 2;
mineSpeed = 1.5f;
itemCapacity = 80;
speed = 0.36f;
boostSpeed = 0.6f;
mass = 4f;
shake = 4f;
weaponOffsetX = 1;
weaponOffsetY = 0;
engineColor = Color.valueOf("feb380");
health = 350f;
buildSpeed = 1.5f;
weapons.add(new Weapon("swarmer"){{
length = 1.5f;
recoil = 4f;
reload = 38f;
shots = 4;
spacing = 8f;
inaccuracy = 8f;
alternate = true;
ejectEffect = Fx.none;
shake = 3f;
bullet = Bullets.missileSwarm;
shootSound = Sounds.shootBig;
}};
}
@Override
public float getRotationAlpha(Playerc player){
return 0.6f - player.shootHeat * 0.3f;
}
@Override
public float spreadX(Playerc player){
return player.shootHeat * 2f;
}
@Override
public void load(){
super.load();
armorRegion = Core.atlas.find(name + "-armor");
}
@Override
public void update(Playerc player){
float scl = 1f - player.shootHeat / 2f*Time.delta();
player.vel().scl(scl);
}
@Override
public float getExtraArmor(Playerc player){
return player.shootHeat * 30f;
}
@Override
public void draw(Playerc player){
if(player.shootHeat <= 0.01f) return;
Shaders.build.progress = player.shootHeat;
Shaders.build.region = armorRegion;
Shaders.build.time = Time.time() / 10f;
Shaders.build.color.set(Pal.accent).a = player.shootHeat;
Draw.shader(Shaders.build);
Draw.rect(armorRegion, player.x, player.y, player.rotation);
Draw.shader();
}
};
dart = new UnitType("dart-ship"){
float effectRange = 60f;
float effectReload = 60f * 5;
float effectDuration = 60f * 10f;
{
flying = true;
drillPower = 1;
mineSpeed = 2f;
speed = 0.5f;
drag = 0.09f;
health = 200f;
weaponOffsetX = -1;
weaponOffsetY = -1;
engineColor = Pal.lightTrail;
cellTrnsY = 1f;
buildSpeed = 1.1f;
weapons.add(new Weapon("blaster"){{
length = 1.5f;
reload = 15f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}};
}
@Override
public void update(Playerc player){
super.update(player);
if(player.timer.get(Playerc.timerAbility, effectReload)){
Units.nearby(player.team(), player.x, player.y, effectRange, unit -> {
//unit.applyEffect(StatusEffects.overdrive, effectDuration);
});
indexer.eachBlock(player, effectRange, other -> other.entity.damaged(), other -> {
other.entity.applyBoost(1.5f, effectDuration);
Fx.healBlockFull.at(other.drawx(), other.drawy(), other.block().size, Pal.heal);
});
Fx.overdriveWave.at(player);
}
}
};
javelin = new UnitType("javelin-ship"){
float minV = 3.6f;
float maxV = 6f;
TextureRegion shield;
{
flying = true;
drillPower = -1;
speed = 0.11f;
drag = 0.01f;
mass = 2f;
health = 170f;
engineColor = Color.valueOf("d3ddff");
cellTrnsY = 1f;
weapons.add(new Weapon("missiles"){{
length = 1.5f;
reload = 70f;
shots = 4;
inaccuracy = 2f;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 0.2f;
spacing = 1f;
bullet = Bullets.missileJavelin;
shootSound = Sounds.missile;
}};
}
@Override
public void load(){
super.load();
shield = Core.atlas.find(name + "-shield");
}
@Override
public float getRotationAlpha(Playerc player){
return 0.5f;
}
@Override
public void update(Playerc player){
float scl = scld(player);
if(Mathf.chance(Time.delta() * (0.15 * scl))){
Fx.hitLancer.at(Pal.lancerLaser, player.x, player.y);
Lightning.create(player.team(), Pal.lancerLaser, 10f * Vars.state.rules.playerDamageMultiplier,
player.x + player.vel().x, player.y + player.vel().y, player.rotation, 14);
}
}
@Override
public void draw(Playerc player){
float scl = scld(player);
if(scl < 0.01f) return;
Draw.color(Pal.lancerLaser);
Draw.alpha(scl / 2f);
Draw.blend(Blending.additive);
Draw.rect(shield, player.x + Mathf.range(scl / 2f), player.y + Mathf.range(scl / 2f), player.rotation - 90);
Draw.blend();
}
float scld(Playerc player){
return Mathf.clamp((player.vel().len() - minV) / (maxV - minV));
}
};
trident = new UnitType("trident-ship"){
{
flying = true;
drillPower = 2;
speed = 0.15f;
drag = 0.034f;
mass = 2.5f;
turnCursor = false;
health = 250f;
itemCapacity = 30;
engineColor = Color.valueOf("84f491");
cellTrnsY = 1f;
buildSpeed = 2.5f;
weapons.add(new Weapon("bomber"){{
length = 0f;
width = 2f;
reload = 25f;
shots = 2;
shotDelay = 1f;
shots = 8;
alternate = true;
ejectEffect = Fx.none;
velocityRnd = 1f;
inaccuracy = 20f;
ignoreRotation = true;
bullet = new BombBulletType(16f, 25f, "shell"){{
bulletWidth = 10f;
bulletHeight = 14f;
hitEffect = Fx.flakExplosion;
shootEffect = Fx.none;
smokeEffect = Fx.none;
shootSound = Sounds.artillery;
}});
}};
}
@Override
public boolean canShoot(Playerc player){
return player.vel().len() > 1.2f;
}
};
glaive = new UnitType("glaive-ship"){
{
flying = true;
drillPower = 4;
mineSpeed = 1.3f;
speed = 0.32f;
drag = 0.06f;
mass = 3f;
health = 240f;
itemCapacity = 60;
engineColor = Color.valueOf("feb380");
cellTrnsY = 1f;
buildSpeed = 1.2f;
weapons.add(new Weapon("bomber"){{
length = 1.5f;
reload = 13f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardGlaive;
shootSound = Sounds.shootSnap;
}};
}
};
starter = vanguard;*/
}
}

View File

@@ -0,0 +1,53 @@
package mindustry.content;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.type.*;
import static mindustry.Vars.world;
public class Weathers implements ContentList{
public static Weather
rain,
snow;
@Override
public void load(){
snow = new Weather("snow"){
Rand rand = new Rand();
@Override
public void draw(){
rand.setSeed(0);
float yspeed = 2f, xspeed = 0.25f;
float padding = 16f;
float size = 12f;
Core.camera.bounds(Tmp.r1);
Tmp.r1.grow(padding);
for(int i = 0; i < 100; i++){
float scl = rand.random(0.5f, 1f);
float scl2 = rand.random(0.5f, 1f);
float sscl = rand.random(0.2f, 1f);
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
x -= Tmp.r1.x;
y -= Tmp.r1.y;
x = Mathf.mod(x, Tmp.r1.width);
y = Mathf.mod(y, Tmp.r1.height);
x += Tmp.r1.x;
y += Tmp.r1.y;
Draw.rect("circle-shadow", x, y, size * sscl, size * sscl);
}
//TODO
}
};
}
}

View File

@@ -1,19 +1,17 @@
package mindustry.content;
import mindustry.ctype.ContentList;
import mindustry.game.*;
import mindustry.ctype.*;
import mindustry.game.Objectives.*;
import mindustry.maps.generators.*;
import mindustry.maps.generators.MapGenerator.*;
import mindustry.maps.zonegen.*;
import mindustry.type.*;
import static arc.struct.Array.with;
import static mindustry.content.Items.*;
import static mindustry.content.Planets.starter;
import static mindustry.type.ItemStack.list;
public class Zones implements ContentList{
public static Zone
public static SectorPreset
groundZero, desertWastes,
craters, frozenForest, ruinousShores, stainedMountains, tarFields, fungalPass,
saltFlats, overgrowth, impact0078, crags,
@@ -22,7 +20,7 @@ public class Zones implements ContentList{
@Override
public void load(){
groundZero = new Zone("groundZero", new MapGenerator("groundZero", 1)){{
groundZero = new SectorPreset("groundZero", starter, new FileMapGenerator("groundZero")){{
baseLaunchCost = list(copper, -60);
startingItems = list(copper, 60);
alwaysUnlocked = true;
@@ -31,7 +29,9 @@ public class Zones implements ContentList{
resources = with(copper, scrap, lead);
}};
desertWastes = new Zone("desertWastes", new DesertWastesGenerator(260, 260)){{
//TODO remove
/*
desertWastes = new Zone("desertWastes", starter, new FileMapGenerator("groundZero")){{
startingItems = list(copper, 120);
conditionWave = 20;
launchPeriod = 10;
@@ -80,9 +80,9 @@ public class Zones implements ContentList{
new ZoneWave(groundZero, 20),
new Unlock(Blocks.combustionGenerator)
);
}};
}};*/
saltFlats = new Zone("saltFlats", new MapGenerator("saltFlats")){{
saltFlats = new SectorPreset("saltFlats", starter, new FileMapGenerator("saltFlats")){{
startingItems = list(copper, 200, Items.silicon, 200, lead, 200);
loadout = Loadouts.basicFoundation;
conditionWave = 10;
@@ -91,15 +91,14 @@ public class Zones implements ContentList{
resources = with(copper, scrap, lead, coal, sand, titanium);
requirements = with(
new ZoneWave(desertWastes, 60),
new Unlock(Blocks.daggerFactory),
new Unlock(Blocks.draugFactory),
//new Unlock(Blocks.daggerFactory),
//new Unlock(Blocks.draugFactory),
new Unlock(Blocks.door),
new Unlock(Blocks.waterExtractor)
);
}};
frozenForest = new Zone("frozenForest", new MapGenerator("frozenForest", 1)
.decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.02))){{
frozenForest = new SectorPreset("frozenForest", starter, new FileMapGenerator("frozenForest")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 250);
conditionWave = 10;
@@ -111,7 +110,7 @@ public class Zones implements ContentList{
);
}};
craters = new Zone("craters", new MapGenerator("craters", 1).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.004))){{
craters = new SectorPreset("craters", starter, new FileMapGenerator("craters")){{
startingItems = list(copper, 100);
conditionWave = 10;
resources = with(copper, lead, coal, sand, scrap);
@@ -122,7 +121,7 @@ public class Zones implements ContentList{
);
}};
ruinousShores = new Zone("ruinousShores", new MapGenerator("ruinousShores", 1)){{
ruinousShores = new SectorPreset("ruinousShores", starter, new FileMapGenerator("ruinousShores")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 140, lead, 50);
conditionWave = 20;
@@ -138,8 +137,7 @@ public class Zones implements ContentList{
);
}};
stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2)
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
stainedMountains = new SectorPreset("stainedMountains", starter, new FileMapGenerator("stainedMountains")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 200, lead, 50);
conditionWave = 10;
@@ -153,20 +151,20 @@ public class Zones implements ContentList{
);
}};
fungalPass = new Zone("fungalPass", new MapGenerator("fungalPass")){{
fungalPass = new SectorPreset("fungalPass", starter, new FileMapGenerator("fungalPass")){{
startingItems = list(copper, 250, lead, 250, Items.metaglass, 100, Items.graphite, 100);
resources = with(copper, lead, coal, titanium, sand);
configureObjective = new Launched(this);
requirements = with(
new ZoneWave(stainedMountains, 15),
new Unlock(Blocks.daggerFactory),
new Unlock(Blocks.crawlerFactory),
//new Unlock(Blocks.daggerFactory),
//new Unlock(Blocks.crawlerFactory),
new Unlock(Blocks.door),
new Unlock(Blocks.siliconSmelter)
);
}};
overgrowth = new Zone("overgrowth", new MapGenerator("overgrowth")){{
overgrowth = new SectorPreset("overgrowth", starter, new FileMapGenerator("overgrowth")){{
startingItems = list(copper, 1500, lead, 1000, Items.silicon, 500, Items.metaglass, 250);
conditionWave = 12;
launchPeriod = 4;
@@ -177,14 +175,13 @@ public class Zones implements ContentList{
new ZoneWave(craters, 40),
new Launched(fungalPass),
new Unlock(Blocks.cultivator),
new Unlock(Blocks.sporePress),
new Unlock(Blocks.titanFactory),
new Unlock(Blocks.wraithFactory)
new Unlock(Blocks.sporePress)
//new Unlock(Blocks.titanFactory),
//new Unlock(Blocks.wraithFactory)
);
}};
tarFields = new Zone("tarFields", new MapGenerator("tarFields")
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
tarFields = new SectorPreset("tarFields", starter, new FileMapGenerator("tarFields")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 250, lead, 100);
conditionWave = 15;
@@ -198,7 +195,7 @@ public class Zones implements ContentList{
);
}};
desolateRift = new Zone("desolateRift", new MapGenerator("desolateRift")){{
desolateRift = new SectorPreset("desolateRift", starter, new FileMapGenerator("desolateRift")){{
loadout = Loadouts.basicNucleus;
startingItems = list(copper, 1000, lead, 1000, Items.graphite, 250, titanium, 250, Items.silicon, 250);
conditionWave = 3;
@@ -223,8 +220,7 @@ public class Zones implements ContentList{
resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
}};*/
nuclearComplex = new Zone("nuclearComplex", new MapGenerator("nuclearProductionComplex", 1)
.decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{
nuclearComplex = new SectorPreset("nuclearComplex", starter, new FileMapGenerator("nuclearProductionComplex")){{
loadout = Loadouts.basicNucleus;
startingItems = list(copper, 1250, lead, 1500, Items.silicon, 400, Items.metaglass, 250);
conditionWave = 30;

View File

@@ -30,21 +30,17 @@ public class ContentLoader{
private @Nullable Content lastAdded;
private ObjectSet<Cons<Content>> initialization = new ObjectSet<>();
private ContentList[] content = {
new Fx(),
new Items(),
new StatusEffects(),
new Liquids(),
new Bullets(),
new Mechs(),
new UnitTypes(),
new Blocks(),
new Loadouts(),
new TechTree(),
new Zones(),
new TypeIDs(),
//these are not really content classes, but this makes initialization easier
new LegacyColorMapper(),
new Weathers(),
new Planets(),
new Zones()
};
public ContentLoader(){
@@ -137,13 +133,15 @@ public class ContentLoader{
if(blocks().size > i){
int color = pixmap.getPixel(i, 0);
if(color == 0) continue;
if(color == 0 || color == 255) continue;
Block block = block(i);
block.color.set(color);
block.mapColor.rgba8888(color);
block.hasColor = true;
}
}
pixmap.dispose();
ColorMapper.load();
}
public void dispose(){
@@ -239,6 +237,10 @@ public class ContentLoader{
return (Block)getByID(ContentType.block, id);
}
public Block block(String name){
return (Block)getByName(ContentType.block, name);
}
public Array<Item> items(){
return getBy(ContentType.item);
}
@@ -263,11 +265,15 @@ public class ContentLoader{
return (BulletType)getByID(ContentType.bullet, id);
}
public Array<Zone> zones(){
public Array<SectorPreset> zones(){
return getBy(ContentType.zone);
}
public Array<UnitType> units(){
return getBy(ContentType.unit);
}
public Array<Planet> planets(){
return getBy(ContentType.planet);
}
}

View File

@@ -3,26 +3,24 @@ package mindustry.core;
import arc.*;
import arc.assets.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.geom.*;
import arc.scene.ui.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Saves.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.io.SaveIO.*;
import mindustry.maps.Map;
import mindustry.type.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
import java.io.*;
import java.text.*;
@@ -63,21 +61,19 @@ public class Control implements ApplicationListener, Loadable{
});
Events.on(PlayEvent.class, event -> {
player.setTeam(netServer.assignTeam(player, playerGroup.all()));
player.setDead(true);
player.team(netServer.assignTeam(player));
player.add();
state.set(State.playing);
});
Events.on(WorldLoadEvent.class, event -> {
Core.app.post(() -> Core.app.post(() -> {
if(net.active() && player.getClosestCore() != null){
//set to closest core since that's where the player will probably respawn; prevents camera jumps
Core.camera.position.set(player.isDead() ? player.getClosestCore() : player);
}else{
//locally, set to player position since respawning occurs immediately
Core.camera.position.set(player);
//TODO test this
app.post(() -> app.post(() -> {
//TODO 0,0 seems like a bad choice?
Tilec core = state.teams.closestCore(0, 0, player.team());
if(core != null){
camera.position.set(core);
}
}));
});
@@ -92,9 +88,9 @@ public class Control implements ApplicationListener, Loadable{
});
Events.on(WaveEvent.class, event -> {
if(world.getMap().getHightScore() < state.wave){
if(state.map.getHightScore() < state.wave){
hiscore = true;
world.getMap().setHighScore(state.wave);
state.map.setHighScore(state.wave);
}
Sounds.wave.play();
@@ -105,20 +101,23 @@ public class Control implements ApplicationListener, Loadable{
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
//the restart dialog can show info for any number of scenarios
Call.onGameOver(event.winner);
//TODO set meta to indicate game over
/*
if(state.rules.zone != null && !net.client()){
//remove zone save on game over
if(saves.getZoneSlot() != null && !state.rules.tutorial){
saves.getZoneSlot().delete();
}
}
}*/
});
//autohost for pvp maps
Events.on(WorldLoadEvent.class, event -> app.post(() -> {
player.add();
if(state.rules.pvp && !net.active()){
try{
net.host(port);
player.isAdmin = true;
player.admin(true);
}catch(IOException e){
ui.showException("$server.error", e);
state.set(State.menu);
@@ -129,7 +128,7 @@ public class Control implements ApplicationListener, Loadable{
Events.on(UnlockEvent.class, e -> ui.hudfrag.showUnlock(e.content));
Events.on(BlockBuildEndEvent.class, e -> {
if(e.team == player.getTeam()){
if(e.team == player.team()){
if(e.breaking){
state.stats.buildingsDeconstructed++;
}else{
@@ -139,13 +138,13 @@ public class Control implements ApplicationListener, Loadable{
});
Events.on(BlockDestroyEvent.class, e -> {
if(e.tile.getTeam() == player.getTeam()){
if(e.tile.team() == player.team()){
state.stats.buildingsDestroyed++;
}
});
Events.on(UnitDestroyEvent.class, e -> {
if(e.unit.getTeam() != player.getTeam()){
if(e.unit.team() != player.team()){
state.stats.enemyUnitsDestroyed++;
}
});
@@ -163,22 +162,29 @@ public class Control implements ApplicationListener, Loadable{
});
Events.on(Trigger.newGame, () -> {
TileEntity core = player.getClosestCore();
Tilec core = player.closestCore();
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(() -> Effects.effect(Fx.coreLand, core.x, core.y, 0, core.block));
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block()));
Time.run(Fx.coreLand.lifetime, () -> {
Effects.effect(Fx.launch, core);
Fx.launch.at(core);
Effects.shake(5f, 5f, core);
});
});
Events.on(UnitDestroyEvent.class, e -> {
if(e.unit instanceof BaseUnit && world.isZone()){
data.unlockContent(((BaseUnit)e.unit).getType());
if(state.isCampaign()){
data.unlockContent(e.unit.type());
}
});
}
@@ -204,11 +210,9 @@ public class Control implements ApplicationListener, Loadable{
}
void createPlayer(){
player = new Player();
player.name = Core.settings.getString("name");
player.color.set(Core.settings.getInt("color-0"));
player.isLocal = true;
player.isMobile = mobile;
player = PlayerEntity.create();
player.name(Core.settings.getString("name"));
player.color().set(Core.settings.getInt("color-0"));
if(mobile){
input = new MobileInput();
@@ -216,7 +220,7 @@ public class Control implements ApplicationListener, Loadable{
input = new DesktopInput();
}
if(!state.is(State.menu)){
if(state.isGame()){
player.add();
}
@@ -239,7 +243,7 @@ public class Control implements ApplicationListener, Loadable{
logic.reset();
world.loadMap(map, rules);
state.rules = rules;
state.rules.zone = null;
state.rules.sector = null;
state.rules.editor = false;
logic.play();
if(settings.getBool("savecreate") && !world.isInvalidMap()){
@@ -249,27 +253,42 @@ public class Control implements ApplicationListener, Loadable{
});
}
public void playZone(Zone zone){
public void playSector(Sector sector){
ui.loadAnd(() -> {
logic.reset();
net.reset();
world.loadGenerator(zone.generator);
zone.rules.get(state.rules);
state.rules.zone = zone;
for(TileEntity core : state.teams.playerCores()){
for(ItemStack stack : zone.getStartingItems()){
core.items.add(stack.item, stack.amount);
ui.planet.hide();
SaveSlot slot = sector.save;
//TODO comment for new sector states
slot = null;
if(slot != null){
try{
net.reset();
slot.load();
state.rules.sector = sector;
state.set(State.playing);
}catch(SaveException e){
Log.err(e);
sector.save = null;
ui.showErrorMessage("$save.corrupted");
slot.delete();
playSector(sector);
}
ui.planet.hide();
}else{
net.reset();
logic.reset();
world.loadSector(sector);
state.rules.sector = sector;
logic.play();
control.saves.saveSector(sector);
Events.fire(Trigger.newGame);
}
state.set(State.playing);
state.wavetime = state.rules.waveSpacing;
control.saves.zoneSave();
logic.play();
Events.fire(Trigger.newGame);
});
}
public void playTutorial(){
//TODO implement
ui.showInfo("death");
/*
Zone zone = Zones.groundZero;
ui.loadAnd(() -> {
logic.reset();
@@ -277,8 +296,8 @@ public class Control implements ApplicationListener, Loadable{
world.beginMapLoad();
world.createTiles(zone.generator.width, zone.generator.height);
zone.generator.generate(world.getTiles());
world.resize(zone.generator.width, zone.generator.height);
zone.generator.generate(world.tiles);
Tile coreb = null;
@@ -294,7 +313,7 @@ public class Control implements ApplicationListener, Loadable{
Geometry.circle(coreb.x, coreb.y, 10, (cx, cy) -> {
Tile tile = world.ltile(cx, cy);
if(tile != null && tile.getTeam() == state.rules.defaultTeam && !(tile.block() instanceof CoreBlock)){
if(tile != null && tile.team() == state.rules.defaultTeam && !(tile.block() instanceof CoreBlock)){
tile.remove();
}
});
@@ -304,14 +323,15 @@ public class Control implements ApplicationListener, Loadable{
world.endMapLoad();
zone.rules.get(state.rules);
state.rules.zone = zone;
for(TileEntity core : state.teams.playerCores()){
//TODO assign zone!!
//state.rules.zone = zone;
for(Tilec core : state.teams.playerCores()){
for(ItemStack stack : zone.getStartingItems()){
core.items.add(stack.item, stack.amount);
core.items().add(stack.item, stack.amount);
}
}
TileEntity core = state.teams.playerCores().first();
core.items.clear();
Tilec core = state.teams.playerCores().first();
core.items().clear();
logic.play();
state.rules.waveTimer = false;
@@ -319,7 +339,7 @@ public class Control implements ApplicationListener, Loadable{
state.rules.buildCostMultiplier = 0.3f;
state.rules.tutorial = true;
Events.fire(Trigger.newGame);
});
});*/
}
public boolean isHighScore(){
@@ -430,13 +450,13 @@ public class Control implements ApplicationListener, Loadable{
settings.save();
}
if(!state.is(State.menu)){
if(state.isGame()){
input.update();
if(world.isZone()){
for(TileEntity tile : state.teams.cores(player.getTeam())){
if(state.isCampaign()){
for(Tilec tile : state.teams.cores(player.team())){
for(Item item : content.items()){
if(tile.items.has(item)){
if(tile.items().has(item)){
data.unlockContent(item);
}
}

View File

@@ -19,6 +19,8 @@ public class FileTree implements FileHandleResolver{
return files.get(path);
}else if(files.containsKey("/" + path)){
return files.get("/" + path);
}else if(Core.files == null){ //headless
return Fi.get(path);
}else{
return Core.files.internal(path);
}

View File

@@ -1,9 +1,12 @@
package mindustry.core;
import arc.*;
import mindustry.entities.type.*;
import arc.util.ArcAnnotate.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.maps.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@@ -14,6 +17,8 @@ public class GameState{
public float wavetime;
/** Whether the game is in game over state. */
public boolean gameOver = false, launched = false;
/** Map that is currently being played on. */
public @NonNull Map map = emptyMap;
/** The current game rules. */
public Rules rules = new Rules();
/** Statistics for this save/game. Displayed after game over. */
@@ -25,8 +30,8 @@ public class GameState{
/** Current game state. */
private State state = State.menu;
public BaseUnit boss(){
return unitGroup.find(u -> u.isBoss() && u.getTeam() == rules.waveTeam);
public Unitc boss(){
return Groups.unit.find(u -> u.isBoss() && u.team() == rules.waveTeam);
}
public void set(State astate){
@@ -34,6 +39,20 @@ public class GameState{
state = astate;
}
/** Note that being in a campaign does not necessarily mean having a sector. */
public boolean isCampaign(){
return rules.sector != null || rules.region != null;
}
public boolean hasSector(){
return rules.sector != null;
}
@Nullable
public Sector getSector(){
return rules.sector;
}
public boolean isEditor(){
return rules.editor;
}
@@ -42,6 +61,15 @@ public class GameState{
return (is(State.paused) && !net.active()) || (gameOver && !net.active());
}
/** @return whether the current state is *not* the menu. */
public boolean isGame(){
return state != State.menu;
}
public boolean isMenu(){
return state == State.menu;
}
public boolean is(State astate){
return state == astate;
}

View File

@@ -6,12 +6,10 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -32,13 +30,15 @@ import static mindustry.Vars.*;
public class Logic implements ApplicationListener{
public Logic(){
Events.on(WaveEvent.class, event -> {
for(Player p : playerGroup.all()){
p.respawns = state.rules.respawns;
}
Events.on(WorldLoadEvent.class, event -> {
//TODO remove later
//Weathers.snow.create();
});
if(world.isZone()){
world.getZone().updateWave(state.wave);
Events.on(WaveEvent.class, event -> {
if(state.isCampaign()){
//TODO implement
//state.getSector().updateWave(state.wave);
}
});
@@ -62,7 +62,7 @@ public class Logic implements ApplicationListener{
}
}
TeamData data = state.teams.get(tile.getTeam());
TeamData data = state.teams.get(tile.team());
//remove existing blocks that have been placed here.
//painful O(n) iteration + copy
@@ -99,19 +99,20 @@ public class Logic implements ApplicationListener{
}
}
/** Adds starting items, resets wave time, and sets state to playing. */
public void play(){
state.set(State.playing);
state.wavetime = state.rules.waveSpacing * 2; //grace period of 2x wave time before game starts
Events.fire(new PlayEvent());
//add starting items
if(!world.isZone()){
if(!state.isCampaign()){
for(TeamData team : state.teams.getActive()){
if(team.hasCore()){
TileEntity entity = team.core();
entity.items.clear();
Tilec entity = team.core();
entity.items().clear();
for(ItemStack stack : state.rules.loadout){
entity.items.add(stack.item, stack.amount);
entity.items().add(stack.item, stack.amount);
}
}
}
@@ -119,24 +120,21 @@ public class Logic implements ApplicationListener{
}
public void reset(){
state.wave = 1;
state.wavetime = state.rules.waveSpacing;
state.gameOver = state.launched = false;
state.teams = new Teams();
state.rules = new Rules();
state.stats = new Stats();
State prev = state.getState();
//recreate gamestate - sets state to menu
state = new GameState();
//fire change event, since it was technically changed
Events.fire(new StateChangeEvent(prev, State.menu));
entities.clear();
Groups.all.clear();
Time.clear();
TileEntity.sleepingEntities = 0;
Events.fire(new ResetEvent());
}
public void runWave(){
spawner.spawnEnemies();
state.wave++;
state.wavetime = world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
state.wavetime = state.hasSector() && state.getSector().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
Events.fire(new WaveEvent());
}
@@ -158,7 +156,7 @@ public class Logic implements ApplicationListener{
}
if(alive != null && !state.gameOver){
if(world.isZone() && alive == state.rules.defaultTeam){
if(state.isCampaign() && alive == state.rules.defaultTeam){
//in attack maps, a victorious game over is equivalent to a launch
Call.launchZone();
}else{
@@ -175,21 +173,22 @@ public class Logic implements ApplicationListener{
ui.hudfrag.showLaunch();
}
for(TileEntity tile : state.teams.playerCores()){
Effects.effect(Fx.launch, tile);
for(Tilec tile : state.teams.playerCores()){
Fx.launch.at(tile);
}
if(world.getZone() != null){
world.getZone().setLaunched();
if(state.isCampaign()){
//TODO implement
//state.getSector().setLaunched();
}
Time.runTask(30f, () -> {
for(TileEntity entity : state.teams.playerCores()){
for(Tilec entity : state.teams.playerCores()){
for(Item item : content.items()){
data.addItem(item, entity.items.get(item));
Events.fire(new LaunchItemEvent(item, entity.items.get(item)));
data.addItem(item, entity.items().get(item));
Events.fire(new LaunchItemEvent(item, entity.items().get(item)));
}
entity.tile.remove();
entity.tile().remove();
}
state.launched = true;
state.gameOver = true;
@@ -209,13 +208,17 @@ public class Logic implements ApplicationListener{
@Override
public void update(){
Events.fire(Trigger.update);
universe.updateGlobal();
if(!state.is(State.menu)){
if(state.isGame()){
if(!net.client()){
state.enemies = unitGroup.count(b -> b.getTeam() == state.rules.waveTeam && b.countsAsEnemy());
state.enemies = Groups.unit.count(b -> b.team() == state.rules.waveTeam && b.type().isCounted);
}
if(!state.isPaused()){
if(state.isCampaign()){
universe.update();
}
Time.update();
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){
@@ -228,35 +231,7 @@ public class Logic implements ApplicationListener{
runWave();
}
if(!headless){
effectGroup.update();
groundEffectGroup.update();
}
if(!state.isEditor()){
unitGroup.update();
puddleGroup.update();
shieldGroup.update();
bulletGroup.update();
tileGroup.update();
fireGroup.update();
}else{
unitGroup.updateEvents();
collisions.updatePhysics(unitGroup);
}
playerGroup.update();
//effect group only contains item transfers in the headless version, update it!
if(headless){
effectGroup.update();
}
if(!state.isEditor()){
//bulletGroup
collisions.collideGroups(bulletGroup, unitGroup);
collisions.collideGroups(bulletGroup, playerGroup);
}
Groups.update();
}
if(!net.client() && !world.isInvalidMap() && !state.isEditor() && state.rules.canGameOver){

View File

@@ -1,7 +1,6 @@
package mindustry.core;
import arc.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
@@ -11,12 +10,7 @@ import arc.util.serialization.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -24,7 +18,6 @@ import mindustry.net.Administration.*;
import mindustry.net.Net.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.modules.*;
@@ -62,7 +55,7 @@ public class NetClient implements ApplicationListener{
net.handleClient(Connect.class, packet -> {
Log.info("Connecting to server: {0}", packet.addressTCP);
player.isAdmin = false;
player.admin(false);
reset();
@@ -77,11 +70,11 @@ public class NetClient implements ApplicationListener{
});
ConnectPacket c = new ConnectPacket();
c.name = player.name;
c.name = player.name();
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();
@@ -99,11 +92,10 @@ public class NetClient implements ApplicationListener{
if(quietReset) return;
connecting = false;
state.set(State.menu);
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;
@@ -130,21 +122,20 @@ public class NetClient implements ApplicationListener{
});
net.handleClient(InvokePacket.class, packet -> {
packet.writeBuffer.position(0);
RemoteReadClient.readPacket(packet.writeBuffer, packet.type);
RemoteReadClient.readPacket(packet.reader(), packet.type);
});
}
//called on all clients
@Remote(targets = Loc.server, variants = Variant.both)
public static void sendMessage(String message, String sender, Player playersender){
public static void sendMessage(String message, String sender, Playerc playersender){
if(Vars.ui != null){
Vars.ui.chatfrag.addMessage(message, sender);
}
if(playersender != null){
playersender.lastText = message;
playersender.textFadeTime = 1f;
playersender.lastText(message);
playersender.textFadeTime(1f);
}
}
@@ -158,7 +149,7 @@ public class NetClient implements ApplicationListener{
//called when a server recieves a chat message from a player
@Remote(called = Loc.server, targets = Loc.client)
public static void sendChatMessage(Player player, String message){
public static void sendChatMessage(Playerc player, String message){
if(message.length() > maxTextLength){
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
@@ -176,18 +167,18 @@ public class NetClient implements ApplicationListener{
//special case; graphical server needs to see its message
if(!headless){
sendMessage(message, colorizeName(player.id, player.name), player);
sendMessage(message, colorizeName(player.id(), player.name()), player);
}
//server console logging
Log.info("&y{0}: &lb{1}", player.name, message);
Log.info("&y{0}: &lb{1}", player.name(), message);
//invoke event for all clients but also locally
//this is required so other clients get the correct name even if they don't know who's sending it yet
Call.sendMessage(message, colorizeName(player.id, player.name), player);
Call.sendMessage(message, colorizeName(player.id(), player.name()), player);
}else{
//log command to console but with brackets
Log.info("<&y{0}: &lm{1}&lg>", player.name, message);
Log.info("<&y{0}: &lm{1}&lg>", player.name(), message);
//a command was sent, now get the output
if(response.type != ResponseType.valid){
@@ -208,23 +199,22 @@ public class NetClient implements ApplicationListener{
}
public static String colorizeName(int id, String name){
Player player = playerGroup.getByID(id);
Playerc player = Groups.player.getByID(id);
if(name == null || player == null) return null;
return "[#" + player.color.toString().toUpperCase() + "]" + name;
return "[#" + player.color().toString().toUpperCase() + "]" + name;
}
@Remote(called = Loc.client, variants = Variant.one)
public static void onConnect(String ip, int port){
netClient.disconnectQuietly();
state.set(State.menu);
logic.reset();
ui.join.connect(ip, port);
}
@Remote(targets = Loc.client)
public static void onPing(Player player, long time){
Call.onPingResponse(player.con, time);
public static void onPing(Playerc player, long time){
Call.onPingResponse(player.con(), time);
}
@Remote(variants = Variant.one)
@@ -233,7 +223,7 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.one)
public static void onTraceInfo(Player player, TraceInfo info){
public static void onTraceInfo(Playerc player, TraceInfo info){
if(player != null){
ui.traces.show(player, info);
}
@@ -242,7 +232,6 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one, priority = PacketPriority.high)
public static void onKick(KickReason reason){
netClient.disconnectQuietly();
state.set(State.menu);
logic.reset();
if(!reason.quiet){
@@ -258,7 +247,6 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one, priority = PacketPriority.high)
public static void onKick(String reason){
netClient.disconnectQuietly();
state.set(State.menu);
logic.reset();
ui.showText("$disconnect", reason, Align.left);
ui.loadfrag.hide();
@@ -303,17 +291,18 @@ public class NetClient implements ApplicationListener{
ui.showLabel(message, duration, worldx, worldy);
}
/*
@Remote(variants = Variant.both, unreliable = true)
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
if(effect == null) return;
Effects.effect(effect, color, x, y, rotation);
effect.at(x, y, rotation, color);
}
@Remote(variants = Variant.both)
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
onEffect(effect, x, y, rotation, color);
}
}*/
@Remote(variants = Variant.both)
public static void onInfoToast(String message, float duration){
@@ -329,7 +318,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void onWorldDataBegin(){
entities.clear();
Groups.all.clear();
netClient.removed.clear();
logic.reset();
@@ -347,60 +336,54 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one)
public static void onPositionSet(float x, float y){
player.x = x;
player.y = y;
player.set(x, y);
}
@Remote
public static void onPlayerDisconnect(int playerid){
playerGroup.removeByID(playerid);
Groups.player.removeByID(playerid);
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onEntitySnapshot(byte groupID, short amount, short dataLen, byte[] data){
public static void onEntitySnapshot(short amount, short dataLen, byte[] data){
try{
netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen));
DataInputStream input = netClient.dataStream;
EntityGroup group = entities.get(groupID);
//go through each entity
for(int j = 0; j < amount; j++){
int id = input.readInt();
byte typeID = input.readByte();
SyncTrait entity = group == null ? null : (SyncTrait)group.getByID(id);
Syncc entity = Groups.sync.getByID(id);
boolean add = false, created = false;
if(entity == null && id == player.id){
if(entity == null && id == player.id()){
entity = player;
add = true;
}
//entity must not be added yet, so create it
if(entity == null){
entity = (SyncTrait)content.<TypeID>getByID(ContentType.typeid, typeID).constructor.get();
entity.resetID(id);
if(!netClient.isEntityUsed(entity.getID())){
entity = (Syncc)EntityMapping.map(typeID).get();
entity.id(id);
if(!netClient.isEntityUsed(entity.id())){
add = true;
}
created = true;
}
//read the entity
entity.read(input);
entity.read(Reads.get(input));
if(created && entity.getInterpolator() != null && entity.getInterpolator().target != null){
if(created && entity.interpolator().target != null){
//set initial starting position
entity.setNet(entity.getInterpolator().target.x, entity.getInterpolator().target.y);
if(entity instanceof Unit && entity.getInterpolator().targets.length > 0){
((Unit)entity).rotation = entity.getInterpolator().targets[0];
}
entity.setNet(entity.interpolator().target.x, entity.interpolator().target.y);
}
if(add){
entity.add();
netClient.addRemovedEntity(entity.getID());
netClient.addRemovedEntity(entity.id());
}
}
}catch(IOException e){
@@ -421,7 +404,7 @@ public class NetClient implements ApplicationListener{
Log.warn("Missing entity at {0}. Skipping block snapshot.", tile);
break;
}
tile.entity.read(input, tile.entity.version());
tile.entity.readAll(Reads.get(input), tile.entity.version());
}
}catch(Exception e){
e.printStackTrace();
@@ -449,9 +432,9 @@ public class NetClient implements ApplicationListener{
Tile tile = world.tile(pos);
if(tile != null && tile.entity != null){
tile.entity.items.read(input);
tile.entity.items().read(Reads.get(input));
}else{
new ItemModule().read(input);
new ItemModule().read(Reads.get(input));
}
}
@@ -464,7 +447,7 @@ public class NetClient implements ApplicationListener{
public void update(){
if(!net.client()) return;
if(!state.is(State.menu)){
if(state.isGame()){
if(!connecting) sync();
}else if(!connecting){
net.disconnect();
@@ -508,7 +491,7 @@ public class NetClient implements ApplicationListener{
quiet = false;
lastSent = 0;
entities.clear();
Groups.all.clear();
ui.chatfrag.clearMessages();
}
@@ -542,22 +525,28 @@ public class NetClient implements ApplicationListener{
}
void sync(){
if(timer.get(0, playerSyncTime)){
BuildRequest[] requests;
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.buildQueue().size, 10);
BuildRequest[] requests = null;
if(player.isBuilder() && control.input.isBuilding){
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.builder().requests().size, 10);
requests = new BuildRequest[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.buildQueue().get(i);
requests = new BuildRequest[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.builder().requests().get(i);
}
}
Call.onClientShapshot(lastSent++, player.x, player.y,
player.pointerX, player.pointerY, player.rotation, player.baseRotation,
player.velocity().x, player.velocity().y,
player.getMineTile(),
player.isBoosting, player.isShooting, ui.chatfrag.shown(), player.isBuilding,
Unitc unit = player.dead() ? Nulls.unit : player.unit();
Call.onClientShapshot(lastSent++,
unit.x(), unit.y(),
player.mouseX(), player.mouseY(),
unit.rotation(),
unit instanceof Legsc ? ((Legsc)unit).baseRotation() : 0,
unit.vel().x, unit.vel().y,
player.miner().mineTile(),
/*player.isBoosting*/false, control.input.isShooting, ui.chatfrag.shown(),
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);

View File

@@ -6,16 +6,14 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.CommandHandler.*;
import arc.util.io.*;
import arc.util.serialization.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
@@ -51,8 +49,8 @@ public class NetServer implements ApplicationListener{
if((state.rules.waveTeam == data.team && state.rules.waves) || !data.team.active()) return Integer.MAX_VALUE;
int count = 0;
for(Player other : players){
if(other.getTeam() == data.team && other != player){
for(Playerc other : players){
if(other.team() == data.team && other != player){
count++;
}
}
@@ -67,8 +65,8 @@ public class NetServer implements ApplicationListener{
private boolean closing = false;
private Interval timer = new Interval();
private ByteBuffer writeBuffer = ByteBuffer.allocate(127);
private ByteBufferOutput outputBuffer = new ByteBufferOutput(writeBuffer);
private ReusableByteOutStream writeBuffer = new ReusableByteOutStream(127);
private Writes outputBuffer = new Writes(new DataOutputStream(writeBuffer));
/** Stream for writing player sync data to. */
private ReusableByteOutStream syncStream = new ReusableByteOutStream();
@@ -133,7 +131,7 @@ public class NetServer implements ApplicationListener{
return;
}
if(admins.getPlayerLimit() > 0 && playerGroup.size() >= admins.getPlayerLimit() && !netServer.admins.isAdmin(uuid, packet.usid)){
if(admins.getPlayerLimit() > 0 && Groups.player.size() >= admins.getPlayerLimit() && !netServer.admins.isAdmin(uuid, packet.usid)){
con.kick(KickReason.playerLimit);
return;
}
@@ -174,16 +172,14 @@ public class NetServer implements ApplicationListener{
boolean preventDuplicates = headless && netServer.admins.getStrict();
if(preventDuplicates){
for(Player player : playerGroup.all()){
if(player.name.trim().equalsIgnoreCase(packet.name.trim())){
con.kick(KickReason.nameInUse);
return;
}
if(Groups.player.contains(p -> p.name().trim().equalsIgnoreCase(packet.name.trim()))){
con.kick(KickReason.nameInUse);
return;
}
if(player.uuid != null && player.usid != null && (player.uuid.equals(packet.uuid) || player.usid.equals(packet.usid))){
con.kick(KickReason.idInUse);
return;
}
if(Groups.player.contains(player -> player.uuid().equals(packet.uuid) || player.usid().equals(packet.usid))){
con.kick(KickReason.idInUse);
return;
}
}
@@ -207,25 +203,22 @@ public class NetServer implements ApplicationListener{
con.modclient = true;
}
Player player = new Player();
player.isAdmin = admins.isAdmin(uuid, packet.usid);
player.con = con;
player.usid = packet.usid;
player.name = packet.name;
player.uuid = uuid;
player.isMobile = packet.mobile;
player.dead = true;
player.setNet(player.x, player.y);
player.color.set(packet.color);
player.color.a = 1f;
Playerc player = PlayerEntity.create();
player.admin(admins.isAdmin(uuid, packet.usid));
player.con(con);
player.con().usid = packet.usid;
player.con().uuid = uuid;
player.con().mobile = packet.mobile;
player.name(packet.name);
player.color().set(packet.color).a(1f);
//save admin ID but don't overwrite it
if(!player.isAdmin && !info.admin){
if(!player.admin() && !info.admin){
info.adminUsid = packet.usid;
}
try{
writeBuffer.position(0);
writeBuffer.reset();
player.write(outputBuffer);
}catch(Throwable t){
t.printStackTrace();
@@ -236,7 +229,7 @@ public class NetServer implements ApplicationListener{
con.player = player;
//playing in pvp mode automatically assigns players to teams
player.setTeam(assignTeam(player, playerGroup.all()));
player.team(assignTeam(player));
sendWorldData(player);
@@ -248,7 +241,7 @@ public class NetServer implements ApplicationListener{
net.handleServer(InvokePacket.class, (con, packet) -> {
if(con.player == null) return;
try{
RemoteReadServer.readPacket(packet.writeBuffer, packet.type, con.player);
RemoteReadServer.readPacket(packet.reader(), packet.type, con.player);
}catch(ValidateException e){
Log.debug("Validation failed for '{0}': {1}", e.player, e.getMessage());
}catch(RuntimeException e){
@@ -270,7 +263,7 @@ public class NetServer implements ApplicationListener{
}
private void registerCommands(){
clientCommands.<Player>register("help", "[page]", "Lists all commands.", (args, player) -> {
clientCommands.<Playerc>register("help", "[page]", "Lists all commands.", (args, player) -> {
if(args.length > 0 && !Strings.canParseInt(args[0])){
player.sendMessage("[scarlet]'page' must be a number.");
return;
@@ -296,8 +289,8 @@ public class NetServer implements ApplicationListener{
player.sendMessage(result.toString());
});
clientCommands.<Player>register("t", "<message...>", "Send a message only to your teammates.", (args, player) -> {
playerGroup.all().each(p -> p.getTeam() == player.getTeam(), o -> o.sendMessage(args[0], player, "[#" + player.getTeam().color.toString() + "]<T>" + NetClient.colorizeName(player.id, player.name)));
clientCommands.<Playerc>register("t", "<message...>", "Send a message only to your teammates.", (args, player) -> {
Groups.player.each(p -> p.team() == player.team(), o -> o.sendMessage(args[0], player, "[#" + player.team().color.toString() + "]<T>" + NetClient.colorizeName(player.id(), player.name())));
});
//duration of a a kick in seconds
@@ -308,37 +301,37 @@ public class NetServer implements ApplicationListener{
int voteCooldown = 60 * 1;
class VoteSession{
Player target;
Playerc target;
ObjectSet<String> voted = new ObjectSet<>();
VoteSession[] map;
Timer.Task task;
int votes;
public VoteSession(VoteSession[] map, Player target){
public VoteSession(VoteSession[] map, Playerc target){
this.target = target;
this.map = map;
this.task = Timer.schedule(() -> {
if(!checkPass()){
Call.sendMessage(Strings.format("[lightgray]Vote failed. Not enough votes to kick[orange] {0}[lightgray].", target.name));
Call.sendMessage(Strings.format("[lightgray]Vote failed. Not enough votes to kick[orange] {0}[lightgray].", target.name()));
map[0] = null;
task.cancel();
}
}, voteDuration);
}
void vote(Player player, int d){
void vote(Playerc player, int d){
votes += d;
voted.addAll(player.uuid, admins.getInfo(player.uuid).lastIP);
voted.addAll(player.uuid(), admins.getInfo(player.uuid()).lastIP);
Call.sendMessage(Strings.format("[orange]{0}[lightgray] has voted on kicking[orange] {1}[].[accent] ({2}/{3})\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
player.name, target.name, votes, votesRequired()));
player.name(), target.name(), votes, votesRequired()));
}
boolean checkPass(){
if(votes >= votesRequired()){
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] {0}[orange] will be banned from the server for {1} minutes.", target.name, (kickDuration/60)));
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] {0}[orange] will be banned from the server for {1} minutes.", target.name(), (kickDuration/60)));
target.getInfo().lastKicked = Time.millis() + kickDuration*1000;
playerGroup.all().each(p -> p.uuid != null && p.uuid.equals(target.uuid), p -> p.con.kick(KickReason.vote));
Groups.player.each(p -> p.uuid().equals(target.uuid()), p -> p.kick(KickReason.vote));
map[0] = null;
task.cancel();
return true;
@@ -351,18 +344,18 @@ public class NetServer implements ApplicationListener{
//current kick sessions
VoteSession[] currentlyKicking = {null};
clientCommands.<Player>register("votekick", "[player...]", "Vote to kick a player, with a cooldown.", (args, player) -> {
clientCommands.<Playerc>register("votekick", "[player...]", "Vote to kick a player, with a cooldown.", (args, player) -> {
if(!Config.enableVotekick.bool()){
player.sendMessage("[scarlet]Vote-kick is disabled on this server.");
return;
}
if(playerGroup.size() < 3){
if(Groups.player.size() < 3){
player.sendMessage("[scarlet]At least 3 players are needed to start a votekick.");
return;
}
if(player.isLocal){
if(player.isLocal()){
player.sendMessage("[scarlet]Just kick them yourself if you're the host.");
return;
}
@@ -370,27 +363,26 @@ public class NetServer implements ApplicationListener{
if(args.length == 0){
StringBuilder builder = new StringBuilder();
builder.append("[orange]Players to kick: \n");
for(Player p : playerGroup.all()){
if(p.isAdmin || p.con == null || p == player) continue;
builder.append("[lightgray] ").append(p.name).append("[accent] (#").append(p.id).append(")\n");
}
Groups.player.each(p -> !p.admin() && p.con() != null && p != player, p -> {
builder.append("[lightgray] ").append(p.name()).append("[accent] (#").append(p.id()).append(")\n");
});
player.sendMessage(builder.toString());
}else{
Player found;
Playerc found;
if(args[0].length() > 1 && args[0].startsWith("#") && Strings.canParseInt(args[0].substring(1))){
int id = Strings.parseInt(args[0].substring(1));
found = playerGroup.find(p -> p.id == id);
found = Groups.player.find(p -> p.id() == id);
}else{
found = playerGroup.find(p -> p.name.equalsIgnoreCase(args[0]));
found = Groups.player.find(p -> p.name().equalsIgnoreCase(args[0]));
}
if(found != null){
if(found.isAdmin){
if(found.admin()){
player.sendMessage("[scarlet]Did you really expect to be able to kick an admin?");
}else if(found.isLocal){
}else if(found.isLocal()){
player.sendMessage("[scarlet]Local players cannot be kicked.");
}else if(found.getTeam() != player.getTeam()){
}else if(found.team() != player.team()){
player.sendMessage("[scarlet]Only players on your team can be kicked.");
}else{
if(!vtime.get()){
@@ -409,17 +401,17 @@ public class NetServer implements ApplicationListener{
}
});
clientCommands.<Player>register("vote", "<y/n>", "Vote to kick the current player.", (arg, player) -> {
clientCommands.<Playerc>register("vote", "<y/n>", "Vote to kick the current player.", (arg, player) -> {
if(currentlyKicking[0] == null){
player.sendMessage("[scarlet]Nobody is being voted on.");
}else{
if(player.isLocal){
if(player.isLocal()){
player.sendMessage("Local players can't vote. Kick the player yourself instead.");
return;
}
//hosts can vote all they want
if(player.uuid != null && (currentlyKicking[0].voted.contains(player.uuid) || currentlyKicking[0].voted.contains(admins.getInfo(player.uuid).lastIP))){
if((currentlyKicking[0].voted.contains(player.uuid()) || currentlyKicking[0].voted.contains(admins.getInfo(player.uuid()).lastIP))){
player.sendMessage("[scarlet]You've already voted. Sit down.");
return;
}
@@ -440,8 +432,8 @@ public class NetServer implements ApplicationListener{
});
clientCommands.<Player>register("sync", "Re-synchronize world state.", (args, player) -> {
if(player.isLocal){
clientCommands.<Playerc>register("sync", "Re-synchronize world state.", (args, player) -> {
if(player.isLocal()){
player.sendMessage("[scarlet]Re-synchronizing as the host is pointless.");
}else{
if(Time.timeSinceMillis(player.getInfo().lastSyncTime) < 1000 * 5){
@@ -450,69 +442,73 @@ public class NetServer implements ApplicationListener{
}
player.getInfo().lastSyncTime = Time.millis();
Call.onWorldDataBegin(player.con);
Call.onWorldDataBegin(player.con());
netServer.sendWorldData(player);
}
});
}
public int votesRequired(){
return 2 + (playerGroup.size() > 4 ? 1 : 0);
return 2 + (Groups.player.size() > 4 ? 1 : 0);
}
public Team assignTeam(Player current, Iterable<Player> players){
public Team assignTeam(Playerc current){
return assigner.assign(current, Groups.player);
}
public Team assignTeam(Playerc current, Iterable<Playerc> players){
return assigner.assign(current, players);
}
public void sendWorldData(Player player){
public void sendWorldData(Playerc player){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
DeflaterOutputStream def = new FastDeflaterOutputStream(stream);
NetworkIO.writeWorld(player, def);
WorldStream data = new WorldStream();
data.stream = new ByteArrayInputStream(stream.toByteArray());
player.con.sendStream(data);
player.con().sendStream(data);
Log.debug("Packed {0} compressed bytes of world data.", stream.size());
}
public static void onDisconnect(Player player, String reason){
public static void onDisconnect(Playerc player, String reason){
//singleplayer multiplayer wierdness
if(player.con == null){
if(player.con() == null){
player.remove();
return;
}
if(!player.con.hasDisconnected){
if(player.con.hasConnected){
if(!player.con().hasDisconnected){
if(player.con().hasConnected){
Events.fire(new PlayerLeave(player));
if(Config.showConnectMessages.bool()) Call.sendMessage("[accent]" + player.name + "[accent] has disconnected.");
Call.onPlayerDisconnect(player.id);
if(Config.showConnectMessages.bool()) Call.sendMessage("[accent]" + player.name() + "[accent] has disconnected.");
Call.onPlayerDisconnect(player.id());
}
if(Config.showConnectMessages.bool()) Log.info("&lm[{1}] &lc{0} has disconnected. &lg&fi({2})", player.name, player.uuid, reason);
if(Config.showConnectMessages.bool()) Log.info("&lm[{1}] &lc{0} has disconnected. &lg&fi({2})", player.name(), player.uuid(), reason);
}
player.remove();
player.con.hasDisconnected = true;
player.con().hasDisconnected = true;
}
@Remote(targets = Loc.client, unreliable = true)
public static void onClientShapshot(
Player player,
Playerc player,
int snapshotID,
float x, float y,
float pointerX, float pointerY,
float rotation, float baseRotation,
float xVelocity, float yVelocity,
Tile mining,
boolean boosting, boolean shooting, boolean chatting, boolean building,
BuildRequest[] requests,
boolean boosting, boolean shooting, boolean chatting,
@Nullable BuildRequest[] requests,
float viewX, float viewY, float viewWidth, float viewHeight
){
NetConnection connection = player.con;
NetConnection connection = player.con();
if(connection == null || snapshotID < connection.lastRecievedClientSnapshot) return;
boolean verifyPosition = !player.isDead() && netServer.admins.getStrict() && headless;
boolean verifyPosition = !player.dead() && netServer.admins.getStrict() && headless;
if(connection.lastRecievedClientTime == 0) connection.lastRecievedClientTime = Time.millis() - 16;
@@ -521,91 +517,103 @@ public class NetServer implements ApplicationListener{
connection.viewWidth = viewWidth;
connection.viewHeight = viewHeight;
long elapsed = Time.timeSinceMillis(connection.lastRecievedClientTime);
player.mouseX(pointerX);
player.mouseY(pointerY);
player.typing(chatting);
float maxSpeed = boosting && !player.mech.flying ? player.mech.compoundSpeedBoost : player.mech.compoundSpeed;
float maxMove = elapsed / 1000f * 60f * Math.min(maxSpeed, player.mech.maxSpeed) * 1.2f;
player.unit().controlWeapons(shooting, shooting);
player.unit().aim(pointerX, pointerY);
player.pointerX = pointerX;
player.pointerY = pointerY;
player.setMineTile(mining);
player.isTyping = chatting;
player.isBoosting = boosting;
player.isShooting = shooting;
player.isBuilding = building;
player.buildQueue().clear();
if(player.isBuilder()){
player.builder().clearBuilding();
}
for(BuildRequest req : requests){
if(req == null) continue;
Tile tile = world.tile(req.x, req.y);
if(tile == null || (!req.breaking && req.block == null)) continue;
//auto-skip done requests
if(req.breaking && tile.block() == Blocks.air){
continue;
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){
continue;
}else if(connection.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
continue;
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
action.block = req.block;
action.rotation = req.rotation;
action.config = req.config;
})){
//force the player to remove this request if that's not the case
Call.removeQueueBlock(player.con, req.x, req.y, req.breaking);
connection.rejectedRequests.add(req);
continue;
if(player.isMiner()){
player.miner().mineTile(mining);
}
if(requests != null){
for(BuildRequest req : requests){
if(req == null) continue;
Tile tile = world.tile(req.x, req.y);
if(tile == null || (!req.breaking && req.block == null)) continue;
//auto-skip done requests
if(req.breaking && tile.block() == Blocks.air){
continue;
}else if(!req.breaking && tile.block() == req.block && (!req.block.rotate || tile.rotation() == req.rotation)){
continue;
}else if(connection.rejectedRequests.contains(r -> r.breaking == req.breaking && r.x == req.x && r.y == req.y)){ //check if request was recently rejected, and skip it if so
continue;
}else if(!netServer.admins.allowAction(player, req.breaking ? ActionType.breakBlock : ActionType.placeBlock, tile, action -> { //make sure request is allowed by the server
action.block = req.block;
action.rotation = req.rotation;
action.config = req.config;
})){
//force the player to remove this request if that's not the case
Call.removeQueueBlock(player.con(), req.x, req.y, req.breaking);
connection.rejectedRequests.add(req);
continue;
}
player.builder().requests().addLast(req);
}
player.buildQueue().addLast(req);
}
connection.rejectedRequests.clear();
vector.set(x - player.getInterpolator().target.x, y - player.getInterpolator().target.y);
vector.limit(maxMove);
if(!player.dead()){
Unitc unit = player.unit();
long elapsed = Time.timeSinceMillis(connection.lastRecievedClientTime);
float maxSpeed = player.dead() ? Float.MAX_VALUE : player.unit().type().speed;
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
float prevx = player.x, prevy = player.y;
player.set(player.getInterpolator().target.x, player.getInterpolator().target.y);
if(!player.mech.flying && player.boostHeat < 0.01f){
player.move(vector.x, vector.y);
vector.set(x - unit.interpolator().target.x, y - unit.interpolator().target.y);
vector.limit(maxMove);
float prevx = unit.x(), prevy = unit.y();
unit.set(unit.interpolator().target.x, unit.interpolator().target.y);
if(!unit.isFlying()){
unit.move(vector.x, vector.y);
}else{
unit.trns(vector.x, vector.y);
}
float newx = unit.x(), newy = unit.y();
if(!verifyPosition){
unit.x(prevx);
unit.y(prevy);
newx = x;
newy = y;
}else if(Mathf.dst(x, y, newx, newy) > correctDist){
Call.onPositionSet(player.con(), newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
unit.x(prevx);
unit.y(prevy);
//set interpolator target to *new* position so it moves toward it
unit.interpolator().read(unit.x(), unit.y(), newx, newy, rotation, baseRotation);
unit.vel().set(xVelocity, yVelocity); //only for visual calculation purposes, doesn't actually update the player
}else{
player.x += vector.x;
player.y += vector.y;
player.x(x);
player.y(y);
}
float newx = player.x, newy = player.y;
if(!verifyPosition){
player.x = prevx;
player.y = prevy;
newx = x;
newy = y;
}else if(Mathf.dst(x, y, newx, newy) > correctDist){
Call.onPositionSet(player.con, newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
player.x = prevx;
player.y = prevy;
//set interpolator target to *new* position so it moves toward it
player.getInterpolator().read(player.x, player.y, newx, newy, rotation, baseRotation);
player.velocity().set(xVelocity, yVelocity); //only for visual calculation purposes, doesn't actually update the player
connection.lastRecievedClientSnapshot = snapshotID;
connection.lastRecievedClientTime = Time.millis();
}
@Remote(targets = Loc.client, called = Loc.server)
public static void onAdminRequest(Player player, Player other, AdminAction action){
public static void onAdminRequest(Playerc player, Playerc other, AdminAction action){
if(!player.isAdmin){
if(!player.admin()){
Log.warn("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.",
player.name, player.con.address);
player.name(), player.con().address);
return;
}
if(other == null || ((other.isAdmin && !player.isLocal) && other != player)){
Log.warn("{0} attempted to perform admin action on nonexistant or admin player.", player.name);
if(other == null || ((other.admin() && !player.isLocal()) && other != player)){
Log.warn("{0} attempted to perform admin action on nonexistant or admin player.", player.name());
return;
}
@@ -614,32 +622,32 @@ public class NetServer implements ApplicationListener{
//not a real issue, because server owners may want to do just that
state.wavetime = 0f;
}else if(action == AdminAction.ban){
netServer.admins.banPlayerIP(other.con.address);
other.con.kick(KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
netServer.admins.banPlayerIP(other.con().address);
other.kick(KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name(), other.name());
}else if(action == AdminAction.kick){
other.con.kick(KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
other.kick(KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name(), other.name());
}else if(action == AdminAction.trace){
TraceInfo info = new TraceInfo(other.con.address, other.uuid, other.con.modclient, other.con.mobile);
if(player.con != null){
Call.onTraceInfo(player.con, other, info);
TraceInfo info = new TraceInfo(other.con().address, other.uuid(), other.con().modclient, other.con().mobile);
if(player.con() != null){
Call.onTraceInfo(player.con(), other, info);
}else{
NetClient.onTraceInfo(other, info);
}
Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name);
Log.info("&lc{0} has requested trace info of {1}.", player.name(), other.name());
}
}
@Remote(targets = Loc.client)
public static void connectConfirm(Player player){
if(player.con == null || player.con.hasConnected) return;
public static void connectConfirm(Playerc player){
if(player.con() == null || player.con().hasConnected) return;
player.add();
player.con.hasConnected = true;
player.con().hasConnected = true;
if(Config.showConnectMessages.bool()){
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
Log.info("&lm[{1}] &y{0} has connected. ", player.name, player.uuid);
Call.sendMessage("[accent]" + player.name() + "[accent] has connected.");
Log.info("&lm[{1}] &y{0} has connected. ", player.name(), player.uuid());
}
if(!Config.motd.string().equalsIgnoreCase("off")){
@@ -653,7 +661,7 @@ public class NetServer implements ApplicationListener{
if(state.rules.pvp){
int used = 0;
for(TeamData t : state.teams.getActive()){
if(playerGroup.count(p -> p.getTeam() == t.team) > 0){
if(Groups.player.count(p -> p.team() == t.team) > 0){
used++;
}
}
@@ -665,7 +673,7 @@ public class NetServer implements ApplicationListener{
@Override
public void update(){
if(!headless && !closing && net.server() && state.is(State.menu)){
if(!headless && !closing && net.server() && state.isMenu()){
closing = true;
ui.loadfrag.show("$server.closing");
Time.runTask(5f, () -> {
@@ -675,7 +683,7 @@ public class NetServer implements ApplicationListener{
});
}
if(!state.is(State.menu) && net.server()){
if(state.isGame() && net.server()){
sync();
}
}
@@ -705,12 +713,12 @@ public class NetServer implements ApplicationListener{
syncStream.reset();
short sent = 0;
for(TileEntity entity : tileGroup.all()){
if(!entity.block.sync) continue;
for(Tilec entity : Groups.tile){
if(!entity.block().sync) continue;
sent ++;
dataStream.writeInt(entity.tile.pos());
entity.write(dataStream);
dataStream.writeInt(entity.tile().pos());
entity.writeAll(Writes.get(dataStream));
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
@@ -728,65 +736,53 @@ public class NetServer implements ApplicationListener{
}
}
public void writeEntitySnapshot(Player player) throws IOException{
public void writeEntitySnapshot(Playerc player) throws IOException{
syncStream.reset();
Array<CoreEntity> cores = state.teams.cores(player.getTeam());
Array<CoreEntity> cores = state.teams.cores(player.team());
dataStream.writeByte(cores.size);
for(CoreEntity entity : cores){
dataStream.writeInt(entity.tile.pos());
entity.items.write(dataStream);
dataStream.writeInt(entity.tile().pos());
entity.items().write(Writes.get(dataStream));
}
dataStream.close();
byte[] stateBytes = syncStream.toByteArray();
//write basic state data.
Call.onStateSnapshot(player.con, state.wavetime, state.wave, state.enemies, (short)stateBytes.length, net.compressSnapshot(stateBytes));
Call.onStateSnapshot(player.con(), state.wavetime, state.wave, state.enemies, (short)stateBytes.length, net.compressSnapshot(stateBytes));
viewport.setSize(player.con.viewWidth, player.con.viewHeight).setCenter(player.con.viewX, player.con.viewY);
viewport.setSize(player.con().viewWidth, player.con().viewHeight).setCenter(player.con().viewX, player.con().viewY);
//check for syncable groups
for(EntityGroup<?> group : entities.all()){
if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue;
syncStream.reset();
//make sure mapping is enabled for this group
if(!group.mappingEnabled()){
throw new RuntimeException("Entity group '" + group.getType() + "' contains SyncTrait entities, yet mapping is not enabled. In order for syncing to work, you must enable mapping for this group.");
}
int sent = 0;
syncStream.reset();
for(Syncc entity : Groups.sync){
//write all entities now
dataStream.writeInt(entity.id()); //write id
dataStream.writeByte(entity.classId()); //write type ID
entity.write(Writes.get(dataStream)); //write entity
int sent = 0;
sent++;
for(Entity entity : group.all()){
SyncTrait sync = (SyncTrait)entity;
if(!sync.isSyncing()) continue;
//write all entities now
dataStream.writeInt(entity.getID()); //write id
dataStream.writeByte(sync.getTypeID().id); //write type ID
sync.write(dataStream); //write entity
sent++;
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.onEntitySnapshot(player.con, (byte)group.getID(), (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
sent = 0;
syncStream.reset();
}
}
if(sent > 0){
if(syncStream.size() > maxSnapshotSize){
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.onEntitySnapshot(player.con, (byte)group.getID(), (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
Call.onEntitySnapshot(player.con(), (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
sent = 0;
syncStream.reset();
}
}
if(sent > 0){
dataStream.close();
byte[] syncBytes = syncStream.toByteArray();
Call.onEntitySnapshot(player.con(), (short)sent, (short)syncBytes.length, net.compressSnapshot(syncBytes));
}
}
String fixName(String name){
@@ -842,24 +838,24 @@ public class NetServer implements ApplicationListener{
void sync(){
try{
//iterate through each player
for(int i = 0; i < playerGroup.size(); i++){
Player player = playerGroup.all().get(i);
if(player.isLocal) continue;
if(player.con == null || !player.con.isConnected()){
Groups.player.each(p -> !p.isLocal(), player -> {
if(player.con() == null || !player.con().isConnected()){
onDisconnect(player, "disappeared");
continue;
return;
}
NetConnection connection = player.con;
NetConnection connection = player.con();
if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue;
if(!player.timer(0, serverSyncTime) || !connection.hasConnected) return;
writeEntitySnapshot(player);
}
try{
writeEntitySnapshot(player);
}catch(IOException e){
e.printStackTrace();
}
});
if(playerGroup.size() > 0 && Core.settings.getBool("blocksync") && timer.get(timerBlockSync, blockSyncTime)){
if(Groups.player.size() > 0 && Core.settings.getBool("blocksync") && timer.get(timerBlockSync, blockSyncTime)){
writeBlockSnapshots();
}
@@ -869,6 +865,6 @@ public class NetServer implements ApplicationListener{
}
public interface TeamAssigner{
Team assign(Player player, Iterable<Player> players);
Team assign(Playerc player, Iterable<Playerc> players);
}
}

View File

@@ -2,11 +2,12 @@ package mindustry.core;
import arc.*;
import arc.Input.*;
import arc.struct.*;
import arc.files.*;
import arc.func.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.struct.*;
import arc.util.*;
import arc.util.serialization.*;
import mindustry.mod.*;
import mindustry.net.*;
@@ -15,7 +16,7 @@ import mindustry.type.*;
import mindustry.ui.dialogs.*;
import org.mozilla.javascript.*;
import static mindustry.Vars.mobile;
import static mindustry.Vars.*;
public interface Platform{
@@ -103,6 +104,32 @@ public interface Platform{
default void shareFile(Fi file){
}
default void export(String name, String extension, FileWriter writer){
if(!ios){
platform.showFileChooser(false, extension, file -> {
ui.loadAnd(() -> {
try{
writer.write(file);
}catch(Throwable e){
ui.showException(e);
Log.err(e);
}
});
});
}else{
ui.loadAnd(() -> {
try{
Fi result = Core.files.local(name+ "." + extension);
writer.write(result);
platform.shareFile(result);
}catch(Throwable e){
ui.showException(e);
Log.err(e);
}
});
}
}
/**
* Show a file chooser.
* @param cons Selection listener
@@ -130,4 +157,8 @@ public interface Platform{
/** Stops forcing the app into landscape orientation.*/
default void endForceLandscape(){
}
interface FileWriter{
void write(Fi file) throws Throwable;
}
}

View File

@@ -2,27 +2,18 @@ package mindustry.core;
import arc.*;
import arc.files.*;
import arc.func.*;
import arc.fx.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.effect.GroundEffectEntity.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.ui.*;
import mindustry.world.blocks.defense.ForceProjector.*;
import static arc.Core.*;
import static mindustry.Vars.*;
@@ -34,63 +25,26 @@ public class Renderer implements ApplicationListener{
public final LightRenderer lights = new LightRenderer();
public final Pixelator pixelator = new Pixelator();
public FrameBuffer shieldBuffer = new FrameBuffer(2, 2);
public FrameBuffer effectBuffer = new FrameBuffer(2, 2);
private Bloom bloom;
private Color clearColor;
private FxProcessor fx = new FxProcessor();
private Color clearColor = new Color(0f, 0f, 0f, 1f);
private float targetscale = Scl.scl(4);
private float camerascale = targetscale;
private float landscale = 0f, landTime;
private float minZoomScl = Scl.scl(0.01f);
private Rect rect = new Rect(), rect2 = new Rect();
private float shakeIntensity, shaketime;
public Renderer(){
camera = new Camera();
Shaders.init();
Effects.setScreenShakeProvider((intensity, duration) -> {
shakeIntensity = Math.max(intensity, shakeIntensity);
shaketime = Math.max(shaketime, duration);
});
//fx.addEffect(new SnowFilter());
}
Effects.setEffectProvider((effect, color, x, y, rotation, data) -> {
if(effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = camera.bounds(rect);
Rect pos = rect2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
if(!(effect instanceof GroundEffect)){
EffectEntity entity = Pools.obtain(EffectEntity.class, EffectEntity::new);
entity.effect = effect;
entity.color.set(color);
entity.rotation = rotation;
entity.data = data;
entity.id++;
entity.set(x, y);
if(data instanceof Entity){
entity.setParent((Entity)data);
}
effectGroup.add(entity);
}else{
GroundEffectEntity entity = Pools.obtain(GroundEffectEntity.class, GroundEffectEntity::new);
entity.effect = effect;
entity.color.set(color);
entity.rotation = rotation;
entity.id++;
entity.data = data;
entity.set(x, y);
if(data instanceof Entity){
entity.setParent((Entity)data);
}
groundEffectGroup.add(entity);
}
}
}
});
clearColor = new Color(0f, 0f, 0f, 1f);
public void shake(float intensity, float duration){
shakeIntensity = Math.max(shakeIntensity, intensity);
shaketime = Math.max(shaketime, duration);
}
@Override
@@ -115,25 +69,10 @@ public class Renderer implements ApplicationListener{
camera.width = graphics.getWidth() / camerascale;
camera.height = graphics.getHeight() / camerascale;
if(state.is(State.menu)){
if(state.isMenu()){
landTime = 0f;
graphics.clear(Color.black);
}else{
Vec2 position = Tmp.v3.set(player);
if(player.isDead()){
TileEntity core = player.getClosestCore();
if(core != null){
if(player.spawner == null){
camera.position.lerpDelta(core.x, core.y, 0.08f);
}else{
camera.position.lerpDelta(position, 0.08f);
}
}
}else if(control.input instanceof DesktopInput && !state.isPaused()){
camera.position.lerpDelta(position, 0.08f);
}
updateShake(0.75f);
if(pixelator.enabled()){
pixelator.drawPixelate();
@@ -150,7 +89,7 @@ public class Renderer implements ApplicationListener{
@Override
public void dispose(){
minimap.dispose();
shieldBuffer.dispose();
effectBuffer.dispose();
blocks.dispose();
if(bloom != null){
bloom.dispose();
@@ -164,6 +103,8 @@ public class Renderer implements ApplicationListener{
if(settings.getBool("bloom")){
setupBloom();
}
fx.resize(width, height);
}
@Override
@@ -181,7 +122,7 @@ public class Renderer implements ApplicationListener{
}
bloom = new Bloom(true);
bloom.setClearColor(0f, 0f, 0f, 0f);
}catch(Exception e){
}catch(Throwable e){
e.printStackTrace();
settings.put("bloom", false);
settings.save();
@@ -202,6 +143,23 @@ public class Renderer implements ApplicationListener{
}
}
void beginFx(){
if(!fx.hasEnabledEffects()) return;
Draw.flush();
fx.clear();
fx.begin();
}
void endFx(){
if(!fx.hasEnabledEffects()) return;
Draw.flush();
fx.end();
fx.applyEffects();
fx.render(0, 0, fx.getWidth(), fx.getHeight());
}
void updateShake(float scale){
if(shaketime > 0){
float intensity = shakeIntensity * (settings.getInt("screenshake", 4) / 4f) * scale;
@@ -218,26 +176,28 @@ public class Renderer implements ApplicationListener{
camera.update();
if(Float.isNaN(camera.position.x) || Float.isNaN(camera.position.y)){
camera.position.x = player.x;
camera.position.y = player.y;
camera.position.set(player);
}
graphics.clear(clearColor);
if(!graphics.isHidden() && (Core.settings.getBool("animatedwater") || Core.settings.getBool("animatedshields")) && (shieldBuffer.getWidth() != graphics.getWidth() || shieldBuffer.getHeight() != graphics.getHeight())){
shieldBuffer.resize(graphics.getWidth(), graphics.getHeight());
if(!graphics.isHidden() && (Core.settings.getBool("animatedwater") || Core.settings.getBool("animatedshields")) && (effectBuffer.getWidth() != graphics.getWidth() || effectBuffer.getHeight() != graphics.getHeight())){
effectBuffer.resize(graphics.getWidth(), graphics.getHeight());
}
Draw.proj(camera.projection());
Draw.proj(camera);
beginFx();
drawBackground();
blocks.floor.checkChanges();
blocks.floor.drawFloor();
groundEffectGroup.draw(e -> e instanceof BelowLiquidTrait);
puddleGroup.draw();
groundEffectGroup.draw(e -> !(e instanceof BelowLiquidTrait));
Groups.drawFloor();
Groups.drawFloorOver();
blocks.processBlocks();
blocks.drawShadows();
Draw.color();
@@ -246,7 +206,9 @@ public class Renderer implements ApplicationListener{
blocks.floor.endDraw();
blocks.drawBlocks(Layer.block);
blocks.drawFog();
if(state.rules.drawFog){
blocks.drawFog();
}
blocks.drawDestroyed();
@@ -256,57 +218,45 @@ public class Renderer implements ApplicationListener{
blocks.drawBlocks(Layer.overlay);
drawGroundShadows();
drawAllTeams(false);
Groups.drawGroundShadows();
Groups.drawGroundUnder();
Groups.drawGround();
blocks.drawBlocks(Layer.turret);
drawFlyerShadows();
blocks.drawBlocks(Layer.power);
blocks.drawBlocks(Layer.lights);
drawAllTeams(true);
overlays.drawBottom();
Groups.drawFlyingShadows();
Groups.drawFlying();
Draw.flush();
if(bloom != null){
bloom.capture();
}
bulletGroup.draw();
effectGroup.draw();
Groups.drawBullets();
Groups.drawEffects();
Draw.flush();
if(bloom != null){
bloom.render();
}
overlays.drawBottom();
playerGroup.draw(p -> p.isLocal, Player::drawBuildRequests);
if(shieldGroup.countInBounds() > 0){
if(settings.getBool("animatedshields") && Shaders.shield != null){
Draw.flush();
shieldBuffer.begin();
graphics.clear(Color.clear);
shieldGroup.draw();
shieldGroup.draw(shield -> true, ShieldEntity::drawOver);
Draw.flush();
shieldBuffer.end();
Draw.shader(Shaders.shield);
Draw.color(Pal.accent);
Draw.rect(Draw.wrap(shieldBuffer.getTexture()), camera.position.x, camera.position.y, camera.width, -camera.height);
Draw.color();
Draw.shader();
}else{
shieldGroup.draw(shield -> true, ShieldEntity::drawSimple);
}
}
Groups.drawOverlays();
overlays.drawTop();
playerGroup.draw(p -> !p.isDead(), Player::drawName);
Groups.drawWeather();
endFx();
if(!pixelator.enabled()){
Groups.drawNames();
}
if(state.rules.lighting){
lights.draw();
@@ -318,70 +268,35 @@ public class Renderer implements ApplicationListener{
Draw.flush();
}
private void drawLanding(){
if(landTime > 0 && player.getClosestCore() != null){
float fract = landTime / Fx.coreLand.lifetime;
TileEntity entity = player.getClosestCore();
private void drawBackground(){
TextureRegion reg = entity.block.icon(Cicon.full);
}
private void drawLanding(){
if(landTime > 0 && player.closestCore() != null){
float fract = landTime / Fx.coreLand.lifetime;
Tilec entity = player.closestCore();
TextureRegion reg = entity.block().icon(Cicon.full);
float scl = Scl.scl(4f) / camerascale;
float s = reg.getWidth() * Draw.scl * scl * 4f * fract;
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.x, entity.y, s, s);
Draw.rect("circle-shadow", entity.getX(), entity.getY(), 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);
Lines.lineAngle(entity.getX() + x, entity.getY() + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.x, entity.y, reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.rect(reg, entity.getX(), entity.getY(), reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.reset();
}
}
private void drawGroundShadows(){
Draw.color(0, 0, 0, 0.4f);
float rad = 1.6f;
Cons<Unit> draw = u -> {
float size = Math.max(u.getIconRegion().getWidth(), u.getIconRegion().getHeight()) * Draw.scl;
Draw.rect("circle-shadow", u.x, u.y, size * rad, size * rad);
};
unitGroup.draw(unit -> !unit.isDead(), draw::get);
if(!playerGroup.isEmpty()){
playerGroup.draw(unit -> !unit.isDead(), draw::get);
}
Draw.color();
}
private void drawFlyerShadows(){
float trnsX = -12, trnsY = -13;
Draw.color(0, 0, 0, 0.22f);
unitGroup.draw(unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY));
playerGroup.draw(unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY));
Draw.color();
}
private void drawAllTeams(boolean flying){
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder);
playerGroup.draw(p -> p.isFlying() == flying && !p.isDead(), Unit::drawUnder);
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawAll);
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawOver);
}
public void scaleCamera(float amount){
targetscale += amount;
clampScale();
@@ -407,7 +322,7 @@ public class Renderer implements ApplicationListener{
}
public void takeMapScreenshot(){
drawGroundShadows();
Groups.drawGroundShadows();
int w = world.width() * tilesize, h = world.height() * tilesize;
int memory = w * h * 4 / 1024 / 1024;
@@ -425,10 +340,8 @@ public class Renderer implements ApplicationListener{
camera.height = h;
camera.position.x = w / 2f + tilesize / 2f;
camera.position.y = h / 2f + tilesize / 2f;
Draw.flush();
buffer.begin();
draw();
Draw.flush();
buffer.end();
disableUI = false;
camera.width = vpW;

View File

@@ -20,7 +20,6 @@ import arc.scene.ui.Tooltip.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.editor.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
@@ -63,7 +62,7 @@ public class UI implements ApplicationListener, Loadable{
public TraceDialog traces;
public DatabaseDialog database;
public ContentInfoDialog content;
public DeployDialog deploy;
public PlanetDialog planet;
public TechTreeDialog tech;
//public MinimapDialog minimap;
public SchematicsDialog schematics;
@@ -176,7 +175,7 @@ public class UI implements ApplicationListener, Loadable{
traces = new TraceDialog();
maps = new MapsDialog();
content = new ContentInfoDialog();
deploy = new DeployDialog();
planet = new PlanetDialog();
tech = new TechTreeDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
@@ -185,10 +184,10 @@ public class UI implements ApplicationListener, Loadable{
menuGroup.setFillParent(true);
menuGroup.touchable(Touchable.childrenOnly);
menuGroup.visible(() -> state.is(State.menu));
menuGroup.visible(() -> state.isMenu());
hudGroup.setFillParent(true);
hudGroup.touchable(Touchable.childrenOnly);
hudGroup.visible(() -> !state.is(State.menu));
hudGroup.visible(() -> state.isGame());
Core.scene.add(menuGroup);
Core.scene.add(hudGroup);
@@ -296,7 +295,7 @@ public class UI implements ApplicationListener, Loadable{
table.setFillParent(true);
table.touchable(Touchable.disabled);
table.update(() -> {
if(state.is(State.menu)) table.remove();
if(state.isMenu()) table.remove();
});
table.actions(Actions.delay(duration * 0.9f), Actions.fadeOut(duration * 0.1f, Interpolation.fade), Actions.remove());
table.top().table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).padTop(10);
@@ -309,7 +308,7 @@ public class UI implements ApplicationListener, Loadable{
table.setFillParent(true);
table.touchable(Touchable.disabled);
table.update(() -> {
if(state.is(State.menu)) table.remove();
if(state.isMenu()) table.remove();
});
table.actions(Actions.delay(duration), Actions.remove());
table.align(align).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).pad(top, left, bottom, right);
@@ -322,7 +321,7 @@ public class UI implements ApplicationListener, Loadable{
table.setFillParent(true);
table.touchable(Touchable.disabled);
table.update(() -> {
if(state.is(State.menu)) table.remove();
if(state.isMenu()) table.remove();
});
table.actions(Actions.delay(duration), Actions.remove());
table.align(Align.center).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).update(t -> {

View File

@@ -1,31 +1,32 @@
package mindustry.core;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.maps.filters.*;
import mindustry.maps.filters.GenerateFilter.*;
import mindustry.maps.generators.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.legacy.*;
import static mindustry.Vars.*;
public class World{
public final Context context = new Context();
private Map currentMap;
private Tile[][] tiles;
public @NonNull Tiles tiles = new Tiles(0, 0);
private boolean generating, invalidMap;
@@ -58,20 +59,12 @@ public class World{
return !wallSolid(x, y - 1) || !wallSolid(x, y + 1) || !wallSolid(x - 1, y) || !wallSolid(x + 1, y);
}
public Map getMap(){
return currentMap;
}
public void setMap(Map map){
this.currentMap = map;
}
public int width(){
return tiles == null ? 0 : tiles.length;
return tiles.width;
}
public int height(){
return tiles == null ? 0 : tiles[0].length;
return tiles.height;
}
public int unitWidth(){
@@ -82,51 +75,61 @@ public class World{
return height()*tilesize;
}
public @Nullable
Tile tile(int pos){
return tiles == null ? null : tile(Pos.x(pos), Pos.y(pos));
@Nullable
public Tile tile(int pos){
return tile(Point2.x(pos), Point2.y(pos));
}
public @Nullable Tile tile(int x, int y){
if(tiles == null){
return null;
}
if(!Structs.inBounds(x, y, tiles)) return null;
return tiles[x][y];
@Nullable
public Tile tile(int x, int y){
return tiles.get(x, y);
}
public @Nullable Tile ltile(int x, int y){
@Nullable
public Tile tilec(int x, int y){
Tile tile = tiles.get(x, y);
if(tile == null) return null;
if(tile.entity != null) return tile.entity.tile();
return tile;
}
@Nullable
public Tilec ent(int x, int y){
Tile tile = tile(x, y);
if(tile == null) return null;
return tile.block().linked(tile);
return tile.entity;
}
@Nullable
public Tilec ent(int pos){
Tile tile = tile(pos);
if(tile == null) return null;
return tile.entity;
}
@NonNull
public Tile rawTile(int x, int y){
return tiles[x][y];
return tiles.getn(x, y);
}
public @Nullable Tile tileWorld(float x, float y){
@Nullable
public Tile tileWorld(float x, float y){
return tile(Math.round(x / tilesize), Math.round(y / tilesize));
}
public @Nullable Tile ltileWorld(float x, float y){
return ltile(Math.round(x / tilesize), Math.round(y / tilesize));
@Nullable
public Tilec entWorld(float x, float y){
return ent(Math.round(x / tilesize), Math.round(y / tilesize));
}
public int toTile(float coord){
return Math.round(coord / tilesize);
}
public Tile[][] getTiles(){
return tiles;
}
private void clearTileEntities(){
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
if(tiles[x][y] != null && tiles[x][y].entity != null){
tiles[x][y].entity.remove();
}
for(Tile tile : tiles){
if(tile != null && tile.entity != null){
tile.entity.remove();
}
}
}
@@ -135,15 +138,11 @@ public class World{
* Resizes the tile array to the specified size and returns the resulting tile array.
* Only use for loading saves!
*/
public Tile[][] createTiles(int width, int height){
if(tiles != null){
clearTileEntities();
public Tiles resize(int width, int height){
clearTileEntities();
if(tiles.length != width || tiles[0].length != height){
tiles = new Tile[width][height];
}
}else{
tiles = new Tile[width][height];
if(tiles.width != width || tiles.height != height){
tiles = new Tiles(width, height);
}
return tiles;
@@ -162,16 +161,18 @@ public class World{
* A WorldLoadEvent will be fire.
*/
public void endMapLoad(){
prepareTiles(tiles);
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
tile.updateOcclusion();
for(Tile tile : tiles){
//remove legacy blocks; they need to stop existing
if(tile.block() instanceof LegacyBlock){
tile.remove();
continue;
}
if(tile.entity != null){
tile.entity.updateProximity();
}
tile.updateOcclusion();
if(tile.entity != null){
tile.entity.updateProximity();
}
}
@@ -179,7 +180,7 @@ public class World{
addDarkness(tiles);
}
entities.all().each(group -> group.resize(-finalWorldBounds, -finalWorldBounds, tiles.length * tilesize + finalWorldBounds * 2, tiles[0].length * tilesize + finalWorldBounds * 2));
Groups.resize(-finalWorldBounds, -finalWorldBounds, tiles.width * tilesize + finalWorldBounds * 2, tiles.height * tilesize + finalWorldBounds * 2);
generating = false;
Events.fire(new WorldLoadEvent());
@@ -193,23 +194,22 @@ public class World{
return generating;
}
public boolean isZone(){
return getZone() != null;
}
public Zone getZone(){
return state.rules.zone;
}
public void loadGenerator(Generator generator){
public void loadGenerator(int width, int height, Cons<Tiles> generator){
beginMapLoad();
createTiles(generator.width, generator.height);
generator.generate(tiles);
resize(width, height);
generator.get(tiles);
endMapLoad();
}
public void loadSector(Sector sector){
state.map = new Map(StringMap.of("name", sector.planet.localizedName + "; Sector " + sector.id));
state.rules.sector = sector;
int size = sector.getSize();
loadGenerator(size, size, tiles -> sector.planet.generator.generate(tiles, sector));
}
public void loadMap(Map map){
loadMap(map, new Rules());
}
@@ -228,7 +228,7 @@ public class World{
return;
}
this.currentMap = map;
state.map = map;
invalidMap = false;
@@ -297,109 +297,99 @@ public class World{
}
}
public void addDarkness(Tile[][] tiles){
byte[][] dark = new byte[tiles.length][tiles[0].length];
byte[][] writeBuffer = new byte[tiles.length][tiles[0].length];
public void addDarkness(Tiles tiles){
byte[] dark = new byte[tiles.width * tiles.height];
byte[] writeBuffer = new byte[tiles.width * tiles.height];
byte darkIterations = 4;
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.isDarkened()){
dark[x][y] = darkIterations;
}
for(int i = 0; i < dark.length; i++){
Tile tile = tiles.geti(i);
if(tile.isDarkened()){
dark[i] = darkIterations;
}
}
for(int i = 0; i < darkIterations; i++){
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
boolean min = false;
for(Point2 point : Geometry.d4){
int newX = x + point.x, newY = y + point.y;
if(Structs.inBounds(newX, newY, tiles) && dark[newX][newY] < dark[x][y]){
min = true;
break;
}
for(Tile tile : tiles){
int idx = tile.y * tiles.width + tile.x;
boolean min = false;
for(Point2 point : Geometry.d4){
int newX = tile.x + point.x, newY = tile.y + point.y;
int nidx = newY * tiles.width + newX;
if(tiles.in(newX, newY) && dark[nidx] < dark[idx]){
min = true;
break;
}
writeBuffer[x][y] = (byte)Math.max(0, dark[x][y] - Mathf.num(min));
}
writeBuffer[idx] = (byte)Math.max(0, dark[idx] - Mathf.num(min));
}
for(int x = 0; x < tiles.length; x++){
System.arraycopy(writeBuffer[x], 0, dark[x], 0, tiles[0].length);
}
System.arraycopy(writeBuffer, 0, dark, 0, writeBuffer.length);
}
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.isDarkened()){
tiles[x][y].rotation(dark[x][y]);
}
if(dark[x][y] == 4){
boolean full = true;
for(Point2 p : Geometry.d4){
int px = p.x + x, py = p.y + y;
if(Structs.inBounds(px, py, tiles) && !(tiles[px][py].isDarkened() && dark[px][py] == 4)){
full = false;
break;
}
}
for(Tile tile : tiles){
int idx = tile.y * tiles.width + tile.x;
if(full) tiles[x][y].rotation(5);
if(tile.isDarkened()){
tile.rotation(dark[idx]);
}
if(dark[idx] == 4){
boolean full = true;
for(Point2 p : Geometry.d4){
int px = p.x + tile.x, py = p.y + tile.y;
int nidx = py * tiles.width + px;
if(tiles.in(px, py) && !(tile.isDarkened() && dark[nidx] == 4)){
full = false;
break;
}
}
if(full) tile.rotation(5);
}
}
}
/**
* 'Prepares' a tile array by:<br>
* - setting up multiblocks<br>
* - updating occlusion<br>
* Usually used before placing structures on a tile array.
*/
public void prepareTiles(Tile[][] tiles){
public float getDarkness(int x, int y){
int edgeBlend = 2;
//find multiblocks
IntArray multiblocks = new IntArray();
float dark = 0;
int edgeDst = Math.min(x, Math.min(y, Math.min(Math.abs(x - (tiles.width - 1)), Math.abs(y - (tiles.height - 1)))));
if(edgeDst <= edgeBlend){
dark = Math.max((edgeBlend - edgeDst) * (4f / edgeBlend), dark);
}
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(state.hasSector()){
int circleBlend = 14;
//quantized angle
float offset = state.getSector().rect.rotation + 90;
float angle = Angles.angle(x, y, tiles.width/2, tiles.height/2) + offset;
//polygon sides, depends on sector
int sides = state.getSector().tile.corners.length;
float step = 360f / sides;
//prev and next angles of poly
float prev = Mathf.round(angle, step);
float next = prev + step;
//raw line length to be translated
float length = tiles.width/2f;
float rawDst = Intersector.distanceLinePoint(Tmp.v1.trns(prev, length), Tmp.v2.trns(next, length), Tmp.v3.set(x - tiles.width/2, y - tiles.height/2).rotate(offset)) / Mathf.sqrt3 - 1;
if(tile.block().isMultiblock()){
multiblocks.add(tile.pos());
}
//noise
rawDst += Noise.noise(x, y, 11f, 7f) + Noise.noise(x, y, 22f, 15f);
int circleDst = (int)(rawDst - (tiles.width / 2 - circleBlend));
if(circleDst > 0){
dark = Math.max(circleDst / 1f, dark);
}
}
//place multiblocks now
for(int i = 0; i < multiblocks.size; i++){
int pos = multiblocks.get(i);
int x = Pos.x(pos);
int y = Pos.y(pos);
Block result = tiles[x][y].block();
Team team = tiles[x][y].getTeam();
int offsetx = -(result.size - 1) / 2;
int offsety = -(result.size - 1) / 2;
for(int dx = 0; dx < result.size; dx++){
for(int dy = 0; dy < result.size; dy++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(!(worldx == x && worldy == y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null){
toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team);
}
}
}
}
Tile tile = world.tile(x, y);
if(tile != null && tile.block().solid && tile.block().fillsTile && !tile.block().synthetic()){
dark = Math.max(dark, tile.rotation());
}
return dark;
}
public interface Raycaster{
@@ -408,18 +398,20 @@ public class World{
private class Context implements WorldContext{
@Override
public Tile tile(int x, int y){
return tiles[x][y];
public Tile tile(int index){
return tiles.geti(index);
}
@Override
public void resize(int width, int height){
createTiles(width, height);
World.this.resize(width, height);
}
@Override
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
return (tiles[x][y] = new Tile(x, y, floorID, overlayID, wallID));
Tile tile = new Tile(x, y, floorID, overlayID, wallID);
tiles.set(x, y, tile);
return tile;
}
@Override
@@ -454,23 +446,8 @@ public class World{
GenerateInput input = new GenerateInput();
for(GenerateFilter filter : filters){
input.begin(filter, width(), height(), (x, y) -> tiles[x][y]);
//actually apply the filter
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
Tile tile = rawTile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
tile.setFloor((Floor)input.floor);
tile.setOverlay(input.ore);
if(!tile.block().synthetic() && !input.block.synthetic()){
tile.setBlock(input.block);
}
}
}
input.begin(filter, width(), height(), (x, y) -> tiles.getn(x, y));
filter.apply(tiles, input);
}
}

View File

@@ -4,17 +4,18 @@ package mindustry.ctype;
public enum ContentType{
item,
block,
mech,
mech_UNUSED,
bullet,
liquid,
status,
unit,
weather,
effect,
effect_UNUSED,
zone,
loadout,
typeid,
error;
loadout_UNUSED,
typeid_UNUSED,
error,
planet;
public static final ContentType[] all = values();
}

View File

@@ -11,7 +11,7 @@ import mindustry.ui.Cicon;
/** Base interface for an unlockable content type. */
public abstract class UnlockableContent extends MappableContent{
/** Localized, formal name. Never null. Set to block name if not found in bundle. */
/** Localized, formal name. Never null. Set to internal name if not found in bundle. */
public String localizedName;
/** Localized description. May be null. */
public @Nullable String description;
@@ -54,7 +54,7 @@ public abstract class UnlockableContent extends MappableContent{
public void onUnlock(){
}
/** Whether this content is always hidden in the content info dialog. */
/** Whether this content is always hidden in the content database dialog. */
public boolean isHidden(){
return false;
}
@@ -70,7 +70,7 @@ public abstract class UnlockableContent extends MappableContent{
/** @return whether this content is unlocked, or the player is in a custom game. */
public final boolean unlockedCur(){
return Vars.data.isUnlocked(this) || !Vars.world.isZone();
return Vars.data.isUnlocked(this) || !Vars.state.isCampaign();
}
public final boolean locked(){

View File

@@ -6,7 +6,7 @@ import mindustry.game.Team;
import mindustry.gen.TileOp;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.blocks.Floor;
import mindustry.world.blocks.environment.Floor;
import static mindustry.Vars.content;
@@ -65,7 +65,7 @@ public class DrawOperation{
tile.setFloor((Floor)content.block(to));
}else if(type == OpType.block.ordinal()){
Block block = content.block(to);
tile.setBlock(block, tile.getTeam(), tile.rotation());
tile.setBlock(block, tile.team(), tile.rotation());
}else if(type == OpType.rotation.ordinal()){
tile.rotation(to);
}else if(type == OpType.team.ordinal()){

View File

@@ -1,19 +1,16 @@
package mindustry.editor;
import mindustry.content.Blocks;
import mindustry.core.GameState.State;
import mindustry.editor.DrawOperation.OpType;
import mindustry.game.Team;
import mindustry.gen.TileOp;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.blocks.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.editor.DrawOperation.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.modules.*;
import static mindustry.Vars.state;
import static mindustry.Vars.ui;
import static mindustry.Vars.*;
//TODO somehow remove or replace this class with a more flexible solution
public class EditorTile extends Tile{
public EditorTile(int x, int y, int floor, int overlay, int wall){
@@ -21,8 +18,8 @@ public class EditorTile extends Tile{
}
@Override
public void setFloor(Floor type){
if(state.is(State.playing)){
public void setFloor(@NonNull Floor type){
if(state.isGame()){
super.setFloor(type);
return;
}
@@ -41,35 +38,22 @@ public class EditorTile extends Tile{
super.setFloor(type);
}
@Override
public void setBlock(Block type){
if(state.is(State.playing)){
super.setBlock(type);
return;
}
if(block == type) return;
op(OpType.block, block.id);
if(rotation != 0) op(OpType.rotation, rotation);
if(team != 0) op(OpType.team, team);
super.setBlock(type);
}
@Override
public void setBlock(Block type, Team team, int rotation){
if(state.is(State.playing)){
if(state.isGame()){
super.setBlock(type, team, rotation);
return;
}
setBlock(type);
setTeam(team);
rotation(rotation);
op(OpType.block, block.id);
if(rotation != 0) op(OpType.rotation, (byte)rotation);
if(team() != Team.derelict) op(OpType.team, team().id);
super.setBlock(type, team, rotation);
}
@Override
public void setTeam(Team team){
if(state.is(State.playing)){
if(state.isGame()){
super.setTeam(team);
return;
}
@@ -81,7 +65,7 @@ public class EditorTile extends Tile{
@Override
public void rotation(int rotation){
if(state.is(State.playing)){
if(state.isGame()){
super.rotation(rotation);
return;
}
@@ -93,57 +77,49 @@ public class EditorTile extends Tile{
@Override
public void setOverlay(Block overlay){
setOverlayID(overlay.id);
}
@Override
public void setOverlayID(short overlay){
if(state.is(State.playing)){
super.setOverlayID(overlay);
if(state.isGame()){
super.setOverlay(overlay);
return;
}
if(floor.isLiquid) return;
if(overlayID() == overlay) return;
if(overlay() == overlay) return;
op(OpType.overlay, this.overlay.id);
super.setOverlayID(overlay);
super.setOverlay(overlay);
}
@Override
protected void preChanged(){
if(state.is(State.playing)){
super.preChanged();
return;
}
super.setTeam(Team.derelict);
super.preChanged();
}
@Override
protected void changed(){
if(state.is(State.playing)){
super.changed();
public void recache(){
if(state.isGame()){
super.recache();
}
}
@Override
protected void changed(Team team){
if(state.isGame()){
super.changed(team);
return;
}
entity = null;
if(block == null){
block = Blocks.air;
}
if(floor == null){
floor = (Floor)Blocks.air;
}
if(block == null) block = Blocks.air;
if(floor == null) floor = (Floor)Blocks.air;
Block block = block();
if(block.hasEntity()){
entity = block.newEntity().init(this, false);
entity.cons = new ConsumeModule(entity);
if(block.hasItems) entity.items = new ItemModule();
if(block.hasLiquids) entity.liquids = new LiquidModule();
if(block.hasPower) entity.power = new PowerModule();
entity = block.newEntity().init(this, team, false);
entity.cons(new ConsumeModule(entity));
if(block.hasItems) entity.items(new ItemModule());
if(block.hasLiquids) entity.liquids(new LiquidModule());
if(block.hasPower) entity.power(new PowerModule());
}
}

View File

@@ -8,7 +8,6 @@ import arc.util.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
public enum EditorTool{
zoom,
@@ -16,7 +15,7 @@ public enum EditorTool{
public void touched(MapEditor editor, int x, int y){
if(!Structs.inBounds(x, y, editor.width(), editor.height())) return;
Tile tile = editor.tile(x, y).link();
Tile tile = editor.tile(x, y);
editor.drawBlock = tile.block() == Blocks.air ? tile.overlay() == Blocks.air ? tile.floor() : tile.overlay() : tile.block();
}
},
@@ -63,7 +62,7 @@ public enum EditorTool{
editor.drawBlocks(x, y, true, tile -> true);
}else if(mode == 2){
//draw teams
editor.drawCircle(x, y, tile -> tile.link().setTeam(editor.drawTeam));
editor.drawCircle(x, y, tile -> tile.setTeam(editor.drawTeam));
}
}
@@ -108,7 +107,7 @@ public enum EditorTool{
//mode 0 or 1, fill everything with the floor/tile or replace it
if(mode == 0 || mode == -1){
//can't fill parts or multiblocks
if(tile.block() instanceof BlockPart || tile.block().isMultiblock()){
if(tile.block().isMultiblock()){
return;
}
@@ -137,10 +136,10 @@ public enum EditorTool{
}else if(mode == 1){ //mode 1 is team fill
//only fill synthetic blocks, it's meaningless otherwise
if(tile.link().synthetic()){
Team dest = tile.getTeam();
if(tile.synthetic()){
Team dest = tile.team();
if(dest == editor.drawTeam) return;
fill(editor, x, y, false, t -> t.getTeamID() == (int)dest.id && t.link().synthetic(), t -> t.setTeam(editor.drawTeam));
fill(editor, x, y, false, t -> t.getTeamID() == (int)dest.id && t.synthetic(), t -> t.setTeam(editor.drawTeam));
}
}
}
@@ -164,35 +163,44 @@ public enum EditorTool{
int x1;
stack.clear();
stack.add(Pos.get(x, y));
stack.add(Point2.pack(x, y));
while(stack.size > 0){
int popped = stack.pop();
x = Pos.x(popped);
y = Pos.y(popped);
try{
while(stack.size > 0 && stack.size < width*height){
int popped = stack.pop();
x = Point2.x(popped);
y = Point2.y(popped);
x1 = x;
while(x1 >= 0 && tester.get(editor.tile(x1, y))) x1--;
x1++;
boolean spanAbove = false, spanBelow = false;
while(x1 < width && tester.get(editor.tile(x1, y))){
filler.get(editor.tile(x1, y));
if(!spanAbove && y > 0 && tester.get(editor.tile(x1, y - 1))){
stack.add(Pos.get(x1, y - 1));
spanAbove = true;
}else if(spanAbove && !tester.get(editor.tile(x1, y - 1))){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && tester.get(editor.tile(x1, y + 1))){
stack.add(Pos.get(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && !tester.get(editor.tile(x1, y + 1))){
spanBelow = false;
}
x1 = x;
while(x1 >= 0 && tester.get(editor.tile(x1, y))) x1--;
x1++;
boolean spanAbove = false, spanBelow = false;
while(x1 < width && tester.get(editor.tile(x1, y))){
filler.get(editor.tile(x1, y));
if(!spanAbove && y > 0 && tester.get(editor.tile(x1, y - 1))){
stack.add(Point2.pack(x1, y - 1));
spanAbove = true;
}else if(spanAbove && !tester.get(editor.tile(x1, y - 1))){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && tester.get(editor.tile(x1, y + 1))){
stack.add(Point2.pack(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && !tester.get(editor.tile(x1, y + 1))){
spanBelow = false;
}
x1++;
}
}
stack.clear();
}catch(OutOfMemoryError e){
//hack
stack = null;
System.gc();
e.printStackTrace();
stack = new IntArray();
}
}
}

View File

@@ -1,19 +1,16 @@
package mindustry.editor;
import arc.struct.StringMap;
import arc.files.Fi;
import arc.func.Cons;
import arc.func.Boolf;
import arc.graphics.Pixmap;
import arc.math.Mathf;
import arc.util.Structs;
import mindustry.content.Blocks;
import mindustry.game.Team;
import mindustry.gen.TileOp;
import mindustry.io.MapIO;
import mindustry.maps.Map;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.world.*;
import mindustry.world.blocks.BlockPart;
import static mindustry.Vars.*;
@@ -55,7 +52,6 @@ public class MapEditor{
tags.put("steamid", map.file.parent().name());
}
MapIO.loadMap(map, context);
checkLinkedTiles();
renderer.resize(width(), height());
loading = false;
}
@@ -64,33 +60,10 @@ public class MapEditor{
reset();
createTiles(pixmap.getWidth(), pixmap.getHeight());
load(() -> MapIO.readPixmap(pixmap, tiles()));
load(() -> MapIO.readImage(pixmap, tiles()));
renderer.resize(width(), height());
}
//adds missing blockparts
public void checkLinkedTiles(){
Tile[][] tiles = world.getTiles();
//clear block parts first
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
if(tiles[x][y].block() instanceof BlockPart){
tiles[x][y].setBlock(Blocks.air);
}
}
}
//set up missing blockparts
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
if(tiles[x][y].block().isMultiblock()){
tiles[x][y].set(tiles[x][y].block(), tiles[x][y].getTeam());
}
}
}
}
public void load(Runnable r){
loading = true;
r.run();
@@ -99,11 +72,11 @@ public class MapEditor{
/** Creates a 2-D array of EditorTiles with stone as the floor block. */
private void createTiles(int width, int height){
Tile[][] tiles = world.createTiles(width, height);
Tiles tiles = world.resize(width, height);
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
tiles.set(x, y, new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0));
}
}
}
@@ -119,8 +92,8 @@ public class MapEditor{
tags = new StringMap();
}
public Tile[][] tiles(){
return world.getTiles();
public Tiles tiles(){
return world.tiles;
}
public Tile tile(int x, int y){
@@ -151,42 +124,13 @@ public class MapEditor{
if(drawBlock.isMultiblock()){
x = Mathf.clamp(x, (drawBlock.size - 1) / 2, width() - drawBlock.size / 2 - 1);
y = Mathf.clamp(y, (drawBlock.size - 1) / 2, height() - drawBlock.size / 2 - 1);
int offsetx = -(drawBlock.size - 1) / 2;
int offsety = -(drawBlock.size - 1) / 2;
for(int dx = 0; dx < drawBlock.size; dx++){
for(int dy = 0; dy < drawBlock.size; dy++){
int worldx = dx + offsetx + x;
int worldy = dy + offsety + y;
if(Structs.inBounds(worldx, worldy, width(), height())){
Tile tile = tile(worldx, worldy);
Block block = tile.block();
//bail out if there's anything blocking the way
if(block.isMultiblock() || block instanceof BlockPart){
return;
}
renderer.updatePoint(worldx, worldy);
}
}
}
tile(x, y).set(drawBlock, drawTeam);
tile(x, y).setBlock(drawBlock, drawTeam, 0);
}else{
boolean isFloor = drawBlock.isFloor() && drawBlock != Blocks.air;
Cons<Tile> drawer = tile -> {
if(!tester.get(tile)) return;
//remove linked tiles blocking the way
if(!isFloor && (tile.isLinked() || tile.block().isMultiblock())){
tile.link().remove();
}
if(isFloor){
tile.setFloor(drawBlock.asFloor());
}else{
@@ -211,7 +155,7 @@ public class MapEditor{
public void drawCircle(int x, int y, Cons<Tile> drawer){
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
if(Mathf.dst2(rx, ry) <= (brushSize - 0.5f) * (brushSize - 0.5f)){
if(Mathf.within(rx, ry, brushSize - 0.5f + 0.0001f)){
int wx = x + rx, wy = y + ry;
if(wx < 0 || wy < 0 || wx >= width() || wy >= height()){
@@ -245,20 +189,20 @@ public class MapEditor{
public void resize(int width, int height){
clearOp();
Tile[][] previous = world.getTiles();
Tiles previous = world.tiles;
int offsetX = -(width - width()) / 2, offsetY = -(height - height()) / 2;
loading = true;
Tile[][] tiles = world.createTiles(width, height);
Tiles tiles = world.resize(width, height);
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
int px = offsetX + x, py = offsetY + y;
if(Structs.inBounds(px, py, previous.length, previous[0].length)){
tiles[x][y] = previous[px][py];
tiles[x][y].x = (short)x;
tiles[x][y].y = (short)y;
if(previous.in(px, py)){
tiles.set(x, y, previous.getn(px, py));
tiles.getn(x, y).x = (short)x;
tiles.getn(x, y).y = (short)y;
}else{
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
tiles.set(x, y, new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0));
}
}
}
@@ -308,18 +252,20 @@ public class MapEditor{
class Context implements WorldContext{
@Override
public Tile tile(int x, int y){
return world.tile(x, y);
public Tile tile(int index){
return world.tiles.geti(index);
}
@Override
public void resize(int width, int height){
world.createTiles(width, height);
world.resize(width, height);
}
@Override
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
return (tiles()[x][y] = new EditorTile(x, y, floorID, overlayID, wallID));
Tile tile = new EditorTile(x, y, floorID, overlayID, wallID);
tiles().set(x, y, tile);
return tile;
}
@Override

View File

@@ -28,7 +28,7 @@ import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.storage.*;
import static mindustry.Vars.*;
@@ -87,62 +87,42 @@ public class MapEditorDialog extends Dialog implements Disposable{
t.row();
t.addImageTextButton("$editor.import", Icon.download, () ->
createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
})),
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
Pixmap pixmap = new Pixmap(file);
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
Log.err(e);
}
})))
);
t.addImageTextButton("$editor.export", Icon.upload, () -> {
if(!ios){
platform.showFileChooser(false, mapExtension, file -> {
ui.loadAnd(() -> {
try{
if(!editor.getTags().containsKey("name")){
editor.getTags().put("name", file.nameWithoutExtension());
}
MapIO.writeMap(file, editor.createMap(file));
}catch(Exception e){
ui.showException("$editor.errorsave", e);
Log.err(e);
}
});
});
}else{
ui.loadAnd(() -> {
try{
Fi result = Core.files.local(editor.getTags().get("name", "unknown") + "." + mapExtension);
MapIO.writeMap(result, editor.createMap(result));
platform.shareFile(result);
}catch(Exception e){
ui.showException("$editor.errorsave", e);
Log.err(e);
t.addImageTextButton("$editor.import", Icon.download, () -> createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
}
});
})),
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
Pixmap pixmap = new Pixmap(file);
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
Log.err(e);
}
})))
);
t.addImageTextButton("$editor.export", Icon.upload, () -> createDialog("$editor.export",
"$editor.exportfile", "$editor.exportfile.description", Icon.file,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
"$editor.exportimage", "$editor.exportimage.description", Icon.fileImage,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), "png", file -> {
Pixmap out = MapIO.writeImage(editor.tiles());
file.writePNG(out);
out.dispose();
})));
});
menu.cont.row();
@@ -176,7 +156,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name()) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();
}
@@ -266,24 +246,21 @@ public class MapEditorDialog extends Dialog implements Disposable{
state.teams = new Teams();
player.reset();
state.rules = Gamemode.editor.apply(lastSavedRules.copy());
state.rules.zone = null;
world.setMap(new Map(StringMap.of(
"name", "Editor Playtesting",
"width", editor.width(),
"height", editor.height()
)));
state.rules.sector = null;
state.map = new Map(StringMap.of(
"name", "Editor Playtesting",
"width", editor.width(),
"height", editor.height()
));
world.endMapLoad();
//add entities so they update. is this really needed?
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.rawTile(x, y);
if(tile.entity != null){
tile.entity.add();
}
for(Tile tile : world.tiles){
if(tile.entity != null){
tile.entity.add();
}
}
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
player.setDead(false);
player.clearUnit();
logic.play();
});
}
@@ -295,7 +272,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.getTags().put("rules", JsonIO.write(state.rules));
editor.getTags().remove("width");
editor.getTags().remove("height");
player.dead = true;
//TODO unkill player
//player.dead = true;
Map returned = null;

View File

@@ -21,7 +21,7 @@ import mindustry.maps.filters.GenerateFilter.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@@ -30,7 +30,7 @@ public class MapGenerateDialog extends FloatingDialog{
private 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
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new, EnemySpawnFilter::new
};
private final MapEditor editor;
private final boolean applied;
@@ -52,7 +52,7 @@ public class MapGenerateDialog extends FloatingDialog{
private CachedTile ctile = new CachedTile(){
//nothing.
@Override
protected void changed(){
protected void changed(Team team){
}
};
@@ -124,7 +124,7 @@ public class MapGenerateDialog extends FloatingDialog{
Tile tile = editor.tile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation());
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.team(), tile.rotation());
}
}
@@ -146,7 +146,6 @@ public class MapGenerateDialog extends FloatingDialog{
}
//reset undo stack as generation... messes things up
editor.load(editor::checkLinkedTiles);
editor.renderer().updateAll();
editor.clearOp();
}
@@ -263,7 +262,7 @@ public class MapGenerateDialog extends FloatingDialog{
//all the options
c.table(f -> {
f.left().top();
for(FilterOption option : filter.options){
for(FilterOption option : filter.options()){
option.changed = this::update;
f.table(t -> {
@@ -292,7 +291,7 @@ public class MapGenerateDialog extends FloatingDialog{
for(Prov<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
if(!applied && filter.buffered) continue;
if((!applied && filter.isBuffered()) || (filter.isPost() && applied)) continue;
selection.cont.addButton(filter.name(), () -> {
filters.add(filter);
@@ -360,21 +359,17 @@ public class MapGenerateDialog extends FloatingDialog{
for(GenerateFilter filter : copy){
input.begin(filter, editor.width(), editor.height(), (x, y) -> buffer1[Mathf.clamp(x / scaling, 0, pixmap.getWidth()-1)][Mathf.clamp(y / scaling, 0, pixmap.getHeight()-1)].tile());
//read from buffer1 and write to buffer2
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
int x = px * scaling, y = py * scaling;
GenTile tile = buffer1[px][py];
input.apply(x, y, content.block(tile.floor), content.block(tile.block), content.block(tile.ore));
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, Team.get(tile.team), tile.rotation);
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(buffer2[px][py]);
}
}
pixmap.each((px, py) -> {
int x = px * scaling, y = py * scaling;
GenTile tile = buffer1[px][py];
input.apply(x, y, content.block(tile.floor), content.block(tile.block), content.block(tile.ore));
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, Team.get(tile.team), tile.rotation);
});
pixmap.each((px, py) -> buffer1[px][py].set(buffer2[px][py]));
}
for(int px = 0; px < pixmap.getWidth(); px++){
@@ -428,7 +423,7 @@ public class MapGenerateDialog extends FloatingDialog{
}
public GenTile set(Tile other){
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.rotation());
set(other.floor(), other.block(), other.overlay(), other.team(), other.rotation());
return this;
}

View File

@@ -15,7 +15,6 @@ import mindustry.game.Team;
import mindustry.graphics.IndexedRenderer;
import mindustry.world.Block;
import mindustry.world.Tile;
import mindustry.world.blocks.BlockPart;
import static mindustry.Vars.tilesize;
@@ -109,9 +108,9 @@ public class MapRenderer implements Disposable{
private void render(int wx, int wy){
int x = wx / chunkSize, y = wy / chunkSize;
IndexedRenderer mesh = chunks[x][y];
Tile tile = editor.tiles()[wx][wy];
Tile tile = editor.tiles().getn(wx, wy);
Team team = tile.getTeam();
Team team = tile.team();
Block floor = tile.floor();
Block wall = tile.block();
@@ -119,9 +118,10 @@ public class MapRenderer implements Disposable{
int idxWall = (wx % chunkSize) + (wy % chunkSize) * chunkSize;
int idxDecal = (wx % chunkSize) + (wy % chunkSize) * chunkSize + chunkSize * chunkSize;
boolean center = tile.isCenter();
if(wall != Blocks.air && (wall.synthetic() || wall instanceof BlockPart)){
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
if(wall != Blocks.air && wall.synthetic()){
region = !Core.atlas.isFound(wall.editorIcon()) || !center ? Core.atlas.find("clear-editor") : wall.editorIcon();
if(wall.rotate){
mesh.draw(idxWall, region,
@@ -143,14 +143,14 @@ public class MapRenderer implements Disposable{
float offsetX = -(wall.size / 3) * tilesize, offsetY = -(wall.size / 3) * tilesize;
if(wall.update || wall.destructible){
if((wall.update || wall.destructible) && center){
mesh.setColor(team.color);
region = Core.atlas.find("block-border-editor");
}else if(!wall.synthetic() && wall != Blocks.air){
}else if(!wall.synthetic() && wall != Blocks.air && center){
region = !Core.atlas.isFound(wall.editorIcon()) ? Core.atlas.find("clear-editor") : wall.editorIcon();
offsetX = tilesize / 2f - region.getWidth() / 2f * Draw.scl;
offsetY = tilesize / 2f - region.getHeight() / 2f * Draw.scl;
}else if(wall == Blocks.air && tile.overlay() != null){
}else if(wall == Blocks.air){
region = tile.overlay().editorVariantRegions()[Mathf.randomSeed(idxWall, 0, tile.overlay().editorVariantRegions().length - 1)];
}else{
region = Core.atlas.find("clear-editor");

View File

@@ -236,7 +236,7 @@ public class MapView extends Element implements GestureListener{
image.setImageSize(editor.width(), editor.height());
if(!ScissorStack.pushScissors(rect.set(x, y, width, height))){
if(!ScissorStack.push(rect.set(x, y, width, height))){
return;
}
@@ -304,7 +304,7 @@ public class MapView extends Element implements GestureListener{
Lines.rect(x, y, width, height);
Draw.reset();
ScissorStack.popScissors();
ScissorStack.pop();
}
private boolean active(){

View File

@@ -0,0 +1,37 @@
package mindustry.entities;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
class AllDefs{
@GroupDef(Entityc.class)
class all{
}
@GroupDef(value = Playerc.class, mapping = true)
class player{
}
@GroupDef(value = Bulletc.class, spatial = true, collide = {unit.class})
class bullet{
}
@GroupDef(value = Unitc.class, spatial = true, collide = {unit.class}, mapping = true)
class unit{
}
@GroupDef(Tilec.class)
class tile{
}
@GroupDef(value = Syncc.class, mapping = true)
class sync{
}
}

View File

@@ -1,17 +1,14 @@
package mindustry.entities;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.Effects.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -33,12 +30,11 @@ public class Damage{
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3,
x, y, Mathf.random(360f), branches + Mathf.range(2)));
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
}
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), 1, 1));
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, -1f, Mathf.random(360f), 1, 1));
}
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
@@ -47,21 +43,21 @@ public class Damage{
int f = i;
Time.run(i * 2f, () -> {
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
Effects.effect(Fx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius));
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
});
}
if(explosiveness > 15f){
Effects.effect(Fx.shockwave, x, y);
Fx.shockwave.at(x, y);
}
if(explosiveness > 30f){
Effects.effect(Fx.bigShockwave, x, y);
Fx.bigShockwave.at(x, y);
}
float shake = Math.min(explosiveness / 4f + 3f, 9f);
Effects.shake(shake, shake, x, y);
Effects.effect(Fx.dynamicExplosion, x, y, radius / 8f);
Fx.dynamicExplosion.at(x, y, radius / 8f);
}
public static void createIncend(float x, float y, float range, int amount){
@@ -70,12 +66,12 @@ public class Damage{
float cy = y + Mathf.range(range);
Tile tile = world.tileWorld(cx, cy);
if(tile != null){
Fire.create(tile);
Fires.create(tile);
}
}
}
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
public static void collideLine(Bulletc hitter, Team team, Effect effect, float x, float y, float angle, float length){
collideLine(hitter, team, effect, x, y, angle, length, false);
}
@@ -83,15 +79,15 @@ public class Damage{
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
public static void collideLine(Bulletc hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
collidedBlocks.clear();
tr.trns(angle, length);
Intc2 collider = (cx, cy) -> {
Tile tile = world.ltile(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.entity != null && tile.getTeamID() != team.id && tile.entity.collide(hitter)){
tile.entity.collision(hitter);
Tilec tile = world.ent(cx, cy);
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team() != team && tile.collide(hitter)){
tile.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.getBulletType().hit(hitter, tile.worldx(), tile.worldy());
hitter.type().hit(hitter, tile.x(), tile.y());
}
};
@@ -125,7 +121,7 @@ public class Damage{
rect.width += expand * 2;
rect.height += expand * 2;
Cons<Unit> cons = e -> {
Cons<Unitc> cons = e -> {
e.hitbox(hitrect);
Rect other = hitrect;
other.y -= expand;
@@ -136,7 +132,7 @@ public class Damage{
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, other);
if(vec != null){
Effects.effect(effect, vec.x, vec.y);
effect.at(vec.x, vec.y);
e.collision(hitter, vec.x, vec.y);
hitter.collision(e, vec.x, vec.y);
}
@@ -146,8 +142,8 @@ public class Damage{
}
/** Damages all entities and blocks in a radius that are enemies of the team. */
public static void damageUnits(Team team, float x, float y, float size, float damage, Boolf<Unit> predicate, Cons<Unit> acceptor){
Cons<Unit> cons = entity -> {
public static void damageUnits(Team team, float x, float y, float size, float damage, Boolf<Unitc> predicate, Cons<Unitc> acceptor){
Cons<Unitc> cons = entity -> {
if(!predicate.get(entity)) return;
entity.hitbox(hitrect);
@@ -178,15 +174,15 @@ public class Damage{
/** Damages all entities and blocks in a radius that are enemies of the team. */
public static void damage(Team team, float x, float y, float radius, float damage, boolean complete){
Cons<Unit> cons = entity -> {
if(entity.getTeam() == team || entity.dst(x, y) > radius){
Cons<Unitc> cons = entity -> {
if(entity.team() == team || entity.dst(x, y) > radius){
return;
}
float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage);
float amount = calculateDamage(x, y, entity.getX(), entity.getY(), radius, damage);
entity.damage(amount);
//TODO better velocity displacement
float dst = tr.set(entity.x - x, entity.y - y).len();
entity.velocity().add(tr.setLength((1f - dst / radius) * 2f / entity.mass()));
float dst = tr.set(entity.getX() - x, entity.getY() - y).len();
entity.vel().add(tr.setLength((1f - dst / radius) * 2f / entity.mass()));
if(complete && damage >= 9999999f && entity == player){
Events.fire(Trigger.exclusionDeath);
@@ -231,15 +227,15 @@ public class Damage{
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
bits.set(bitOffset + x, bitOffset + y);
Tile tile = world.ltile(startx + x, starty + y);
Tilec tile = world.ent(startx + x, starty + y);
if(scaledDamage <= 0 || tile == null) continue;
//apply damage to entity if needed
if(tile.entity != null && tile.getTeam() != team){
int health = (int)tile.entity.health;
if(tile.entity.health > 0){
tile.entity.damage(scaledDamage);
if(tile.team() != team){
int health = (int)tile.health();
if(tile.health() > 0){
tile.damage(scaledDamage);
scaledDamage -= health;
if(scaledDamage <= 0) continue;
@@ -259,7 +255,7 @@ public class Damage{
for(int dx = -trad; dx <= trad; dx++){
for(int dy = -trad; dy <= trad; dy++){
Tile tile = world.tile(Math.round(x / tilesize) + dx, Math.round(y / tilesize) + dy);
if(tile != null && tile.entity != null && (team == null ||team.isEnemy(tile.getTeam())) && Mathf.dst(dx, dy) <= trad){
if(tile != null && tile.entity != null && (team == null ||team.isEnemy(tile.team())) && Mathf.dst(dx, dy) <= trad){
tile.entity.damage(damage);
}
}

View File

@@ -0,0 +1,118 @@
package mindustry.entities;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
public class Effect{
private static final EffectContainer container = new EffectContainer();
private static int lastid = 0;
public final int id;
public final Cons<EffectContainer> renderer;
public final float lifetime;
/** Clip size. */
public float size;
public boolean ground;
public float groundDuration;
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
this.id = lastid++;
this.lifetime = life;
this.renderer = renderer;
this.size = clipsize;
}
public Effect(float life, Cons<EffectContainer> renderer){
this(life, 28f, renderer);
}
public Effect ground(){
ground = true;
return this;
}
public Effect ground(float duration){
ground = true;
this.groundDuration = duration;
return this;
}
public void at(Position pos){
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
}
public void at(Position pos, float rotation){
Effects.create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
}
public void at(float x, float y){
Effects.create(this, x, y, 0, Color.white, null);
}
public void at(float x, float y, float rotation){
Effects.create(this, x, y, rotation, Color.white, null);
}
public void at(float x, float y, float rotation, Color color){
Effects.create(this, x, y, rotation, color, null);
}
public void at(float x, float y, Color color){
Effects.create(this, x, y, 0, color, null);
}
public void at(float x, float y, float rotation, Color color, Object data){
Effects.create(this, x, y, rotation, color, data);
}
public void at(float x, float y, float rotation, Object data){
Effects.create(this, x, y, rotation, Color.white, data);
}
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
container.set(id, color, life, lifetime, rotation, x, y, data);
renderer.get(container);
Draw.reset();
}
public static class EffectContainer implements Scaled{
public float x, y, time, lifetime, rotation;
public Color color;
public int id;
public Object data;
private EffectContainer innerContainer;
public void set(int id, Color color, float life, float lifetime, float rotation, float x, float y, Object data){
this.x = x;
this.y = y;
this.color = color;
this.time = life;
this.lifetime = lifetime;
this.id = id;
this.rotation = rotation;
this.data = data;
}
public <T> T data(){
return (T)data;
}
public void scaled(float lifetime, Cons<EffectContainer> cons){
if(innerContainer == null) innerContainer = new EffectContainer();
if(time <= lifetime){
innerContainer.set(id, color, time, lifetime, rotation, x, y, data);
cons.get(innerContainer);
}
}
@Override
public float fin(){
return time / lifetime;
}
}
}

View File

@@ -1,90 +1,25 @@
package mindustry.entities;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.pooling.*;
import mindustry.entities.type.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Effects{
private static final EffectContainer container = new EffectContainer();
private static Array<Effect> effects = new Array<>();
private static ScreenshakeProvider shakeProvider;
private static float shakeFalloff = 10000f;
private static EffectProvider provider = (effect, color, x, y, rotation, data) -> {
EffectEntity entity = Pools.obtain(EffectEntity.class, EffectEntity::new);
entity.effect = effect;
entity.color = color;
entity.rotation = rotation;
entity.data = data;
entity.set(x, y);
entity.add();
};
public static void setEffectProvider(EffectProvider prov){
provider = prov;
}
public static void setScreenShakeProvider(ScreenshakeProvider provider){
shakeProvider = provider;
}
public static void renderEffect(int id, Effect render, Color color, float life, float rotation, float x, float y, Object data){
container.set(id, color, life, render.lifetime, rotation, x, y, data);
render.draw.render(container);
Draw.reset();
}
public static Effect getEffect(int id){
if(id >= effects.size || id < 0)
throw new IllegalArgumentException("The effect with ID \"" + id + "\" does not exist!");
return effects.get(id);
}
public static Array<Effect> all(){
return effects;
}
public static void effect(Effect effect, float x, float y, float rotation){
provider.createEffect(effect, Color.white, x, y, rotation, null);
}
public static void effect(Effect effect, float x, float y){
effect(effect, x, y, 0);
}
public static void effect(Effect effect, Color color, float x, float y){
provider.createEffect(effect, color, x, y, 0f, null);
}
public static void effect(Effect effect, Position loc){
provider.createEffect(effect, Color.white, loc.getX(), loc.getY(), 0f, null);
}
public static void effect(Effect effect, Color color, float x, float y, float rotation){
provider.createEffect(effect, color, x, y, rotation, null);
}
public static void effect(Effect effect, Color color, float x, float y, float rotation, Object data){
provider.createEffect(effect, color, x, y, rotation, data);
}
public static void effect(Effect effect, float x, float y, float rotation, Object data){
provider.createEffect(effect, Color.white, x, y, rotation, data);
}
/** Default value is 1000. Higher numbers mean more powerful shake (less falloff). */
public static void setShakeFalloff(float falloff){
shakeFalloff = falloff;
}
private static final float shakeFalloff = 10000f;
private static void shake(float intensity, float duration){
if(shakeProvider == null) throw new RuntimeException("Screenshake provider is null! Set it first.");
shakeProvider.accept(intensity, duration);
if(!headless){
renderer.shake(intensity, duration);
}
}
public static void shake(float intensity, float duration, float x, float y){
@@ -100,68 +35,58 @@ public class Effects{
shake(intensity, duration, loc.getX(), loc.getY());
}
public interface ScreenshakeProvider{
void accept(float intensity, float duration);
}
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
if(headless || effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = Core.camera.bounds(Tmp.r1);
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
public static class Effect{
private static int lastid = 0;
public final int id;
public final EffectRenderer draw;
public final float lifetime;
/** Clip size. */
public float size;
public Effect(float life, float clipsize, EffectRenderer draw){
this.id = lastid++;
this.lifetime = life;
this.draw = draw;
this.size = clipsize;
effects.add(this);
}
public Effect(float life, EffectRenderer draw){
this(life, 28f, draw);
}
}
public static class EffectContainer implements Scaled{
public float x, y, time, lifetime, rotation;
public Color color;
public int id;
public Object data;
private EffectContainer innerContainer;
public void set(int id, Color color, float life, float lifetime, float rotation, float x, float y, Object data){
this.x = x;
this.y = y;
this.color = color;
this.time = life;
this.lifetime = lifetime;
this.id = id;
this.rotation = rotation;
this.data = data;
}
public void scaled(float lifetime, Cons<EffectContainer> cons){
if(innerContainer == null) innerContainer = new EffectContainer();
if(time <= lifetime){
innerContainer.set(id, color, time, lifetime, rotation, x, y, data);
cons.get(innerContainer);
if(view.overlaps(pos)){
Effectc entity = effect.ground ? GroundEffectEntity.create() : StandardEffectEntity.create();
entity.effect(effect);
entity.rotation(rotation);
entity.data(data);
entity.lifetime(effect.lifetime);
entity.set(x, y);
entity.color().set(color);
if(data instanceof Posc) entity.parent((Posc)data);
entity.add();
}
}
@Override
public float fin(){
return time / lifetime;
}
}
public interface EffectProvider{
void createEffect(Effect effect, Color color, float x, float y, float rotation, Object data);
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
if(headless || region == null || !Core.atlas.isFound(region)) return;
Decalc decal = DecalEntity.create();
decal.set(x, y);
decal.rotation(rotation);
decal.lifetime(lifetime);
decal.color().set(color);
decal.region(region);
decal.add();
}
public interface EffectRenderer{
void render(EffectContainer effect);
public static void scorch(float x, float y, int size){
if(headless) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
size = Mathf.clamp(size, 0, 9);
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
public static void rubble(float x, float y, int blockSize){
if(headless) return;
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().isLiquid) return;
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
}
}

View File

@@ -1,33 +0,0 @@
package mindustry.entities;
import arc.struct.*;
import mindustry.entities.traits.*;
/** Simple container for managing entity groups.*/
public class Entities{
private final Array<EntityGroup<?>> groupArray = new Array<>();
public void clear(){
for(EntityGroup group : groupArray){
group.clear();
}
}
public EntityGroup<?> get(int id){
return groupArray.get(id);
}
public Array<EntityGroup<?>> all(){
return groupArray;
}
public <T extends Entity> EntityGroup<T> add(Class<T> type){
return add(type, true);
}
public <T extends Entity> EntityGroup<T> add(Class<T> type, boolean useTree){
EntityGroup<T> group = new EntityGroup<>(groupArray.size, type, useTree);
groupArray.add(group);
return group;
}
}

View File

@@ -1,14 +1,12 @@
package mindustry.entities;
import arc.struct.Array;
import arc.math.Mathf;
import arc.math.*;
import arc.math.geom.*;
import mindustry.entities.traits.Entity;
import mindustry.entities.traits.SolidTrait;
import mindustry.world.Tile;
import arc.struct.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.tilesize;
import static mindustry.Vars.world;
import static mindustry.Vars.*;
public class EntityCollisions{
//range for tile collision scanning
@@ -24,15 +22,19 @@ public class EntityCollisions{
private Rect r2 = new Rect();
//entity collisions
private Array<SolidTrait> arrOut = new Array<>();
private Array<Hitboxc> arrOut = new Array<>();
public void move(SolidTrait entity, float deltax, float deltay){
public void move(Hitboxc entity, float deltax, float deltay){
move(entity, deltax, deltay, EntityCollisions::solid);
}
public void move(Hitboxc entity, float deltax, float deltay, SolidPred solidCheck){
boolean movedx = false;
while(Math.abs(deltax) > 0 || !movedx){
movedx = true;
moveDelta(entity, Math.min(Math.abs(deltax), seg) * Mathf.sign(deltax), 0, true);
moveDelta(entity, Math.min(Math.abs(deltax), seg) * Mathf.sign(deltax), 0, true, solidCheck);
if(Math.abs(deltax) >= seg){
deltax -= seg * Mathf.sign(deltax);
@@ -45,7 +47,7 @@ public class EntityCollisions{
while(Math.abs(deltay) > 0 || !movedy){
movedy = true;
moveDelta(entity, 0, Math.min(Math.abs(deltay), seg) * Mathf.sign(deltay), false);
moveDelta(entity, 0, Math.min(Math.abs(deltay), seg) * Mathf.sign(deltay), false, solidCheck);
if(Math.abs(deltay) >= seg){
deltay -= seg * Mathf.sign(deltay);
@@ -55,33 +57,30 @@ public class EntityCollisions{
}
}
public void moveDelta(SolidTrait entity, float deltax, float deltay, boolean x){
Rect rect = r1;
entity.hitboxTile(rect);
public void moveDelta(Hitboxc entity, float deltax, float deltay, boolean x, SolidPred solidCheck){
entity.hitboxTile(r1);
entity.hitboxTile(r2);
rect.x += deltax;
rect.y += deltay;
r1.x += deltax;
r1.y += deltay;
int tilex = Math.round((rect.x + rect.width / 2) / tilesize), tiley = Math.round((rect.y + rect.height / 2) / tilesize);
int tilex = Math.round((r1.x + r1.width / 2) / tilesize), tiley = Math.round((r1.y + r1.height / 2) / tilesize);
for(int dx = -r; dx <= r; dx++){
for(int dy = -r; dy <= r; dy++){
int wx = dx + tilex, wy = dy + tiley;
if(solid(wx, wy) && entity.collidesGrid(wx, wy)){
if(solidCheck.solid(wx, wy)){
tmp.setSize(tilesize).setCenter(wx * tilesize, wy * tilesize);
if(tmp.overlaps(rect)){
Vec2 v = Geometry.overlap(rect, tmp, x);
rect.x += v.x;
rect.y += v.y;
if(tmp.overlaps(r1)){
Vec2 v = Geometry.overlap(r1, tmp, x);
if(x) r1.x += v.x;
if(!x) r1.y += v.y;
}
}
}
}
entity.setX(entity.getX() + rect.x - r2.x);
entity.setY(entity.getY() + rect.y - r2.y);
entity.trns(r1.x - r2.x, r1.y - r2.y);
}
public boolean overlapsTile(Rect rect){
@@ -108,44 +107,43 @@ public class EntityCollisions{
}
@SuppressWarnings("unchecked")
public <T extends Entity> void updatePhysics(EntityGroup<T> group){
public <T extends Hitboxc> void updatePhysics(EntityGroup<T> group){
QuadTree tree = group.tree();
tree.clear();
for(Entity entity : group.all()){
if(entity instanceof SolidTrait){
SolidTrait s = (SolidTrait)entity;
s.lastPosition().set(s.getX(), s.getY());
tree.insert(s);
}
}
group.each(s -> {
s.updateLastPosition();
tree.insert(s);
});
}
private static boolean solid(int x, int y){
public static boolean waterSolid(int x, int y){
Tile tile = world.tile(x, y);
return tile != null && (tile.solid() || !tile.floor().isLiquid);
}
public static boolean solid(int x, int y){
Tile tile = world.tile(x, y);
return tile != null && tile.solid();
}
private void checkCollide(Entity entity, Entity other){
SolidTrait a = (SolidTrait)entity;
SolidTrait b = (SolidTrait)other;
private void checkCollide(Hitboxc a, Hitboxc b){
a.hitbox(this.r1);
b.hitbox(this.r2);
r1.x += (a.lastPosition().x - a.getX());
r1.y += (a.lastPosition().y - a.getY());
r2.x += (b.lastPosition().x - b.getX());
r2.y += (b.lastPosition().y - b.getY());
r1.x += (a.lastX() - a.getX());
r1.y += (a.lastY() - a.getY());
r2.x += (b.lastX() - b.getX());
r2.y += (b.lastY() - b.getY());
float vax = a.getX() - a.lastPosition().x;
float vay = a.getY() - a.lastPosition().y;
float vbx = b.getX() - b.lastPosition().x;
float vby = b.getY() - b.lastPosition().y;
float vax = a.getX() - a.lastX();
float vay = a.getY() - a.lastY();
float vbx = b.getX() - b.lastX();
float vby = b.getY() - b.lastY();
if(a != b && a.collides(b) && b.collides(a)){
if(a != b && a.collides(b)){
l1.set(a.getX(), a.getY());
boolean collide = r1.overlaps(r2) || collide(r1.x, r1.y, r1.width, r1.height, vax, vay,
r2.x, r2.y, r2.width, r2.height, vbx, vby, l1);
@@ -207,17 +205,12 @@ public class EntityCollisions{
}
@SuppressWarnings("unchecked")
public void collideGroups(EntityGroup<?> groupa, EntityGroup<?> groupb){
for(Entity entity : groupa.all()){
if(!(entity instanceof SolidTrait))
continue;
SolidTrait solid = (SolidTrait)entity;
public void collideGroups(EntityGroup<? extends Hitboxc> groupa, EntityGroup<? extends Hitboxc> groupb){
groupa.each(solid -> {
solid.hitbox(r1);
r1.x += (solid.lastPosition().x - solid.getX());
r1.y += (solid.lastPosition().y - solid.getY());
r1.x += (solid.lastX() - solid.getX());
r1.y += (solid.lastY() - solid.getY());
solid.hitbox(r2);
r2.merge(r1);
@@ -225,12 +218,18 @@ public class EntityCollisions{
arrOut.clear();
groupb.tree().getIntersect(arrOut, r2);
for(SolidTrait sc : arrOut){
for(Hitboxc sc : arrOut){
sc.hitbox(r1);
if(r2.overlaps(r1)){
checkCollide(entity, sc);
checkCollide(solid, sc);
//break out of loop when this object hits something
if(!solid.isAdded()) return;
}
}
}
});
}
public interface SolidPred{
boolean solid(int x, int y);
}
}

View File

@@ -1,11 +1,10 @@
package mindustry.entities;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import arc.struct.*;
import mindustry.gen.*;
import java.util.*;
@@ -13,128 +12,82 @@ import static mindustry.Vars.collisions;
/** Represents a group of a certain type of entity.*/
@SuppressWarnings("unchecked")
public class EntityGroup<T extends Entity> implements Iterable<T>{
private final boolean useTree;
private final int id;
private final Class<T> type;
private final Array<T> entityArray = new Array<>(false, 32);
private final Array<T> entitiesToRemove = new Array<>(false, 32);
private final Array<T> entitiesToAdd = new Array<>(false, 32);
public class EntityGroup<T extends Entityc> implements Iterable<T>{
private static int lastId = 0;
private final Array<T> array;
private final Array<T> intersectArray = new Array<>();
private final Rect viewport = new Rect();
private final Rect intersectRect = new Rect();
private IntMap<T> map;
private QuadTree tree;
private Cons<T> removeListener;
private Cons<T> addListener;
private boolean clearing;
private final Rect viewport = new Rect();
private int count = 0;
private int index;
public EntityGroup(int id, Class<T> type, boolean useTree){
this.useTree = useTree;
this.id = id;
this.type = type;
public static int nextId(){
return lastId++;
}
if(useTree){
public EntityGroup(Class<T> type, boolean spatial, boolean mapping){
array = new Array<>(false, 32, type);
if(spatial){
tree = new QuadTree<>(new Rect(0, 0, 0, 0));
}
if(mapping){
map = new IntMap<>();
}
}
public void sort(Comparator<? super T> comp){
array.sort(comp);
}
public void collide(EntityGroup<? extends Hitboxc> other){
collisions.collideGroups((EntityGroup<? extends Hitboxc>)this, other);
}
public void updatePhysics(){
collisions.updatePhysics((EntityGroup<? extends Hitboxc>)this);
}
public void update(){
updateEvents();
each(Entityc::update);
}
if(useTree()){
collisions.updatePhysics(this);
}
for(Entity e : all()){
e.update();
public void each(Cons<T> cons){
for(index = 0; index < array.size; index++){
cons.get(array.items[index]);
}
}
public int countInBounds(){
count = 0;
draw(e -> true, e -> count++);
return count;
public void each(Boolf<T> filter, Cons<T> cons){
for(index = 0; index < array.size; index++){
if(filter.get(array.items[index])) cons.get(array.items[index]);
}
}
public void draw(){
draw(e -> true);
}
public void draw(Cons<T> cons){
Core.camera.bounds(viewport);
public void draw(Boolf<T> toDraw){
draw(toDraw, t -> ((DrawTrait)t).draw());
}
public void draw(Boolf<T> toDraw, Cons<T> cons){
Camera cam = Core.camera;
viewport.set(cam.position.x - cam.width / 2, cam.position.y - cam.height / 2, cam.width, cam.height);
for(Entity e : all()){
if(!(e instanceof DrawTrait) || !toDraw.get((T)e) || !e.isAdded()) continue;
DrawTrait draw = (DrawTrait)e;
if(viewport.overlaps(draw.getX() - draw.drawSize()/2f, draw.getY() - draw.drawSize()/2f, draw.drawSize(), draw.drawSize())){
cons.get((T)e);
each(e -> {
Drawc draw = (Drawc)e;
if(viewport.overlaps(draw.x() - draw.clipSize()/2f, draw.y() - draw.clipSize()/2f, draw.clipSize(), draw.clipSize())){
cons.get(e);
}
}
});
}
public boolean useTree(){
return useTree;
}
public void setRemoveListener(Cons<T> removeListener){
this.removeListener = removeListener;
}
public void setAddListener(Cons<T> addListener){
this.addListener = addListener;
}
public EntityGroup<T> enableMapping(){
map = new IntMap<>();
return this;
return map != null;
}
public boolean mappingEnabled(){
return map != null;
}
public Class<T> getType(){
return type;
}
public int getID(){
return id;
}
public void updateEvents(){
for(T e : entitiesToAdd){
if(e == null)
continue;
entityArray.add(e);
e.added();
if(map != null){
map.put(e.getID(), e);
}
}
entitiesToAdd.clear();
for(T e : entitiesToRemove){
entityArray.remove(e, true);
if(map != null){
map.remove(e.getID());
}
e.removed();
}
entitiesToRemove.clear();
}
public T getByID(int id){
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
return map.get(id);
@@ -145,123 +98,95 @@ public class EntityGroup<T extends Entity> implements Iterable<T>{
T t = map.get(id);
if(t != null){ //remove if present in map already
remove(t);
}else{ //maybe it's being queued?
for(T check : entitiesToAdd){
if(check.getID() == id){ //if it is indeed queued, remove it
entitiesToAdd.remove(check, true);
if(removeListener != null){
removeListener.get(check);
}
break;
}
}
}
}
@SuppressWarnings("unchecked")
public void intersect(float x, float y, float width, float height, Cons<? super T> out){
//don't waste time for empty groups
if(isEmpty()) return;
tree().getIntersect(out, x, y, width, height);
tree.getIntersect(out, x, y, width, height);
}
@SuppressWarnings("unchecked")
public Array<T> intersect(float x, float y, float width, float height){
intersectArray.clear();
//don't waste time for empty groups
if(isEmpty()) return intersectArray;
tree().getIntersect(intersectArray, intersectRect.set(x, y, width, height));
tree.getIntersect(intersectArray, intersectRect.set(x, y, width, height));
return intersectArray;
}
public QuadTree tree(){
if(!useTree) throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
if(tree == null) throw new RuntimeException("This group does not support quadtrees! Enable quadtrees when creating it.");
return tree;
}
/** Resizes the internal quadtree, if it is enabled.*/
public void resize(float x, float y, float w, float h){
if(useTree){
if(tree != null){
tree = new QuadTree<>(new Rect(x, y, w, h));
}
}
public boolean isEmpty(){
return entityArray.size == 0;
return array.size == 0;
}
public T index(int i){
return array.get(i);
}
public int size(){
return entityArray.size;
return array.size;
}
public boolean contains(Boolf<T> pred){
return array.contains(pred);
}
public int count(Boolf<T> pred){
int count = 0;
for(int i = 0; i < entityArray.size; i++){
if(pred.get(entityArray.get(i))) count++;
}
return count;
return array.count(pred);
}
public void add(T type){
if(type == null) throw new RuntimeException("Cannot add a null entity!");
if(type.getGroup() != null) return;
type.setGroup(this);
entitiesToAdd.add(type);
array.add(type);
if(mappingEnabled()){
map.put(type.getID(), type);
}
if(addListener != null){
addListener.get(type);
map.put(type.id(), type);
}
}
public void remove(T type){
if(clearing) return;
if(type == null) throw new RuntimeException("Cannot remove a null entity!");
type.setGroup(null);
entitiesToRemove.add(type);
int idx = array.indexOf(type, true);
if(idx != -1){
array.remove(idx);
if(removeListener != null){
removeListener.get(type);
//fix iteration index when removing
if(index >= idx){
index --;
}
}
}
public void clear(){
for(T entity : entityArray){
entity.removed();
entity.setGroup(null);
}
clearing = true;
for(T entity : entitiesToAdd)
entity.setGroup(null);
for(T entity : entitiesToRemove)
entity.setGroup(null);
entitiesToAdd.clear();
entitiesToRemove.clear();
entityArray.clear();
array.each(Entityc::remove);
array.clear();
if(map != null)
map.clear();
clearing = false;
}
public T find(Boolf<T> pred){
for(int i = 0; i < entityArray.size; i++){
if(pred.get(entityArray.get(i))) return entityArray.get(i);
}
return null;
}
/** Returns the array for iteration. */
public Array<T> all(){
return entityArray;
return array.find(pred);
}
@Override
public Iterator<T> iterator(){
return entityArray.iterator();
return array.iterator();
}
}

View File

@@ -0,0 +1,70 @@
package mindustry.entities;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Fires{
private static final float baseLifetime = 1000f;
private static final IntMap<Firec> map = new IntMap<>();
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null) return; //not clientside.
Firec fire = map.get(tile.pos());
if(fire == null){
fire = FireEntity.create();
fire.tile(tile);
fire.lifetime(baseLifetime);
fire.set(tile.worldx(), tile.worldy());
fire.add();
map.put(tile.pos(), fire);
}else{
fire.lifetime(baseLifetime);
fire.time(0f);
}
}
public static Firec get(int x, int y){
return map.get(Point2.pack(x, y));
}
public static boolean has(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Point2.pack(x, y))){
return false;
}
Firec fire = map.get(Point2.pack(x, y));
return fire.isAdded() && fire.fin() < 1f && fire.tile() != null && fire.tile().x == x && fire.tile().y == y;
}
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.pos())){
Firec fire = map.get(tile.pos());
fire.time(fire.time() + intensity * Time.delta());
Fx.steam.at(fire);
if(fire.time() >= fire.lifetime()){
Events.fire(Trigger.fireExtinguish);
}
}
}
public static void remove(Tile tile){
map.remove(tile.pos());
}
public static void register(Firec fire){
map.put(fire.tile().pos(), fire);
}
}

View File

@@ -0,0 +1,86 @@
package mindustry.entities;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
//TODO move into a different class
public class Lightning{
private static final Rand random = new Rand();
private static final Rect rect = new Rect();
private static final Array<Unitc> entities = new Array<>();
private static final IntSet hit = new IntSet();
private static final int maxChain = 8;
private static final float hitRange = 30f;
private static boolean bhit = false;
private static int lastSeed = 0;
/** Create a lighting branch at a location. Use Team.none to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
createLightingInternal(lastSeed++, team, color, damage, x, y, targetAngle, length);
}
//TODO remote method
//@Remote(called = Loc.server, unreliable = true)
private static void createLightingInternal(int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
random.setSeed(seed);
hit.clear();
Array<Vec2> lines = new Array<>();
bhit = false;
for(int i = 0; i < length / 2; i++){
Bullets.damageLightning.create(null, team, x, y, 0f, damage, 1f, 1f, null);
lines.add(new Vec2(x + Mathf.range(3f), y + Mathf.range(3f)));
if(lines.size > 1){
bhit = false;
Vec2 from = lines.get(lines.size - 2);
Vec2 to = lines.get(lines.size - 1);
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
Tile tile = world.tile(wx, wy);
if(tile != null && tile.block().insulated){
bhit = true;
//snap it instead of removing
lines.get(lines.size -1).set(wx * tilesize, wy * tilesize);
return true;
}
return false;
});
if(bhit) break;
}
rect.setSize(hitRange).setCenter(x, y);
entities.clear();
if(hit.size < maxChain){
Units.nearbyEnemies(team, rect, u -> {
if(!hit.contains(u.id())){
entities.add(u);
}
});
}
Unitc furthest = Geometry.findFurthest(x, y, entities);
if(furthest != null){
hit.add(furthest.id());
x = furthest.x();
y = furthest.y();
}else{
rotation += random.range(20f);
x += Angles.trnsx(rotation, hitRange / 2f);
y += Angles.trnsy(rotation, hitRange / 2f);
}
}
Fx.lightning.at(x, y, rotation, color, lines);
}
}

View File

@@ -3,7 +3,7 @@ package mindustry.entities;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.entities.traits.*;
import mindustry.gen.*;
/**
* Class for predicting shoot angles based on velocities of targets.
@@ -51,11 +51,24 @@ public class Predict{
return sol;
}
public static Vec2 intercept(Position src, Hitboxc dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.deltaX(), dst.deltaY(), v);
}
public static Vec2 intercept(Position src, Position dst, float v){
float ddx = 0, ddy = 0;
if(dst instanceof Hitboxc){
ddx = ((Hitboxc)dst).deltaX();
ddy = ((Hitboxc)dst).deltaY();
}
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), ddx, ddy, v);
}
/**
* See {@link #intercept(float, float, float, float, float, float, float)}.
*/
public static Vec2 intercept(TargetTrait src, TargetTrait dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getTargetVelocityX() - src.getTargetVelocityX()/(2f*Time.delta()), dst.getTargetVelocityY() - src.getTargetVelocityY()/(2f*Time.delta()), v);
public static Vec2 intercept(Hitboxc src, Hitboxc dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.deltaX() - src.deltaX()/(2f*Time.delta()), dst.deltaY() - src.deltaX()/(2f*Time.delta()), v);
}
private static Vec2 quad(float a, float b, float c){

View File

@@ -0,0 +1,109 @@
package mindustry.entities;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
public class Puddles{
private static final IntMap<Puddlec> map = new IntMap<>();
public static final float maxLiquid = 70f;
/** Deposists a Puddlec between tile and source. */
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
deposit(tile, source, liquid, amount, 0);
}
/** Deposists a Puddlec at a tile. */
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
}
/** Returns the Puddlec on the specified tile. May return null. */
public static Puddlec get(Tile tile){
return map.get(tile.pos());
}
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){
if(tile == null) return;
if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){
reactPuddle(tile.floor().liquidDrop, liquid, amount, tile,
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
Puddlec p = map.get(tile.pos());
if(generation == 0 && p != null && p.lastRipple() <= Time.time() - 40f){
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, tile.floor().liquidDrop.color);
p.lastRipple(Time.time());
}
return;
}
Puddlec p = map.get(tile.pos());
if(p == null){
Puddlec puddle = PuddleEntity.create();
puddle.tile(tile);
puddle.liquid(liquid);
puddle.amount(amount);
puddle.generation(generation);
puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
puddle.add();
map.put(tile.pos(), puddle);
}else if(p.liquid() == liquid){
p.accepting(Math.max(amount, p.accepting()));
if(generation == 0 && p.lastRipple() <= Time.time() - 40f && p.amount() >= maxLiquid / 2f){
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, p.liquid().color);
p.lastRipple(Time.time());
}
}else{
p.amount(p.amount() + reactPuddle(p.liquid(), liquid, amount, p.tile(), p.x(), p.y()));
}
}
public static void remove(Tile tile){
if(tile == null) return;
map.remove(tile.pos());
}
public static void register(Puddlec puddle){
map.put(puddle.tile().pos(), puddle);
}
/** Reacts two liquids together at a location. */
private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){
if((dest.flammability > 0.3f && liquid.temperature > 0.7f) ||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
Fires.create(tile);
if(Mathf.chance(0.006 * amount)){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
}
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot Puddlec
if(Mathf.chance(0.5f * amount)){
Fx.steam.at(x, y);
}
return -0.1f * amount;
}else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold Puddlec
if(Mathf.chance(0.8f * amount)){
Fx.steam.at(x, y);
}
return -0.4f * amount;
}
return 0f;
}
/**
* Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.
*/
private static boolean canStayOn(Liquid liquid, Liquid other){
return liquid == Liquids.oil && other == Liquids.water;
}
}

View File

@@ -1,11 +1,9 @@
package mindustry.entities;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -13,13 +11,13 @@ import static mindustry.Vars.*;
/** Utility class for unit and team interactions.*/
public class Units{
private static Rect hitrect = new Rect();
private static Unit result;
private static Unitc result;
private static float cdist;
private static boolean boolResult;
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
public static boolean canInteract(Player player, Tile tile){
return player == null || tile == null || tile.interactable(player.getTeam());
public static boolean canInteract(Playerc player, Tilec tile){
return player == null || tile == null || tile.interactable(player.team());
}
/**
@@ -31,18 +29,18 @@ public class Units{
* @param range The maximum distance from the target X/Y the targeter can be for it to be valid
* @return whether the target is invalid
*/
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range){
return target == null || (range != Float.MAX_VALUE && !target.withinDst(x, y, range)) || target.getTeam() == team || !target.isValid();
public static boolean invalidateTarget(Posc target, Team team, float x, float y, float range){
return target == null || !target.isAdded() || (range != Float.MAX_VALUE && !target.withinDst(x, y, range)) || (target instanceof Teamc && ((Teamc)target).team() == team) || (target instanceof Healthc && !((Healthc)target).isValid());
}
/** See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} */
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y){
/** See {@link #invalidateTarget(Posc, Team, float, float, float)} */
public static boolean invalidateTarget(Posc target, Team team, float x, float y){
return invalidateTarget(target, team, x, y, Float.MAX_VALUE);
}
/** See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} */
public static boolean invalidateTarget(TargetTrait target, Unit targeter){
return invalidateTarget(target, targeter.getTeam(), targeter.x, targeter.y, targeter.getWeapon().bullet.range());
/** See {@link #invalidateTarget(Posc, Team, float, float, float)} */
public static boolean invalidateTarget(Teamc target, Unitc targeter, float range){
return invalidateTarget(target, targeter.team(), targeter.x(), targeter.y(), range);
}
/** Returns whether there are any entities on this tile. */
@@ -56,7 +54,7 @@ public class Units{
nearby(x, y, width, height, unit -> {
if(boolResult) return;
if(!unit.isFlying()){
if(unit.isGrounded()){
unit.hitbox(hitrect);
if(hitrect.overlaps(x, y, width, height)){
@@ -69,38 +67,38 @@ public class Units{
}
/** Returns the neareset damaged tile. */
public static TileEntity findDamagedTile(Team team, float x, float y){
public static Tilec findDamagedTile(Team team, float x, float y){
Tile tile = Geometry.findClosest(x, y, indexer.getDamaged(team));
return tile == null ? null : tile.entity;
}
/** Returns the neareset ally tile in a range. */
public static TileEntity findAllyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public static Tilec findAllyTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
return indexer.findTile(team, x, y, range, pred);
}
/** Returns the neareset enemy tile in a range. */
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public static Tilec findEnemyTile(Team team, float x, float y, float range, Boolf<Tilec> pred){
if(team == Team.derelict) return null;
return indexer.findEnemyTile(team, x, y, range, pred);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range){
return closestTarget(team, x, y, range, Unit::isValid);
public static Teamc closestTarget(Team team, float x, float y, float range){
return closestTarget(team, x, y, range, Unitc::isValid);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred){
public static Teamc closestTarget(Team team, float x, float y, float range, Boolf<Unitc> unitPred){
return closestTarget(team, x, y, range, unitPred, t -> true);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
public static TargetTrait closestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Tile> tilePred){
public static Teamc closestTarget(Team team, float x, float y, float range, Boolf<Unitc> unitPred, Boolf<Tilec> tilePred){
if(team == Team.derelict) return null;
Unit unit = closestEnemy(team, x, y, range, unitPred);
Unitc unit = closestEnemy(team, x, y, range, unitPred);
if(unit != null){
return unit;
}else{
@@ -109,16 +107,16 @@ public class Units{
}
/** Returns the closest enemy of this team. Filter by predicate. */
public static Unit closestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate){
public static Unitc closestEnemy(Team team, float x, float y, float range, Boolf<Unitc> predicate){
if(team == Team.derelict) return null;
result = null;
cdist = 0f;
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.isDead() || !predicate.get(e)) return;
if(e.dead() || !predicate.get(e)) return;
float dst2 = Mathf.dst2(e.x, e.y, x, y);
float dst2 = e.dst2(x, y);
if(dst2 < range*range && (result == null || dst2 < cdist)){
result = e;
cdist = dst2;
@@ -129,14 +127,14 @@ public class Units{
}
/** Returns the closest ally of this team. Filter by predicate. */
public static Unit closest(Team team, float x, float y, float range, Boolf<Unit> predicate){
public static Unitc closest(Team team, float x, float y, float range, Boolf<Unitc> predicate){
result = null;
cdist = 0f;
nearby(team, x, y, range, e -> {
if(!predicate.get(e)) return;
float dist = Mathf.dst2(e.x, e.y, x, y);
float dist = e.dst2(x, y);
if(result == null || dist < cdist){
result = e;
cdist = dist;
@@ -147,73 +145,45 @@ public class Units{
}
/** Iterates over all units in a rectangle. */
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
unitGroup.intersect(x, y, width, height, u -> {
if(u.getTeam() == team){
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unitc> cons){
Groups.unit.intersect(x, y, width, height, u -> {
if(u.team() == team){
cons.get(u);
}
});
playerGroup.intersect(x, y, width, height, player -> {
if(player.getTeam() == team){
cons.get(player);
}
});
}
/** 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){
unitGroup.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.getTeam() == team && unit.withinDst(x, y, radius)){
cons.get(unit);
}
});
playerGroup.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.getTeam() == team && unit.withinDst(x, y, radius)){
public static void nearby(Team team, float x, float y, float radius, Cons<Unitc> cons){
Groups.unit.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.team() == team && unit.withinDst(x, y, radius)){
cons.get(unit);
}
});
}
/** Iterates over all units in a rectangle. */
public static void nearby(float x, float y, float width, float height, Cons<Unit> cons){
unitGroup.intersect(x, y, width, height, cons);
playerGroup.intersect(x, y, width, height, cons);
public static void nearby(float x, float y, float width, float height, Cons<Unitc> cons){
Groups.unit.intersect(x, y, width, height, cons);
}
/** Iterates over all units in a rectangle. */
public static void nearby(Rect rect, Cons<Unit> cons){
public static void nearby(Rect rect, Cons<Unitc> cons){
nearby(rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
unitGroup.intersect(x, y, width, height, u -> {
if(team.isEnemy(u.getTeam())){
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unitc> cons){
Groups.unit.intersect(x, y, width, height, u -> {
if(team.isEnemy(u.team())){
cons.get(u);
}
});
playerGroup.intersect(x, y, width, height, player -> {
if(team.isEnemy(player.getTeam())){
cons.get(player);
}
});
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, Rect rect, Cons<Unit> cons){
public static void nearbyEnemies(Team team, Rect rect, Cons<Unitc> cons){
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units. */
public static void all(Cons<Unit> cons){
unitGroup.all().each(cons);
playerGroup.all().each(cons);
}
public static void each(Team team, Cons<BaseUnit> cons){
unitGroup.all().each(t -> t.getTeam() == team, cons);
}
}

View File

@@ -3,8 +3,6 @@ package mindustry.entities.bullet;
import arc.graphics.g2d.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
//TODO scale velocity depending on fslope()
@@ -25,25 +23,25 @@ public class ArtilleryBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(b.timer.get(0, 3 + b.fslope() * 2f)){
Effects.effect(trailEffect, backColor, b.x, b.y, b.fslope() * 4f);
if(b.timer(0, 3 + b.fslope() * 2f)){
trailEffect.at(b.x(), b.y(), b.fslope() * 4f, backColor);
}
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
float baseScale = 0.7f;
float scale = (baseScale + b.fslope() * (1f - baseScale));
float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout());
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, bulletWidth * scale, height * scale, b.rot() - 90);
Draw.rect(backRegion, b.x(), b.y(), bulletWidth * scale, height * scale, b.rotation() - 90);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, bulletWidth * scale, height * scale, b.rot() - 90);
Draw.rect(frontRegion, b.x(), b.y(), bulletWidth * scale, height * scale, b.rotation() - 90);
Draw.color();
}
}

View File

@@ -4,7 +4,7 @@ import arc.Core;
import arc.graphics.Color;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.graphics.Pal;
/** An extended BulletType for most ammo-based bullets shot from turrets and units. */
@@ -34,13 +34,13 @@ public class BasicBulletType extends BulletType{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout());
Draw.color(backColor);
Draw.rect(backRegion, b.x, b.y, bulletWidth, height, b.rot() - 90);
Draw.rect(backRegion, b.x(), b.y(), bulletWidth, height, b.rotation() - 90);
Draw.color(frontColor);
Draw.rect(frontRegion, b.x, b.y, bulletWidth, height, b.rot() - 90);
Draw.rect(frontRegion, b.x(), b.y(), bulletWidth, height, b.rotation() - 90);
Draw.color();
}
}

View File

@@ -2,17 +2,16 @@ package mindustry.entities.bullet;
import arc.audio.*;
import arc.math.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.Effects.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
public abstract class BulletType extends Content{
public float lifetime;
@@ -56,8 +55,8 @@ public abstract class BulletType extends Content{
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */
public boolean collidesTeam = false;
/** Whether this bullet type collides with air units. */
public boolean collidesAir = true;
/** Whether this bullet type collides with air/ground units. */
public boolean collidesAir = true, collidesGround = true;
/** Whether this bullet types collides with anything at all. */
public boolean collides = true;
/** Whether velocity is inherited from the shooter. */
@@ -97,20 +96,20 @@ public abstract class BulletType extends Content{
return speed * lifetime * (1f - drag);
}
public boolean collides(Bullet bullet, Tile tile){
public boolean collides(Bulletc bullet, Tilec tile){
return true;
}
public void hitTile(Bullet b, Tile tile){
public void hitTile(Bulletc b, Tilec tile){
hit(b);
}
public void hit(Bullet b){
hit(b, b.x, b.y);
public void hit(Bulletc b){
hit(b, b.getX(), b.getY());
}
public void hit(Bullet b, float x, float y){
Effects.effect(hitEffect, x, y, b.rot());
public void hit(Bulletc b, float x, float y){
hitEffect.at(x, y, b.rotation());
hitSound.at(b);
Effects.shake(hitShake, hitShake, b);
@@ -119,7 +118,7 @@ public abstract class BulletType extends Content{
for(int i = 0; i < fragBullets; i++){
float len = Mathf.random(1f, 7f);
float a = Mathf.random(360f);
Bullet.create(fragBullet, b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax));
}
}
@@ -128,12 +127,12 @@ public abstract class BulletType extends Content{
}
if(splashDamageRadius > 0){
Damage.damage(b.getTeam(), x, y, splashDamageRadius, splashDamage * b.damageMultiplier());
Damage.damage(b.team(), x, y, splashDamageRadius, splashDamage * b.damageMultiplier());
}
}
public void despawned(Bullet b){
Effects.effect(despawnEffect, b.x, b.y, b.rot());
public void despawned(Bulletc b){
despawnEffect.at(b.getX(), b.getY(), b.rotation());
hitSound.at(b);
if(fragBullet != null || splashDamageRadius > 0){
@@ -141,16 +140,16 @@ public abstract class BulletType extends Content{
}
for(int i = 0; i < lightining; i++){
Lightning.createLighting(Lightning.nextSeed(), b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength);
Lightning.create(b.team(), Pal.surge, damage, b.getX(), b.getY(), Mathf.random(360f), lightningLength);
}
}
public void draw(Bullet b){
public void draw(Bulletc b){
}
public void init(Bullet b){
if(killShooter && b.getOwner() instanceof HealthTrait){
((HealthTrait)b.getOwner()).kill();
public void init(Bulletc b){
if(killShooter && b.owner() instanceof Healthc){
((Healthc)b.owner()).kill();
}
if(instantDisappear){
@@ -158,12 +157,11 @@ public abstract class BulletType extends Content{
}
}
public void update(Bullet b){
public void update(Bulletc b){
if(homingPower > 0.0001f){
TargetTrait target = Units.closestTarget(b.getTeam(), b.x, b.y, homingRange, e -> !e.isFlying() || collidesAir);
Teamc target = Units.closestTarget(b.team(), b.getX(), b.getY(), homingRange, e -> e.isGrounded() || collidesAir);
if(target != null){
b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f));
b.vel().setAngle(Mathf.slerpDelta(b.rotation(), b.angleTo(target), 0.08f));
}
}
}
@@ -172,4 +170,58 @@ public abstract class BulletType extends Content{
public ContentType getContentType(){
return ContentType.bullet;
}
//TODO change 'create' to 'at'
public Bulletc create(Teamc owner, float x, float y, float angle){
return create(owner, owner.team(), x, y, angle);
}
public Bulletc create(Entityc owner, Team team, float x, float y, float angle){
return create(owner, team, x, y, angle, 1f);
}
public Bulletc create(Entityc owner, Team team, float x, float y, float angle, float velocityScl){
return create(owner, team, x, y, angle, -1, velocityScl, 1f, null);
}
public Bulletc create(Entityc owner, Team team, float x, float y, float angle, float velocityScl, float lifetimeScl){
return create(owner, team, x, y, angle, -1, velocityScl, lifetimeScl, null);
}
public Bulletc create(Bulletc parent, float x, float y, float angle){
return create(parent.owner(), parent.team(), x, y, angle);
}
public Bulletc create(Bulletc parent, float x, float y, float angle, float velocityScl){
return create(parent.owner(), parent.team(), x, y, angle, velocityScl);
}
public Bulletc create(@Nullable Entityc owner, Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl, Object data){
Bulletc bullet = BulletEntity.create();
bullet.type(this);
bullet.owner(owner);
bullet.team(team);
bullet.vel().trns(angle, speed * velocityScl);
bullet.set(x - bullet.vel().x * Time.delta(), y - bullet.vel().y * Time.delta());
bullet.lifetime(lifetime * lifetimeScl);
bullet.data(data);
bullet.drag(drag);
bullet.hitSize(hitSize);
bullet.damage(damage < 0 ? this.damage : damage);
bullet.add();
if(keepVelocity && owner instanceof Velc) bullet.vel().add(((Velc)owner).vel());
return bullet;
}
public void createNet(Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
Call.createBullet(this, team, x, y, damage, angle, velocityScl, lifetimeScl);
}
@Remote(called = Loc.server, unreliable = true)
public static void createBullet(BulletType type, Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
type.create(null, team, x, y, angle, damage, velocityScl, lifetimeScl, null);
}
}

View File

@@ -1,14 +1,12 @@
package mindustry.entities.bullet;
import arc.math.geom.Rect;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Units;
import mindustry.entities.type.Bullet;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
public class FlakBulletType extends BasicBulletType{
protected static Rect rect = new Rect();
protected float explodeRange = 30f;
public float explodeRange = 30f;
public FlakBulletType(float speed, float damage){
super(speed, damage, "shell");
@@ -17,6 +15,7 @@ public class FlakBulletType extends BasicBulletType{
hitEffect = Fx.flakExplosionBig;
bulletWidth = 8f;
bulletHeight = 10f;
collidesGround = false;
}
public FlakBulletType(){
@@ -24,18 +23,18 @@ public class FlakBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(b.getData() instanceof Integer) return;
if(b.data() instanceof Integer) return;
if(b.timer.get(2, 6)){
Units.nearbyEnemies(b.getTeam(), rect.setSize(explodeRange * 2f).setCenter(b.x, b.y), unit -> {
if(b.getData() instanceof Float) 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.isFlying() && !collidesAir) || (unit.isGrounded() && !collidesGround)) return;
if(unit.dst(b) < explodeRange){
b.setData(0);
b.data(0);
Time.run(5f, () -> {
if(b.getData() instanceof Integer){
if(b.data() instanceof Integer){
b.time(b.lifetime());
}
});

View File

@@ -3,14 +3,14 @@ package mindustry.entities.bullet;
import arc.graphics.*;
import arc.graphics.g2d.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
public class HealBulletType extends BulletType{
protected float healPercent = 3f;
protected float bulletHeight = 7f, bulletWidth = 2f;
protected Color backColor = Pal.heal, frontColor = Color.white;
public HealBulletType(float speed, float damage){
super(speed, damage);
@@ -27,28 +27,27 @@ public class HealBulletType extends BulletType{
}
@Override
public boolean collides(Bullet b, Tile tile){
return tile.getTeam() != b.getTeam() || tile.entity.healthf() < 1f;
public boolean collides(Bulletc b, Tilec tile){
return tile.team() != b.team() || tile.healthf() < 1f;
}
@Override
public void draw(Bullet b){
Draw.color(Pal.heal);
Lines.stroke(2f);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 7f);
Draw.color(Color.white);
Lines.lineAngleCenter(b.x, b.y, b.rot(), 3f);
public void draw(Bulletc b){
Draw.color(backColor);
Lines.stroke(bulletWidth);
Lines.lineAngleCenter(b.x(), b.y(), b.rotation(), bulletHeight);
Draw.color(frontColor);
Lines.lineAngleCenter(b.x(), b.y(), b.rotation(), bulletHeight / 2f);
Draw.reset();
}
@Override
public void hitTile(Bullet b, Tile tile){
public void hitTile(Bulletc b, Tilec tile){
super.hit(b);
tile = tile.link();
if(tile.entity != null && tile.getTeam() == b.getTeam() && !(tile.block() instanceof BuildBlock)){
Effects.effect(Fx.healBlockFull, Pal.heal, tile.drawx(), tile.drawy(), tile.block().size);
tile.entity.healBy(healPercent / 100f * tile.entity.maxHealth());
if(tile.team() == b.team() && !(tile.block() instanceof BuildBlock)){
Fx.healBlockFull.at(tile.x(), tile.y(), tile.block().size, Pal.heal);
tile.heal(healPercent / 100f * tile.maxHealth());
}
}
}

View File

@@ -0,0 +1,73 @@
package mindustry.entities.bullet;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class LaserBulletType extends BulletType{
protected Color[] colors = {Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
protected float length = 160f;
protected float width = 15f;
protected float lengthFalloff = 0.5f;
protected float sideLength = 29f, sideWidth = 0.7f;
protected float sideAngle = 90f;
public LaserBulletType(float damage){
super(0.01f, damage);
keepVelocity = false;
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
shootEffect = Fx.hitLancer;
smokeEffect = Fx.lancerLaserShootSmoke;
hitSize = 4;
lifetime = 16f;
pierce = true;
}
public LaserBulletType(){
this(1f);
}
@Override
public float range(){
return length;
}
@Override
public void init(Bulletc b){
Damage.collideLine(b, b.team(), hitEffect, b.x(), b.y(), b.rotation(), length);
}
@Override
public void draw(Bulletc b){
float f = Mathf.curve(b.fin(), 0f, 0.2f);
float baseLen = length * f;
float cwidth = width;
float compound = 1f;
Lines.lineAngle(b.x(), b.y(), b.rotation(), baseLen);
Lines.precise(true);
for(Color color : colors){
Draw.color(color);
Lines.stroke((cwidth *= lengthFalloff) * b.fout());
Lines.lineAngle(b.x(), b.y(), b.rotation(), baseLen, CapStyle.none);
Tmp.v1.trns(b.rotation(), baseLen);
Drawf.tri(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, Lines.getStroke() * 1.22f, cwidth * 2f + width / 2f, b.rotation());
Fill.circle(b.x(), b.y(), 1f * cwidth * b.fout());
for(int i : Mathf.signs){
Drawf.tri(b.x(), b.y(), sideWidth * b.fout() * cwidth, sideLength * compound, b.rotation() + sideAngle * i);
}
compound *= lengthFalloff;
}
Lines.precise(false);
Draw.reset();
}
}

View File

@@ -0,0 +1,30 @@
package mindustry.entities.bullet;
import arc.graphics.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class LightningBulletType extends BulletType{
protected Color lightningColor = Pal.lancerLaser;
protected int lightningLength = 25;
public LightningBulletType(){
super(0.0001f, 1f);
lifetime = 1;
despawnEffect = Fx.none;
hitEffect = Fx.hitLancer;
keepVelocity = false;
}
@Override
public void draw(Bulletc b){
}
@Override
public void init(Bulletc b){
Lightning.create(b.team(), lightningColor, damage, b.x(), b.y(), b.rotation(), lightningLength);
}
}

View File

@@ -6,8 +6,7 @@ import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -45,13 +44,13 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(liquid.canExtinguish()){
Tile tile = world.tileWorld(b.x, b.y);
if(tile != null && Fire.has(tile.x, tile.y)){
Fire.extinguish(tile, 100f);
Tile tile = world.tileWorld(b.x(), b.y());
if(tile != null && Fires.has(tile.x, tile.y)){
Fires.extinguish(tile, 100f);
b.remove();
hit(b);
}
@@ -59,22 +58,22 @@ public class LiquidBulletType extends BulletType{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
Draw.color(liquid.color, Color.white, b.fout() / 100f);
Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f);
Fill.circle(b.x(), b.y(), 0.5f + b.fout() * 2.5f);
}
@Override
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hitEffect, liquid.color, hitx, hity);
Puddle.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
public void hit(Bulletc b, float hitx, float hity){
hitEffect.at(hitx, hity, liquid.color);
Puddles.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){
float intensity = 400f;
Fire.extinguish(world.tileWorld(hitx, hity), intensity);
Fires.extinguish(world.tileWorld(hitx, hity), intensity);
for(Point2 p : Geometry.d4){
Fire.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
Fires.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
}
}
}

View File

@@ -5,8 +5,7 @@ import arc.graphics.g2d.Draw;
import arc.math.Angles;
import arc.math.Mathf;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
@@ -24,32 +23,32 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void draw(mindustry.entities.type.Bullet b){
public void draw(Bulletc b){
float w = 11f, h = 13f;
Draw.color(Pal.bulletYellowBack);
Draw.rect("shell-back", b.x, b.y, w, h, b.rot() + 90);
Draw.rect("shell-back", b.x(), b.y(), w, h, b.rotation() + 90);
Draw.color(Pal.bulletYellow);
Draw.rect("shell", b.x, b.y, w, h, b.rot() + 90);
Draw.rect("shell", b.x(), b.y(), w, h, b.rotation() + 90);
Draw.reset();
}
@Override
public void update(mindustry.entities.type.Bullet b){
public void update(Bulletc b){
//data MUST be an instance of DriverBulletData
if(!(b.getData() instanceof DriverBulletData)){
if(!(b.data() instanceof DriverBulletData)){
hit(b);
return;
}
float hitDst = 7f;
DriverBulletData data = (DriverBulletData)b.getData();
DriverBulletData data = (DriverBulletData)b.data();
//if the target is dead, just keep flying until the bullet explodes
if(data.to.isDead()){
if(data.to.dead()){
return;
}
@@ -68,7 +67,7 @@ public class MassDriverBolt extends BulletType{
if(Angles.near(angleTo, baseAngle, 2f)){
intersect = true;
//snap bullet position back; this is used for low-FPS situations
b.set(data.to.x + Angles.trnsx(baseAngle, hitDst), data.to.y + Angles.trnsy(baseAngle, hitDst));
b.set(data.to.x() + Angles.trnsx(baseAngle, hitDst), data.to.y() + Angles.trnsy(baseAngle, hitDst));
}
}
@@ -83,24 +82,24 @@ public class MassDriverBolt extends BulletType{
}
@Override
public void despawned(mindustry.entities.type.Bullet b){
public void despawned(Bulletc b){
super.despawned(b);
if(!(b.getData() instanceof DriverBulletData)) return;
if(!(b.data() instanceof DriverBulletData)) return;
DriverBulletData data = (DriverBulletData)b.getData();
DriverBulletData data = (DriverBulletData)b.data();
for(int i = 0; i < data.items.length; i++){
int amountDropped = Mathf.random(0, data.items[i]);
if(amountDropped > 0){
float angle = b.rot() + Mathf.range(100f);
Effects.effect(Fx.dropItem, Color.white, b.x, b.y, angle, content.item(i));
float angle = b.rotation() + Mathf.range(100f);
Fx.dropItem.at(b.x(), b.y(), angle, Color.white, content.item(i));
}
}
}
@Override
public void hit(Bullet b, float hitx, float hity){
public void hit(Bulletc b, float hitx, float hity){
super.hit(b, hitx, hity);
despawned(b);
}

View File

@@ -4,8 +4,6 @@ import arc.graphics.Color;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Effects;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
import mindustry.graphics.Pal;
@@ -28,15 +26,15 @@ public class MissileBulletType extends BasicBulletType{
}
@Override
public void update(Bullet b){
public void update(Bulletc b){
super.update(b);
if(Mathf.chance(Time.delta() * 0.2)){
Effects.effect(Fx.missileTrail, trailColor, b.x, b.y, 2f);
Fx.missileTrail.at(b.x(), b.y(), 2f, trailColor);
}
if(weaveMag > 0){
b.velocity().rotate(Mathf.sin(Time.time() + b.id * 4422, weaveScale, weaveMag) * Time.delta());
b.vel().rotate(Mathf.sin(Time.time() + b.id() * 442, weaveScale, weaveMag) * Time.delta());
}
}
}

View File

@@ -0,0 +1,36 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@Component
abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
static final float warpDst = 180f;
@Import float x, y;
@Import Vec2 vel;
@Override
public void update(){
//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;
//clamp position if not flying
if(isGrounded()){
x = Mathf.clamp(x, 0, world.width() * tilesize - tilesize);
y = Mathf.clamp(y, 0, world.height() * tilesize - tilesize);
}
//kill when out of bounds
if(x < -finalWorldBounds || y < -finalWorldBounds || x >= world.width() * tilesize + finalWorldBounds || y >= world.height() * tilesize + finalWorldBounds){
kill();
}
}
}

View File

@@ -0,0 +1,239 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.Queue;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import java.util.*;
import static mindustry.Vars.*;
@Component
abstract class BuilderComp implements Unitc, DrawLayerFlyingc{
static final Vec2[] vecs = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
@Import float x, y, rotation;
Queue<BuildRequest> requests = new Queue<>();
transient float buildSpeed = 1f;
//boolean building;
@Override
public void update(){
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange;
Iterator<BuildRequest> it = requests.iterator();
while(it.hasNext()){
BuildRequest req = it.next();
Tile tile = world.tile(req.x, req.y);
if(tile == null || (req.breaking && tile.block() == Blocks.air) || (!req.breaking && (tile.rotation() == req.rotation || !req.block.rotate) && tile.block() == req.block)){
it.remove();
}
}
Tilec core = closestCore();
//nothing to build.
if(buildRequest() == null) return;
//find the next build request
if(requests.size > 1){
int total = 0;
BuildRequest req;
while((dst((req = buildRequest()).tile()) > finalPlaceDst || shouldSkip(req, core)) && total < requests.size){
requests.removeFirst();
requests.addLast(req);
total++;
}
}
BuildRequest current = buildRequest();
if(dst(current.tile()) > finalPlaceDst) return;
Tile tile = world.tile(current.x, current.y);
if(dst(tile) <= finalPlaceDst){
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
}
if(!(tile.block() instanceof BuildBlock)){
if(!current.initialized && !current.breaking && Build.validPlace(team(), current.x, current.y, current.block, current.rotation)){
boolean hasAll = !Structs.contains(current.block.requirements, i -> !core.items().has(i.item));
if(hasAll){
Build.beginPlace(team(), current.x, current.y, current.block, current.rotation);
}else{
current.stuck = true;
}
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
Build.beginBreak(team(), current.x, current.y);
}else{
requests.removeFirst();
return;
}
}else if(tile.team() != team()){
requests.removeFirst();
return;
}
if(tile.entity instanceof BuildEntity && !current.initialized){
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team(), (Builderc)this, current.breaking)));
current.initialized = true;
}
//if there is no core to build with or no build entity, stop building!
if((core == null && !state.rules.infiniteResources) || !(tile.entity instanceof BuildEntity)){
return;
}
//otherwise, update it.
BuildEntity entity = tile.ent();
if(current.breaking){
entity.deconstruct(this, core, 1f / entity.buildCost * Time.delta() * buildSpeed * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(this, core, 1f / entity.buildCost * Time.delta() * buildSpeed * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.onTileConfig(null, tile.entity, current.config);
}
}
}
current.stuck = Mathf.equal(current.progress, entity.progress);
current.progress = entity.progress;
}
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
void drawBuildRequests(){
for(BuildRequest request : requests){
if(request.progress > 0.01f || (buildRequest() == request && request.initialized && (dst(request.x * tilesize, request.y * tilesize) <= buildingRange || state.isEditor()))) continue;
request.animScale = 1f;
if(request.breaking){
control.input.drawBreaking(request);
}else{
request.block.drawRequest(request, control.input.allRequests(),
Build.validPlace(team(), request.x, request.y, request.block, request.rotation) || control.input.requestMatches(request));
}
}
Draw.reset();
}
/** @return whether this request should be skipped, in favor of the next one. */
boolean shouldSkip(BuildRequest request, @Nullable Tilec core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || request.breaking || core == null) return false;
//TODO these are bad criteria
return (request.stuck && !core.items().has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items().has(i.item)) && !request.initialized);
}
void removeBuild(int x, int y, boolean breaking){
//remove matching request
int idx = requests.indexOf(req -> req.breaking == breaking && req.x == x && req.y == y);
if(idx != -1){
requests.removeIndex(idx);
}
}
/** Return whether this builder's place queue contains items. */
boolean isBuilding(){
return requests.size != 0;
}
/** Clears the placement queue. */
void clearBuilding(){
requests.clear();
}
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
void addBuild(BuildRequest place){
addBuild(place, true);
}
/** Add another build requests to the queue, if it doesn't exist there yet. */
void addBuild(BuildRequest place, boolean tail){
BuildRequest replace = null;
for(BuildRequest request : requests){
if(request.x == place.x && request.y == place.y){
replace = request;
break;
}
}
if(replace != null){
requests.remove(replace);
}
Tile tile = world.tile(place.x, place.y);
if(tile != null && tile.entity instanceof BuildEntity){
place.progress = tile.<BuildEntity>ent().progress;
}
if(tail){
requests.addLast(place);
}else{
requests.addFirst(place);
}
}
/** Return the build requests currently active, or the one at the top of the queue.*/
@Nullable BuildRequest buildRequest(){
return requests.size == 0 ? null : requests.first();
}
@Override
public void drawFlying(){
if(!isBuilding()) return;
BuildRequest request = buildRequest();
Tile tile = world.tile(request.x, request.y);
if(dst(tile) > buildingRange && !state.isEditor()){
return;
}
int size = request.breaking ? tile.block().size : request.block.size;
float tx = request.drawx(), ty = request.drawy();
Lines.stroke(1f, Pal.accent);
float focusLen = 3.8f + Mathf.absin(Time.time(), 1.1f, 0.6f);
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
float sz = Vars.tilesize * size / 2f;
float ang = angleTo(tx, ty);
vecs[0].set(tx - sz, ty - sz);
vecs[1].set(tx + sz, ty - sz);
vecs[2].set(tx - sz, ty + sz);
vecs[3].set(tx + sz, ty + sz);
Arrays.sort(vecs, Structs.comparingFloat(vec -> -Angles.angleDist(angleTo(vec), ang)));
float x1 = vecs[0].x, y1 = vecs[0].y,
x3 = vecs[1].x, y3 = vecs[1].y;
Draw.alpha(1f);
Lines.line(px, py, x1, y1);
Lines.line(px, py, x3, y3);
Fill.circle(px, py, 1.6f + Mathf.absin(Time.time(), 0.8f, 1.5f));
Draw.color();
}
}

View File

@@ -0,0 +1,130 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import static mindustry.Vars.*;
@EntityDef(value = {Bulletc.class}, pooled = true)
@Component
abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Drawc, Shielderc, Ownerc, Velc, Bulletc, Timerc, DrawLayerBulletsc{
Object data;
BulletType type;
float damage;
@Override
public void drawBullets(){
type.draw(this);
}
@Override
public void add(){
type.init(this);
}
@Override
public void remove(){
type.despawned(this);
}
@Override
public float damageMultiplier(){
if(owner() instanceof Unitc){
return ((Unitc)owner()).damageMultiplier();
}
return 1f;
}
@Override
public void absorb(){
//TODO
remove();
}
@Override
public float clipSize(){
return type.drawSize;
}
@Override
public float damage(){
return damage * damageMultiplier();
}
@Replace
@Override
public boolean collides(Hitboxc other){
return type.collides && (other instanceof Teamc && ((Teamc)other).team() != team())
&& !(other instanceof Flyingc && ((((Flyingc)other).isFlying() && !type.collidesAir) || (((Flyingc)other).isGrounded() && !type.collidesGround)));
}
@MethodPriority(100)
@Override
public void collision(Hitboxc other, float x, float y){
type.hit(this, x, y);
if(other instanceof Healthc){
Healthc h = (Healthc)other;
h.damage(damage);
}
if(other instanceof Unitc){
Unitc unit = (Unitc)other;
unit.vel().add(Tmp.v3.set(other.x(), other.y()).sub(x, y).setLength(type.knockback / unit.mass()));
unit.apply(type.status, type.statusDuration);
}
//must be last.
if(!type.pierce) remove();
}
@Override
public void update(){
type.update(this);
if(type.hitTiles){
world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Tilec tile = world.ent(x, y);
if(tile == null) return false;
if(tile.collide(this) && type.collides(this, tile) && !tile.dead() && (type.collidesTeam || tile.team() != team())){
if(tile.team() != team()){
tile.collision(this);
}
type.hitTile(this, tile);
remove();
return true;
}
return false;
});
}
}
@Override
public void draw(){
type.draw(this);
//TODO refactor
renderer.lights.add(x(), y(), 16f, Pal.powerLight, 0.3f);
}
/** Sets the bullet's rotation in degrees. */
@Override
public void rotation(float angle){
vel().setAngle(angle);
}
/** @return the bullet's rotation. */
@Override
public float rotation(){
float angle = Mathf.atan2(vel().x, vel().y) * Mathf.radiansToDegrees;
if(angle < 0) angle += 360;
return angle;
}
}

View File

@@ -0,0 +1,29 @@
package mindustry.entities.def;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class ChildComp implements Posc{
@Import float x, y;
@Nullable Posc parent;
float offsetX, offsetY;
@Override
public void add(){
if(parent != null){
offsetX = x - parent.getX();
offsetY = y - parent.getY();
}
}
@Override
public void update(){
if(parent != null){
x = parent.getX() + offsetX;
y = parent.getY() + offsetY;
}
}
}

View File

@@ -0,0 +1,8 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
@Component
abstract class DamageComp{
abstract float damage();
}

View File

@@ -0,0 +1,30 @@
package mindustry.entities.def;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@EntityDef(value = {Decalc.class}, pooled = true)
@Component
abstract class DecalComp implements Drawc, Timedc, Rotc, Posc, DrawLayerFloorc{
@Import float x, y, rotation;
Color color = new Color(1, 1, 1, 1);
TextureRegion region;
@Override
public void drawFloor(){
Draw.color(color);
Draw.alpha(1f - Mathf.curve(fin(), 0.98f));
Draw.rect(region, x, y, rotation);
Draw.color();
}
@Override
public float clipSize(){
return region.getWidth()*2;
}
}

View File

@@ -0,0 +1,9 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class DrawComp implements Posc{
abstract float clipSize();
}

View File

@@ -0,0 +1,22 @@
package mindustry.entities.def;
import arc.graphics.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
@Component
abstract class EffectComp implements Posc, Drawc, Timedc, Rotc, Childc{
Color color = new Color(Color.white);
Effect effect;
Object data;
void draw(){
effect.render(id(), color, time(), rotation(), x(), y(), data);
}
@Override
public float clipSize(){
return effect.size;
}
}

View File

@@ -0,0 +1,22 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import static mindustry.Vars.collisions;
@Component
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
@Import float x, y;
@Replace
@Override
public void move(float cx, float cy){
if(isFlying()){
x += cx;
y += cy;
}else{
collisions.move(this, cx, cy);
}
}
}

View File

@@ -0,0 +1,66 @@
package mindustry.entities.def;
import arc.func.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
import static mindustry.Vars.player;
@Component
@BaseComponent
abstract class EntityComp{
private transient boolean added;
transient int id = EntityGroup.nextId();
boolean isAdded(){
return added;
}
void update(){}
void remove(){
added = false;
}
void add(){
added = true;
}
boolean isLocal(){
return ((Object)this) == player || ((Object)this) instanceof Unitc && ((Unitc)((Object)this)).controller() == player;
}
boolean isNull(){
return false;
}
<T> T as(Class<T> type){
return (T)this;
}
<T> T with(Cons<T> cons){
cons.get((T)this);
return (T)this;
}
@InternalImpl
abstract int classId();
@InternalImpl
abstract boolean serialize();
@MethodPriority(1)
void read(Reads read){
afterRead();
}
void write(Writes write){
}
void afterRead(){
}
}

View File

@@ -0,0 +1,102 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@EntityDef(value = {Firec.class}, pooled = true)
@Component
abstract class FireComp implements Timedc, Posc, Firec{
private static final float spreadChance = 0.05f, fireballChance = 0.07f;
@Import float time, lifetime, x, y;
Tile tile;
private Block block;
private float baseFlammability = -1, puddleFlammability;
@Override
public void update(){
if(Mathf.chance(0.1 * 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));
}
if(Mathf.chance(0.001 * Time.delta())){
Sounds.fire.at(this);
}
time = Mathf.clamp(time + Time.delta(), 0, lifetime());
if(Vars.net.client()){
return;
}
if(time >= lifetime() || tile == null){
remove();
return;
}
Tilec entity = tile.entity;
boolean damage = entity != null;
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Time.delta() * 8;
}
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.entity == null ? 0 : tile.entity.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))){
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.1 * Time.delta())){
Puddlec p = Puddles.get(tile);
puddleFlammability = p != null ? p.getFlammability() / 3f : 0;
if(damage){
entity.damage(0.4f);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.apply(StatusEffects.burning, 60 * 5));
}
}
@Override
public void remove(){
Fires.remove(tile);
}
@Override
public void afterRead(){
Fires.register(this);
}
}

View File

@@ -0,0 +1,79 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.net;
@Component
abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
@Import float x, y, drag;
@Import Vec2 vel;
float elevation;
float drownTime;
transient float splashTimer;
boolean isGrounded(){
return elevation < 0.001f;
}
boolean isFlying(){
return elevation >= 0.001f;
}
boolean canDrown(){
return isGrounded();
}
void wobble(){
x += Mathf.sin(Time.time() + id() * 99, 25f, 0.05f) * Time.delta() * elevation;
y += Mathf.cos(Time.time() + id() * 99, 25f, 0.05f) * Time.delta() * elevation;
}
void moveAt(Vec2 vector, float acceleration){
Vec2 t = tmp1.set(vector).scl(floorSpeedMultiplier()); //target vector
tmp2.set(t).sub(vel).limit(acceleration * vector.len()); //delta vector
vel.add(tmp2);
}
float floorSpeedMultiplier(){
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return on.speedMultiplier;
}
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded() && floor.isLiquid){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= 7f){
floor.walkEffect.at(x, y, 0, floor.mapColor);
splashTimer = 0f;
}
}
if(canDrown() && floor.isLiquid && floor.drownTime > 0){
drownTime += Time.delta() * 1f / floor.drownTime;
drownTime = Mathf.clamp(drownTime);
if(Mathf.chance(Time.delta() * 0.05f)){
floor.drownUpdateEffect.at(x, y, 0f, floor.mapColor);
}
//TODO is the netClient check necessary?
if(drownTime >= 0.999f && !net.client()){
kill();
//TODO drown event!
}
}else{
drownTime = Mathf.lerpDelta(drownTime, 0f, 0.03f);
}
}
}

View File

@@ -0,0 +1,14 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@EntityDef(value = {GroundEffectc.class, Childc.class}, pooled = true)
@Component
abstract class GroundEffectComp implements Effectc, DrawLayerFloorOverc{
@Override
public void drawFloorOver(){
draw();
}
}

View File

@@ -0,0 +1,82 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HealthComp implements Entityc{
static final float hitDuration = 9f;
float health;
transient float hitTime;
transient float maxHealth = 1f;
transient boolean dead;
boolean isValid(){
return !dead && isAdded();
}
float healthf(){
return health / maxHealth;
}
@Override
public void update(){
hitTime -= Time.delta() / hitDuration;
}
void killed(){
//implement by other components
}
void kill(){
if(dead) return;
health = 0;
dead = true;
killed();
remove();
}
void heal(){
dead = false;
health = maxHealth;
}
boolean damaged(){
return health < maxHealth - 0.001f;
}
void damage(float amount){
health -= amount;
hitTime = 1f;
if(health <= 0 && !dead){
kill();
}
}
void damage(float amount, boolean withEffect){
float pre = hitTime;
damage(amount);
if(!withEffect){
hitTime = pre;
}
}
void damageContinuous(float amount){
damage(amount * Time.delta(), hitTime <= -20 + hitDuration);
}
void clampHealth(){
health = Mathf.clamp(health, 0, maxHealth);
}
void heal(float amount){
health += amount;
clampHealth();
}
}

View File

@@ -0,0 +1,59 @@
package mindustry.entities.def;
import arc.math.geom.*;
import arc.math.geom.QuadTree.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HitboxComp implements Posc, QuadTreeObject{
@Import float x, y;
transient float lastX, lastY, hitSize;
@Override
public void update(){
}
@Override
public void add(){
updateLastPosition();
}
@Override
public void afterRead(){
updateLastPosition();
}
void updateLastPosition(){
lastX = x;
lastY = y;
}
void collision(Hitboxc other, float x, float y){
}
float deltaX(){
return x - lastX;
}
float deltaY(){
return y - lastY;
}
boolean collides(Hitboxc other){
return true;
}
@Override
public void hitbox(Rect rect){
rect.setCentered(x, y, hitSize, hitSize);
}
public void hitboxTile(Rect rect){
float scale = 0.66f;
rect.setCentered(x, y, hitSize * scale, hitSize * scale);
}
}

View File

@@ -0,0 +1,50 @@
package mindustry.entities.def;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.type.*;
@Component
abstract class ItemsComp implements Posc{
@ReadOnly ItemStack stack = new ItemStack();
transient float itemTime;
abstract int itemCapacity();
@Override
public void update(){
stack.amount = Mathf.clamp(stack.amount, 0, itemCapacity());
itemTime = Mathf.lerpDelta(itemTime, Mathf.num(hasItem()), 0.05f);
}
Item item(){
return stack.item;
}
void clearItem(){
stack.amount = 0;
}
boolean acceptsItem(Item item){
return !hasItem() || item == stack.item && stack.amount + 1 <= itemCapacity();
}
boolean hasItem(){
return stack.amount > 0;
}
void addItem(Item item){
addItem(item, 1);
}
void addItem(Item item, int amount){
stack.amount = stack.item == item ? stack.amount + amount : amount;
stack.item = item;
stack.amount = Mathf.clamp(stack.amount, 0, itemCapacity());
}
int maxAccepted(Item item){
return stack.item != item && stack.amount > 0 ? 0 : itemCapacity() - stack.amount;
}
}

View File

@@ -0,0 +1,26 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class LegsComp implements Posc, Flyingc, Hitboxc, DrawLayerGroundUnderc, Unitc, Legsc, ElevationMovec{
@Import float x, y;
float baseRotation;
transient float walkTime;
@Override
public void update(){
float len = vel().len();
baseRotation = Angles.moveToward(baseRotation, vel().angle(), type().baseRotateSpeed * Mathf.clamp(len / type().speed));
walkTime += Time.delta()*len/1f;
}
@Override
public void drawGroundUnder(){
type().drawLegs(this);
}
}

View File

@@ -0,0 +1,13 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class MassComp implements Velc{
transient float mass = 1f;
public void impulse(float x, float y){
vel().add(x / mass, y / mass);
}
}

View File

@@ -0,0 +1,107 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@Component
abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, DrawLayerGroundc{
@Import float x, y, rotation;
transient float mineTimer;
@Nullable Tile mineTile;
abstract boolean canMine(Item item);
abstract float miningSpeed();
abstract boolean offloadImmediately();
boolean mining(){
return mineTile != null;
}
@Override
public void update(){
Tilec core = closestCore();
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && dst(core) < mineTransferRange){
int accepted = core.acceptStack(item(), stack().amount, this);
if(accepted > 0){
Call.transferItemTo(item(), accepted,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
clearItem();
}
}
if(mineTile == null || core == null || mineTile.block() != Blocks.air || dst(mineTile.worldx(), mineTile.worldy()) > miningRange
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).isBuilding())
|| mineTile.drop() == null || !acceptsItem(mineTile.drop()) || !canMine(mineTile.drop())){
mineTile = null;
mineTimer = 0f;
}else{
Item item = mineTile.drop();
rotation(Mathf.slerpDelta(rotation(), angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f));
mineTimer += Time.delta()*miningSpeed();
if(mineTimer >= 50f + item.hardness*10f){
mineTimer = 0;
if(dst(core) < mineTransferRange && core.acceptStack(item, 1, this) == 1 && offloadImmediately()){
Call.transferItemTo(item, 1,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
}else if(acceptsItem(item)){
//this is clientside, since items are synced anyway
InputHandler.transferItemToUnit(item,
mineTile.worldx() + Mathf.range(tilesize / 2f),
mineTile.worldy() + Mathf.range(tilesize / 2f),
this);
}
}
if(Mathf.chance(0.06 * Time.delta())){
Fx.pulverizeSmall.at(mineTile.worldx() + Mathf.range(tilesize / 2f), mineTile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
}
}
}
@Override
public void drawGround(){
if(!mining()) return;
float focusLen = 4f + Mathf.absin(Time.time(), 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
float ex = mineTile.worldx() + Mathf.sin(Time.time() + 48, swingScl, swingMag);
float ey = mineTile.worldy() + Mathf.sin(Time.time() + 48, swingScl + 2f, swingMag);
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time(), 0.5f, flashScl));
Drawf.laser(Core.atlas.find("minelaser"), Core.atlas.find("minelaser-end"), px, py, ex, ey, 0.75f);
//TODO hack?
if(isLocal()){
Lines.stroke(1f, Pal.accent);
Lines.poly(mineTile.worldx(), mineTile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time());
}
Draw.color();
}
}

View File

@@ -0,0 +1,9 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
class OwnerComp{
Entityc owner;
}

View File

@@ -0,0 +1,211 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.pooling.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
@EntityDef(value = {Playerc.class}, serialize = false)
@Component
abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc{
@NonNull @ReadOnly Unitc unit = Nulls.unit;
@ReadOnly Team team = Team.sharded;
String name = "noname";
@Nullable NetConnection con;
boolean admin, typing;
Color color = new Color();
float mouseX, mouseY;
String lastText = "";
float textFadeTime;
public boolean isBuilder(){
return unit instanceof Builderc;
}
public boolean isMiner(){
return unit instanceof Minerc;
}
public @Nullable CoreEntity closestCore(){
return state.teams.closestCore(x(), y(), team);
}
public void reset(){
team = state.rules.defaultTeam;
admin = typing = false;
textFadeTime = 0f;
if(!dead()){
unit.controller(unit.type().createController());
unit = Nulls.unit;
}
}
public void update(){
if(unit.dead()){
clearUnit();
}
CoreEntity core = closestCore();
if(!dead()){
x(unit.x());
y(unit.y());
unit.team(team);
}else if(core != null){
core.requestSpawn((Playerc)this);
}
textFadeTime -= Time.delta() / (60 * 5);
}
public void team(Team team){
this.team = team;
unit.team(team);
}
public void clearUnit(){
unit(Nulls.unit);
}
public Unitc unit(){
return unit;
}
public Minerc miner(){
return !(unit instanceof Minerc) ? Nulls.miner : (Minerc)unit;
}
public Builderc builder(){
return !(unit instanceof Builderc) ? Nulls.builder : (Builderc)unit;
}
public void unit(Unitc unit){
if(unit == null) throw new IllegalArgumentException("Unit cannot be null. Use clearUnit() instead.");
if(this.unit != Nulls.unit){
//un-control the old unit
this.unit.controller(this.unit.type().createController());
}
this.unit = unit;
if(unit != Nulls.unit){
unit.team(team);
unit.controller(this);
}
}
boolean dead(){
return unit.isNull();
}
String uuid(){
return con == null ? "[LOCAL]" : con.uuid;
}
String usid(){
return con == null ? "[LOCAL]" : con.usid;
}
void kick(KickReason reason){
con.kick(reason);
}
void kick(String reason){
con.kick(reason);
}
void drawName(){
BitmapFont font = Fonts.def;
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
final float nameHeight = 11;
final float textHeight = 15;
boolean ints = font.usesIntegerPositions();
font.setUseIntegerPositions(false);
font.getData().setScale(0.25f / Scl.scl(1f));
layout.setText(font, name);
if(!isLocal()){
Draw.color(0f, 0f, 0f, 0.3f);
Fill.rect(unit.x(), unit.y() + nameHeight - layout.height / 2, layout.width + 2, layout.height + 3);
Draw.color();
font.setColor(color);
font.draw(name, unit.x(), unit.y() + nameHeight, 0, Align.center, false);
if(admin){
float s = 3f;
Draw.color(color.r * 0.5f, color.g * 0.5f, color.b * 0.5f, 1f);
Draw.rect(Icon.adminSmall.getRegion(), unit.x() + layout.width / 2f + 2 + 1, unit.y() + nameHeight - 1.5f, s, s);
Draw.color(color);
Draw.rect(Icon.adminSmall.getRegion(), unit.x() + layout.width / 2f + 2 + 1, unit.y() + nameHeight - 1f, s, s);
}
}
if(Core.settings.getBool("playerchat") && ((textFadeTime > 0 && lastText != null) || typing)){
String text = textFadeTime <= 0 || lastText == null ? "[LIGHT_GRAY]" + Strings.animated(Time.time(), 4, 15f, ".") : lastText;
float width = 100f;
float visualFadeTime = 1f - Mathf.curve(1f - textFadeTime, 0.9f);
font.setColor(1f, 1f, 1f, textFadeTime <= 0 || lastText == null ? 1f : visualFadeTime);
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.reset();
Pools.free(layout);
font.getData().setScale(1f);
font.setColor(Color.white);
font.setUseIntegerPositions(ints);
}
void sendMessage(String text){
if(isLocal()){
if(ui != null){
ui.chatfrag.addMessage(text, null);
}
}else{
Call.sendMessage(con, text, null, null);
}
}
void sendMessage(String text, Playerc from){
sendMessage(text, from, NetClient.colorizeName(from.id(), from.name()));
}
void sendMessage(String text, Playerc from, String fromName){
if(isLocal()){
if(ui != null){
ui.chatfrag.addMessage(text, fromName);
}
}else{
Call.sendMessage(con, text, fromName, from);
}
}
PlayerInfo getInfo(){
if(isLocal()){
throw new IllegalArgumentException("Local players cannot be traced and do not have info.");
}else{
return netServer.admins.getInfo(uuid());
}
}
}

View File

@@ -0,0 +1,58 @@
package mindustry.entities.def;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.world;
@Component
abstract class PosComp implements Position{
float x, y;
void set(float x, float y){
this.x = x;
this.y = y;
}
void set(Position pos){
set(pos.getX(), pos.getY());
}
void trns(float x, float y){
set(this.x + x, this.y + y);
}
int tileX(){
return Vars.world.toTile(x);
}
int tileY(){
return Vars.world.toTile(y);
}
/** Returns air if this unit is on a non-air top block. */
public Floor floorOn(){
Tile tile = tileOn();
return tile == null || tile.block() != Blocks.air ? (Floor)Blocks.air : tile.floor();
}
public @Nullable
Tile tileOn(){
return world.tileWorld(x, y);
}
@Override
public float getX(){
return x;
}
@Override
public float getY(){
return y;
}
}

View File

@@ -0,0 +1,129 @@
package mindustry.entities.def;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.world;
import static mindustry.entities.Puddles.maxLiquid;
@EntityDef(value = {Puddlec.class}, pooled = true)
@Component
abstract class PuddleComp implements Posc, DrawLayerFloorOverc, Puddlec{
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rect rect = new Rect();
private static final Rect rect2 = new Rect();
private static int seeds;
@Import float x, y;
float amount, lastRipple, accepting, updateTime;
int generation;
Tile tile;
Liquid liquid;
public float getFlammability(){
return liquid.flammability * amount;
}
@Override
public void update(){
//update code
float addSpeed = accepting > 0 ? 3f : 0f;
amount -= Time.delta() * (1f - liquid.viscosity) / (5f + addSpeed);
amount += accepting;
accepting = 0f;
if(amount >= maxLiquid / 1.5f && generation < maxGeneration){
float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Time.delta();
for(Point2 point : Geometry.d4){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if(other != null && other.block() == Blocks.air){
Puddles.deposit(other, tile, liquid, deposited, generation + 1);
amount -= deposited / 2f; //tweak to speed up/slow down Puddlec propagation
}
}
}
amount = Mathf.clamp(amount, 0, maxLiquid);
if(amount <= 0f){
remove();
}
//effects-only code
if(amount >= maxLiquid / 2f && updateTime <= 0f){
Units.nearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> {
if(unit.isGrounded()){
unit.hitbox(rect2);
if(rect.overlaps(rect2)){
unit.apply(liquid.effect, 60 * 2);
if(unit.vel().len() > 0.1){
Fx.ripple.at(unit.x(), unit.y(), liquid.color);
}
}
}
});
if(liquid.temperature > 0.7f && (tile.entity != null) && Mathf.chance(0.3 * Time.delta())){
Fires.create(tile);
}
updateTime = 20f;
}
updateTime -= Time.delta();
}
@Override
public void drawFloorOver(){
seeds = id();
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 20f;
Draw.color(tmp.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),
y + ey + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 5f);
seeds++;
});
Draw.color();
if(liquid.lightColor.a > 0.001f && f > 0){
Color color = liquid.lightColor;
float opacity = color.a * f;
Vars.renderer.lights.add(tile.drawx(), tile.drawy(), 30f * f, color, opacity * 0.8f);
}
}
@Override
public float clipSize(){
return 20;
}
@Override
public void remove(){
Puddles.remove(tile);
}
@Override
public void afterRead(){
Puddles.register(this);
}
}

View File

@@ -0,0 +1,17 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class RotComp implements Entityc{
float rotation;
void interpolate(){
Syncc sync = as(Syncc.class);
if(sync.interpolator().values.length > 0){
rotation = sync.interpolator().values[0];
}
}
}

View File

@@ -0,0 +1,12 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class ShielderComp implements Damagec, Teamc, Posc{
void absorb(){
}
}

View File

@@ -0,0 +1,14 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@EntityDef(value = {StandardEffectc.class, Childc.class}, pooled = true)
@Component
abstract class StandardEffectComp implements Effectc, DrawLayerEffectsc{
@Override
public void drawEffects(){
draw();
}
}

View File

@@ -1,49 +1,52 @@
package mindustry.entities.units;
package mindustry.entities.def;
import arc.struct.Bits;
import arc.struct.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.ctype.ContentType;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.ctype.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.environment.*;
import java.io.*;
import static mindustry.Vars.content;
/** Class for controlling status effects on an entity. */
public class Statuses implements Saveable{
private static final StatusEntry globalResult = new StatusEntry();
private static final Array<StatusEntry> removals = new Array<>();
@Component
abstract class StatusComp implements Posc, Flyingc{
private Array<StatusEntry> statuses = new Array<>();
private Bits applied = new Bits(content.getBy(ContentType.status).size);
private float speedMultiplier;
private float damageMultiplier;
private float armorMultiplier;
@ReadOnly transient float speedMultiplier, damageMultiplier, armorMultiplier;
public void handleApply(Unit unit, StatusEffect effect, float duration){
if(effect == StatusEffects.none || effect == null || unit.isImmune(effect)) return; //don't apply empty or immune effects
/** @return damage taken based on status armor multipliers */
float getShieldDamage(float amount){
return amount * Mathf.clamp(1f - armorMultiplier / 100f);
}
void apply(StatusEffect effect, float duration){
if(effect == StatusEffects.none || effect == null || isImmune(effect)) return; //don't apply empty or immune effects
if(statuses.size > 0){
//check for opposite effects
for(StatusEntry entry : statuses){
for(int i = 0; i < statuses.size; i ++){
StatusEntry entry = statuses.get(i);
//extend effect
if(entry.effect == effect){
entry.time = Math.max(entry.time, duration);
return;
}else if(entry.effect.reactsWith(effect)){ //find opposite
globalResult.effect = entry.effect;
entry.effect.getTransition(unit, effect, entry.time, duration, globalResult);
entry.time = globalResult.time;
StatusEntry.tmp.effect = entry.effect;
entry.effect.getTransition((Unitc)this, effect, entry.time, duration, StatusEntry.tmp);
entry.time = StatusEntry.tmp.time;
if(globalResult.effect != entry.effect){
entry.effect = globalResult.effect;
if(StatusEntry.tmp.effect != entry.effect){
entry.effect = StatusEntry.tmp.effect;
}
//stop looking when one is found
@@ -58,70 +61,71 @@ public class Statuses implements Saveable{
statuses.add(entry);
}
public Color getStatusColor(){
boolean isBoss(){
return hasEffect(StatusEffects.boss);
}
abstract boolean isImmune(StatusEffect effect);
Color statusColor(){
if(statuses.size == 0){
return Tmp.c1.set(Color.white);
}
float r = 0f, g = 0f, b = 0f;
float r = 1f, g = 1f, b = 1f, total = 0f;
for(StatusEntry entry : statuses){
r += entry.effect.color.r;
g += entry.effect.color.g;
b += entry.effect.color.b;
float intensity = entry.time < 10f ? entry.time/10f : 1f;
r += entry.effect.color.r * intensity;
g += entry.effect.color.g * intensity;
b += entry.effect.color.b * intensity;
total += intensity;
}
return Tmp.c1.set(r / statuses.size, g / statuses.size, b / statuses.size, 1f);
float count = statuses.size + total;
return Tmp.c1.set(r / count, g / count, b / count, 1f);
}
public void clear(){
statuses.clear();
}
@Override
public void update(){
Floor floor = floorOn();
if(isGrounded() && floor.status != null){
//apply effect
apply(floor.status, floor.statusDuration);
}
public void update(Unit unit){
applied.clear();
speedMultiplier = damageMultiplier = armorMultiplier = 1f;
if(statuses.size == 0) return;
if(statuses.isEmpty()) return;
removals.clear();
int index = 0;
while(index < statuses.size){
StatusEntry entry = statuses.get(index++);
for(StatusEntry entry : statuses){
entry.time = Math.max(entry.time - Time.delta(), 0);
applied.set(entry.effect.id);
if(entry.time <= 0){
Pools.free(entry);
removals.add(entry);
index --;
statuses.remove(index);
}else{
speedMultiplier *= entry.effect.speedMultiplier;
armorMultiplier *= entry.effect.armorMultiplier;
damageMultiplier *= entry.effect.damageMultiplier;
entry.effect.update(unit, entry.time);
//TODO ugly casting
entry.effect.update((Unitc)this, entry.time);
}
}
if(removals.size > 0){
statuses.removeAll(removals, true);
}
}
public float getSpeedMultiplier(){
return speedMultiplier;
}
public float getDamageMultiplier(){
return damageMultiplier;
}
public float getArmorMultiplier(){
return armorMultiplier;
}
public boolean hasEffect(StatusEffect effect){
boolean hasEffect(StatusEffect effect){
return applied.get(effect.id);
}
@Override
public void writeSave(DataOutput stream) throws IOException{
//TODO autogen io code
void writeSave(DataOutput stream) throws IOException{
stream.writeByte(statuses.size);
for(StatusEntry entry : statuses){
stream.writeByte(entry.effect.id);
@@ -129,8 +133,7 @@ public class Statuses implements Saveable{
}
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
void readSave(DataInput stream, byte version) throws IOException{
for(StatusEntry effect : statuses){
Pools.free(effect);
}
@@ -146,5 +149,4 @@ public class Statuses implements Saveable{
statuses.add(entry);
}
}
}

View File

@@ -0,0 +1,37 @@
package mindustry.entities.def;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.net.*;
@Component
abstract class SyncComp implements Posc{
@Import float x, y;
Interpolator interpolator = new Interpolator();
void setNet(float x, float y){
set(x, y);
//TODO change interpolator API
interpolator.target.set(x, y);
interpolator.last.set(x, y);
interpolator.pos.set(0, 0);
interpolator.updateSpacing = 16;
interpolator.lastUpdated = 0;
}
@Override
public void update(){
if(Vars.net.client() && !isLocal()){
interpolate();
}
}
void interpolate(){
interpolator.update();
x = interpolator.pos.x;
y = interpolator.pos.y;
}
}

View File

@@ -0,0 +1,23 @@
package mindustry.entities.def;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.state;
@Component
abstract class TeamComp implements Posc{
@Import float x, y;
Team team = Team.derelict;
public @Nullable Tilec closestCore(){
return state.teams.closestCore(x, y, team);
}
public @Nullable Tilec closestEnemyCore(){
return state.teams.closestEnemyCore(x, y, team);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
package mindustry.entities.def;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class TimedComp implements Entityc, Scaled{
float time, lifetime;
//called last so pooling and removal happens then.
@MethodPriority(100)
@Override
public void update(){
time = Math.min(time + Time.delta(), lifetime);
if(time >= lifetime){
remove();
}
}
@Override
public float fin(){
return time / lifetime;
}
}

View File

@@ -0,0 +1,13 @@
package mindustry.entities.def;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@Component
abstract class TimerComp{
Interval timer = new Interval(6);
public boolean timer(int index, float time){
return timer.get(index, time);
}
}

View File

@@ -0,0 +1,240 @@
package mindustry.entities.def;
import arc.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@Component
abstract class UnitComp implements Healthc, Velc, Statusc, Teamc, Itemsc, Hitboxc, Rotc, Massc, Unitc, Weaponsc, Drawc, Boundedc,
DrawLayerGroundc, DrawLayerFlyingc, DrawLayerGroundShadowsc, DrawLayerFlyingShadowsc, Syncc{
@Import float x, y, rotation, elevation;
private UnitController controller;
private UnitType type;
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
}
public void aimLook(Position pos){
aim(pos);
lookAt(pos);
}
public void aimLook(float x, float y){
aim(x, y);
lookAt(x, y);
}
@Override
public float clipSize(){
return type.region.getWidth() * 2f;
}
@Override
public int itemCapacity(){
return type.itemCapacity;
}
@Override
public float bounds(){
return hitSize() * 2f;
}
@Override
public void controller(UnitController controller){
this.controller = controller;
if(controller.unit() != this) controller.unit(this);
}
@Override
public UnitController controller(){
return controller;
}
@Override
public void set(UnitType def, UnitController controller){
type(type);
controller(controller);
}
@Override
public void collision(Hitboxc other, float x, float y){
if(!(other instanceof Unitc)) return;
Unitc unit = (Unitc)other;
if(isGrounded() != unit.isGrounded()) return;
float scale = 2f;
hitbox(Tmp.r1);
other.hitbox(Tmp.r2);
Vec2 v = Geometry.overlap(Tmp.r1, Tmp.r2, true);
float tm = mass() + unit.mass();
float s1 = mass() / tm, s2 = unit.mass() / tm;
move(v.x*s2/scale, v.y*s2/scale);
unit.move(-v.x*s1/scale, -v.y*s1/scale);
}
@Override
public void type(UnitType type){
this.type = type;
maxHealth(type.health);
heal();
drag(type.drag);
hitSize(type.hitsize);
controller(type.createController());
setupWeapons(type);
elevation = type.flying ? 1f : 0f;
}
@Override
public UnitType type(){
return type;
}
public void lookAt(float angle){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta());
}
public void lookAt(Position pos){
lookAt(angleTo(pos));
}
public void lookAt(float x, float y){
lookAt(angleTo(x, y));
}
public boolean isAI(){
return controller instanceof AIController;
}
@Override
public void afterRead(){
//set up type info after reading
type(this.type);
}
@Override
public void update(){
drag(type.drag * (isGrounded() ? (floorOn().dragMultiplier) : 1f));
//apply knockback based on spawns
if(team() != state.rules.waveTeam){
float relativeSize = state.rules.dropZoneRadius + bounds()/2f + 1f;
for(Tile spawn : spawner.getSpawns()){
if(withinDst(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()));
}
}
}
Tile tile = tileOn();
Floor floor = floorOn();
if(tile != null && isGrounded()){
//unit block update
if(tile.entity != null){
tile.entity.unitOn(this);
}
//kill when stuck in wall
if(tile.solid()){
kill();
}
//apply damage
if(floor.damageTaken > 0f){
damageContinuous(floor.damageTaken);
}
}
controller.update();
}
@Override
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
@Override
public void draw(){
type.drawEngine(this);
type.drawBody(this);
type.drawWeapons(this);
if(type.drawCell) type.drawCell(this);
if(type.drawItems) type.drawItems(this);
type.drawLight(this);
}
@Override
public void drawFlyingShadows(){
if(isFlying()) type.drawShadow(this);
}
@Override
public void drawGroundShadows(){
type.drawOcclusion(this);
}
@Override
public void drawFlying(){
if(isFlying()) draw();
}
@Override
public void drawGround(){
if(isGrounded()) draw();
}
@Override
public boolean isPlayer(){
return controller instanceof Playerc;
}
@Override
public void killed(){
float explosiveness = 2f + item().explosiveness * stack().amount;
float flammability = item().flammability * stack().amount;
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, Pal.darkFlame);
Effects.scorch(x, y, (int)(hitSize() / 5));
Fx.explosion.at(this);
Effects.shake(2f, 2f, this);
type.deathSound.at(this);
Events.fire(new UnitDestroyEvent(this));
if(explosiveness > 7f && isLocal()){
Events.fire(Trigger.suicideBomb);
}
}
@Override
public boolean canMine(Item item){
return type.drillTier >= item.hardness;
}
@Override
public float miningSpeed(){
return type.mineSpeed;
}
@Override
public boolean offloadImmediately(){
return false;
}
}

View File

@@ -0,0 +1,27 @@
package mindustry.entities.def;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class VelComp implements Posc{
@Import float x, y;
final Vec2 vel = new Vec2();
transient float drag = 0f;
//velocity needs to be called first, as it affects delta and lastPosition
@MethodPriority(-1)
@Override
public void update(){
move(vel.x * Time.delta(), vel.y * Time.delta());
vel.scl(1f - drag * Time.delta());
}
void move(float cx, float cy){
x += cx;
y += cy;
}
}

View File

@@ -0,0 +1,41 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.collisions;
//just a proof of concept
@Component
abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc{
@Import float x, y;
@Replace
@Override
public void move(float cx, float cy){
if(isGrounded()){
if(!EntityCollisions.waterSolid(tileX(), tileY())){
collisions.move(this, cx, cy, EntityCollisions::waterSolid);
}
}else{
x += cx;
y += cy;
}
}
@Replace
@Override
public boolean canDrown(){
return false;
}
@Replace
public float floorSpeedMultiplier(){
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
return on.isDeep() ? 1.3f : 1f;
}
}

View File

@@ -0,0 +1,153 @@
package mindustry.entities.def;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc{
@Import float x, y, rotation;
/** minimum cursor distance from player, fixes 'cross-eyed' shooting */
static final float minAimDst = 20f;
/** temporary weapon sequence number */
static int sequenceNum = 0;
/** weapon mount array, never null */
@ReadOnly WeaponMount[] mounts = {};
@ReadOnly transient float range, aimX, aimY;
@ReadOnly transient boolean isRotate, isShooting;
boolean inRange(Position other){
return within(other, range);
}
void setupWeapons(UnitType def){
mounts = new WeaponMount[def.weapons.size];
range = 0f;
for(int i = 0; i < mounts.length; i++){
mounts[i] = new WeaponMount(def.weapons.get(i));
range = Math.max(range, def.weapons.get(i).bullet.range());
}
}
void controlWeapons(boolean rotate, boolean shoot){
for(WeaponMount mount : mounts){
mount.rotate = rotate;
mount.shoot = shoot;
}
isRotate = rotate;
isShooting = shoot;
}
void aim(Position pos){
aim(pos.getX(), pos.getY());
}
/** Aim at something. This will make all mounts point at it. */
void aim(float x, float y){
Tmp.v1.set(x, y).sub(this.x, this.y);
if(Tmp.v1.len() < minAimDst) Tmp.v1.setLength(minAimDst);
x = Tmp.v1.x + this.x;
y = Tmp.v1.y + this.y;
for(WeaponMount mount : mounts){
mount.aimX = x;
mount.aimY = y;
}
aimX = x;
aimY = y;
}
/** Update shooting and rotation for this unit. */
@Override
public void update(){
for(WeaponMount mount : mounts){
Weapon weapon = mount.weapon;
mount.reload = Math.max(mount.reload - Time.delta(), 0);
//rotate if applicable
if(weapon.rotate && (mount.rotate || mount.shoot)){
float axisXOffset = weapon.mirror ? 0f : weapon.x;
float axisX = this.x + Angles.trnsx(rotation, axisXOffset, weapon.y),
axisY = this.y + Angles.trnsy(rotation, axisXOffset, weapon.y);
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation();
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta());
}else{
mount.rotation = this.rotation;
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
}
if(mount.shoot){
float rotation = this.rotation - 90;
//shoot if applicable
if(mount.reload <= 0.0001f && Angles.within(mount.rotation, mount.targetRotation, 1.5f)){
for(int i : (weapon.mirror && !weapon.alternate ? Mathf.signs : Mathf.one)){
i *= Mathf.sign(weapon.flipped) * Mathf.sign(mount.side);
//m a t h
float weaponRotation = rotation + (weapon.rotate ? mount.rotation : 0);
float mountX = this.x + Angles.trnsx(rotation, weapon.x * i, weapon.y),
mountY = this.y + Angles.trnsy(rotation, weapon.x * i, weapon.y);
float shootX = mountX + Angles.trnsx(weaponRotation, weapon.shootX * i, weapon.shootY),
shootY = mountY + Angles.trnsy(weaponRotation, weapon.shootX * i, weapon.shootY);
float shootAngle = weapon.rotate ? weaponRotation + 90 : Angles.angle(shootX, shootY, mount.aimX, mount.aimY) + (this.rotation - angleTo(mount.aimX, mount.aimY));
shoot(weapon, shootX, shootY, shootAngle, -i);
}
mount.side = !mount.side;
mount.reload = weapon.reload;
}
}
}
}
private void shoot(Weapon weapon, float x, float y, float rotation, int side){
float baseX = this.x, baseY = this.y;
weapon.shootSound.at(x, y, Mathf.random(0.8f, 1.0f));
sequenceNum = 0;
if(weapon.shotDelay > 0.01f){
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> {
Time.run(sequenceNum * weapon.shotDelay, () -> bullet(weapon, x + this.x - baseX, y + this.y - baseY, f + Mathf.range(weapon.inaccuracy)));
sequenceNum++;
});
}else{
Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> bullet(weapon, x, y, f + Mathf.range(weapon.inaccuracy)));
}
BulletType ammo = weapon.bullet;
Tmp.v1.trns(rotation + 180f, ammo.recoil);
if(this instanceof Velc){
//TODO apply force?
((Velc)this).vel().add(Tmp.v1);
}
Tmp.v1.trns(rotation, 3f);
boolean parentize = ammo.keepVelocity;
Effects.shake(weapon.shake, weapon.shake, x, y);
weapon.ejectEffect.at(x, y, rotation * side);
ammo.shootEffect.at(x + Tmp.v1.x, y + Tmp.v1.y, rotation, parentize ? this : null);
ammo.smokeEffect.at(x + Tmp.v1.x, y + Tmp.v1.y, rotation, parentize ? this : null);
}
private void bullet(Weapon weapon, float x, float y, float angle){
Tmp.v1.trns(angle, 3f);
weapon.bullet.create(this, team(), x + Tmp.v1.x, y + Tmp.v1.y, angle, (1f - weapon.velocityRnd) + Mathf.random(weapon.velocityRnd));
}
}

View File

@@ -1,36 +0,0 @@
package mindustry.entities.effect;
import arc.graphics.g2d.Draw;
import arc.math.Mathf;
import mindustry.entities.EntityGroup;
import mindustry.entities.type.TimedEntity;
import mindustry.entities.traits.BelowLiquidTrait;
import mindustry.entities.traits.DrawTrait;
import mindustry.graphics.Pal;
import static mindustry.Vars.groundEffectGroup;
/**
* Class for creating block rubble on the ground.
*/
public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait{
@Override
public float lifetime(){
return 3600;
}
@Override
public void draw(){
Draw.color(Pal.rubble.r, Pal.rubble.g, Pal.rubble.b, 1f - Mathf.curve(fin(), 0.98f));
drawDecal();
Draw.color();
}
@Override
public EntityGroup targetGroup(){
return groundEffectGroup;
}
abstract void drawDecal();
}

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