This commit is contained in:
Anuken
2020-01-06 17:20:16 -05:00
163 changed files with 2568 additions and 1713 deletions

View File

@@ -21,6 +21,7 @@ import mindustry.gen.*;
import mindustry.input.*;
import mindustry.maps.*;
import mindustry.mod.*;
import mindustry.net.*;
import mindustry.net.Net;
import mindustry.type.Weather.*;
import mindustry.world.blocks.defense.ForceProjector.*;
@@ -54,18 +55,16 @@ public class Vars implements Loadable{
public static final String crashReportURL = "http://192.99.169.18/report";
/** URL the links to the wiki's modding guide.*/
public static final String modGuideURL = "https://mindustrygame.github.io/wiki/modding/";
/** URL to the JSON file containing all the global, public servers. */
/** URL to the JSON file containing all the global, public servers. Not queried in BE. */
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers.json";
/** URL to the JSON file containing all the BE servers. Only queried in BE. */
public static final String serverJsonBeURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_be.json";
/** URL the links to the wiki's modding guide.*/
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?template=bug_report.md";
/** list of built-in servers.*/
public static final Array<String> defaultServers = Array.with();
/** maximum distance between mine and core that supports automatic transferring */
public static final float mineTransferRange = 220f;
/** team of the player by default */
public static final Team defaultTeam = Team.sharded;
/** team of the enemy in waves/sectors */
public static final Team waveTeam = Team.crux;
/** whether to enable editing of units in the editor */
public static final boolean enableUnitEditing = false;
/** max chat message length */
@@ -129,9 +128,9 @@ public class Vars implements Loadable{
public static Fi dataDirectory;
/** data subdirectory used for screenshots */
public static Fi screenshotDirectory;
/** data subdirectory used for custom mmaps */
/** data subdirectory used for custom maps */
public static Fi customMapDirectory;
/** data subdirectory used for custom mmaps */
/** data subdirectory used for custom map previews */
public static Fi mapPreviewDirectory;
/** tmp subdirectory for map conversion */
public static Fi tmpDirectory;
@@ -141,6 +140,8 @@ public class Vars implements Loadable{
public static Fi modDirectory;
/** data subdirectory used for schematics */
public static Fi schematicDirectory;
/** data subdirectory used for bleeding edge build versions */
public static Fi bebuildDirectory;
/** map file extension */
public static final String mapExtension = "msav";
/** save file extension */
@@ -162,6 +163,7 @@ public class Vars implements Loadable{
public static Platform platform = new Platform(){};
public static Mods mods;
public static Schematics schematics = new Schematics();
public static BeControl becontrol;
public static World world;
public static Maps maps;
@@ -186,7 +188,7 @@ public class Vars implements Loadable{
public static EntityGroup<Puddle> puddleGroup;
public static EntityGroup<Fire> fireGroup;
public static EntityGroup<WeatherEntity> weatherGroup;
public static EntityGroup<BaseUnit>[] unitGroups;
public static EntityGroup<BaseUnit> unitGroup;
public static Player player;
@@ -226,6 +228,7 @@ public class Vars implements Loadable{
defaultWaves = new DefaultWaves();
collisions = new EntityCollisions();
world = new World();
becontrol = new BeControl();
maps = new Maps();
spawner = new WaveSpawner();
@@ -241,12 +244,8 @@ public class Vars implements Loadable{
puddleGroup = entities.add(Puddle.class).enableMapping();
shieldGroup = entities.add(ShieldEntity.class, false);
fireGroup = entities.add(Fire.class).enableMapping();
unitGroup = entities.add(BaseUnit.class).enableMapping();
weatherGroup = entities.add(WeatherEntity.class);
unitGroups = new EntityGroup[Team.all.length];
for(Team team : Team.all){
unitGroups[team.ordinal()] = entities.add(BaseUnit.class).enableMapping();
}
for(EntityGroup<?> group : entities.all()){
group.setRemoveListener(entity -> {
@@ -271,6 +270,7 @@ public class Vars implements Loadable{
tmpDirectory = dataDirectory.child("tmp/");
modDirectory = dataDirectory.child("mods/");
schematicDirectory = dataDirectory.child("schematics/");
bebuildDirectory = dataDirectory.child("be_builds/");
modDirectory.mkdirs();

View File

@@ -1,15 +1,15 @@
package mindustry.ai;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -28,15 +28,17 @@ public class BlockIndexer{
private final ObjectSet<Item> itemSet = new ObjectSet<>();
/** Stores all ore quadtrants on the map. */
private ObjectMap<Item, ObjectSet<Tile>> ores = new ObjectMap<>();
/** Tags all quadrants. */
/** 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 ObjectSet<Tile>[] damagedTiles = new ObjectSet[Team.all().length];
/**All ores available on this map.*/
private ObjectSet<Item> allOres = new ObjectSet<>();
/**Stores teams that are present here as tiles.*/
private ObjectSet<Team> activeTeams = new ObjectSet<>();
/** Maps teams to a map of flagged tiles by type. */
private ObjectSet<Tile>[][] flagMap = new ObjectSet[Team.all.length][BlockFlag.all.length];
private ObjectSet<Tile>[][] flagMap = new ObjectSet[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. */
@@ -59,8 +61,8 @@ 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 ObjectSet[Team.all().length];
flagMap = new ObjectSet[Team.all().length][BlockFlag.all.length];
for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
@@ -73,10 +75,7 @@ public class BlockIndexer{
ores = null;
//create bitset for each team type that contains each quadrant
structQuadrants = new GridBits[Team.all.length];
for(int i = 0; i < Team.all.length; i++){
structQuadrants[i] = new GridBits(Mathf.ceil(world.width() / (float)quadrantSize), Mathf.ceil(world.height() / (float)quadrantSize));
}
structQuadrants = new GridBits[Team.all().length];
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
@@ -103,7 +102,32 @@ public class BlockIndexer{
}
private ObjectSet<Tile>[] getFlagged(Team team){
return flagMap[team.ordinal()];
return flagMap[team.id];
}
private GridBits structQuadrant(Team t){
int id = Pack.u(t.id);
if(structQuadrants[id] == null){
structQuadrants[id] = new GridBits(Mathf.ceil(world.width() / (float)quadrantSize), Mathf.ceil(world.height() / (float)quadrantSize));
}
return structQuadrants[id];
}
/** Updates all the structure quadrants for a newly activated team. */
public void updateTeamIndex(Team team){
if(structQuadrants == null) return;
//go through every tile... ouch
for(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);
}
}
}
}
/** @return whether this item is present on this map.*/
@@ -115,11 +139,11 @@ public class BlockIndexer{
public ObjectSet<Tile> getDamaged(Team team){
returnArray.clear();
if(damagedTiles[team.ordinal()] == null){
damagedTiles[team.ordinal()] = new ObjectSet<>();
if(damagedTiles[team.id] == null){
damagedTiles[team.id] = new ObjectSet<>();
}
ObjectSet<Tile> set = damagedTiles[team.ordinal()];
ObjectSet<Tile> set = damagedTiles[team.id];
for(Tile tile : set){
if((tile.entity == null || tile.entity.getTeam() != team || !tile.entity.damaged()) || tile.block() instanceof BuildBlock){
returnArray.add(tile);
@@ -135,13 +159,13 @@ public class BlockIndexer{
/** Get all allied blocks with a flag. */
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
return flagMap[team.ordinal()][type.ordinal()];
return flagMap[team.id][type.ordinal()];
}
/** Get all enemy blocks with a flag. */
public Array<Tile> getEnemy(Team team, BlockFlag type){
returnArray.clear();
for(Team enemy : state.teams.enemiesOf(team)){
for(Team enemy : team.enemies()){
if(state.teams.isActive(enemy)){
ObjectSet<Tile> set = getFlagged(enemy)[type.ordinal()];
if(set != null){
@@ -155,14 +179,27 @@ public class BlockIndexer{
}
public void notifyTileDamaged(TileEntity entity){
if(damagedTiles[entity.getTeam().ordinal()] == null){
damagedTiles[entity.getTeam().ordinal()] = new ObjectSet<>();
if(damagedTiles[(int)entity.getTeam().id] == null){
damagedTiles[(int)entity.getTeam().id] = new ObjectSet<>();
}
ObjectSet<Tile> set = damagedTiles[entity.getTeam().ordinal()];
ObjectSet<Tile> set = damagedTiles[(int)entity.getTeam().id];
set.add(entity.tile);
}
public TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
for(Team enemy : activeTeams){
if(!team.isEnemy(enemy)) continue;
TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
return entity;
}
}
return null;
}
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred){
return findTile(team, x, y, range, pred, false);
}
@@ -188,7 +225,7 @@ public class BlockIndexer{
TileEntity e = other.entity;
float ndst = Mathf.dst(x, y, e.x, e.y);
if(ndst < range && (closest == null || ndst < dst || (usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
if(ndst < range && (closest == null || ndst < dst || (usePriority && closest.block.priority.ordinal() <= e.block.priority.ordinal()))){
dst = ndst;
closest = e;
}
@@ -242,6 +279,7 @@ public class BlockIndexer{
}
typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.getTeam()));
}
activeTeams.add(tile.getTeam());
if(ores == null) return;
@@ -280,26 +318,25 @@ public class BlockIndexer{
//this quadrant is now 'dirty', re-scan the whole thing
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
int index = quadrantX + quadrantY * quadWidth();
for(Team team : Team.all){
TeamData data = state.teams.get(team);
for(Team team : activeTeams){
GridBits bits = structQuadrant(team);
//fast-set this quadrant to 'occupied' if the tile just placed is already of this team
if(tile.getTeam() == data.team && tile.entity != null && tile.block().targetable){
structQuadrants[data.team.ordinal()].set(quadrantX, quadrantY);
if(tile.getTeam() == team && tile.entity != null && tile.block().targetable){
bits.set(quadrantX, quadrantY);
continue; //no need to process futher
}
structQuadrants[data.team.ordinal()].set(quadrantX, quadrantY, false);
bits.set(quadrantX, quadrantY, false);
outer:
for(int x = quadrantX * quadrantSize; x < world.width() && x < (quadrantX + 1) * quadrantSize; x++){
for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){
Tile result = world.ltile(x, y);
//when a targetable block is found, mark this quadrant as occupied and stop searching
if(result.entity != null && result.getTeam() == data.team){
structQuadrants[data.team.ordinal()].set(quadrantX, quadrantY);
if(result.entity != null && result.getTeam() == team){
bits.set(quadrantX, quadrantY);
break outer;
}
}
@@ -308,7 +345,7 @@ public class BlockIndexer{
}
private boolean getQuad(Team team, int quadrantX, int quadrantY){
return structQuadrants[team.ordinal()].get(quadrantX, quadrantY);
return structQuadrant(team).get(quadrantX, quadrantY);
}
private int quadWidth(){

View File

@@ -27,9 +27,9 @@ public class Pathfinder implements Runnable{
/** unordered array of path data for iteration only. DO NOT iterate ot access this in the main thread.*/
private Array<PathData> list = new Array<>();
/** Maps teams + flags to a valid path to get to that flag for that team. */
private PathData[][] pathMap = new PathData[Team.all.length][PathTarget.all.length];
private PathData[][] pathMap = new PathData[Team.all().length][PathTarget.all.length];
/** Grid map of created path data that should not be queued again. */
private GridBits created = new GridBits(Team.all.length, PathTarget.all.length);
private GridBits created = new GridBits(Team.all().length, PathTarget.all.length);
/** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue();
/** current pathfinding thread */
@@ -42,8 +42,8 @@ public class Pathfinder implements Runnable{
//reset and update internal tile array
tiles = new int[world.width()][world.height()];
pathMap = new PathData[Team.all.length][PathTarget.all.length];
created = new GridBits(Team.all.length, PathTarget.all.length);
pathMap = new PathData[Team.all().length][PathTarget.all.length];
created = new GridBits(Team.all().length, PathTarget.all.length);
list = new Array<>();
for(int x = 0; x < world.width(); x++){
@@ -53,7 +53,7 @@ public class Pathfinder implements Runnable{
}
//special preset which may help speed things up; this is optional
preloadPath(waveTeam, PathTarget.enemyCores);
preloadPath(state.rules.waveTeam, PathTarget.enemyCores);
start();
});
@@ -84,8 +84,8 @@ public class Pathfinder implements Runnable{
}
public int debugValue(Team team, int x, int y){
if(pathMap[team.ordinal()][PathTarget.enemyCores.ordinal()] == null) return 0;
return pathMap[team.ordinal()][PathTarget.enemyCores.ordinal()].weights[x][y];
if(pathMap[team.id][PathTarget.enemyCores.ordinal()] == null) return 0;
return pathMap[team.id][PathTarget.enemyCores.ordinal()].weights[x][y];
}
/** Update a tile in the internal pathfinding grid. Causes a complete pathfinding reclaculation. */
@@ -139,7 +139,7 @@ public class Pathfinder implements Runnable{
//stop looping when interrupted externally
return;
}
}catch(Exception e){
}catch(Throwable e){
e.printStackTrace();
}
}
@@ -149,12 +149,12 @@ public class Pathfinder implements Runnable{
public Tile getTargetTile(Tile tile, Team team, PathTarget target){
if(tile == null) return null;
PathData data = pathMap[team.ordinal()][target.ordinal()];
PathData data = pathMap[team.id][target.ordinal()];
if(data == null){
//if this combination is not found, create it on request
if(!created.get(team.ordinal(), target.ordinal())){
created.set(team.ordinal(), target.ordinal());
if(!created.get(team.id, target.ordinal())){
created.set(team.id, target.ordinal());
//grab targets since this is run on main thread
IntArray targets = target.getTargets(team, new IntArray());
queue.post(() -> createPath(team, target, targets));
@@ -188,7 +188,7 @@ public class Pathfinder implements Runnable{
/** @return whether a tile can be passed through by this team. Pathfinding thread only.*/
private boolean passable(int x, int y, Team team){
int tile = tiles[x][y];
return PathTile.passable(tile) || (PathTile.team(tile) != team.ordinal() && PathTile.team(tile) != Team.derelict.ordinal());
return PathTile.passable(tile) || (PathTile.team(tile) != team.id && PathTile.team(tile) != (int)Team.derelict.id);
}
/**
@@ -238,7 +238,7 @@ public class Pathfinder implements Runnable{
PathData path = new PathData(team, target, world.width(), world.height());
list.add(path);
pathMap[team.ordinal()][target.ordinal()] = path;
pathMap[team.id][target.ordinal()] = path;
//grab targets from passed array
synchronized(path.targets){
@@ -303,7 +303,7 @@ public class Pathfinder implements Runnable{
}
//spawn points are also enemies.
if(state.rules.waves && team == defaultTeam){
if(state.rules.waves && team == state.rules.defaultTeam){
for(Tile other : spawner.getGroundSpawns()){
out.add(other.pos());
}

View File

@@ -11,7 +11,7 @@ import mindustry.content.Blocks;
import mindustry.content.Fx;
import mindustry.entities.Damage;
import mindustry.entities.Effects;
import mindustry.entities.type.BaseUnit;
import mindustry.entities.type.*;
import mindustry.game.EventType.WorldLoadEvent;
import mindustry.game.SpawnGroup;
import mindustry.world.Tile;
@@ -53,7 +53,7 @@ public class WaveSpawner{
eachFlyerSpawn((spawnX, spawnY) -> {
for(int i = 0; i < spawned; i++){
BaseUnit unit = group.createUnit(waveTeam);
BaseUnit unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
unit.add();
}
@@ -66,7 +66,7 @@ public class WaveSpawner{
for(int i = 0; i < spawned; i++){
Tmp.v1.rnd(spread);
BaseUnit unit = group.createUnit(waveTeam);
BaseUnit 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));
@@ -78,7 +78,7 @@ public class WaveSpawner{
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
if(doShockwave){
Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawnX, spawnY, state.rules.dropZoneRadius));
Time.run(40f, () -> Damage.damage(waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
}
});
@@ -90,11 +90,11 @@ public class WaveSpawner{
cons.accept(spawn.worldx(), spawn.worldy(), true);
}
if(state.rules.attackMode && state.teams.isActive(waveTeam) && !state.teams.get(defaultTeam).cores.isEmpty()){
Tile firstCore = state.teams.get(defaultTeam).cores.first();
for(Tile core : state.teams.get(waveTeam).cores){
Tmp.v1.set(firstCore).sub(core.worldx(), core.worldy()).limit(coreMargin + core.block().size*tilesize);
cons.accept(core.worldx() + Tmp.v1.x, core.worldy() + Tmp.v1.y, false);
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);
}
}
}
@@ -107,9 +107,9 @@ public class WaveSpawner{
cons.get(spawnX, spawnY);
}
if(state.rules.attackMode && state.teams.isActive(waveTeam)){
for(Tile core : state.teams.get(waveTeam).cores){
cons.get(core.worldx(), core.worldy());
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);
}
}
}

View File

@@ -909,10 +909,11 @@ public class Blocks implements ContentList{
}};
junction = new Junction("junction"){{
requirements(Category.distribution, ItemStack.with(Items.copper, 1), true);
requirements(Category.distribution, ItemStack.with(Items.copper, 2), true);
speed = 26;
capacity = 12;
health = 30;
buildCostMultiplier = 6f;
}};
itemBridge = new BufferedItemBridge("bridge-conveyor"){{
@@ -932,16 +933,18 @@ public class Blocks implements ContentList{
sorter = new Sorter("sorter"){{
requirements(Category.distribution, ItemStack.with(Items.lead, 2, Items.copper, 2));
buildCostMultiplier = 3f;
}};
invertedSorter = new Sorter("inverted-sorter"){{
requirements(Category.distribution, ItemStack.with(Items.lead, 2, Items.copper, 2));
buildCostMultiplier = 3f;
invert = true;
}};
router = new Router("router"){{
requirements(Category.distribution, ItemStack.with(Items.copper, 3));
buildCostMultiplier = 2f;
}};
distributor = new Router("distributor"){{
@@ -951,6 +954,7 @@ public class Blocks implements ContentList{
overflowGate = new OverflowGate("overflow-gate"){{
requirements(Category.distribution, ItemStack.with(Items.lead, 2, Items.copper, 4));
buildCostMultiplier = 3f;
}};
massDriver = new MassDriver("mass-driver"){{
@@ -1245,7 +1249,7 @@ public class Blocks implements ContentList{
//region storage
coreShard = new CoreBlock("core-shard"){{
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 4000));
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with());
alwaysUnlocked = true;
health = 1100;
@@ -1254,7 +1258,7 @@ public class Blocks implements ContentList{
}};
coreFoundation = new CoreBlock("core-foundation"){{
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 400, Items.silicon, 3000));
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with());
health = 2000;
itemCapacity = 9000;
@@ -1262,7 +1266,7 @@ public class Blocks implements ContentList{
}};
coreNucleus = new CoreBlock("core-nucleus"){{
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with(Items.titanium, 4000, Items.silicon, 2000, Items.surgealloy, 3000));
requirements(Category.effect, BuildVisibility.debugOnly, ItemStack.with());
health = 4000;
itemCapacity = 13000;
@@ -1399,15 +1403,6 @@ public class Blocks implements ContentList{
range = 110f;
health = 250 * size * size;
shootSound = Sounds.splash;
drawer = (tile, entity) -> {
Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90);
Draw.color(entity.liquids.current().color);
Draw.alpha(entity.liquids.total() / liquidCapacity);
Draw.rect(name + "-liquid", tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90);
Draw.color();
};
}};
lancer = new ChargeTurret("lancer"){{

View File

@@ -1079,6 +1079,7 @@ public class Fx implements ContentList{
healBlockFull = new Effect(20, e -> {
Draw.color(e.color);
Draw.alpha(e.fout());
Fill.square(e.x, e.y, e.rotation * tilesize / 2f);
});
overdriveBlockFull = new Effect(60, e -> {

View File

@@ -3,8 +3,6 @@ package mindustry.content;
import mindustry.ctype.*;
import mindustry.game.*;
import java.io.*;
public class Loadouts implements ContentList{
public static Schematic
basicShard,
@@ -14,13 +12,9 @@ public class Loadouts implements ContentList{
@Override
public void load(){
try{
basicShard = Schematics.readBase64("bXNjaAB4nD2K2wqAIBiD5ymibnoRn6YnEP1BwUMoBL19FuJ2sbFvUFgYZDaJsLeQrkinN9UJHImsNzlYE7WrIUastuSbnlKx2VJJt+8IQGGKdfO/8J5yrGJSMegLg+YUIA==");
advancedShard = Schematics.readBase64("bXNjaAB4nD2LjQqAIAyET7OMIOhFfJqeYMxBgSkYCL199gu33fFtB4tOwUTaBCP5QpHFzwtl32DahBeKK1NwPq8hoOcUixwpY+CUxe3XIwBbB/pa6tadVCUP02hgHvp5vZq/0b7pBHPYFOQ=");
basicFoundation = Schematics.readBase64("bXNjaAB4nD1OSQ6DMBBzFhVu8BG+0X8MQyoiJTNSukj8nlCi2Adbtg/GA4OBF8oB00rvyE/9ykafqOIw58A7SWRKy1ZiShhZ5RcOLZhYS1hefQ1gRIeptH9jq/qW2lvc1d2tgWsOfVX/tOwE86AYBA==");
basicNucleus = Schematics.readBase64("bXNjaAB4nD2MUQqAIBBEJy0s6qOLdJXuYNtCgikYBd2+LNmdj308hkGHtkId7M4YFns4mk/yfB4a48602eDI+mlNznu0FMPFd0wYKCaewl8F0EOueqM+yKSLVfJrNKWnSw/FZGzEGXFG9sy/px4gEBW1");
}catch(IOException e){
throw new RuntimeException(e);
}
basicShard = Schematics.readBase64("bXNjaAB4nD2K2wqAIBiD5ymibnoRn6YnEP1BwUMoBL19FuJ2sbFvUFgYZDaJsLeQrkinN9UJHImsNzlYE7WrIUastuSbnlKx2VJJt+8IQGGKdfO/8J5yrGJSMegLg+YUIA==");
advancedShard = Schematics.readBase64("bXNjaAB4nD2LjQqAIAyET7OMIOhFfJqeYMxBgSkYCL199gu33fFtB4tOwUTaBCP5QpHFzwtl32DahBeKK1NwPq8hoOcUixwpY+CUxe3XIwBbB/pa6tadVCUP02hgHvp5vZq/0b7pBHPYFOQ=");
basicFoundation = Schematics.readBase64("bXNjaAB4nD1OSQ6DMBBzFhVu8BG+0X8MQyoiJTNSukj8nlCi2Adbtg/GA4OBF8oB00rvyE/9ykafqOIw58A7SWRKy1ZiShhZ5RcOLZhYS1hefQ1gRIeptH9jq/qW2lvc1d2tgWsOfVX/tOwE86AYBA==");
basicNucleus = Schematics.readBase64("bXNjaAB4nD2MUQqAIBBEJy0s6qOLdJXuYNtCgikYBd2+LNmdj308hkGHtkId7M4YFns4mk/yfB4a48602eDI+mlNznu0FMPFd0wYKCaewl8F0EOueqM+yKSLVfJrNKWnSw/FZGzEGXFG9sy/px4gEBW1");
}
}

View File

@@ -6,8 +6,7 @@ import mindustry.entities.Effects;
import mindustry.ctype.ContentList;
import mindustry.game.EventType.*;
import mindustry.type.StatusEffect;
import static mindustry.Vars.waveTeam;
import static mindustry.Vars.*;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, wet, melting, tarred, overdrive, shielded, shocked, corroded, boss;
@@ -48,7 +47,7 @@ public class StatusEffects implements ContentList{
init(() -> {
trans(shocked, ((unit, time, newTime, result) -> {
unit.damage(20f);
if(unit.getTeam() == waveTeam){
if(unit.getTeam() == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
result.set(this, time);

View File

@@ -63,7 +63,7 @@ public class Control implements ApplicationListener, Loadable{
});
Events.on(PlayEvent.class, event -> {
player.setTeam(state.rules.pvp ? netServer.assignTeam(player, playerGroup.all()) : defaultTeam);
player.setTeam(netServer.assignTeam(player, playerGroup.all()));
player.setDead(true);
player.add();
@@ -256,9 +256,9 @@ public class Control implements ApplicationListener, Loadable{
world.loadGenerator(zone.generator);
zone.rules.get(state.rules);
state.rules.zone = zone;
for(Tile core : state.teams.get(defaultTeam).cores){
for(TileEntity core : state.teams.playerCores()){
for(ItemStack stack : zone.getStartingItems()){
core.entity.items.add(stack.item, stack.amount);
core.items.add(stack.item, stack.amount);
}
}
state.set(State.playing);
@@ -294,8 +294,8 @@ 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() == defaultTeam && !(tile.block() instanceof CoreBlock)){
world.removeBlock(tile);
if(tile != null && tile.getTeam() == state.rules.defaultTeam && !(tile.block() instanceof CoreBlock)){
tile.remove();
}
});
@@ -305,13 +305,13 @@ public class Control implements ApplicationListener, Loadable{
zone.rules.get(state.rules);
state.rules.zone = zone;
for(Tile core : state.teams.get(defaultTeam).cores){
for(TileEntity core : state.teams.playerCores()){
for(ItemStack stack : zone.getStartingItems()){
core.entity.items.add(stack.item, stack.amount);
core.items.add(stack.item, stack.amount);
}
}
Tile core = state.teams.get(defaultTeam).cores.first();
core.entity.items.clear();
TileEntity core = state.teams.playerCores().first();
core.items.clear();
logic.play();
state.rules.waveTimer = false;
@@ -434,9 +434,9 @@ public class Control implements ApplicationListener, Loadable{
input.update();
if(world.isZone()){
for(Tile tile : state.teams.get(player.getTeam()).cores){
for(TileEntity tile : state.teams.cores(player.getTeam())){
for(Item item : content.items()){
if(tile.entity != null && tile.entity.items.has(item)){
if(tile.items.has(item)){
data.unlockContent(item);
}
}
@@ -456,7 +456,7 @@ public class Control implements ApplicationListener, Loadable{
state.set(state.is(State.playing) ? State.paused : State.playing);
}
if(Core.input.keyTap(Binding.menu) && !ui.restart.isShown()){
if(Core.input.keyTap(Binding.menu) && !ui.restart.isShown() && !ui.minimapfrag.shown()){
if(ui.chatfrag.shown()){
ui.chatfrag.hide();
}else if(!ui.paused.isShown() && !scene.hasDialog()){

View File

@@ -2,7 +2,6 @@ package mindustry.core;
import arc.*;
import mindustry.entities.type.*;
import mindustry.entities.type.base.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -26,12 +25,8 @@ public class GameState{
/** Current game state. */
private State state = State.menu;
public int enemies(){
return net.client() ? enemies : unitGroups[waveTeam.ordinal()].count(b -> !(b instanceof BaseDrone));
}
public BaseUnit boss(){
return unitGroups[waveTeam.ordinal()].find(BaseUnit::isBoss);
return unitGroup.find(u -> u.isBoss() && u.getTeam() == rules.waveTeam);
}
public void set(State astate){

View File

@@ -1,8 +1,8 @@
package mindustry.core;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
@@ -47,8 +47,8 @@ public class Logic implements ApplicationListener{
//blocks that get broken are appended to the team's broken block queue
Tile tile = event.tile;
Block block = tile.block();
//skip null entities or nukes, for obvious reasons
if(tile.entity == null || tile.block() instanceof NuclearReactor) return;
//skip null entities or nukes, for obvious reasons; also skip client since they can't modify these requests
if(tile.entity == null || tile.block() instanceof NuclearReactor || net.client()) return;
if(block instanceof BuildBlock){
@@ -107,9 +107,9 @@ public class Logic implements ApplicationListener{
//add starting items
if(!world.isZone()){
for(Team team : Team.all){
if(!state.teams.get(team).cores.isEmpty()){
TileEntity entity = state.teams.get(team).cores.first().entity;
for(TeamData team : state.teams.getActive()){
if(team.hasCore()){
TileEntity entity = team.core();
entity.items.clear();
for(ItemStack stack : state.rules.loadout){
entity.items.add(stack.item, stack.amount);
@@ -143,23 +143,23 @@ public class Logic implements ApplicationListener{
}
private void checkGameOver(){
if(!state.rules.attackMode && state.teams.get(defaultTeam).cores.size == 0 && !state.gameOver){
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
state.gameOver = true;
Events.fire(new GameOverEvent(waveTeam));
Events.fire(new GameOverEvent(state.rules.waveTeam));
}else if(state.rules.attackMode){
Team alive = null;
for(Team team : Team.all){
if(state.teams.get(team).cores.size > 0){
for(TeamData team : state.teams.getActive()){
if(team.hasCore()){
if(alive != null){
return;
}
alive = team;
alive = team.team;
}
}
if(alive != null && !state.gameOver){
if(world.isZone() && alive == defaultTeam){
if(world.isZone() && alive == state.rules.defaultTeam){
//in attack maps, a victorious game over is equivalent to a launch
Call.launchZone();
}else{
@@ -176,7 +176,7 @@ public class Logic implements ApplicationListener{
ui.hudfrag.showLaunch();
}
for(Tile tile : state.teams.get(defaultTeam).cores){
for(TileEntity tile : state.teams.playerCores()){
Effects.effect(Fx.launch, tile);
}
@@ -185,19 +185,18 @@ public class Logic implements ApplicationListener{
}
Time.runTask(30f, () -> {
for(Tile tile : state.teams.get(defaultTeam).cores){
for(TileEntity entity : state.teams.playerCores()){
for(Item item : content.items()){
if(tile == null || tile.entity == null || tile.entity.items == null) continue;
data.addItem(item, tile.entity.items.get(item));
Events.fire(new LaunchItemEvent(item, tile.entity.items.get(item)));
data.addItem(item, entity.items.get(item));
Events.fire(new LaunchItemEvent(item, entity.items.get(item)));
}
world.removeBlock(tile);
entity.tile.remove();
}
state.launched = true;
state.gameOver = true;
Events.fire(new LaunchEvent());
//manually fire game over event now
Events.fire(new GameOverEvent(defaultTeam));
Events.fire(new GameOverEvent(state.rules.defaultTeam));
});
}
@@ -210,14 +209,18 @@ public class Logic implements ApplicationListener{
@Override
public void update(){
Events.fire(Trigger.update);
if(!state.is(State.menu)){
if(!net.client()){
state.enemies = unitGroup.count(b -> b.getTeam() == state.rules.waveTeam && b.countsAsEnemy());
}
if(!state.isPaused()){
Time.update();
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){
if(!state.rules.waitForWaveToEnd || unitGroups[waveTeam.ordinal()].size() == 0){
if(!state.rules.waitForWaveToEnd || state.enemies == 0){
state.wavetime = Math.max(state.wavetime - Time.delta(), 0);
}
}
@@ -232,10 +235,7 @@ public class Logic implements ApplicationListener{
}
if(!state.isEditor()){
for(EntityGroup group : unitGroups){
group.update();
}
unitGroup.update();
puddleGroup.update();
shieldGroup.update();
bulletGroup.update();
@@ -243,10 +243,8 @@ public class Logic implements ApplicationListener{
fireGroup.update();
weatherGroup.update();
}else{
for(EntityGroup<?> group : unitGroups){
group.updateEvents();
collisions.updatePhysics(group);
}
unitGroup.updateEvents();
collisions.updatePhysics(unitGroup);
}
@@ -258,17 +256,13 @@ public class Logic implements ApplicationListener{
}
if(!state.isEditor()){
for(EntityGroup group : unitGroups){
if(group.isEmpty()) continue;
collisions.collideGroups(bulletGroup, group);
}
//bulletGroup
collisions.collideGroups(bulletGroup, unitGroup);
collisions.collideGroups(bulletGroup, playerGroup);
}
}
if(!net.client() && !world.isInvalidMap() && !state.isEditor()){
if(!net.client() && !world.isInvalidMap() && !state.isEditor() && state.rules.canGameOver){
checkGameOver();
}
}

View File

@@ -160,9 +160,17 @@ public class NetClient implements ApplicationListener{
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
String original = message;
//check if it's a command
CommandResponse response = netServer.clientCommands.handleMessage(message, player);
if(response.type == ResponseType.noCommand){ //no command to handle
message = netServer.admins.filterMessage(player, message);
//supress chat message if it's filtered out
if(message == null){
return;
}
//server console logging
Log.info("&y{0}: &lb{1}", player.name, message);
@@ -190,7 +198,7 @@ public class NetClient implements ApplicationListener{
}
}
Events.fire(new PlayerChatEvent(player, message));
Events.fire(new PlayerChatEvent(player, message, original));
}
public static String colorizeName(int id, String name){

View File

@@ -1,14 +1,14 @@
package mindustry.core;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.CommandHandler.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
@@ -17,28 +17,49 @@ import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.net.*;
import mindustry.net.Administration.*;
import mindustry.net.Packets.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import java.io.*;
import java.net.*;
import java.nio.*;
import java.util.zip.*;
import static arc.util.Log.*;
import static mindustry.Vars.*;
public class NetServer implements ApplicationListener{
private final static int maxSnapshotSize = 430, timerBlockSync = 0;
private final static float serverSyncTime = 12, kickDuration = 30 * 1000, blockSyncTime = 60 * 10;
private final static float serverSyncTime = 12, kickDuration = 30 * 1000, blockSyncTime = 60 * 8;
private final static Vec2 vector = new Vec2();
private final static Rectangle viewport = new Rectangle();
private final static Rect viewport = new Rect();
/** If a player goes away of their server-side coordinates by this distance, they get teleported back. */
private final static float correctDist = 16f;
public final Administration admins = new Administration();
public final CommandHandler clientCommands = new CommandHandler("/");
public TeamAssigner assigner = (player, players) -> {
if(state.rules.pvp){
//find team with minimum amount of players and auto-assign player to that.
TeamData re = state.teams.getActive().min(data -> {
int count = 0;
for(Player other : players){
if(other.getTeam() == data.team && other != player){
count++;
}
}
return count;
});
return re == null ? null : re.team;
}
return state.rules.defaultTeam;
};
private boolean closing = false;
private Interval timer = new Interval();
@@ -197,10 +218,7 @@ public class NetServer implements ApplicationListener{
con.player = player;
//playing in pvp mode automatically assigns players to teams
if(state.rules.pvp){
player.setTeam(assignTeam(player, playerGroup.all()));
Log.info("Auto-assigned player {0} to team {1}.", player.name, player.getTeam());
}
player.setTeam(assignTeam(player, playerGroup.all()));
sendWorldData(player);
@@ -303,6 +321,11 @@ public class NetServer implements ApplicationListener{
VoteSession[] currentlyKicking = {null};
clientCommands.<Player>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){
player.sendMessage("[scarlet]At least 3 players are needed to start a votekick.");
return;
@@ -401,19 +424,7 @@ public class NetServer implements ApplicationListener{
}
public Team assignTeam(Player current, Iterable<Player> players){
//find team with minimum amount of players and auto-assign player to that.
return Structs.findMin(Team.all, team -> {
if(state.teams.isActive(team) && !state.teams.get(team).cores.isEmpty()){
int count = 0;
for(Player other : players){
if(other.getTeam() == team && other != current){
count++;
}
}
return count;
}
return Integer.MAX_VALUE;
});
return assigner.assign(current, players);
}
public void sendWorldData(Player player){
@@ -437,7 +448,7 @@ public class NetServer implements ApplicationListener{
if(!player.con.hasDisconnected){
if(player.con.hasConnected){
Events.fire(new PlayerLeave(player));
Call.sendMessage("[accent]" + player.name + "[accent] has disconnected.");
if(Config.showConnectMessages.bool()) Call.sendMessage("[accent]" + player.name + "[accent] has disconnected.");
Call.onPlayerDisconnect(player.id);
}
@@ -575,17 +586,21 @@ public class NetServer implements ApplicationListener{
player.add();
player.con.hasConnected = true;
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
if(Config.showConnectMessages.bool()) 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")){
player.sendMessage(Config.motd.string());
}
Events.fire(new PlayerJoin(player));
}
public boolean isWaitingForPlayers(){
if(state.rules.pvp){
int used = 0;
for(Team t : Team.all){
if(playerGroup.count(p -> p.getTeam() == t) > 0){
for(TeamData t : state.teams.getActive()){
if(playerGroup.count(p -> p.getTeam() == t.team) > 0){
used++;
}
}
@@ -594,6 +609,7 @@ public class NetServer implements ApplicationListener{
return false;
}
@Override
public void update(){
if(!headless && !closing && net.server() && state.is(State.menu)){
@@ -611,6 +627,20 @@ public class NetServer implements ApplicationListener{
}
}
/** Should only be used on the headless backend. */
public void openServer(){
try{
net.host(Config.port.num());
info("&lcOpened a server on port {0}.", Config.port.num());
}catch(BindException e){
Log.err("Unable to host: Port already in use! Make sure no other servers are running on the same port in your network.");
state.set(State.menu);
}catch(IOException e){
err(e);
state.set(State.menu);
}
}
public void kickAll(KickReason reason){
for(NetConnection con : net.getConnections()){
con.kick(reason);
@@ -647,20 +677,20 @@ public class NetServer implements ApplicationListener{
public void writeEntitySnapshot(Player player) throws IOException{
syncStream.reset();
ObjectSet<Tile> cores = state.teams.get(player.getTeam()).cores;
Array<CoreEntity> cores = state.teams.cores(player.getTeam());
dataStream.writeByte(cores.size);
for(Tile tile : cores){
dataStream.writeInt(tile.pos());
tile.entity.items.write(dataStream);
for(CoreEntity entity : cores){
dataStream.writeInt(entity.tile.pos());
entity.items.write(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);
@@ -784,4 +814,8 @@ public class NetServer implements ApplicationListener{
e.printStackTrace();
}
}
public interface TeamAssigner{
Team assign(Player player, Iterable<Player> players);
}
}

View File

@@ -18,11 +18,10 @@ import mindustry.entities.effect.*;
import mindustry.entities.effect.GroundEffectEntity.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.ui.Cicon;
import mindustry.ui.*;
import mindustry.world.blocks.defense.ForceProjector.*;
import static arc.Core.*;
@@ -42,7 +41,7 @@ public class Renderer implements ApplicationListener{
private float camerascale = targetscale;
private float landscale = 0f, landTime;
private float minZoomScl = Scl.scl(0.01f);
private Rectangle rect = new Rectangle(), rect2 = new Rectangle();
private Rect rect = new Rect(), rect2 = new Rect();
private float shakeIntensity, shaketime;
public Renderer(){
@@ -57,8 +56,8 @@ public class Renderer implements ApplicationListener{
Effects.setEffectProvider((effect, color, x, y, rotation, data) -> {
if(effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rectangle view = camera.bounds(rect);
Rectangle pos = rect2.setSize(effect.size).setCenter(x, y);
Rect view = camera.bounds(rect);
Rect pos = rect2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
@@ -124,12 +123,14 @@ public class Renderer implements ApplicationListener{
if(player.isDead()){
TileEntity core = player.getClosestCore();
if(core != null && player.spawner == null){
camera.position.lerpDelta(core.x, core.y, 0.08f);
}else{
camera.position.lerpDelta(position, 0.08f);
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){
}else if(control.input instanceof DesktopInput && !state.isPaused()){
camera.position.lerpDelta(position, 0.08f);
}
@@ -344,11 +345,7 @@ public class Renderer implements ApplicationListener{
Draw.rect("circle-shadow", u.x, u.y, size * rad, size * rad);
};
for(EntityGroup<? extends BaseUnit> group : unitGroups){
if(!group.isEmpty()){
group.draw(unit -> !unit.isDead(), draw::get);
}
}
unitGroup.draw(unit -> !unit.isDead(), draw::get);
if(!playerGroup.isEmpty()){
playerGroup.draw(unit -> !unit.isDead(), draw::get);
@@ -361,34 +358,21 @@ public class Renderer implements ApplicationListener{
float trnsX = -12, trnsY = -13;
Draw.color(0, 0, 0, 0.22f);
for(EntityGroup<? extends BaseUnit> group : unitGroups){
if(!group.isEmpty()){
group.draw(unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY));
}
}
if(!playerGroup.isEmpty()){
playerGroup.draw(unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY));
}
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){
for(Team team : Team.all){
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder);
playerGroup.draw(p -> p.isFlying() == flying && !p.isDead(), Unit::drawUnder);
if(group.count(p -> p.isFlying() == flying) + playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue;
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawAll);
unitGroups[team.ordinal()].draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder);
playerGroup.draw(p -> p.isFlying() == flying && p.getTeam() == team && !p.isDead(), Unit::drawUnder);
unitGroups[team.ordinal()].draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
playerGroup.draw(p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawAll);
unitGroups[team.ordinal()].draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
playerGroup.draw(p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver);
}
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawOver);
}
public void scaleCamera(float amount){

View File

@@ -43,6 +43,7 @@ public class UI implements ApplicationListener, Loadable{
public HudFragment hudfrag;
public ChatFragment chatfrag;
public ScriptConsoleFragment scriptfrag;
public MinimapFragment minimapfrag;
public PlayerListFragment listfrag;
public LoadingFragment loadfrag;
@@ -68,7 +69,7 @@ public class UI implements ApplicationListener, Loadable{
public ContentInfoDialog content;
public DeployDialog deploy;
public TechTreeDialog tech;
public MinimapDialog minimap;
//public MinimapDialog minimap;
public SchematicsDialog schematics;
public ModsDialog mods;
public ColorPicker picker;
@@ -210,6 +211,7 @@ public class UI implements ApplicationListener, Loadable{
menufrag = new MenuFragment();
hudfrag = new HudFragment();
chatfrag = new ChatFragment();
minimapfrag = new MinimapFragment();
listfrag = new PlayerListFragment();
loadfrag = new LoadingFragment();
scriptfrag = new ScriptConsoleFragment();
@@ -235,7 +237,6 @@ public class UI implements ApplicationListener, Loadable{
content = new ContentInfoDialog();
deploy = new DeployDialog();
tech = new TechTreeDialog();
minimap = new MinimapDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
@@ -254,6 +255,7 @@ public class UI implements ApplicationListener, Loadable{
hudfrag.build(hudGroup);
menufrag.build(menuGroup);
chatfrag.container().build(hudGroup);
minimapfrag.build(hudGroup);
listfrag.build(hudGroup);
scriptfrag.container().build(hudGroup);
loadfrag.build(group);
@@ -305,7 +307,19 @@ public class UI implements ApplicationListener, Loadable{
hide();
}).disabled(b -> field.getText().isEmpty());
buttons.addButton("$cancel", this::hide);
}}.show();
keyDown(KeyCode.ENTER, () -> {
String text = field.getText();
if(!text.isEmpty()){
confirmed.get(text);
hide();
}
});
keyDown(KeyCode.ESCAPE, this::hide);
keyDown(KeyCode.BACK, this::hide);
show();
Core.scene.setKeyboardFocus(field);
field.setCursorPosition(def.length());
}};
}
}

View File

@@ -9,9 +9,9 @@ import arc.util.io.*;
public class Version{
/** Build type. 'official' for official releases; 'custom' or 'bleeding edge' are also used. */
public static String type;
public static String type = "unknown";
/** Build modifier, e.g. 'alpha' or 'release' */
public static String modifier;
public static String modifier = "unknown";
/** Number specifying the major version, e.g. '4' */
public static int number;
/** Build number, e.g. '43'. set to '-1' for custom builds. */

View File

@@ -1,15 +1,15 @@
package mindustry.core;
import arc.*;
import arc.struct.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.maps.filters.*;
@@ -200,7 +200,7 @@ public class World{
public void loadMap(Map map, Rules checkRules){
try{
SaveIO.load(map.file, new FilterContext(map));
}catch(Exception e){
}catch(Throwable e){
Log.err(e);
if(!headless){
ui.showErrorMessage("$map.invalid");
@@ -216,33 +216,22 @@ public class World{
invalidMap = false;
if(!headless){
if(state.teams.get(defaultTeam).cores.size == 0 && !checkRules.pvp){
if(state.teams.playerCores().size == 0 && !checkRules.pvp){
ui.showErrorMessage("$map.nospawn");
invalidMap = true;
}else if(checkRules.pvp){ //pvp maps need two cores to be valid
int teams = 0;
for(Team team : Team.all){
if(state.teams.get(team).cores.size != 0){
teams ++;
}
}
if(teams < 2){
if(state.teams.getActive().count(TeamData::hasCore) < 2){
invalidMap = true;
ui.showErrorMessage("$map.nospawn.pvp");
}
}else if(checkRules.attackMode){ //attack maps need two cores to be valid
invalidMap = state.teams.get(waveTeam).cores.isEmpty();
invalidMap = state.teams.get(state.rules.waveTeam).noCores();
if(invalidMap){
ui.showErrorMessage("$map.nospawn.attack");
}
}
}else{
invalidMap = true;
for(Team team : Team.all){
if(state.teams.get(team).cores.size != 0){
invalidMap = false;
}
}
invalidMap = !state.teams.getActive().contains(TeamData::hasCore);
if(invalidMap){
throw new MapException(map, "Map has no cores!");
@@ -258,36 +247,6 @@ public class World{
}
}
public void removeBlock(Tile tile){
if(tile == null) return;
tile.link().getLinkedTiles(other -> other.setBlock(Blocks.air));
}
public void setBlock(Tile tile, Block block, Team team){
setBlock(tile, block, team, 0);
}
public void setBlock(Tile tile, Block block, Team team, int rotation){
tile.setBlock(block, team, rotation);
if(block.isMultiblock()){
int offsetx = -(block.size - 1) / 2;
int offsety = -(block.size - 1) / 2;
for(int dx = 0; dx < block.size; dx++){
for(int dy = 0; dy < block.size; dy++){
int worldx = dx + offsetx + tile.x;
int worldy = dy + offsety + tile.y;
if(!(worldx == tile.x && worldy == tile.y)){
Tile toplace = world.tile(worldx, worldy);
if(toplace != null){
toplace.setBlock(BlockPart.get(dx + offsetx, dy + offsety), team);
}
}
}
}
}
}
public void raycastEachWorld(float x0, float y0, float x1, float y1, Raycaster cons){
raycastEach(toTile(x0), toTile(y0), toTile(x1), toTile(y1), cons);
}

View File

@@ -69,7 +69,7 @@ public class DrawOperation{
}else if(type == OpType.rotation.ordinal()){
tile.rotation(to);
}else if(type == OpType.team.ordinal()){
tile.setTeam(Team.all[to]);
tile.setTeam(Team.get(to));
}else if(type == OpType.overlay.ordinal()){
tile.setOverlayID(to);
}

View File

@@ -73,7 +73,7 @@ public class EditorTile extends Tile{
return;
}
if(getTeamID() == team.ordinal()) return;
if(getTeamID() == team.id) return;
op(OpType.team, getTeamID());
super.setTeam(team);
}

View File

@@ -1,15 +1,14 @@
package mindustry.editor;
import arc.struct.IntArray;
import arc.func.*;
import arc.math.Mathf;
import arc.math.geom.Bresenham2;
import arc.util.Structs;
import mindustry.Vars;
import mindustry.content.Blocks;
import mindustry.game.Team;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.world.*;
import mindustry.world.blocks.BlockPart;
import mindustry.world.blocks.*;
public enum EditorTool{
zoom,
@@ -80,7 +79,7 @@ public enum EditorTool{
editor.drawCircle(x, y, tile -> {
if(mode == -1){
//erase block
Vars.world.removeBlock(tile);
tile.remove();
}else if(mode == 0){
//erase ore
tile.clearOverlay();
@@ -141,7 +140,7 @@ public enum EditorTool{
if(tile.link().synthetic()){
Team dest = tile.getTeam();
if(dest == editor.drawTeam) return;
fill(editor, x, y, false, t -> t.getTeamID() == dest.ordinal() && t.link().synthetic(), t -> t.setTeam(editor.drawTeam));
fill(editor, x, y, false, t -> t.getTeamID() == (int)dest.id && t.link().synthetic(), t -> t.setTeam(editor.drawTeam));
}
}
}

View File

@@ -10,7 +10,6 @@ import arc.util.Structs;
import mindustry.content.Blocks;
import mindustry.game.Team;
import mindustry.gen.TileOp;
import mindustry.io.LegacyMapIO;
import mindustry.io.MapIO;
import mindustry.maps.Map;
import mindustry.world.*;
@@ -65,7 +64,7 @@ public class MapEditor{
reset();
createTiles(pixmap.getWidth(), pixmap.getHeight());
load(() -> LegacyMapIO.readPixmap(pixmap, tiles()));
load(() -> MapIO.readPixmap(pixmap, tiles()));
renderer.resize(width(), height());
}
@@ -84,7 +83,7 @@ public class MapEditor{
//re-add them
for(Tile tile : tiles){
if(tile.block().isMultiblock()){
world.setBlock(tile, tile.block(), tile.getTeam());
tile.set(tile.block(), tile.getTeam());
}
}
@@ -174,7 +173,7 @@ public class MapEditor{
}
}
world.setBlock(tile(x, y), drawBlock, drawTeam);
tile(x, y).set(drawBlock, drawTeam);
}else{
boolean isFloor = drawBlock.isFloor() && drawBlock != Blocks.air;
@@ -183,7 +182,7 @@ public class MapEditor{
//remove linked tiles blocking the way
if(!isFloor && (tile.isLinked() || tile.block().isMultiblock())){
world.removeBlock(tile.link());
tile.link().remove();
}
if(isFloor){

View File

@@ -551,7 +551,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
int i = 0;
for(Team team : Team.all){
for(Team team : Team.base()){
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglePartiali);
button.margin(4f);
button.getImageCell().grow();

View File

@@ -138,7 +138,7 @@ public class MapGenerateDialog extends FloatingDialog{
tile.rotation(write.rotation);
tile.setFloor((Floor)content.block(write.floor));
tile.setBlock(content.block(write.block));
tile.setTeam(Team.all[write.team]);
tile.setTeam(Team.get(write.team));
tile.setOverlay(content.block(write.ore));
}
}
@@ -367,7 +367,7 @@ public class MapGenerateDialog extends FloatingDialog{
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.all[tile.team], tile.rotation);
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]));
@@ -411,7 +411,7 @@ public class MapGenerateDialog extends FloatingDialog{
this.floor = floor.id;
this.block = wall.id;
this.ore = ore.id;
this.team = (byte)team.ordinal();
this.team = (byte) team.id;
this.rotation = (byte)rotation;
}
@@ -433,7 +433,7 @@ public class MapGenerateDialog extends FloatingDialog{
ctile.setBlock(content.block(block));
ctile.setOverlay(content.block(ore));
ctile.rotation(rotation);
ctile.setTeam(Team.all[team]);
ctile.setTeam(Team.get(team));
return ctile;
}
}

View File

@@ -28,7 +28,7 @@ public class MapView extends Element implements GestureListener{
private boolean grid = false;
private GridImage image = new GridImage(0, 0);
private Vec2 vec = new Vec2();
private Rectangle rect = new Rectangle();
private Rect rect = new Rect();
private Vec2[][] brushPolygons = new Vec2[MapEditor.brushSizes.length][0];
private boolean drawing;

View File

@@ -22,8 +22,8 @@ import static mindustry.Vars.*;
/** Utility class for damaging in an area. */
public class Damage{
private static Rectangle rect = new Rectangle();
private static Rectangle hitrect = new Rectangle();
private static Rect rect = new Rect();
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2();
private static GridBits bits = new GridBits(30, 30);
private static IntQueue propagation = new IntQueue();
@@ -88,7 +88,7 @@ public class Damage{
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.ordinal() && tile.entity.collide(hitter)){
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.entity != null && tile.getTeamID() != team.id && tile.entity.collide(hitter)){
tile.entity.collision(hitter);
collidedBlocks.add(tile.pos());
hitter.getBulletType().hit(hitter, tile.worldx(), tile.worldy());
@@ -127,7 +127,7 @@ public class Damage{
Cons<Unit> cons = e -> {
e.hitbox(hitrect);
Rectangle other = hitrect;
Rect other = hitrect;
other.y -= expand;
other.x -= expand;
other.width += expand * 2;
@@ -259,7 +259,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 || state.teams.areEnemies(team, tile.getTeam())) && Mathf.dst(dx, dy) <= trad){
if(tile != null && tile.entity != null && (team == null ||team.isEnemy(tile.getTeam())) && Mathf.dst(dx, dy) <= trad){
tile.entity.damage(damage);
}
}

View File

@@ -17,11 +17,11 @@ public class EntityCollisions{
private static final float seg = 1f;
//tile collisions
private Rectangle tmp = new Rectangle();
private Rect tmp = new Rect();
private Vec2 vector = new Vec2();
private Vec2 l1 = new Vec2();
private Rectangle r1 = new Rectangle();
private Rectangle r2 = new Rectangle();
private Rect r1 = new Rect();
private Rect r2 = new Rect();
//entity collisions
private Array<SolidTrait> arrOut = new Array<>();
@@ -57,7 +57,7 @@ public class EntityCollisions{
public void moveDelta(SolidTrait entity, float deltax, float deltay, boolean x){
Rectangle rect = r1;
Rect rect = r1;
entity.hitboxTile(rect);
entity.hitboxTile(r2);
rect.x += deltax;
@@ -84,7 +84,7 @@ public class EntityCollisions{
entity.setY(entity.getY() + rect.y - r2.y);
}
public boolean overlapsTile(Rectangle rect){
public boolean overlapsTile(Rect rect){
rect.getCenter(vector);
int r = 1;

View File

@@ -7,11 +7,13 @@ import arc.graphics.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import java.util.*;
import static mindustry.Vars.collisions;
/** Represents a group of a certain type of entity.*/
@SuppressWarnings("unchecked")
public class EntityGroup<T extends Entity>{
public class EntityGroup<T extends Entity> implements Iterable<T>{
private final boolean useTree;
private final int id;
private final Class<T> type;
@@ -19,13 +21,13 @@ public class EntityGroup<T extends Entity>{
private final Array<T> entitiesToRemove = new Array<>(false, 32);
private final Array<T> entitiesToAdd = new Array<>(false, 32);
private final Array<T> intersectArray = new Array<>();
private final Rectangle intersectRect = new Rectangle();
private final Rect intersectRect = new Rect();
private IntMap<T> map;
private QuadTree tree;
private Cons<T> removeListener;
private Cons<T> addListener;
private final Rectangle viewport = new Rectangle();
private final Rect viewport = new Rect();
private int count = 0;
public EntityGroup(int id, Class<T> type, boolean useTree){
@@ -34,7 +36,7 @@ public class EntityGroup<T extends Entity>{
this.type = type;
if(useTree){
tree = new QuadTree<>(new Rectangle(0, 0, 0, 0));
tree = new QuadTree<>(new Rect(0, 0, 0, 0));
}
}
@@ -180,7 +182,7 @@ public class EntityGroup<T extends Entity>{
/** Resizes the internal quadtree, if it is enabled.*/
public void resize(float x, float y, float w, float h){
if(useTree){
tree = new QuadTree<>(new Rectangle(x, y, w, h));
tree = new QuadTree<>(new Rect(x, y, w, h));
}
}
@@ -253,8 +255,13 @@ public class EntityGroup<T extends Entity>{
return null;
}
/** Returns the logic-only array for iteration. */
/** Returns the array for iteration. */
public Array<T> all(){
return entityArray;
}
@Override
public Iterator<T> iterator(){
return entityArray.iterator();
}
}

View File

@@ -1,21 +1,18 @@
package mindustry.entities;
import arc.struct.EnumSet;
import arc.func.Cons;
import arc.func.Boolf;
import arc.math.Mathf;
import arc.math.geom.Geometry;
import arc.math.geom.Rectangle;
import mindustry.entities.traits.TargetTrait;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.Team;
import mindustry.world.Tile;
import mindustry.game.*;
import mindustry.world.*;
import static mindustry.Vars.*;
/** Utility class for unit and team interactions.*/
public class Units{
private static Rectangle hitrect = new Rectangle();
private static Rect hitrect = new Rect();
private static Unit result;
private static float cdist;
private static boolean boolResult;
@@ -86,13 +83,7 @@ public class Units{
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
if(team == Team.derelict) return null;
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
return entity;
}
}
return null;
return indexer.findEnemyTile(team, x, y, range, pred);
}
/** Returns the closest target enemy. First, units are checked, then tile entities. */
@@ -157,7 +148,11 @@ 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){
unitGroups[team.ordinal()].intersect(x, y, width, height, cons);
unitGroup.intersect(x, y, width, height, u -> {
if(u.getTeam() == team){
cons.get(u);
}
});
playerGroup.intersect(x, y, width, height, player -> {
if(player.getTeam() == team){
cons.get(player);
@@ -167,8 +162,8 @@ public class Units{
/** 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){
unitGroups[team.ordinal()].intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.withinDst(x, y, radius)){
unitGroup.intersect(x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.getTeam() == team && unit.withinDst(x, y, radius)){
cons.get(unit);
}
});
@@ -182,45 +177,43 @@ public class Units{
/** Iterates over all units in a rectangle. */
public static void nearby(float x, float y, float width, float height, Cons<Unit> cons){
for(Team team : Team.all){
unitGroups[team.ordinal()].intersect(x, y, width, height, cons);
}
unitGroup.intersect(x, y, width, height, cons);
playerGroup.intersect(x, y, width, height, cons);
}
/** Iterates over all units in a rectangle. */
public static void nearby(Rectangle rect, Cons<Unit> cons){
public static void nearby(Rect rect, Cons<Unit> 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){
EnumSet<Team> targets = state.teams.enemiesOf(team);
for(Team other : targets){
unitGroups[other.ordinal()].intersect(x, y, width, height, cons);
}
unitGroup.intersect(x, y, width, height, u -> {
if(team.isEnemy(u.getTeam())){
cons.get(u);
}
});
playerGroup.intersect(x, y, width, height, player -> {
if(targets.contains(player.getTeam())){
if(team.isEnemy(player.getTeam())){
cons.get(player);
}
});
}
/** Iterates over all units that are enemies of this team. */
public static void nearbyEnemies(Team team, Rectangle rect, Cons<Unit> cons){
public static void nearbyEnemies(Team team, Rect rect, Cons<Unit> cons){
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
}
/** Iterates over all units. */
public static void all(Cons<Unit> cons){
for(Team team : Team.all){
unitGroups[team.ordinal()].all().each(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

@@ -25,7 +25,7 @@ public class ArtilleryBulletType extends BasicBulletType{
}
@Override
public void update(mindustry.entities.type.Bullet b){
public void update(Bullet b){
super.update(b);
if(b.timer.get(0, 3 + b.fslope() * 2f)){

View File

@@ -1,13 +1,13 @@
package mindustry.entities.bullet;
import arc.math.geom.Rectangle;
import arc.math.geom.Rect;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Units;
import mindustry.entities.type.Bullet;
public class FlakBulletType extends BasicBulletType{
protected static Rectangle rect = new Rectangle();
protected static Rect rect = new Rect();
protected float explodeRange = 30f;
public FlakBulletType(float speed, float damage){

View File

@@ -44,7 +44,7 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
create(item, x, y, to, () -> to.addItem(item));
}
@Remote(called = Loc.server)
@Remote(called = Loc.server, unreliable = true)
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null || tile.entity == null || tile.entity.items == null) return;
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){

View File

@@ -28,7 +28,7 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
public static final float lifetime = 10f;
private static final RandomXS128 random = new RandomXS128();
private static final Rectangle rect = new Rectangle();
private static final Rect rect = new Rect();
private static final Array<Unit> entities = new Array<>();
private static final IntSet hit = new IntSet();
private static final int maxChain = 8;

View File

@@ -27,8 +27,8 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
private static final float maxLiquid = 70f;
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rectangle rect = new Rectangle();
private static final Rectangle rect2 = new Rectangle();
private static final Rect rect = new Rect();
private static final Rect rect2 = new Rect();
private static int seeds;
private int loadedPosition = -1;
@@ -151,13 +151,13 @@ public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrai
}
@Override
public void hitbox(Rectangle rectangle){
rectangle.setCenter(x, y).setSize(tilesize);
public void hitbox(Rect rect){
rect.setCenter(x, y).setSize(tilesize);
}
@Override
public void hitboxTile(Rectangle rectangle){
rectangle.setCenter(x, y).setSize(0f);
public void hitboxTile(Rect rect){
rect.setCenter(x, y).setSize(0f);
}
@Override

View File

@@ -343,7 +343,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
return this;
}
public Rectangle bounds(Rectangle rect){
public Rect bounds(Rect rect){
if(breaking){
return rect.set(-100f, -100f, 0f, 0f);
}else{

View File

@@ -7,6 +7,7 @@ import arc.math.*;
import arc.util.Time;
import mindustry.content.*;
import mindustry.entities.Effects;
import mindustry.entities.effect.*;
import mindustry.entities.type.*;
import mindustry.gen.Call;
import mindustry.graphics.*;
@@ -38,11 +39,26 @@ public interface MinerTrait extends Entity{
/** Returns whether or not this builder can mine a specific item type. */
boolean canMine(Item item);
/** @return whether to offload mined items immediately at the core. if false, items are collected and dropped in a burst. */
default boolean offloadImmediately(){
return false;
}
default void updateMining(){
Unit unit = (Unit)this;
Tile tile = getMineTile();
TileEntity core = unit.getClosestCore();
if(core != null && tile != null && tile.drop() != null && !unit.acceptsItem(tile.drop()) && unit.dst(core) < mineTransferRange){
int accepted = core.tile.block().acceptStack(unit.item().item, unit.item().amount, core.tile, unit);
if(accepted > 0){
Call.transferItemTo(unit.item().item, accepted,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), core.tile);
unit.clearItem();
}
}
if(tile == null || core == null || tile.block() != Blocks.air || dst(tile.worldx(), tile.worldy()) > getMiningRange()
|| tile.drop() == null || !unit.acceptsItem(tile.drop()) || !canMine(tile.drop())){
setMineTile(null);
@@ -52,12 +68,13 @@ public interface MinerTrait extends Entity{
if(Mathf.chance(Time.delta() * (0.06 - item.hardness * 0.01) * getMinePower())){
if(unit.dst(core) < mineTransferRange && core.tile.block().acceptStack(item, 1, core.tile, unit) == 1){
if(unit.dst(core) < mineTransferRange && core.tile.block().acceptStack(item, 1, core.tile, unit) == 1 && offloadImmediately()){
Call.transferItemTo(item, 1,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f), core.tile);
}else if(unit.acceptsItem(item)){
Call.transferItemToUnit(item,
//this is clientside, since items are synced anyway
ItemTransfer.transferItemToUnit(item,
tile.worldx() + Mathf.range(tilesize / 2f),
tile.worldy() + Mathf.range(tilesize / 2f),
unit);

View File

@@ -7,9 +7,9 @@ import mindustry.Vars;
public interface SolidTrait extends QuadTreeObject, MoveTrait, VelocityTrait, Entity, Position{
void hitbox(Rectangle rectangle);
void hitbox(Rect rect);
void hitboxTile(Rectangle rectangle);
void hitboxTile(Rect rect);
Vec2 lastPosition();

View File

@@ -126,6 +126,11 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
this.team = team;
}
/** @return whether this unit counts toward the enemy amount in the wave UI. */
public boolean countsAsEnemy(){
return true;
}
public UnitType getType(){
return type;
}
@@ -180,23 +185,16 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
}
public Tile getClosest(BlockFlag flag){
public @Nullable Tile getClosest(BlockFlag flag){
return Geometry.findClosest(x, y, indexer.getAllied(team, flag));
}
public Tile getClosestSpawner(){
public @Nullable Tile getClosestSpawner(){
return Geometry.findClosest(x, y, Vars.spawner.getGroundSpawns());
}
public TileEntity getClosestEnemyCore(){
for(Team enemy : Vars.state.teams.enemiesOf(team)){
Tile tile = Geometry.findClosest(x, y, Vars.state.teams.get(enemy).cores);
if(tile != null){
return tile.entity;
}
}
return null;
public @Nullable TileEntity getClosestEnemyCore(){
return Vars.state.teams.closestEnemyCore(x, y, team);
}
public UnitState getStartState(){
@@ -354,18 +352,18 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
@Override
public void hitbox(Rectangle rectangle){
rectangle.setSize(type.hitsize).setCenter(x, y);
public void hitbox(Rect rect){
rect.setSize(type.hitsize).setCenter(x, y);
}
@Override
public void hitboxTile(Rectangle rectangle){
rectangle.setSize(type.hitsizeTile).setCenter(x, y);
public void hitboxTile(Rect rect){
rect.setSize(type.hitsizeTile).setCenter(x, y);
}
@Override
public EntityGroup targetGroup(){
return unitGroups[team.ordinal()];
return unitGroup;
}
@Override

View File

@@ -246,13 +246,13 @@ public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Pool
}
@Override
public void hitbox(Rectangle rectangle){
rectangle.setSize(type.hitSize).setCenter(x, y);
public void hitbox(Rect rect){
rect.setSize(type.hitSize).setCenter(x, y);
}
@Override
public void hitboxTile(Rectangle rectangle){
rectangle.setSize(type.hitSize).setCenter(x, y);
public void hitboxTile(Rect rect){
rect.setSize(type.hitSize).setCenter(x, y);
}
@Override

View File

@@ -41,7 +41,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
private static final int timerShootRight = 1;
private static final float liftoffBoost = 0.2f;
private static final Rectangle rect = new Rectangle();
private static final Rect rect = new Rect();
//region instance variables
@@ -93,13 +93,13 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
}
@Override
public void hitbox(Rectangle rectangle){
rectangle.setSize(mech.hitsize).setCenter(x, y);
public void hitbox(Rect rect){
rect.setSize(mech.hitsize).setCenter(x, y);
}
@Override
public void hitboxTile(Rectangle rectangle){
rectangle.setSize(mech.hitsize * 2f / 3f).setCenter(x, y);
public void hitboxTile(Rect rect){
rect.setSize(mech.hitsize * 2f / 3f).setCenter(x, y);
}
@Override
@@ -119,6 +119,11 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
heal();
}
@Override
public boolean offloadImmediately(){
return true;
}
@Override
public TypeID getTypeID(){
return TypeIDs.player;
@@ -568,6 +573,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
protected void updateKeyboard(){
Tile tile = world.tileWorld(x, y);
boolean canMove = !Core.scene.hasKeyboard() || ui.minimapfrag.shown();
isBoosting = Core.input.keyDown(Binding.dash) && !mech.flying;
@@ -594,8 +600,8 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
}
if(Core.input.keyDown(Binding.mouse_move)){
movement.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2) * 0.005f, -1, 1) * speed;
movement.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2) * 0.005f, -1, 1) * speed;
movement.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * 0.005f, -1, 1) * speed;
movement.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * 0.005f, -1, 1) * speed;
}
Vec2 vec = Core.input.mouseWorld(control.input.getMouseX(), control.input.getMouseY());
@@ -605,7 +611,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
movement.limit(speed).scl(Time.delta());
if(!Core.scene.hasKeyboard()){
if(canMove){
velocity.add(movement.x, movement.y);
}else{
isShooting = false;
@@ -614,7 +620,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
updateVelocityStatus();
moved = dst(prex, prey) > 0.001f;
if(!Core.scene.hasKeyboard()){
if(canMove){
float baseLerp = mech.getRotationAlpha(this);
if(!isShooting() || !mech.turnCursor){
if(!movement.isZero()){
@@ -631,7 +637,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
if(!state.isEditor() && isShooting() && mech.canShoot(this)){
if(!mech.turnCursor){
//shoot forward ignoring cursor
mech.weapon.update(this, x + Angles.trnsx(rotation, 1f), y + Angles.trnsy(rotation, 1f));
mech.weapon.update(this, x + Angles.trnsx(rotation, mech.weapon.targetDistance), y + Angles.trnsy(rotation, mech.weapon.targetDistance));
}else{
mech.weapon.update(this, pointerX, pointerY);
}

View File

@@ -1,5 +1,6 @@
package mindustry.entities.type;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import arc.Events;
import arc.struct.Array;
@@ -124,7 +125,8 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
@CallSuper
public void write(DataOutput stream) throws IOException{
stream.writeShort((short)health);
stream.writeByte(Pack.byteByte(tile.getTeamID(), tile.rotation())); //team + rotation
stream.writeByte(Pack.byteByte((byte)8, tile.rotation())); //rotation + marker to indicate that team is moved (8 isn't valid)
stream.writeByte(tile.getTeamID());
if(items != null) items.write(stream);
if(power != null) power.write(stream);
if(liquids != null) liquids.write(stream);
@@ -134,11 +136,11 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
@CallSuper
public void read(DataInput stream, byte revision) throws IOException{
health = stream.readUnsignedShort();
byte tr = stream.readByte();
byte team = Pack.leftByte(tr);
byte rotation = Pack.rightByte(tr);
byte packedrot = stream.readByte();
byte team = Pack.leftByte(packedrot) == 8 ? stream.readByte() : Pack.leftByte(packedrot);
byte rotation = Pack.rightByte(packedrot);
tile.setTeam(Team.all[team]);
tile.setTeam(Team.get(team));
tile.rotation(rotation);
if(items != null) items.read(stream);
@@ -164,9 +166,16 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
Call.onTileDestroyed(tile);
}
@Override
public void damage(float damage){
if(dead) return;
if(Mathf.zero(state.rules.blockHealthMultiplier)){
damage = health + 1;
}else{
damage /= state.rules.blockHealthMultiplier;
}
float preHealth = health;
Call.onTileDamage(tile, health - block.handleDamage(tile, damage));
@@ -277,7 +286,7 @@ public class TileEntity extends BaseEntity implements TargetTrait, HealthTrait{
Events.fire(new BlockDestroyEvent(tile));
block.breakSound.at(tile);
block.onDestroyed(tile);
world.removeBlock(tile);
tile.remove();
remove();
}
}

View File

@@ -1,12 +1,12 @@
package mindustry.entities.type;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
@@ -16,13 +16,11 @@ import mindustry.entities.traits.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -90,7 +88,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
if(isDead()) return false;
if(other instanceof DamageTrait){
return other instanceof TeamTrait && state.teams.areEnemies((((TeamTrait)other).getTeam()), team);
return other instanceof TeamTrait && (((TeamTrait)other).getTeam()).isEnemy(team);
}else{
return other instanceof Unit && ((Unit)other).isFlying() == isFlying();
}
@@ -158,7 +156,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
this.item.amount = itemAmount;
this.item.item = content.item(itemID);
this.dead = dead;
this.team = Team.all[team];
this.team = Team.get(team);
this.health = health;
this.x = x;
this.y = y;
@@ -169,7 +167,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
public void writeSave(DataOutput stream, boolean net) throws IOException{
if(item.item == null) item.item = Items.copper;
stream.writeByte(team.ordinal());
stream.writeByte(team.id);
stream.writeBoolean(isDead());
stream.writeFloat(net ? interpolator.target.x : x);
stream.writeFloat(net ? interpolator.target.y : y);
@@ -217,13 +215,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
float fsize = getSize() / radScl;
moveVector.setZero();
float cx = x - fsize/2f, cy = y - fsize/2f;
for(Team team : Team.all){
if(team != getTeam() || !(this instanceof Player)){
avoid(unitGroups[team.ordinal()].intersect(cx, cy, fsize, fsize));
}
}
avoid(unitGroup.intersect(cx, cy, fsize, fsize));
if(!(this instanceof Player)){
avoid(playerGroup.intersect(cx, cy, fsize, fsize));
}
@@ -234,7 +226,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
float radScl = 1.5f;
for(Unit en : arr){
if(en.isFlying() != isFlying() || (en instanceof Player && en.getTeam() != getTeam())) continue;
if(en.isFlying() != isFlying() || (en instanceof Player && en.getTeam() != getTeam()) || (this instanceof Player && en.isFlying())) continue;
float dst = dst(en);
float scl = Mathf.clamp(1f - dst / (getSize()/(radScl*2f) + en.getSize()/(radScl*2f)));
moveVector.add(Tmp.v1.set((x - en.x) * scl, (y - en.y) * scl).limit(0.4f));
@@ -242,14 +234,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
public @Nullable TileEntity getClosestCore(){
TeamData data = state.teams.get(team);
Tile tile = Geometry.findClosest(x, y, data.cores);
if(tile == null){
return null;
}else{
return tile.entity;
}
return state.teams.closestCore(x, y, team);
}
public Floor getFloorOn(){
@@ -275,7 +260,7 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
//apply knockback based on spawns
if(getTeam() != waveTeam){
if(getTeam() != state.rules.waveTeam){
float relativeSize = state.rules.dropZoneRadius + getSize()/2f + 1f;
for(Tile spawn : spawner.getGroundSpawns()){
if(withinDst(spawn.worldx(), spawn.worldy(), relativeSize)){

View File

@@ -32,6 +32,10 @@ public abstract class BaseDrone extends FlyingUnit{
}
};
public boolean countsAsEnemy(){
return false;
}
@Override
public void onCommand(UnitCommand command){
//do nothing, normal commands are not applicable here

View File

@@ -1,10 +1,9 @@
package mindustry.entities.type.base;
import arc.*;
import arc.struct.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
@@ -114,12 +113,10 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
public BuilderDrone(){
if(reset.check()){
Events.on(BuildSelectEvent.class, event -> {
EntityGroup<BaseUnit> group = unitGroups[event.team.ordinal()];
if(!(event.tile.entity instanceof BuildEntity)) return;
for(BaseUnit unit : group.all()){
if(unit instanceof BuilderDrone){
for(BaseUnit unit : unitGroup.all()){
if(unit instanceof BuilderDrone && unit.getTeam() == getTeam()){
BuilderDrone drone = (BuilderDrone)unit;
if(drone.isBuilding()){
//stop building if opposite building begins.
@@ -189,7 +186,7 @@ public class BuilderDrone extends BaseDrone implements BuilderTrait{
}
if(timer.get(timerTarget, 80) && Units.closestEnemy(getTeam(), x, y, 100f, u -> !(u instanceof BaseDrone)) == null && !isBuilding()){
TeamData data = Vars.state.teams.get(team);
TeamData data = team.data();
if(!data.brokenBlocks.isEmpty()){
BrokenBlock block = data.brokenBlocks.removeLast();
if(Build.validPlace(getTeam(), block.x, block.y, content.block(block.block), block.rotation)){

View File

@@ -237,15 +237,15 @@ public class GroundUnit extends BaseUnit{
protected void moveAwayFromCore(){
Team enemy = null;
for(Team team : Vars.state.teams.enemiesOf(team)){
if(Vars.state.teams.isActive(team)){
for(Team team : team.enemies()){
if(team.active()){
enemy = team;
break;
}
}
if(enemy == null){
for(Team team : Vars.state.teams.enemiesOf(team)){
for(Team team : team.enemies()){
enemy = team;
break;
}

View File

@@ -7,13 +7,14 @@ import mindustry.entities.type.BaseUnit;
import mindustry.entities.type.TileEntity;
import mindustry.gen.Call;
import mindustry.type.Item;
import static mindustry.Vars.*;
public class UnitDrops{
private static Item[] dropTable;
public static void dropItems(BaseUnit unit){
//items only dropped in waves for enemy team
if(unit.getTeam() != Vars.waveTeam || !Vars.state.rules.unitDrops){
if(unit.getTeam() != state.rules.waveTeam || !Vars.state.rules.unitDrops){
return;
}

View File

@@ -28,7 +28,9 @@ public class EventType{
exclusionDeath,
suicideBomb,
openWiki,
teamCoreDamage
teamCoreDamage,
socketConfigChanged,
update
}
public static class WinEvent{}
@@ -62,10 +64,13 @@ public class EventType{
public static class PlayerChatEvent{
public final Player player;
public final String message;
/** The original, unfiltered message. */
public final String originalMessage;
public PlayerChatEvent(Player player, String message){
public PlayerChatEvent(Player player, String message, String originalMessage){
this.player = player;
this.message = message;
this.originalMessage = originalMessage;
}
}

View File

@@ -4,7 +4,7 @@ import arc.*;
import arc.func.*;
import mindustry.maps.*;
import static mindustry.Vars.waveTeam;
import static mindustry.Vars.*;
/** Defines preset rule sets. */
public enum Gamemode{
@@ -22,7 +22,7 @@ public enum Gamemode{
attack(rules -> {
rules.unitDrops = true;
rules.attackMode = true;
}, map -> map.teams.contains(waveTeam.ordinal())),
}, map -> map.teams.contains((int)state.rules.waveTeam.id)),
pvp(rules -> {
rules.pvp = true;
rules.enemyCoreBuildRadius = 600f;

View File

@@ -83,7 +83,7 @@ public class MusicControl{
/** Whether to play dark music.*/
private boolean isDark(){
if(!state.teams.get(player.getTeam()).cores.isEmpty() && state.teams.get(player.getTeam()).cores.first().entity.healthf() < 0.85f){
if(state.teams.get(player.getTeam()).hasCore() && state.teams.get(player.getTeam()).core().healthf() < 0.85f){
//core damaged -> dark
return true;
}
@@ -94,7 +94,7 @@ public class MusicControl{
}
//dark based on enemies
return Mathf.chance(state.enemies() / 70f + 0.1f);
return Mathf.chance(state.enemies / 70f + 0.1f);
}
/** Plays and fades in a music track. This must be called every frame.

View File

@@ -34,6 +34,8 @@ public class Rules{
public float unitHealthMultiplier = 1f;
/** How much health players start with. */
public float playerHealthMultiplier = 1f;
/** How much health blocks start with. */
public float blockHealthMultiplier = 1f;
/** How much damage player mechs deal. */
public float playerDamageMultiplier = 1f;
/** How much damage any other units deal. */
@@ -70,6 +72,8 @@ public class Rules{
public boolean editor = false;
/** Whether the tutorial is enabled. False by default. */
public boolean tutorial = false;
/** Whether a gameover can happen at all. Set this to false to implement custom gameover conditions. */
public boolean canGameOver = true;
/** Starting items put in cores */
public Array<ItemStack> loadout = Array.with(ItemStack.with(Items.copper, 100));
/** Blocks that cannot be placed. */
@@ -78,6 +82,12 @@ public class Rules{
public boolean lighting = false;
/** Ambient light color, used when lighting is enabled. */
public Color ambientLight = new Color(0.01f, 0.01f, 0.04f, 0.99f);
/** team of the player by default */
public Team defaultTeam = Team.sharded;
/** team of the enemy in waves/sectors */
public Team waveTeam = Team.crux;
/** special tags for additional info */
public StringMap tags = new StringMap();
/** Copies this ruleset exactly. Not very efficient at all, do not use often. */
public Rules copy(){

View File

@@ -142,6 +142,7 @@ public class Schematics implements Loadable{
ui.showException(e);
}
}
all.sort();
}
public void savePreview(Schematic schematic, Fi file){
@@ -249,12 +250,13 @@ public class Schematics implements Loadable{
public void placeLoadout(Schematic schem, int x, int y){
Stile coreTile = schem.tiles.find(s -> s.block instanceof CoreBlock);
if(coreTile == null) throw new IllegalArgumentException("Schematic has no core tile. Exiting.");
int ox = x - coreTile.x, oy = y - coreTile.y;
schem.tiles.each(st -> {
Tile tile = world.tile(st.x + ox, st.y + oy);
if(tile == null) return;
world.setBlock(tile, st.block, defaultTeam);
tile.set(st.block, state.rules.defaultTeam);
tile.rotation(st.rotation);
if(st.block.posConfig){
tile.configureAny(Pos.get(tile.x - st.x + Pos.x(st.config), tile.y - st.y + Pos.y(st.config)));
@@ -279,6 +281,7 @@ public class Schematics implements Loadable{
ui.showException(e);
Log.err(e);
}
all.sort();
}
public void remove(Schematic s){
@@ -291,6 +294,7 @@ public class Schematics implements Loadable{
previews.get(s).dispose();
previews.remove(s);
}
all.sort();
}
/** Creates a schematic from a world selection. */
@@ -339,7 +343,8 @@ public class Schematics implements Loadable{
for(int cy = oy; cy <= oy2; cy++){
Tile tile = world.ltile(cx, cy);
if(tile != null && tile.entity != null && !counted.contains(tile.pos()) && !(tile.block() instanceof BuildBlock) && tile.entity.block.isVisible()){
if(tile != null && tile.entity != null && !counted.contains(tile.pos()) && !(tile.block() instanceof BuildBlock)
&& (tile.entity.block.isVisible() || (tile.entity.block instanceof CoreBlock && Core.settings.getBool("coreselect")))){
int config = tile.entity.config();
if(tile.block().posConfig){
config = Pos.get(Pos.x(config) + offsetX, Pos.y(config) + offsetY);
@@ -368,8 +373,12 @@ public class Schematics implements Loadable{
//region IO methods
/** Loads a schematic from base64. May throw an exception. */
public static Schematic readBase64(String schematic) throws IOException{
return read(new ByteArrayInputStream(Base64Coder.decode(schematic)));
public static Schematic readBase64(String schematic){
try{
return read(new ByteArrayInputStream(Base64Coder.decode(schematic)));
}catch(IOException e){
throw new RuntimeException(e);
}
}
public static Schematic read(Fi file) throws IOException{

View File

@@ -1,27 +1,102 @@
package mindustry.game;
import arc.Core;
import arc.graphics.Color;
import arc.*;
import arc.graphics.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.game.Teams.*;
import mindustry.graphics.*;
import mindustry.world.blocks.storage.CoreBlock.*;
public enum Team{
derelict(Color.valueOf("4d4e58")),
sharded(Pal.accent),
crux(Color.valueOf("e82d2d")),
green(Color.valueOf("4dd98b")),
purple(Color.valueOf("9a4bdf")),
blue(Color.royal.cpy());
import static mindustry.Vars.*;
public final static Team[] all = values();
public class Team implements Comparable<Team>{
public final byte id;
public final Color color;
public final int intColor;
public String name;
Team(Color color){
/** All 256 registered teams. */
private static final Team[] all = new Team[256];
/** The 6 base teams used in the editor. */
private static final Team[] baseTeams = new Team[6];
public final static Team
derelict = new Team(0, "derelict", Color.valueOf("4d4e58")),
sharded = new Team(1, "sharded", Pal.accent.cpy()),
crux = new Team(2, "crux", Color.valueOf("e82d2d")),
green = new Team(3, "green", Color.valueOf("4dd98b")),
purple = new Team(4, "purple", Color.valueOf("9a4bdf")),
blue = new Team(5, "blue", Color.royal.cpy());
static{
Mathf.random.setSeed(8);
//create the whole 256 placeholder teams
for(int i = 6; i < all.length; i++){
new Team(i, "team#" + i, Color.HSVtoRGB(360f * Mathf.random(), 100f * Mathf.random(0.6f, 1f), 100f * Mathf.random(0.8f, 1f), 1f));
}
Mathf.random.setSeed(new RandomXS128().nextLong());
}
public static Team get(int id){
return all[Pack.u((byte)id)];
}
/** @return the 6 base team colors. */
public static Team[] base(){
return baseTeams;
}
/** @return all the teams - do not use this for lookup! */
public static Team[] all(){
return all;
}
protected Team(int id, String name, Color color){
this.name = name;
this.color = color;
intColor = Color.rgba8888(color);
this.id = (byte)id;
int us = Pack.u(this.id);
if(us < 6) baseTeams[us] = this;
all[us] = this;
}
public Array<Team> enemies(){
return state.teams.enemiesOf(this);
}
public TeamData data(){
return state.teams.get(this);
}
public CoreEntity core(){
return data().core();
}
public boolean active(){
return state.teams.isActive(this);
}
public boolean isEnemy(Team other){
return state.teams.areEnemies(this, other);
}
public Array<CoreEntity> cores(){
return state.teams.cores(this);
}
public String localized(){
return Core.bundle.get("team." + name() + ".name");
return Core.bundle.get("team." + name + ".name", name);
}
@Override
public int compareTo(Team team){
return Integer.compare(id, team.id);
}
@Override
public String toString(){
return name;
}
}

View File

@@ -1,61 +1,168 @@
package mindustry.game;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.*;
import mindustry.world.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.entities.type.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
/** Class for various team-based utilities. */
public class Teams{
private TeamData[] map = new TeamData[Team.all.length];
/** Maps team IDs to team data. */
private TeamData[] map = new TeamData[256];
/** Active teams. */
private Array<TeamData> active = new Array<>();
/**
* Register a team.
* @param team The team type enum.
* @param enemies The array of enemies of this team. Any team not in this array is considered neutral.
*/
public void add(Team team, Team... enemies){
map[team.ordinal()] = new TeamData(team, EnumSet.of(enemies));
public @Nullable CoreEntity closestEnemyCore(float x, float y, Team team){
for(TeamData data : active){
if(areEnemies(team, data.team)){
CoreEntity tile = Geometry.findClosest(x, y, data.cores);
if(tile != null){
return tile;
}
}
}
return null;
}
public @Nullable CoreEntity closestCore(float x, float y, Team team){
return Geometry.findClosest(x, y, get(team).cores);
}
public Array<Team> enemiesOf(Team team){
return get(team).enemies;
}
public boolean eachEnemyCore(Team team, Boolf<CoreEntity> ret){
for(TeamData data : active){
if(areEnemies(team, data.team)){
for(CoreEntity tile : data.cores){
if(ret.get(tile)){
return true;
}
}
}
}
return false;
}
public void eachEnemyCore(Team team, Cons<TileEntity> ret){
for(TeamData data : active){
if(areEnemies(team, data.team)){
for(TileEntity tile : data.cores){
ret.get(tile);
}
}
}
}
/** Returns team data by type. */
public TeamData get(Team team){
if(map[team.ordinal()] == null){
add(team, Array.with(Team.all).select(t -> t != team).toArray(Team.class));
if(map[Pack.u(team.id)] == null){
map[Pack.u(team.id)] = new TeamData(team);
}
return map[team.ordinal()];
return map[Pack.u(team.id)];
}
public Array<CoreEntity> playerCores(){
return get(state.rules.defaultTeam).cores;
}
/** Do not modify! */
public Array<CoreEntity> cores(Team team){
return get(team).cores;
}
/** Returns whether a team is active, e.g. whether it has any cores remaining. */
public boolean isActive(Team team){
//the enemy wave team is always active
return team == Vars.waveTeam || get(team).cores.size > 0;
}
/** Returns a set of all teams that are enemies of this team. */
public EnumSet<Team> enemiesOf(Team team){
return get(team).enemies;
return get(team).active();
}
/** Returns whether {@param other} is an enemy of {@param #team}. */
public boolean areEnemies(Team team, Team other){
return enemiesOf(team).contains(other);
//todo what about derelict?
return team != other;
}
/** Allocates a new array with the active teams.
* Never call in the main game loop.*/
public boolean canInteract(Team team, Team other){
return team == other || other == Team.derelict;
}
/** Do not modify. */
public Array<TeamData> getActive(){
return Array.select(map, t -> t != null);
return active;
}
public static class TeamData{
public final ObjectSet<Tile> cores = new ObjectSet<>();
public final EnumSet<Team> enemies;
public void registerCore(CoreEntity core){
TeamData data = get(core.getTeam());
//add core if not present
if(!data.cores.contains(core)){
data.cores.add(core);
}
//register in active list if needed
if(data.active() && !active.contains(data)){
active.add(data);
updateEnemies();
indexer.updateTeamIndex(data.team);
}
}
public void unregisterCore(CoreEntity entity){
TeamData data = get(entity.getTeam());
//remove core
data.cores.remove(entity);
//unregister in active list
if(!data.active()){
active.remove(data);
updateEnemies();
}
}
private void updateEnemies(){
if(!active.contains(get(state.rules.waveTeam))){
active.add(get(state.rules.waveTeam));
}
for(TeamData data : active){
data.enemies.clear();
for(TeamData other : active){
if(areEnemies(data.team, other.team)){
data.enemies.add(other.team);
}
}
}
}
public class TeamData{
public final Array<CoreEntity> cores = new Array<>();
public final Array<Team> enemies = new Array<>();
public final Team team;
public Queue<BrokenBlock> brokenBlocks = new Queue<>();
public TeamData(Team team, EnumSet<Team> enemies){
public TeamData(Team team){
this.team = team;
this.enemies = enemies;
}
public boolean active(){
return (team == state.rules.waveTeam && state.rules.waves) || cores.size > 0;
}
public boolean hasCore(){
return cores.size > 0;
}
public boolean noCores(){
return cores.isEmpty();
}
public CoreEntity core(){
return cores.first();
}
}

View File

@@ -10,6 +10,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -161,11 +162,11 @@ public class Tutorial{
},
withdraw(() -> event("withdraw")){
void begin(){
state.teams.get(defaultTeam).cores.first().entity.items.add(Items.copper, 10);
state.teams.playerCores().first().items.add(Items.copper, 10);
}
},
deposit(() -> event("deposit")),
waves(() -> state.wave > 2 && state.enemies() <= 0 && !spawner.isSpawning()){
waves(() -> state.wave > 2 && state.enemies <= 0 && !spawner.isSpawning()){
void begin(){
state.rules.waveTimer = true;
logic.runWave();
@@ -239,18 +240,18 @@ public class Tutorial{
//utility
static void placeBlocks(){
Tile core = state.teams.get(defaultTeam).cores.first();
TileEntity core = state.teams.playerCores().first();
for(int i = 0; i < blocksToBreak; i++){
world.removeBlock(world.ltile(core.x + blockOffset, core.y + i));
world.tile(core.x + blockOffset, core.y + i).setBlock(Blocks.scrapWall, defaultTeam);
world.ltile(core.tile.x + blockOffset, core.tile.y + i).remove();
world.tile(core.tile.x + blockOffset, core.tile.y + i).setBlock(Blocks.scrapWall, state.rules.defaultTeam);
}
}
static boolean blocksBroken(){
Tile core = state.teams.get(defaultTeam).cores.first();
TileEntity core = state.teams.playerCores().first();
for(int i = 0; i < blocksToBreak; i++){
if(world.tile(core.x + blockOffset, core.y + i).block() == Blocks.scrapWall){
if(world.tile(core.tile.x + blockOffset, core.tile.y + i).block() == Blocks.scrapWall){
return false;
}
}
@@ -270,7 +271,7 @@ public class Tutorial{
}
static int item(Item item){
return state.teams.get(defaultTeam).cores.isEmpty() ? 0 : state.teams.get(defaultTeam).cores.first().entity.items.get(item);
return state.rules.defaultTeam.data().noCores() ? 0 : state.rules.defaultTeam.core().items.get(item);
}
static boolean toggled(String name){

View File

@@ -9,6 +9,7 @@ import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.pooling.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
@@ -25,7 +26,7 @@ public class MinimapRenderer implements Disposable{
private Pixmap pixmap;
private Texture texture;
private TextureRegion region;
private Rectangle rect = new Rectangle();
private Rect rect = new Rect();
private float zoom = 4;
public MinimapRenderer(){
@@ -42,7 +43,7 @@ public class MinimapRenderer implements Disposable{
return pixmap;
}
public Texture getTexture(){
public @Nullable Texture getTexture(){
return texture;
}
@@ -70,8 +71,13 @@ public class MinimapRenderer implements Disposable{
region = new TextureRegion(texture);
}
public void drawEntities(float x, float y, float w, float h, boolean withLabels){
updateUnitArray();
public void drawEntities(float x, float y, float w, float h, float scaling, boolean withLabels){
if(!withLabels){
updateUnitArray();
}else{
units.clear();
Units.all(units::add);
}
float sz = baseSize * zoom;
float dx = (Core.camera.position.x / tilesize);
@@ -82,8 +88,14 @@ public class MinimapRenderer implements Disposable{
rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize);
for(Unit unit : units){
float rx = (unit.x - rect.x) / rect.width * w;
float ry = (unit.y - rect.y) / rect.width * h;
if(unit.isDead()) continue;
float rx = !withLabels ? (unit.x - rect.x) / rect.width * w : unit.x / (world.width() * tilesize) * w;
float ry = !withLabels ? (unit.y - rect.y) / rect.width * h : unit.y / (world.height() * tilesize) * h;
Draw.mixcol(unit.getTeam().color, 1f);
float scale = Scl.scl(1f) / 2f * scaling * 32f;
Draw.rect(unit.getIconRegion(), x + rx, y + ry, scale, scale, unit.rotation - 90);
Draw.reset();
if(withLabels && unit instanceof Player){
Player pl = (Player) unit;
@@ -92,19 +104,16 @@ public class MinimapRenderer implements Disposable{
drawLabel(x + rx, y + ry, pl.name, unit.getTeam().color);
}
}
Draw.color(unit.getTeam().color);
Fill.rect(x + rx, y + ry, Scl.scl(baseSize / 2f), Scl.scl(baseSize / 2f));
}
Draw.color();
Draw.reset();
}
public void drawEntities(float x, float y, float w, float h){
drawEntities(x, y, w, h, true);
drawEntities(x, y, w, h, 1f, true);
}
public TextureRegion getRegion(){
public @Nullable TextureRegion getRegion(){
if(texture == null) return null;
float sz = Mathf.clamp(baseSize * zoom, baseSize, Math.min(world.width(), world.height()));

View File

@@ -10,20 +10,19 @@ import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.input.*;
import mindustry.type.Category;
import mindustry.ui.Cicon;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.units.MechPad;
import mindustry.world.meta.BlockFlag;
import mindustry.world.blocks.units.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class OverlayRenderer{
private static final float indicatorLength = 14f;
private static final float spawnerMargin = tilesize*11f;
private static final Rectangle rect = new Rectangle();
private static final Rect rect = new Rect();
private float buildFadeTime;
public void drawBottom(){
@@ -95,17 +94,15 @@ public class OverlayRenderer{
Lines.stroke(buildFadeTime * 2f);
if(buildFadeTime > 0.005f){
for(Team enemy : state.teams.enemiesOf(player.getTeam())){
for(Tile core : state.teams.get(enemy).cores){
float dst = Mathf.dst(player.x, player.y, core.drawx(), core.drawy());
if(dst < state.rules.enemyCoreBuildRadius * 1.5f){
Draw.color(Color.darkGray);
Lines.circle(core.drawx(), core.drawy() - 2, state.rules.enemyCoreBuildRadius);
Draw.color(Pal.accent, enemy.color, 0.5f + Mathf.absin(Time.time(), 10f, 0.5f));
Lines.circle(core.drawx(), core.drawy(), state.rules.enemyCoreBuildRadius);
}
state.teams.eachEnemyCore(player.getTeam(), core -> {
float dst = core.dst(player);
if(dst < state.rules.enemyCoreBuildRadius * 2.2f){
Draw.color(Color.darkGray);
Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius);
Draw.color(Pal.accent, core.getTeam().color, 0.5f + Mathf.absin(Time.time(), 10f, 0.5f));
Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius);
}
}
});
}
Lines.stroke(2f);

View File

@@ -10,6 +10,7 @@ import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.core.GameState.*;
import mindustry.entities.traits.BuilderTrait.*;
@@ -135,22 +136,29 @@ public class DesktopInput extends InputHandler{
ui.listfrag.toggle();
}
if(((player.getClosestCore() == null && player.isDead()) || state.isPaused()) && !ui.chatfrag.shown()){
//move camera around
float camSpeed = !Core.input.keyDown(Binding.dash) ? 3f : 8f;
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta() * camSpeed));
if(Core.input.keyDown(Binding.mouse_move)){
Core.camera.position.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * 0.005f, -1, 1) * camSpeed;
Core.camera.position.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * 0.005f, -1, 1) * camSpeed;
}
}
if(Core.input.keyRelease(Binding.select)){
player.isShooting = false;
}
if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && (scene.getKeyboardFocus() == ui.minimap || !scene.hasDialog()) && !Core.scene.hasKeyboard() && !(scene.getKeyboardFocus() instanceof TextField)){
if(!ui.minimap.isShown()){
ui.minimap.show();
}else{
ui.minimap.hide();
}
if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && !scene.hasDialog() && !(scene.getKeyboardFocus() instanceof TextField)){
ui.minimapfrag.toggle();
}
if(state.is(State.menu) || Core.scene.hasDialog()) return;
//zoom camera
if(!Core.scene.hasScroll() && !ui.chatfrag.shown() && Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
if((!Core.scene.hasScroll() || Core.input.keyDown(Binding.diagonal_placement)) && !ui.chatfrag.shown() && Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
}
@@ -300,7 +308,7 @@ public class DesktopInput extends InputHandler{
}
}
if(Core.input.keyTap(Binding.clear_building)){
if(Core.input.keyTap(Binding.clear_building) || isPlacing()){
lastSchematic = null;
selectRequests.clear();
}
@@ -397,7 +405,7 @@ public class DesktopInput extends InputHandler{
}
if(mode == placing && block != null){
if(!overrideLineRotation && !Core.input.keyDown(Binding.diagonal_placement) && (selectX != cursorX || selectY != cursorY) && ((int) Core.input.axisTap(Binding.rotate) != 0)){
if(!overrideLineRotation && !Core.input.keyDown(Binding.diagonal_placement) && (selectX != cursorX || selectY != cursorY) && ((int)Core.input.axisTap(Binding.rotate) != 0)){
rotation = ((int)((Angles.angle(selectX, selectY, cursorX, cursorY) + 45) / 90f)) % 4;
overrideLineRotation = true;
}

View File

@@ -44,7 +44,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
/** Maximum line length. */
final static int maxLength = 100;
final static Vec2 stackTrns = new Vec2();
final static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
final static Rect r1 = new Rect(), r2 = new Rect();
/** Distance on the back from where items originate. */
final static float backTrns = 3f;
@@ -92,7 +92,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Tile tile){
if(player == null || player.timer == null || !player.timer.get(Player.timerTransfer, 40)) return;
if(player == null || player.timer == null) return;
if(net.server() && (player.item().amount <= 0 || player.isTransferring|| !Units.canInteract(player, tile))){
throw new ValidateException(player, "Player cannot transfer an item.");
}
@@ -402,7 +402,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
}
for(BrokenBlock req : state.teams.get(player.getTeam()).brokenBlocks){
for(BrokenBlock req : player.getTeam().data().brokenBlocks){
Block block = content.block(req.block);
if(block.bounds(req.x, req.y, Tmp.r2).overlaps(Tmp.r1)){
drawSelected(req.x, req.y, content.block(req.block), Pal.remove);
@@ -725,7 +725,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
public void tryDropItems(Tile tile, float x, float y){
if(!droppingItem || player.item().amount <= 0 || canTapPlayer(x, y) || state.isPaused() || !player.timer.check(Player.timerTransfer, 40)){
if(!droppingItem || player.item().amount <= 0 || canTapPlayer(x, y) || state.isPaused() ){
droppingItem = false;
return;
}

View File

@@ -77,7 +77,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else{
Tile tile = world.ltileWorld(x, y);
if(tile != null && tile.synthetic() && state.teams.areEnemies(player.getTeam(), tile.getTeam())){
if(tile != null && tile.synthetic() && player.getTeam().isEnemy(tile.getTeam())){
TileEntity entity = tile.entity;
player.setMineTile(null);
player.target = entity;
@@ -439,10 +439,12 @@ public class MobileInput extends InputHandler implements GestureListener{
@Override
public boolean touchDown(int screenX, int screenY, int pointer, KeyCode button){
if(state.is(State.menu) || player.isDead()) return false;
if(state.is(State.menu)) return false;
down = true;
if(player.isDead()) return false;
//get tile on cursor
Tile cursor = tileAt(screenX, screenY);

View File

@@ -20,7 +20,7 @@ public class JsonIO{
@Override
public void writeValue(Object value, Class knownType, Class elementType){
if(value instanceof mindustry.ctype.MappableContent){
if(value instanceof MappableContent){
try{
getWriter().value(((MappableContent)value).name);
}catch(IOException e){
@@ -95,6 +95,18 @@ public class JsonIO{
}
});
json.setSerializer(Team.class, new Serializer<Team>(){
@Override
public void write(Json json, Team object, Class knownType){
json.writeValue(object.id);
}
@Override
public Team read(Json json, JsonValue jsonData, Class type){
return Team.get(jsonData.asInt());
}
});
json.setSerializer(Block.class, new Serializer<Block>(){
@Override
public void write(Json json, Block object, Class knownType){

View File

@@ -1,194 +0,0 @@
package mindustry.io;
import arc.struct.*;
import arc.files.*;
import arc.graphics.*;
import arc.util.*;
import arc.util.serialization.*;
import mindustry.content.*;
import mindustry.ctype.ContentType;
import mindustry.game.*;
import mindustry.io.MapIO.*;
import mindustry.maps.*;
import mindustry.world.*;
import mindustry.world.LegacyColorMapper.*;
import mindustry.world.blocks.*;
import java.io.*;
import java.util.zip.*;
import static mindustry.Vars.*;
/** Map IO for the "old" .mmap format.
* Differentiate between legacy maps and new maps by checking the extension (or the header).*/
public class LegacyMapIO{
private static final ObjectMap<String, String> fallback = ObjectMap.of("alpha-dart-mech-pad", "dart-mech-pad");
private static final Json json = new Json();
public static Map readMap(Fi file, boolean custom) throws IOException{
try(DataInputStream stream = new DataInputStream(file.read(1024))){
StringMap tags = new StringMap();
//meta is uncompressed
int version = stream.readInt();
if(version != 1){
throw new IOException("Outdated legacy map format");
}
int build = stream.readInt();
short width = stream.readShort(), height = stream.readShort();
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
String name = stream.readUTF();
String value = stream.readUTF();
tags.put(name, value);
}
return new Map(file, width, height, tags, custom, version, build);
}
}
public static void readTiles(Map map, Tile[][] tiles) throws IOException{
readTiles(map, (x, y) -> tiles[x][y]);
}
public static void readTiles(Map map, TileProvider tiles) throws IOException{
readTiles(map.file, map.width, map.height, tiles);
}
private static void readTiles(Fi file, int width, int height, Tile[][] tiles) throws IOException{
readTiles(file, width, height, (x, y) -> tiles[x][y]);
}
private static void readTiles(Fi file, int width, int height, TileProvider tiles) throws IOException{
try(BufferedInputStream input = file.read(bufferSize)){
//read map
{
DataInputStream stream = new DataInputStream(input);
stream.readInt(); //version
stream.readInt(); //build
stream.readInt(); //width + height
byte tagAmount = stream.readByte();
for(int i = 0; i < tagAmount; i++){
stream.readUTF(); //key
stream.readUTF(); //val
}
}
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
try{
byte mapped = stream.readByte();
IntMap<Block> idmap = new IntMap<>();
IntMap<String> namemap = new IntMap<>();
for(int i = 0; i < mapped; i++){
byte type = stream.readByte();
short total = stream.readShort();
for(int j = 0; j < total; j++){
String name = stream.readUTF();
if(type == 1){
Block res = content.getByName(ContentType.block, fallback.get(name, name));
idmap.put(j, res == null ? Blocks.air : res);
namemap.put(j, fallback.get(name, name));
}
}
}
//read floor and create tiles first
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
int floorid = stream.readUnsignedByte();
int oreid = stream.readUnsignedByte();
int consecutives = stream.readUnsignedByte();
Tile tile = tiles.get(x, y);
tile.setFloor((Floor)idmap.get(floorid));
tile.setOverlay(idmap.get(oreid));
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
Tile newTile = tiles.get(newx, newy);
newTile.setFloor((Floor)idmap.get(floorid));
newTile.setOverlay(idmap.get(oreid));
}
i += consecutives;
}
//read blocks
for(int i = 0; i < width * height; i++){
int x = i % width, y = i / width;
int id = stream.readUnsignedByte();
Block block = idmap.get(id);
if(block == null) block = Blocks.air;
Tile tile = tiles.get(x, y);
//the spawn block is saved in the block tile layer in older maps, shift it to the overlay
if(block != Blocks.spawn){
tile.setBlock(block);
}else{
tile.setOverlay(block);
}
if(namemap.get(id, "").equals("part")){
stream.readByte(); //link
}else if(tile.entity != null){
byte tr = stream.readByte();
stream.readShort(); //read health (which is actually irrelevant)
byte team = Pack.leftByte(tr);
byte rotation = Pack.rightByte(tr);
tile.setTeam(Team.all[team]);
tile.entity.health = tile.block().health;
tile.rotation(rotation);
if(tile.block() == Blocks.liquidSource || tile.block() == Blocks.unloader || tile.block() == Blocks.sorter){
stream.readByte(); //these blocks have an extra config byte, read it
}
}else{ //no entity/part, read consecutives
int consecutives = stream.readUnsignedByte();
for(int j = i + 1; j < i + 1 + consecutives; j++){
int newx = j % width, newy = j / width;
tiles.get(newx, newy).setBlock(block);
}
i += consecutives;
}
}
}finally{
content.setTemporaryMapper(null);
}
}
}
}
/** Reads a pixmap in the 3.5 pixmap format. */
public static void readPixmap(Pixmap pixmap, Tiles tiles){
for(int x = 0; x < pixmap.getWidth(); x++){
for(int y = 0; y < pixmap.getHeight(); y++){
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
LegacyBlock block = LegacyColorMapper.get(color);
Tile tile = tiles.getn(x, y);
tile.setFloor(block.floor);
tile.setBlock(block.wall);
if(block.ore != null) tile.setOverlay(block.ore);
//place core
if(color == Color.rgba8888(Color.green)){
//actual core parts
tile.setBlock(Blocks.coreShard);
tile.setTeam(Team.sharded);
}
}
}
}
}

View File

@@ -10,6 +10,7 @@ import mindustry.core.*;
import mindustry.game.*;
import mindustry.maps.*;
import mindustry.world.*;
import mindustry.world.LegacyColorMapper.*;
import mindustry.world.blocks.storage.*;
import java.io.*;
@@ -91,7 +92,7 @@ public class MapIO{
public void setTeam(Team team){
super.setTeam(team);
if(block instanceof CoreBlock){
map.teams.add(team.ordinal());
map.teams.add(team.id);
}
}
};
@@ -145,11 +146,33 @@ public class MapIO{
public static int colorFor(Block floor, Block wall, Block ore, Team team){
if(wall.synthetic()){
return team.intColor;
return team.color.rgba();
}
return Color.rgba8888(wall.solid ? wall.color : ore == Blocks.air ? floor.color : ore.color);
}
/** Reads a pixmap in the 3.5 pixmap format. */
public static void readPixmap(Pixmap pixmap, Tiles tiles){
for(int x = 0; x < pixmap.getWidth(); x++){
for(int y = 0; y < pixmap.getHeight(); y++){
int color = pixmap.getPixel(x, pixmap.getHeight() - 1 - y);
LegacyBlock block = LegacyColorMapper.get(color);
Tile tile = tiles.getn(x, y);
tile.setFloor(block.floor);
tile.setBlock(block.wall);
if(block.ore != null) tile.setOverlay(block.ore);
//place core
if(color == Color.rgba8888(Color.green)){
//actual core parts
tile.setBlock(Blocks.coreShard);
tile.setTeam(Team.sharded);
}
}
}
}
interface TileProvider{
Tile get(int x, int y);
}

View File

@@ -217,7 +217,7 @@ public abstract class SaveVersion extends SaveFileReader{
Array<TeamData> data = state.teams.getActive();
stream.writeInt(data.size);
for(TeamData team : data){
stream.writeInt(team.team.ordinal());
stream.writeInt((int)team.team.id);
stream.writeInt(team.brokenBlocks.size);
for(BrokenBlock block : team.brokenBlocks){
stream.writeShort(block.x);
@@ -258,8 +258,8 @@ public abstract class SaveVersion extends SaveFileReader{
public void readEntities(DataInput stream) throws IOException{
int teamc = stream.readInt();
for(int i = 0; i < teamc; i++){
Team team = Team.all[stream.readInt()];
TeamData data = state.teams.get(team);
Team team = Team.get(stream.readInt());
TeamData data = team.data();
int blocks = stream.readInt();
for(int j = 0; j < blocks; j++){
data.brokenBlocks.addLast(new BrokenBlock(stream.readShort(), stream.readShort(), stream.readShort(), content.block(stream.readShort()).id, stream.readInt()));

View File

@@ -87,7 +87,7 @@ public class TypeIO{
@WriteClass(BaseUnit.class)
public static void writeBaseUnit(ByteBuffer buffer, BaseUnit unit){
buffer.put((byte)unit.getTeam().ordinal());
buffer.put((byte) (int)unit.getTeam().id);
buffer.putInt(unit.getID());
}
@@ -95,7 +95,7 @@ public class TypeIO{
public static BaseUnit readBaseUnit(ByteBuffer buffer){
byte tid = buffer.get();
int id = buffer.getInt();
return unitGroups[tid].getByID(id);
return unitGroup.getByID(id);
}
@WriteClass(Tile.class)
@@ -194,12 +194,12 @@ public class TypeIO{
@WriteClass(Team.class)
public static void writeTeam(ByteBuffer buffer, Team reason){
buffer.put((byte)reason.ordinal());
buffer.put((byte) (int)reason.id);
}
@ReadClass(Team.class)
public static Team readTeam(ByteBuffer buffer){
return Team.all[buffer.get()];
return Team.get(buffer.get());
}
@WriteClass(UnitCommand.class)

View File

@@ -23,7 +23,7 @@ public class CoreSpawnFilter extends GenerateFilter{
public void apply(Tiles tiles, GenerateInput in){
IntArray spawns = new IntArray();
for(Tile tile : tiles){
if(tile.getTeam() == defaultTeam && tile.block() instanceof CoreBlock){
if(tile.getTeam() == state.rules.defaultTeam && tile.block() instanceof CoreBlock){
spawns.add(tile.pos());
}
}
@@ -32,8 +32,7 @@ public class CoreSpawnFilter extends GenerateFilter{
int used = Math.min(spawns.size, amount);
for(int i = used; i < spawns.size; i++){
Tile tile = tiles.getp(spawns.get(i));
world.removeBlock(tile);
tiles.getp(spawns.get(i)).remove();
}
}

View File

@@ -68,7 +68,7 @@ public class MapGenerator extends Generator{
});
}
if(tile.block() instanceof CoreBlock && tile.getTeam() == defaultTeam){
if(tile.block() instanceof CoreBlock && tile.getTeam() == state.rules.defaultTeam){
schematics.placeLoadout(loadout, tile.x, tile.y);
anyCores = true;
}

File diff suppressed because one or more lines are too long

View File

@@ -333,8 +333,8 @@ public class ContentParser{
}
private void readBundle(ContentType type, String name, JsonValue value){
UnlockableContent cont = Vars.content.getByName(type, name) instanceof UnlockableContent ?
Vars.content.getByName(type, name) : null;
UnlockableContent cont = locate(type, name) instanceof UnlockableContent ?
locate(type, name) : null;
String entryName = cont == null ? type + "." + currentMod.name + "-" + name + "." : type + "." + cont.name + ".";
I18NBundle bundle = Core.bundle;

View File

@@ -0,0 +1,19 @@
package mindustry.mod;
/** Mod listing as a data class. */
public class ModListing{
public String repo, name, author, lastUpdated, description;
public int stars;
@Override
public String toString(){
return "ModListing{" +
"repo='" + repo + '\'' +
", name='" + name + '\'' +
", author='" + author + '\'' +
", lastUpdated='" + lastUpdated + '\'' +
", description='" + description + '\'' +
", stars=" + stars +
'}';
}
}

View File

@@ -2,7 +2,6 @@ package mindustry.mod;
import arc.*;
import arc.assets.*;
import arc.struct.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
@@ -10,6 +9,7 @@ import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.graphics.g2d.TextureAtlas.*;
import arc.scene.ui.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import arc.util.io.*;
@@ -85,6 +85,7 @@ public class Mods implements Loadable{
try{
mods.add(loadMod(dest));
requiresReload = true;
sortMods();
}catch(IOException e){
dest.delete();
throw e;
@@ -142,6 +143,17 @@ public class Mods implements Loadable{
@Override
public void loadSync(){
for(LoadedMod mod : mods){
//try to load icon for each mod that can have one
if(mod.root.child("icon.png").exists()){
try{
mod.iconTexture = new Texture(mod.root.child("icon.png"));
}catch(Throwable t){
Log.err("Failed to load icon for mod '" + mod.name + "'.", t);
}
}
}
if(packer == null) return;
Time.mark();
@@ -408,6 +420,7 @@ public class Mods implements Loadable{
//TODO make it less epic
Core.atlas = new TextureAtlas(Core.files.internal("sprites/sprites.atlas"));
mods.each(LoadedMod::dispose);
mods.clear();
Core.bundle = I18NBundle.createBundle(Core.files.internal("bundles/bundle"), Core.bundle.getLocale());
load();
@@ -499,10 +512,8 @@ public class Mods implements Loadable{
for(ContentType type : ContentType.all){
Fi folder = contentRoot.child(type.name().toLowerCase() + "s");
if(folder.exists()){
for(Fi file : folder.list()){
if(file.extension().equals("json") || file.extension().equals("hjson")){
runs.add(new LoadRun(type, file, mod));
}
for(Fi file : folder.findAll(f -> f.extension().equals("json") || f.extension().equals("hjson"))){
runs.add(new LoadRun(type, file, mod));
}
}
}
@@ -643,7 +654,7 @@ public class Mods implements Loadable{
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedMod implements Publishable{
public static class LoadedMod implements Publishable, Disposable{
/** The location of this mod's zip file/folder on the disk. */
public final Fi file;
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
@@ -664,6 +675,8 @@ public class Mods implements Loadable{
public ObjectSet<Content> erroredContent = new ObjectSet<>();
/** Current state of this mod. */
public ModState state = ModState.enabled;
/** Icon texture. Should be disposed. */
public @Nullable Texture iconTexture;
public LoadedMod(Fi file, Fi root, Mod main, ModMeta meta){
this.root = root;
@@ -701,6 +714,13 @@ public class Mods implements Loadable{
return Version.build >= Strings.parseInt(meta.minGameVersion, 0);
}
@Override
public void dispose(){
if(iconTexture != null){
iconTexture.dispose();
}
}
@Override
public String getSteamID(){
return Core.settings.getString(name + "-steamid", null);

View File

@@ -21,7 +21,8 @@ public class Scripts implements Disposable{
context.setClassShutter(type -> (ClassAccess.allowedClassNames.contains(type) || type.startsWith("$Proxy") ||
type.startsWith("adapter") || type.contains("PrintStream") ||
type.startsWith("mindustry")) && !type.equals("mindustry.mod.ClassAccess"));
context.getWrapFactory().setJavaPrimitiveWrap(false);
scope = new ImporterTopLevel(context);
wrapper = Core.files.internal("scripts/wrapper.js").readString();

View File

@@ -1,10 +1,11 @@
package mindustry.net;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import mindustry.Vars;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.type.*;
import static mindustry.Vars.headless;
import static mindustry.game.EventType.*;
@@ -14,16 +15,29 @@ public class Administration{
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
private Array<String> bannedIPs = new Array<>();
private Array<String> whitelist = new Array<>();
private Array<ChatFilter> chatFilters = new Array<>();
public Administration(){
Core.settings.defaults(
"strict", true,
"servername", "Server"
);
load();
}
/** Adds a chat filter. This will transform the chat messages of every player.
* This functionality can be used to implement things like swear filters and special commands.
* Note that commands (starting with /) are not filtered.*/
public void addChatFilter(ChatFilter filter){
chatFilters.add(filter);
}
/** Filters out a chat message. */
public @Nullable String filterMessage(Player player, String message){
String current = message;
for(ChatFilter f : chatFilters){
current = f.filter(player, message);
if(current == null) return null;
}
return current;
}
public int getPlayerLimit(){
return Core.settings.getInt("playerlimit", 0);
}
@@ -32,21 +46,12 @@ public class Administration{
Core.settings.putSave("playerlimit", limit);
}
public void setStrict(boolean on){
Core.settings.putSave("strict", on);
}
public boolean getStrict(){
return Core.settings.getBool("strict");
return Config.strict.bool();
}
public boolean allowsCustomClients(){
return Core.settings.getBool("allow-custom", !headless);
}
public void setCustomClients(boolean allowed){
Core.settings.put("allow-custom", allowed);
Core.settings.save();
return Config.allowCustomClients.bool();
}
/** Call when a player joins to update their information here. */
@@ -200,11 +205,7 @@ public class Administration{
}
public boolean isWhitelistEnabled(){
return Core.settings.getBool("whitelist", false);
}
public void setWhitelist(boolean enabled){
Core.settings.putSave("whitelist", enabled);
return Config.whitelist.bool();
}
public boolean isWhitelisted(String id, String usid){
@@ -314,6 +315,83 @@ public class Administration{
whitelist = Core.settings.getObject("whitelisted", Array.class, Array::new);
}
/** Server configuration definition. Each config value can be a string, boolean or number. */
public enum Config{
name("The server name as displayed on clients.", "Server", "servername"),
port("The port to host on.", Vars.port),
autoUpdate("Whether to auto-update and exit when a new bleeding-edge update arrives.", false),
showConnectMessages("Whether to display connect/disconnect messages.", true),
enableVotekick("Whether votekick is enabled.", true),
startCommands("Commands run at startup. This should be a comma-separated list.", ""),
crashReport("Whether to send crash reports.", false, "crashreport"),
logging("Whether to log everything to files.", true),
strict("Whether strict mode is on - corrects positions and prevents duplicate UUIDs.", true),
socketInput("Allows a local application to control this server through a local TCP socket.", false, "socket", () -> Events.fire(Trigger.socketConfigChanged)),
socketInputPort("The port for socket input.", 6859, () -> Events.fire(Trigger.socketConfigChanged)),
socketInputAddress("The bind address for socket input.", "localhost", () -> Events.fire(Trigger.socketConfigChanged)),
allowCustomClients("Whether custom clients are allowed to connect.", !headless, "allow-custom"),
whitelist("Whether the whitelist is used.", false),
motd("The message displayed to people on connection.", "off");
public static final Config[] all = values();
public final Object defaultValue;
public final String key, description;
final Runnable changed;
Config(String description, Object def){
this(description, def, null, null);
}
Config(String description, Object def, String key){
this(description, def, key, null);
}
Config(String description, Object def, Runnable changed){
this(description, def, null, changed);
}
Config(String description, Object def, String key, Runnable changed){
this.description = description;
this.key = key == null ? name() : key;
this.defaultValue = def;
this.changed = changed == null ? () -> {} : changed;
}
public boolean isNum(){
return defaultValue instanceof Integer;
}
public boolean isBool(){
return defaultValue instanceof Boolean;
}
public boolean isString(){
return defaultValue instanceof String;
}
public Object get(){
return Core.settings.get(key, defaultValue);
}
public boolean bool(){
return Core.settings.getBool(key, (Boolean)defaultValue);
}
public int num(){
return Core.settings.getInt(key, (Integer)defaultValue);
}
public String string(){
return Core.settings.getString(key, (String)defaultValue);
}
public void set(Object value){
Core.settings.putSave(key, value);
changed.run();
}
}
@Serialize
public static class PlayerInfo{
public String id;
@@ -334,6 +412,11 @@ public class Administration{
}
}
public interface ChatFilter{
/** @return the filtered message; a null string signals that the message should not be sent. */
@Nullable String filter(Player player, String message);
}
public static class TraceInfo{
public String ip, uuid;
public boolean modded, mobile;

View File

@@ -0,0 +1,173 @@
package mindustry.net;
import arc.*;
import arc.Net.*;
import arc.files.*;
import arc.func.*;
import arc.util.*;
import arc.util.async.*;
import arc.util.serialization.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.net.Administration.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import java.io.*;
import java.net.*;
import static mindustry.Vars.*;
/** Handles control of bleeding edge builds. */
public class BeControl{
private static final int updateInterval = 60 * 2;
private AsyncExecutor executor = new AsyncExecutor(1);
private boolean checkUpdates = true;
private boolean updateAvailable;
private String updateUrl;
private int updateBuild;
/** @return whether this is a bleeding edge build. */
public boolean active(){
return Version.type.equals("bleeding-edge");
}
public BeControl(){
if(active()){
Timer.schedule(() -> {
if(checkUpdates && !mobile){
checkUpdate(t -> {});
}
}, 1, updateInterval);
}
}
/** asynchronously checks for updates. */
public void checkUpdate(Boolc done){
Core.net.httpGet("https://api.github.com/repos/Anuken/MindustryBuilds/releases/latest", res -> {
if(res.getStatus() == HttpStatus.OK){
Jval val = Jval.read(res.getResultAsString());
int newBuild = Strings.parseInt(val.getString("tag_name", "0"));
if(newBuild > Version.build){
Jval asset = val.get("assets").asArray().find(v -> v.getString("name", "").startsWith(headless ? "Mindustry-BE-Server" : "Mindustry-BE-Desktop"));
String url = asset.getString("browser_download_url", "");
updateAvailable = true;
updateBuild = newBuild;
updateUrl = url;
showUpdateDialog();
Core.app.post(() -> done.get(true));
}else{
Core.app.post(() -> done.get(false));
}
}else{
Core.app.post(() -> done.get(false));
}
}, error -> {
if(!headless){
ui.showException(error);
}else{
error.printStackTrace();
}
});
}
/** @return whether a new update is available */
public boolean isUpdateAvailable(){
return updateAvailable;
}
/** shows the dialog for updating the game on desktop, or a prompt for doing so on the server */
public void showUpdateDialog(){
if(!updateAvailable) return;
if(!headless){
checkUpdates = false;
ui.showCustomConfirm(Core.bundle.format("be.update", "") + " " + updateBuild, "$be.update.confirm", "$ok", "$be.ignore", () -> {
boolean[] cancel = {false};
float[] progress = {0};
int[] length = {0};
Fi file = bebuildDirectory.child("client-be-" + updateBuild + ".jar");
FloatingDialog dialog = new FloatingDialog("$be.updating");
download(updateUrl, file, i -> length[0] = i, v -> progress[0] = v, () -> cancel[0], () -> {
try{
Runtime.getRuntime().exec(new String[]{"java", "-DlastBuild=" + Version.build, "-Dberestart", "-jar", file.absolutePath()});
System.exit(0);
}catch(IOException e){
ui.showException(e);
}
}, e -> {
dialog.hide();
ui.showException(e);
});
dialog.cont.add(new Bar(() -> length[0] == 0 ? Core.bundle.get("be.updating") : (int)(progress[0] * length[0]) / 1024/ 1024 + "/" + length[0]/1024/1024 + " MB", () -> Pal.accent, () -> progress[0])).width(400f).height(70f);
dialog.buttons.addImageTextButton("$cancel", Icon.cancelSmall, () -> {
cancel[0] = true;
dialog.hide();
}).size(210f, 64f);
dialog.setFillParent(false);
dialog.show();
}, () -> checkUpdates = false);
}else{
Log.info("&lcA new update is available: &lyBleeding Edge build {0}", updateBuild);
if(Config.autoUpdate.bool()){
Log.info("&lcAuto-downloading next version...");
try{
//download new file from github
Fi source = Fi.get(BeControl.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
Fi dest = source.sibling("server-be-" + updateBuild + ".jar");
download(updateUrl, dest,
len -> Core.app.post(() -> Log.info("&ly| Size: {0} MB.", Strings.fixed((float)len / 1024 / 1024, 2))),
progress -> {},
() -> false,
() -> Core.app.post(() -> {
netServer.kickAll(KickReason.serverRestarting);
Threads.sleep(32);
Log.info("&lcVersion downloaded, exiting. Note that if you are not using a auto-restart script, the server will not restart automatically.");
//replace old file with new
dest.copyTo(source);
dest.delete();
System.exit(2); //this will cause a restart if using the script
}),
Throwable::printStackTrace);
}catch(Exception e){
e.printStackTrace();
}
}
checkUpdates = false;
}
}
private void download(String furl, Fi dest, Intc length, Floatc progressor, Boolp canceled, Runnable done, Cons<Throwable> error){
executor.submit(() -> {
try{
HttpURLConnection con = (HttpURLConnection)new URL(furl).openConnection();
BufferedInputStream in = new BufferedInputStream(con.getInputStream());
OutputStream out = dest.write(false, 4096);
byte[] data = new byte[4096];
long size = con.getContentLength();
long counter = 0;
length.get((int)size);
int x;
while((x = in.read(data, 0, data.length)) >= 0 && !canceled.get()){
counter += x;
progressor.get((float)counter / (float)size);
out.write(data, 0, x);
}
out.close();
in.close();
if(!canceled.get()) done.run();
}catch(Throwable e){
error.get(e);
}
});
}
}

View File

@@ -27,7 +27,9 @@ public class CrashSender{
exception.printStackTrace();
//don't create crash logs for custom builds, as it's expected
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))) return;
if(Version.build == -1 || (System.getProperty("user.name").equals("anuke") && "release".equals(Version.modifier))){
ret();
}
//attempt to load version regardless
if(Version.number == 0){
@@ -63,7 +65,7 @@ public class CrashSender{
try{
//check crash report setting
if(!Core.settings.getBool("crashreport", true)){
return;
ret();
}
}catch(Throwable ignored){
//if there's no settings init we don't know what the user wants but chances are it's an important crash, so send it anyway
@@ -72,14 +74,14 @@ public class CrashSender{
try{
//check any mods - if there are any, don't send reports
if(Vars.mods != null && !Vars.mods.list().isEmpty()){
return;
ret();
}
}catch(Throwable ignored){
}
//do not send exceptions that occur for versions that can't be parsed
if(Version.number == 0){
return;
ret();
}
boolean netActive = false, netServer = false;
@@ -130,12 +132,16 @@ public class CrashSender{
while(!sent[0]){
Thread.sleep(30);
}
}catch(InterruptedException ignored){
}
}catch(InterruptedException ignored){}
}catch(Throwable death){
death.printStackTrace();
System.exit(1);
}
ret();
}
private static void ret(){
System.exit(1);
}
private static void httpPost(String url, String content, Cons<HttpResponse> success, Cons<Throwable> failure){

View File

@@ -1,5 +1,6 @@
package mindustry.net;
import mindustry.*;
import mindustry.game.*;
public class Host{
@@ -11,7 +12,7 @@ public class Host{
public final int version;
public final String versionType;
public final Gamemode mode;
public int ping;
public int ping, port = Vars.port;
public Host(String name, String address, String mapname, int wave, int players, int version, String versionType, Gamemode mode, int playerLimit){
this.name = name;

View File

@@ -1,12 +1,12 @@
package mindustry.net;
import arc.*;
import arc.util.*;
import mindustry.core.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.io.*;
import mindustry.maps.Map;
import mindustry.net.Administration.*;
import java.io.*;
import java.nio.*;
@@ -62,7 +62,7 @@ public class NetworkIO{
}
public static ByteBuffer writeServerData(){
String name = (headless ? Core.settings.getString("servername") : player.name);
String name = (headless ? Config.name.string() : player.name);
String map = world.getMap() == null ? "None" : world.getMap().name();
ByteBuffer buffer = ByteBuffer.allocate(256);

View File

@@ -15,7 +15,8 @@ public class Packets{
public enum KickReason{
kick, clientOutdated, serverOutdated, banned, gameover(true), recentKick,
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch, whitelist, playerLimit;
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch,
whitelist, playerLimit, serverRestarting;
public final boolean quiet;

View File

@@ -55,6 +55,8 @@ public class Weapon{
public float shotDelay = 0;
/** whether shooter rotation is ignored when shooting. */
public boolean ignoreRotation = false;
/** if turnCursor is false for a mech, how far away will the weapon target. */
public float targetDistance = 1f;
public Sound shootSound = Sounds.pew;

View File

@@ -12,7 +12,7 @@ import arc.util.pooling.*;
import mindustry.gen.*;
public class Bar extends Element{
private static Rectangle scissor = new Rectangle();
private static Rect scissor = new Rect();
private Floatp fraction;
private String name = "";

View File

@@ -39,9 +39,9 @@ public class ItemsDisplay extends Table{
private String format(Item item){
builder.setLength(0);
builder.append(ui.formatAmount(data.items().get(item, 0)));
if(!state.is(State.menu) && !state.teams.get(player.getTeam()).cores.isEmpty() && state.teams.get(player.getTeam()).cores.first().entity != null && state.teams.get(player.getTeam()).cores.first().entity.items.get(item) > 0){
if(!state.is(State.menu) && player.getTeam().data().hasCore() && player.getTeam().core().items.get(item) > 0){
builder.append(" [unlaunched]+ ");
builder.append(ui.formatAmount(state.teams.get(player.getTeam()).cores.first().entity.items.get(item)));
builder.append(ui.formatAmount(state.teams.get(player.getTeam()).core().items.get(item)));
}
return builder.toString();
}

View File

@@ -36,7 +36,7 @@ public class Minimap extends Table{
Draw.rect(renderer.minimap.getRegion(), x + width / 2f, y + height / 2f, width, height);
if(renderer.minimap.getTexture() != null){
renderer.minimap.drawEntities(x, y, width, height, false);
renderer.minimap.drawEntities(x, y, width, height, 0.75f, false);
}
}
}).size(140f);
@@ -83,7 +83,7 @@ public class Minimap extends Table{
@Override
public void clicked(InputEvent event, float x, float y){
ui.minimap.show();
ui.minimapfrag.toggle();
}
});

View File

@@ -142,6 +142,7 @@ public class CustomRulesDialog extends FloatingDialog{
check("$rules.reactorexplosions", b -> rules.reactorExplosions = b, () -> rules.reactorExplosions);
number("$rules.buildcostmultiplier", false, f -> rules.buildCostMultiplier = f, () -> rules.buildCostMultiplier, () -> !rules.infiniteResources);
number("$rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier);
number("$rules.blockhealthmultiplier", f -> rules.blockHealthMultiplier = f, () -> rules.blockHealthMultiplier);
main.addButton("$configure",
() -> loadoutDialog.show(Blocks.coreShard.itemCapacity, rules.loadout,

View File

@@ -33,7 +33,7 @@ public class DeployDialog extends FloatingDialog{
private final float nodeSize = Scl.scl(230f);
private ObjectSet<ZoneNode> nodes = new ObjectSet<>();
private ZoneInfoDialog info = new ZoneInfoDialog();
private Rectangle bounds = new Rectangle();
private Rect bounds = new Rect();
private View view = new View();
public DeployDialog(){

View File

@@ -288,7 +288,12 @@ public class JoinDialog extends FloatingDialog{
local.table(Tex.button, t -> t.label(() -> "[accent]" + Core.bundle.get("hosts.discovering.any") + Strings.animated(Time.time(), 4, 10f, ".")).pad(10f)).growX();
net.discoverServers(this::addLocalHost, this::finishLocalHosts);
for(String host : defaultServers){
net.pingHost(host, port, this::addLocalHost, e -> {});
String resaddress = host.contains(":") ? host.split(":")[0] : host;
int resport = host.contains(":") ? Strings.parseInt(host.split(":")[1]) : port;
net.pingHost(resaddress, resport, res -> {
res.port = resport;
addLocalHost(res);
}, e -> {});
}
}
@@ -314,7 +319,7 @@ public class JoinDialog extends FloatingDialog{
local.row();
TextButton button = local.addButton("", Styles.cleart, () -> safeConnect(host.address, port, host.version))
TextButton button = local.addButton("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
.width(w).pad(5f).get();
button.clearChildren();
buildServer(host, button);
@@ -362,7 +367,7 @@ public class JoinDialog extends FloatingDialog{
servers = Core.settings.getObject("server-list", Array.class, Array::new);
//get servers
Core.net.httpGet(serverJsonURL, result -> {
Core.net.httpGet(becontrol.active() ? serverJsonBeURL : serverJsonURL, result -> {
try{
Jval val = Jval.read(result.getResultAsString());
Core.app.post(() -> {

View File

@@ -32,7 +32,7 @@ public class ModsDialog extends FloatingDialog{
buttons.row();
buttons.addImageTextButton("$mods.guide", Icon.wiki,
buttons.addImageTextButton("$mods.guide", Icon.link,
() -> Core.net.openURI(modGuideURL))
.size(210, 64f);

View File

@@ -163,7 +163,7 @@ public class SchematicsDialog extends FloatingDialog{
setup();
ui.showInfoFade("$schematic.saved");
showInfo(s);
}catch(Exception e){
}catch(Throwable e){
ui.showException(e);
}
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null || !Core.app.getClipboardText().startsWith(schematicBaseStart));

View File

@@ -229,6 +229,7 @@ public class SettingsMenuDialog extends SettingsDialog{
game.checkPref("savecreate", true);
game.checkPref("blockreplace", true);
game.checkPref("conveyorpathfinding", true);
game.checkPref("coreselect", false);
game.checkPref("hints", true);
if(!mobile){
game.checkPref("buildautopause", false);

View File

@@ -31,7 +31,7 @@ public class TechTreeDialog extends FloatingDialog{
private final float nodeSize = Scl.scl(60f);
private ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
private TechTreeNode root = new TechTreeNode(TechTree.root, null);
private Rectangle bounds = new Rectangle();
private Rect bounds = new Rect();
private ItemsDisplay items;
private View view;
@@ -123,7 +123,7 @@ public class TechTreeDialog extends FloatingDialog{
miny = Math.min(n.y - n.height/2f, miny);
maxy = Math.max(n.y + n.height/2f, maxy);
}
bounds = new Rectangle(minx, miny, maxx - minx, maxy - miny);
bounds = new Rect(minx, miny, maxx - minx, maxy - miny);
bounds.y += nodeSize*1.5f;
}

View File

@@ -36,7 +36,7 @@ public class BlockInventoryFragment extends Fragment{
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Tile tile, Item item, int amount){
if(player == null || tile == null || !player.timer.get(Player.timerTransfer, 20) || !tile.interactable(player.getTeam())) return;
if(player == null || tile == null || !tile.interactable(player.getTeam())) return;
if(!Units.canInteract(player, tile)) return;
int removed = tile.block().removeStack(tile, item, amount);

View File

@@ -168,7 +168,7 @@ public class HudFragment extends Fragment{
t.table(teams -> {
teams.left();
int i = 0;
for(Team team : Team.all){
for(Team team : Team.base()){
ImageButton button = teams.addImageButton(Tex.whiteui, Styles.clearTogglePartiali, 40f, () -> Call.setPlayerTeamEditor(player, team))
.size(50f).margin(6f).get();
button.getImageCell().grow();
@@ -287,7 +287,7 @@ public class HudFragment extends Fragment{
});
t.top().visible(() -> {
if(state.is(State.menu) || state.teams.get(player.getTeam()).cores.size == 0 || state.teams.get(player.getTeam()).cores.first().entity == null){
if(state.is(State.menu) || !state.teams.get(player.getTeam()).hasCore()){
coreAttackTime[0] = 0f;
return false;
}
@@ -557,7 +557,7 @@ public class HudFragment extends Fragment{
}
private boolean canLaunch(){
return inLaunchWave() && state.enemies() <= 0;
return inLaunchWave() && state.enemies <= 0;
}
private void toggleMenus(){
@@ -604,7 +604,7 @@ public class HudFragment extends Fragment{
if(inLaunchWave()){
builder.append("[#");
Tmp.c1.set(Color.white).lerp(state.enemies() > 0 ? Color.white : Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)).toString(builder);
Tmp.c1.set(Color.white).lerp(state.enemies > 0 ? Color.white : Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)).toString(builder);
builder.append("]");
if(!canLaunch()){
@@ -618,18 +618,18 @@ public class HudFragment extends Fragment{
builder.append("[]\n");
}
if(state.enemies() > 0){
if(state.enemies() == 1){
builder.append(enemyf.get(state.enemies()));
if(state.enemies > 0){
if(state.enemies == 1){
builder.append(enemyf.get(state.enemies));
}else{
builder.append(enemiesf.get(state.enemies()));
builder.append(enemiesf.get(state.enemies));
}
builder.append("\n");
}
if(state.rules.waveTimer){
builder.append((state.rules.waitForWaveToEnd && unitGroups[waveTeam.ordinal()].size() > 0) ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60))));
}else if(state.enemies() == 0){
builder.append((state.rules.waitForWaveToEnd && state.enemies > 0 ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60)))));
}else if(state.enemies == 0){
builder.append(Core.bundle.get("waiting"));
}
@@ -646,7 +646,7 @@ public class HudFragment extends Fragment{
}
private boolean canSkipWave(){
return state.rules.waves && ((net.server() || player.isAdmin) || !net.active()) && state.enemies() == 0 && !spawner.isSpawning() && !state.rules.tutorial;
return state.rules.waves && ((net.server() || player.isAdmin) || !net.active()) && state.enemies == 0 && !spawner.isSpawning() && !state.rules.tutorial;
}
private void addPlayButton(Table table){

View File

@@ -59,6 +59,18 @@ public class MenuFragment extends Fragment{
if(mobile){
parent.fill(c -> c.bottom().left().addButton("", Styles.infot, ui.about::show).size(84, 45));
parent.fill(c -> c.bottom().right().addButton("", Styles.discordt, ui.discord::show).size(84, 45));
}else if(becontrol.active()){
parent.fill(c -> c.bottom().right().addImageTextButton("$be.check", Icon.refreshSmall, () -> {
ui.loadfrag.show();
becontrol.checkUpdate(result -> {
ui.loadfrag.hide();
if(!result){
ui.showInfo("$be.noupdates");
}
});
}).size(200, 60).update(t -> {
t.getLabel().setColor(becontrol.isUpdateAvailable() ? Tmp.c1.set(Color.white).lerp(Pal.accent, Mathf.absin(5f, 1f)) : Color.white);
}));
}
String versionText = "[#ffffffba]" + ((Version.build == -1) ? "[#fc8140aa]custom build" : (Version.type.equals("official") ? Version.modifier : Version.type) + " build " + Version.build + (Version.revision == 0 ? "" : "." + Version.revision));

View File

@@ -0,0 +1,114 @@
package mindustry.ui.fragments;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class MinimapFragment extends Fragment{
private boolean shown;
private float panx, pany, zoom = 1f, lastZoom = -1;
private float baseSize = Scl.scl(5f);
private Element elem;
@Override
public void build(Group parent){
elem = parent.fill((x, y, w, h) -> {
w = Core.graphics.getWidth();
h = Core.graphics.getHeight();
float size = baseSize * zoom * world.width();
Draw.color(Color.black);
Fill.crect(x, y, w, h);
if(renderer.minimap.getTexture() != null){
Draw.color();
float ratio = (float)renderer.minimap.getTexture().getHeight() / renderer.minimap.getTexture().getWidth();
TextureRegion reg = Draw.wrap(renderer.minimap.getTexture());
Draw.rect(reg, w/2f + panx*zoom, h/2f + pany*zoom, size, size * ratio);
renderer.minimap.drawEntities(w/2f + panx*zoom - size/2f, h/2f + pany*zoom - size/2f * ratio, size, size * ratio, zoom, true);
}
Draw.reset();
});
elem.visible(() -> shown);
elem.update(() -> {
elem.requestKeyboard();
elem.requestScroll();
elem.setFillParent(true);
elem.setBounds(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight());
if(Core.input.keyTap(Binding.menu)){
shown = false;
}
});
elem.touchable(Touchable.enabled);
elem.addListener(new ElementGestureListener(){
@Override
public void zoom(InputEvent event, float initialDistance, float distance){
if(lastZoom < 0){
lastZoom = zoom;
}
zoom = Mathf.clamp(distance / initialDistance * lastZoom, 0.25f, 10f);
}
@Override
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY){
panx += deltaX / zoom;
pany += deltaY / zoom;
}
@Override
public void touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
super.touchDown(event, x, y, pointer, button);
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
lastZoom = zoom;
}
});
elem.addListener(new InputListener(){
@Override
public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY){
zoom = Mathf.clamp(zoom - amountY / 10f * zoom, 0.25f, 10f);
return true;
}
});
parent.fill(t -> {
t.setFillParent(true);
t.visible(() -> shown);
t.update(() -> t.setBounds(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight()));
t.add("$minimap").style(Styles.outlineLabel).pad(10f);
t.row();
t.add().growY();
t.row();
t.addImageTextButton("$back", Icon.backSmall, () -> shown = false).size(220f, 60f).pad(10f);
});
}
public boolean shown(){
return shown;
}
public void toggle(){
shown = !shown;
}
}

View File

@@ -8,6 +8,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.net.*;
@@ -65,7 +66,7 @@ public class PlayerListFragment extends Fragment{
float h = 74f;
playerGroup.all().sort((p1, p2) -> p1.getTeam().compareTo(p2.getTeam()));
playerGroup.all().sort(Structs.comparing(Unit::getTeam));
playerGroup.all().each(user -> {
NetConnection connection = user.con;
@@ -129,7 +130,7 @@ public class PlayerListFragment extends Fragment{
t.addImageButton(Icon.zoomSmall, Styles.clearPartiali, () -> Call.onAdminRequest(user, AdminAction.trace));
}).padRight(12).size(bs + 10f, bs);
}else if((!user.isLocal && !user.isAdmin) && net.client() && playerGroup.size() >= 3){ //votekick
}else if((!user.isLocal && !user.isAdmin) && net.client() && playerGroup.size() >= 3 && player.getTeam() != user.getTeam()){ //votekick
button.add().growY();
button.addImageButton(Icon.banSmall, Styles.clearPartiali,

View File

@@ -45,7 +45,7 @@ public class ScriptConsoleFragment extends Table{
font = Fonts.def;
visible(() -> {
if(input.keyTap(Binding.console) && !Vars.net.client() && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
if(input.keyTap(Binding.console) && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
shown = !shown;
if(shown && !open && enableConsole){
toggle();
@@ -53,7 +53,7 @@ public class ScriptConsoleFragment extends Table{
clearChatInput();
}
return shown && !Vars.net.active();
return shown && Vars.net.active();
});
update(() -> {

View File

@@ -66,8 +66,8 @@ public class BranchTreeLayout implements TreeLayout{
}
}
public Rectangle getBounds(){
return new Rectangle(boundsLeft, boundsBottom, boundsRight - boundsLeft, boundsTop - boundsBottom);
public Rect getBounds(){
return new Rect(boundsLeft, boundsBottom, boundsRight - boundsLeft, boundsTop - boundsBottom);
}
private void calcSizeOfLevels(TreeNode node, int level){

View File

@@ -864,7 +864,7 @@ public class Block extends BlockStorage{
return ((size + 1) % 2) * tilesize / 2f;
}
public Rectangle bounds(int x, int y, Rectangle rect){
public Rect bounds(int x, int y, Rect rect){
return rect.setSize(size * tilesize).setCenter(x * tilesize + offset(), y * tilesize + offset());
}

View File

@@ -38,7 +38,7 @@ public class Build{
Block previous = tile.block();
Block sub = BuildBlock.get(previous.size);
world.setBlock(tile, sub, team, rotation);
tile.set(sub, team, rotation);
tile.<BuildEntity>ent().setDeconstruct(previous);
tile.entity.health = tile.entity.maxHealth() * prevPercent;
@@ -60,7 +60,7 @@ public class Build{
Block previous = tile.block();
Block sub = BuildBlock.get(result.size);
world.setBlock(tile, sub, team, rotation);
tile.set(sub, team, rotation);
tile.<BuildEntity>ent().setConstruct(previous, result);
Core.app.post(() -> Events.fire(new BlockBuildBeginEvent(tile, team, false)));
@@ -72,7 +72,7 @@ public class Build{
return false;
}
if(state.rules.bannedBlocks.contains(type) && !(state.rules.waves && team == waveTeam)){
if(state.rules.bannedBlocks.contains(type) && !(state.rules.waves && team == state.rules.waveTeam)){
return false;
}
@@ -80,13 +80,8 @@ public class Build{
return false;
}
//check for enemy cores
for(Team enemy : state.teams.enemiesOf(team)){
for(Tile core : state.teams.get(enemy).cores){
if(Mathf.dst(x * tilesize + type.offset(), y * tilesize + type.offset(), core.drawx(), core.drawy()) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f){
return false;
}
}
if(state.teams.eachEnemyCore(team, core -> Mathf.dst(x * tilesize + type.offset(), y * tilesize + type.offset(), core.x, core.y) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f)){
return false;
}
Tile tile = world.tile(x, y);

View File

@@ -16,7 +16,7 @@ public class CachedTile extends Tile{
@Override
public Team getTeam(){
return Team.all[getTeamID()];
return Team.get(getTeamID());
}
@Override

View File

@@ -142,11 +142,11 @@ public class Tile implements Position, TargetTrait{
@Override
public Team getTeam(){
return Team.all[link().team];
return Team.get(link().team);
}
public void setTeam(Team team){
this.team = (byte)team.ordinal();
this.team = (byte) team.id;
}
public byte getTeamID(){
@@ -156,7 +156,7 @@ public class Tile implements Position, TargetTrait{
public void setBlock(@NonNull Block type, Team team, int rotation){
preChanged();
this.block = type;
this.team = (byte)team.ordinal();
this.team = (byte) team.id;
this.rotation = (byte)Mathf.mod(rotation, 4);
changed();
}
@@ -186,6 +186,35 @@ public class Tile implements Position, TargetTrait{
setOverlay(overlay);
}
public void remove(){
link().getLinkedTiles(other -> other.setBlock(Blocks.air));
}
public void set(Block block, Team team){
set(block, team, 0);
}
public void set(Block block, Team team, int rotation){
setBlock(block, team, rotation);
if(block.isMultiblock()){
int offsetx = -(block.size - 1) / 2;
int offsety = -(block.size - 1) / 2;
for(int dx = 0; dx < block.size; dx++){
for(int dy = 0; dy < block.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);
}
}
}
}
}
}
public byte rotation(){
return rotation;
}
@@ -228,7 +257,7 @@ public class Tile implements Position, TargetTrait{
}
public boolean solid(){
return block.solid || block.isSolidFor(this) || (isLinked() && link().solid());
return block.solid || block.isSolidFor(this) || (isLinked() && link() != this && link().solid());
}
public boolean breakable(){
@@ -240,7 +269,7 @@ public class Tile implements Position, TargetTrait{
}
public boolean isEnemyCheat(){
return getTeam() == waveTeam && state.rules.enemyCheat;
return getTeam() == state.rules.waveTeam && state.rules.enemyCheat;
}
public boolean isLinked(){
@@ -298,7 +327,7 @@ public class Tile implements Position, TargetTrait{
return tmpArray;
}
public Rectangle getHitbox(Rectangle rect){
public Rect getHitbox(Rect rect){
return rect.setSize(block().size * tilesize).setCenter(drawx(), drawy());
}
@@ -344,10 +373,10 @@ public class Tile implements Position, TargetTrait{
}
public boolean interactable(Team team){
return getTeam() == Team.derelict || team == getTeam();
return state.teams.canInteract(team, getTeam());
}
public Item drop(){
public @Nullable Item drop(){
return overlay == Blocks.air || overlay.itemDrop == null ? floor.itemDrop : overlay.itemDrop;
}

View File

@@ -57,7 +57,7 @@ public class BuildBlock extends Block{
public static void onDeconstructFinish(Tile tile, Block block, int builderID){
Team team = tile.getTeam();
Effects.effect(Fx.breakBlock, tile.drawx(), tile.drawy(), block.size);
world.removeBlock(tile);
tile.remove();
Events.fire(new BlockBuildEndEvent(tile, playerGroup.getByID(builderID), team, true));
if(shouldPlay()) Sounds.breaks.at(tile, calcPitch(false));
}
@@ -66,7 +66,7 @@ public class BuildBlock extends Block{
public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team, boolean skipConfig){
if(tile == null) return;
float healthf = tile.entity == null ? 1f : tile.entity.healthf();
world.setBlock(tile, block, team, rotation);
tile.set(block, team, rotation);
if(tile.entity != null){
tile.entity.health = block.health * healthf;
}
@@ -171,9 +171,9 @@ public class BuildBlock extends Block{
return;
}
if(entity.previous == null) return;
if(entity.previous == null || entity.cblock == null) return;
if(Core.atlas.isFound(entity.previous.icon(mindustry.ui.Cicon.full))){
if(Core.atlas.isFound(entity.previous.icon(Cicon.full))){
Draw.rect(entity.previous.icon(Cicon.full), tile.drawx(), tile.drawy(), entity.previous.rotate ? tile.rotation() * 90 : 0);
}
}
@@ -257,7 +257,7 @@ public class BuildBlock extends Block{
if(cblock != null){
ItemStack[] requirements = cblock.requirements;
if(requirements.length != accumulator.length || totalAccumulator.length != requirements.length){
setDeconstruct(previous);
setDeconstruct(cblock);
}
//make sure you take into account that you can't deconstruct more than there is deconstructed
@@ -337,16 +337,17 @@ public class BuildBlock extends Block{
}
public void setDeconstruct(Block previous){
if(previous == null) return;
this.previous = previous;
this.progress = 1f;
if(previous.buildCost >= 0.01f){
this.cblock = previous;
this.accumulator = new float[previous.requirements.length];
this.totalAccumulator = new float[previous.requirements.length];
this.buildCost = previous.buildCost * state.rules.buildCostMultiplier;
}else{
this.buildCost = 20f; //default no-requirement build cost is 20
}
this.accumulator = new float[previous.requirements.length];
this.totalAccumulator = new float[previous.requirements.length];
}
@Override

View File

@@ -5,6 +5,7 @@ import arc.math.*;
import mindustry.entities.type.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.net;
@@ -20,7 +21,7 @@ public class RespawnBlock{
Draw.reset();
if(player != null){
TextureRegion region = player.getIconRegion();
TextureRegion region = to.icon(Cicon.full);
Draw.color(0f, 0f, 0f, 0.4f * progress);
Draw.rect("circle-shadow", tile.drawx(), tile.drawy(), region.getWidth() / 3f, region.getWidth() / 3f);

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