Merge branch '6.0' of https://github.com/Anuken/Mindustry into object-config

# Conflicts:
#	core/src/mindustry/entities/traits/BuilderTrait.java
#	core/src/mindustry/entities/type/TileEntity.java
#	core/src/mindustry/game/EventType.java
#	core/src/mindustry/game/Schematics.java
#	core/src/mindustry/input/InputHandler.java
#	core/src/mindustry/io/TypeIO.java
#	core/src/mindustry/world/Block.java
#	core/src/mindustry/world/blocks/distribution/Sorter.java
This commit is contained in:
Anuken
2020-03-03 21:33:03 -05:00
1356 changed files with 28653 additions and 24877 deletions

View File

@@ -18,6 +18,7 @@ import mindustry.graphics.*;
import mindustry.maps.*;
import mindustry.mod.*;
import mindustry.net.Net;
import mindustry.ui.*;
import static arc.Core.*;
import static mindustry.Vars.*;
@@ -32,6 +33,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void setup(){
Events.fire(new ClientCreateEvent());
Vars.loadLogger();
Vars.loadFileLogger();
Vars.platform = this;
@@ -55,14 +58,15 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Vars.net = new Net(platform.getNet());
mods = new Mods();
UI.loadSystemCursors();
Fonts.loadSystemCursors();
assets.load(new Vars());
UI.loadDefaultFont();
Fonts.loadDefaultFont();
assets.load(new AssetDescriptor<>("sprites/sprites.atlas", TextureAtlas.class)).loaded = t -> {
atlas = (TextureAtlas)t;
Fonts.mergeFontAtlas(atlas);
};
assets.loadRun("maps", Map.class, () -> maps.loadPreviews());
@@ -88,10 +92,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
assets.load(mods);
assets.load(schematics);
assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init();
content.load();
});
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> content.load());
}
@Override

View File

@@ -13,9 +13,6 @@ import arc.util.io.*;
import mindustry.ai.*;
import mindustry.core.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
@@ -24,7 +21,6 @@ import mindustry.maps.*;
import mindustry.mod.*;
import mindustry.net.Net;
import mindustry.net.*;
import mindustry.world.blocks.defense.ForceProjector.*;
import java.io.*;
import java.nio.charset.*;
@@ -32,7 +28,6 @@ import java.util.*;
import static arc.Core.settings;
@SuppressWarnings("unchecked")
public class Vars implements Loadable{
/** Whether to load locales.*/
public static boolean loadLocales = true;
@@ -60,7 +55,7 @@ public class Vars implements Loadable{
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers.json";
/** URL to the JSON file containing all the BE servers. Only queried in BE. */
public static final String serverJsonBeURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_be.json";
/** URL the links to the wiki's modding guide.*/
/** URL of the github issue report template.*/
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();
@@ -78,6 +73,10 @@ public class Vars implements Loadable{
public static final float worldBounds = 100f;
/** units outside of this bound will simply die instantly */
public static final float finalWorldBounds = worldBounds + 500;
/** mining range for manual miners */
public static final float miningRange = 70f;
/** range for building */
public static final float buildingRange = 220f;
/** ticks spent out of bound until self destruct. */
public static final float boundsCountdown = 60 * 7;
/** for map generator dialog */
@@ -88,7 +87,7 @@ public class Vars implements Loadable{
public static final Color[] playerColors = {
Color.valueOf("82759a"),
Color.valueOf("c0c1c5"),
Color.valueOf("fff0e7"),
Color.valueOf("ffffff"),
Color.valueOf("7d2953"),
Color.valueOf("ff074e"),
Color.valueOf("ff072a"),
@@ -153,7 +152,7 @@ public class Vars implements Loadable{
/** list of all locales that can be switched to */
public static Locale[] locales;
public static FileTree tree;
public static FileTree tree = new FileTree();
public static Net net;
public static ContentLoader content;
public static GameState state;
@@ -166,6 +165,7 @@ public class Vars implements Loadable{
public static Schematics schematics = new Schematics();
public static BeControl becontrol;
public static Universe universe;
public static World world;
public static Maps maps;
public static WaveSpawner spawner;
@@ -179,18 +179,7 @@ public class Vars implements Loadable{
public static NetServer netServer;
public static NetClient netClient;
public static Entities entities;
public static EntityGroup<Player> playerGroup;
public static EntityGroup<TileEntity> tileGroup;
public static EntityGroup<Bullet> bulletGroup;
public static EntityGroup<EffectEntity> effectGroup;
public static EntityGroup<DrawTrait> groundEffectGroup;
public static EntityGroup<ShieldEntity> shieldGroup;
public static EntityGroup<Puddle> puddleGroup;
public static EntityGroup<Fire> fireGroup;
public static EntityGroup<BaseUnit> unitGroup;
public static Player player;
public static Playerc player;
@Override
public void loadAsync(){
@@ -200,6 +189,7 @@ public class Vars implements Loadable{
public static void init(){
Serialization.init();
Groups.init();
DefaultSerializers.typeMappings.put("mindustry.type.ContentType", "mindustry.ctype.ContentType");
if(loadLocales){
@@ -228,6 +218,7 @@ public class Vars implements Loadable{
defaultWaves = new DefaultWaves();
collisions = new EntityCollisions();
world = new World();
universe = new Universe();
becontrol = new BeControl();
maps = new Maps();
@@ -235,25 +226,6 @@ public class Vars implements Loadable{
indexer = new BlockIndexer();
pathfinder = new Pathfinder();
entities = new Entities();
playerGroup = entities.add(Player.class).enableMapping();
tileGroup = entities.add(TileEntity.class, false);
bulletGroup = entities.add(Bullet.class).enableMapping();
effectGroup = entities.add(EffectEntity.class, false);
groundEffectGroup = entities.add(DrawTrait.class, false);
puddleGroup = entities.add(Puddle.class).enableMapping();
shieldGroup = entities.add(ShieldEntity.class, false);
fireGroup = entities.add(Fire.class).enableMapping();
unitGroup = entities.add(BaseUnit.class).enableMapping();
for(EntityGroup<?> group : entities.all()){
group.setRemoveListener(entity -> {
if(entity instanceof SyncTrait && net.client()){
netClient.addRemovedEntity((entity).getID());
}
});
}
state = new GameState();
data = new GlobalData();

View File

@@ -7,9 +7,9 @@ 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.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -25,6 +25,7 @@ public class BlockIndexer{
/** Set of all ores that are being scanned. */
private final ObjectSet<Item> scanOres = new ObjectSet<>();
private final IntSet intSet = new IntSet();
private final ObjectSet<Item> itemSet = new ObjectSet<>();
/** Stores all ore quadtrants on the map. */
private ObjectMap<Item, ObjectSet<Tile>> ores = new ObjectMap<>();
@@ -77,18 +78,14 @@ public class BlockIndexer{
//create bitset for each team type that contains each quadrant
structQuadrants = new GridBits[Team.all().length];
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.tile(x, y);
for(Tile tile : world.tiles){
process(tile);
process(tile);
if(tile.entity != null && tile.entity.damaged()){
notifyTileDamaged(tile.entity);
}
if(tile.drop() != null) allOres.add(tile.drop());
if(tile.entity != null && tile.entity.damaged()){
notifyTileDamaged(tile.entity);
}
if(tile.drop() != null) allOres.add(tile.drop());
}
for(int x = 0; x < quadWidth(); x++){
@@ -118,14 +115,11 @@ public class BlockIndexer{
if(structQuadrants == null) return;
//go through every tile... ouch
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.tile(x, y);
if(tile.getTeam() == team){
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
structQuadrant(team).set(quadrantX, quadrantY);
}
for(Tile tile : world.tiles){
if(tile.team() == team){
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
structQuadrant(team).set(quadrantX, quadrantY);
}
}
}
@@ -145,7 +139,7 @@ public class BlockIndexer{
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){
if((tile.entity == null || tile.entity.team() != team || !tile.entity.damaged()) || tile.block() instanceof BuildBlock){
returnArray.add(tile);
}
}
@@ -162,6 +156,39 @@ public class BlockIndexer{
return flagMap[team.id][type.ordinal()];
}
public boolean eachBlock(Teamc team, float range, Boolf<Tile> pred, Cons<Tile> cons){
return eachBlock(team.team(), team.getX(), team.getY(), range, pred, cons);
}
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Tile> pred, Cons<Tile> cons){
intSet.clear();
int tx = world.toTile(wx);
int ty = world.toTile(wy);
int tileRange = (int)(range / tilesize + 1);
intSet.clear();
boolean any = false;
for(int x = -tileRange + tx; x <= tileRange + tx; x++){
for(int y = -tileRange + ty; y <= tileRange + ty; y++){
if(!Mathf.within(x * tilesize, y * tilesize, wx, wy, range)) continue;
Tile other = world.ltile(x, y);
if(other == null) continue;
if(other.team() == team && !intSet.contains(other.pos()) && other.entity != null && pred.get(other)){
cons.get(other);
any = true;
intSet.add(other.pos());
}
}
}
return any;
}
/** Get all enemy blocks with a flag. */
public Array<Tile> getEnemy(Team team, BlockFlag type){
returnArray.clear();
@@ -178,20 +205,20 @@ public class BlockIndexer{
return returnArray;
}
public void notifyTileDamaged(TileEntity entity){
if(damagedTiles[(int)entity.getTeam().id] == null){
damagedTiles[(int)entity.getTeam().id] = new ObjectSet<>();
public void notifyTileDamaged(Tilec entity){
if(damagedTiles[(int)entity.team().id] == null){
damagedTiles[(int)entity.team().id] = new ObjectSet<>();
}
ObjectSet<Tile> set = damagedTiles[(int)entity.getTeam().id];
set.add(entity.tile);
ObjectSet<Tile> set = damagedTiles[(int)entity.team().id];
set.add(entity.tile());
}
public TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public Tilec findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
for(Team enemy : activeTeams){
if(!team.isEnemy(enemy)) continue;
TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
Tilec entity = indexer.findTile(enemy, x, y, range, pred, true);
if(entity != null){
return entity;
}
@@ -200,12 +227,12 @@ public class BlockIndexer{
return null;
}
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred){
public Tilec findTile(Team team, float x, float y, float range, Boolf<Tile> pred){
return findTile(team, x, y, range, pred, false);
}
public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred, boolean usePriority){
TileEntity closest = null;
public Tilec findTile(Team team, float x, float y, float range, Boolf<Tile> pred, boolean usePriority){
Tilec closest = null;
float dst = 0;
float range2 = range*range;
@@ -220,17 +247,17 @@ public class BlockIndexer{
if(other == null) continue;
if(other.entity == null || other.getTeam() != team || !pred.get(other) || !other.block().targetable)
if(other.entity == null || other.team() != team || !pred.get(other) || !other.block().targetable)
continue;
TileEntity e = other.entity;
Tilec e = other.entity;
float ndst = Mathf.dst2(x, y, e.x, e.y);
float ndst = e.dst2(x, y);
if(ndst < range2 && (closest == null ||
//this one is closer, and it is at least of equal priority
(ndst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
(ndst < dst && (!usePriority || closest.block().priority.ordinal() <= e.block().priority.ordinal())) ||
//priority is used, and new block has higher priority regardless of range
(usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
(usePriority && closest.block().priority.ordinal() < e.block().priority.ordinal()))){
dst = ndst;
closest = e;
}
@@ -271,8 +298,8 @@ public class BlockIndexer{
}
private void process(Tile tile){
if(tile.block().flags.size() > 0 && tile.getTeam() != Team.derelict){
ObjectSet<Tile>[] map = getFlagged(tile.getTeam());
if(tile.block().flags.size() > 0 && tile.team() != Team.derelict){
ObjectSet<Tile>[] map = getFlagged(tile.team());
for(BlockFlag flag : tile.block().flags){
@@ -282,9 +309,9 @@ public class BlockIndexer{
map[flag.ordinal()] = arr;
}
typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.getTeam()));
typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.team()));
}
activeTeams.add(tile.getTeam());
activeTeams.add(tile.team());
if(ores == null) return;
@@ -328,7 +355,7 @@ public class BlockIndexer{
GridBits bits = structQuadrant(team);
//fast-set this quadrant to 'occupied' if the tile just placed is already of this team
if(tile.getTeam() == team && tile.entity != null && tile.block().targetable){
if(tile.team() == team && tile.entity != null && tile.block().targetable){
bits.set(quadrantX, quadrantY);
continue; //no need to process futher
}
@@ -340,7 +367,7 @@ public class BlockIndexer{
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() == team){
if(result.entity != null && result.team() == team){
bits.set(quadrantX, quadrantY);
break outer;
}
@@ -369,20 +396,16 @@ public class BlockIndexer{
ores.put(item, new ObjectSet<>());
}
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
int qx = (x / quadrantSize);
int qy = (y / quadrantSize);
for(Tile tile : world.tiles){
int qx = (tile.x / quadrantSize);
int qy = (tile.y / quadrantSize);
Tile tile = world.tile(x, y);
//add position of quadrant to list when an ore is found
if(tile.drop() != null && scanOres.contains(tile.drop()) && tile.block() == Blocks.air){
ores.get(tile.drop()).add(world.tile(
//make sure to clamp quadrant middle position, since it might go off bounds
Mathf.clamp(qx * quadrantSize + quadrantSize / 2, 0, world.width() - 1),
Mathf.clamp(qy * quadrantSize + quadrantSize / 2, 0, world.height() - 1)));
}
//add position of quadrant to list when an ore is found
if(tile.drop() != null && scanOres.contains(tile.drop()) && tile.block() == Blocks.air){
ores.get(tile.drop()).add(world.tile(
//make sure to clamp quadrant middle position, since it might go off bounds
Mathf.clamp(qx * quadrantSize + quadrantSize / 2, 0, world.width() - 1),
Mathf.clamp(qy * quadrantSize + quadrantSize / 2, 0, world.height() - 1)));
}
}
}

View File

@@ -1,13 +1,13 @@
package mindustry.ai;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.func.*;
import arc.math.geom.*;
import arc.util.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.async.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
@@ -33,8 +33,7 @@ public class Pathfinder implements Runnable{
/** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue();
/** current pathfinding thread */
private @Nullable
Thread thread;
private @Nullable Thread thread;
public Pathfinder(){
Events.on(WorldLoadEvent.class, event -> {
@@ -46,10 +45,8 @@ public class Pathfinder implements Runnable{
created = new GridBits(Team.all().length, PathTarget.all.length);
list = new Array<>();
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
tiles[x][y] = packTile(world.rawTile(x, y));
}
for(Tile tile : world.tiles){
tiles[tile.x][tile.y] = packTile(tile);
}
//special preset which may help speed things up; this is optional

View File

@@ -1,20 +1,16 @@
package mindustry.ai;
import arc.Events;
import arc.struct.Array;
import arc.func.Floatc2;
import arc.math.Angles;
import arc.math.Mathf;
import arc.util.Time;
import arc.util.Tmp;
import mindustry.content.Blocks;
import mindustry.content.Fx;
import mindustry.entities.Damage;
import mindustry.entities.Effects;
import mindustry.entities.type.*;
import mindustry.game.EventType.WorldLoadEvent;
import mindustry.game.SpawnGroup;
import mindustry.world.Tile;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -39,7 +35,7 @@ public class WaveSpawner{
/** @return true if the player is near a ground spawn point. */
public boolean playerNear(){
return groundSpawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius);
return !player.dead() && groundSpawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x(), player.y()) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
}
public void spawnEnemies(){
@@ -53,7 +49,7 @@ public class WaveSpawner{
eachFlyerSpawn((spawnX, spawnY) -> {
for(int i = 0; i < spawned; i++){
BaseUnit unit = group.createUnit(state.rules.waveTeam);
Unitc unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
unit.add();
}
@@ -66,9 +62,8 @@ public class WaveSpawner{
for(int i = 0; i < spawned; i++){
Tmp.v1.rnd(spread);
BaseUnit unit = group.createUnit(state.rules.waveTeam);
Unitc unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
Time.run(Math.min(i * 5, 60 * 2), () -> spawnEffect(unit));
}
});
@@ -77,7 +72,7 @@ public class WaveSpawner{
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
if(doShockwave){
Time.run(20f, () -> Effects.effect(Fx.spawnShockwave, spawnX, spawnY, state.rules.dropZoneRadius));
Time.run(20f, () -> Fx.spawnShockwave.at(spawnX, spawnY, state.rules.dropZoneRadius));
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
}
});
@@ -91,10 +86,10 @@ public class WaveSpawner{
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
TileEntity firstCore = state.teams.playerCores().first();
for(TileEntity core : state.rules.waveTeam.cores()){
Tmp.v1.set(firstCore).sub(core.x, core.y).limit(coreMargin + core.block.size*tilesize);
cons.accept(core.x + Tmp.v1.x, core.y + Tmp.v1.y, false);
Tilec firstCore = state.teams.playerCores().first();
for(Tilec core : state.rules.waveTeam.cores()){
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block().size*tilesize);
cons.accept(core.x() + Tmp.v1.x, core.y() + Tmp.v1.y, false);
}
}
}
@@ -108,8 +103,8 @@ public class WaveSpawner{
}
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam)){
for(TileEntity core : state.teams.get(state.rules.waveTeam).cores){
cons.get(core.x, core.y);
for(Tilec core : state.teams.get(state.rules.waveTeam).cores){
cons.get(core.x(), core.y());
}
}
}
@@ -123,12 +118,9 @@ public class WaveSpawner{
flySpawns.clear();
groundSpawns.clear();
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
if(world.tile(x, y).overlay() == Blocks.spawn){
addSpawns(x, y);
}
for(Tile tile : world.tiles){
if(tile.overlay() == Blocks.spawn){
addSpawns(tile.x, tile.y);
}
}
}
@@ -141,11 +133,11 @@ public class WaveSpawner{
flySpawns.add(fspawn);
}
private void spawnEffect(BaseUnit unit){
Effects.effect(Fx.unitSpawn, unit.x, unit.y, 0f, unit);
private void spawnEffect(Unitc unit){
Fx.unitSpawn.at(unit.x(), unit.y(), 0f, unit);
Time.run(30f, () -> {
unit.add();
Effects.effect(Fx.spawn, unit);
Fx.spawn.at(unit);
});
}

View File

@@ -10,7 +10,6 @@ import mindustry.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -34,7 +33,7 @@ public class Blocks implements ContentList{
public static Block
//environment
air, spawn, deepwater, water, taintedWater, tar, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
air, spawn, cliff, deepwater, water, taintedWater, tar, slag, stone, craters, charr, sand, darksand, ice, snow, darksandTaintedWater,
holostone, rocks, sporerocks, icerocks, cliffs, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster,
iceSnow, sandWater, darksandWater, duneRocks, sandRocks, moss, sporeMoss, shale, shaleRocks, shaleBoulder, sandBoulder, grass, salt,
metalFloor, metalFloorDamaged, metalFloor2, metalFloor3, metalFloor5, ignarock, magmarock, hotrock, snowrocks, rock, snowrock, saltRocks,
@@ -57,9 +56,9 @@ public class Blocks implements ContentList{
scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet
//transport
conveyor, titaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router, overflowGate, massDriver,
conveyor, titaniumConveyor, armoredConveyor, distributor, junction, itemBridge, phaseConveyor, sorter, invertedSorter, router, overflowGate, underflowGate, massDriver,
//liquids
//liquid
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
//power
@@ -80,7 +79,7 @@ public class Blocks implements ContentList{
fortressFactory, repairPoint,
//upgrades
dartPad, deltaPad, tauPad, omegaPad, javelinPad, tridentPad, glaivePad;
dartPad, alphaPad, deltaPad, tauPad, omegaPad, javelinPad, tridentPad, glaivePad;
@Override
public void load(){
@@ -124,6 +123,8 @@ public class Blocks implements ContentList{
public void draw(Tile tile){}
};
cliff = new Cliff("cliff");
//Registers build blocks
//no reference is needed here since they can be looked up by name later
for(int i = 1; i <= BuildBlock.maxSize; i++){
@@ -203,6 +204,17 @@ public class Blocks implements ContentList{
cacheLayer = CacheLayer.tar;
}};
slag = new Floor("slag"){{
drownTime = 150f;
status = StatusEffects.melting;
statusDuration = 240f;
speedMultiplier = 0.19f;
variants = 0;
liquidDrop = Liquids.slag;
isLiquid = true;
cacheLayer = CacheLayer.slag;
}};
stone = new Floor("stone"){{
}};
@@ -258,13 +270,13 @@ public class Blocks implements ContentList{
}};
ice = new Floor("ice"){{
//TODO fix drag/speed
dragMultiplier = 1f;
speedMultiplier = 1f;
dragMultiplier = 0.35f;
speedMultiplier = 0.9f;
attributes.set(Attribute.water, 0.4f);
}};
iceSnow = new Floor("ice-snow"){{
dragMultiplier = 0.6f;
variants = 3;
attributes.set(Attribute.water, 0.3f);
}};
@@ -292,6 +304,7 @@ public class Blocks implements ContentList{
icerocks = new StaticWall("icerocks"){{
variants = 2;
iceSnow.asFloor().wall = this;
}};
snowrocks = new StaticWall("snowrocks"){{
@@ -355,11 +368,13 @@ public class Blocks implements ContentList{
moss = new Floor("moss"){{
variants = 3;
attributes.set(Attribute.spores, 0.15f);
wall = sporePine;
}};
sporeMoss = new Floor("spore-moss"){{
variants = 3;
attributes.set(Attribute.spores, 0.3f);
wall = sporerocks;
}};
metalFloor = new Floor("metal-floor"){{
@@ -398,12 +413,37 @@ public class Blocks implements ContentList{
//endregion
//region ore
oreCopper = new OreBlock(Items.copper);
oreLead = new OreBlock(Items.lead);
oreCopper = new OreBlock(Items.copper){{
oreDefault = true;
oreThreshold = 0.81f;
oreScale = 23.47619f;
}};
oreLead = new OreBlock(Items.lead){{
oreDefault = true;
oreThreshold = 0.828f;
oreScale = 23.952381f;
}};
oreScrap = new OreBlock(Items.scrap);
oreCoal = new OreBlock(Items.coal);
oreTitanium = new OreBlock(Items.titanium);
oreThorium = new OreBlock(Items.thorium);
oreCoal = new OreBlock(Items.coal){{
oreDefault = true;
oreThreshold = 0.846f;
oreScale = 24.428572f;
}};
oreTitanium = new OreBlock(Items.titanium){{
oreDefault = true;
oreThreshold = 0.864f;
oreScale = 24.904762f;
}};
oreThorium = new OreBlock(Items.thorium){{
oreDefault = true;
oreThreshold = 0.882f;
oreScale = 25.380953f;
}};
//endregion
//region crafting
@@ -562,7 +602,7 @@ public class Blocks implements ContentList{
drawIcons = () -> new TextureRegion[]{Core.atlas.find(name + "-bottom"), Core.atlas.find(name + "-top")};
drawer = tile -> {
LiquidModule mod = tile.entity.liquids;
LiquidModule mod = tile.entity.liquids();
int rotation = rotate ? tile.rotation() * 90 : 0;
@@ -624,10 +664,6 @@ public class Blocks implements ContentList{
);
hasPower = true;
craftTime = 35f;
spinnerLength = 1.5f;
spinnerRadius = 3.5f;
spinnerThickness = 1.5f;
spinnerSpeed = 3f;
size = 2;
consumes.power(1f);
@@ -657,13 +693,12 @@ public class Blocks implements ContentList{
int topRegion = reg("-top");
drawIcons = () -> new TextureRegion[]{Core.atlas.find(name), Core.atlas.find(name + "-top")};
drawer = tile -> {
GenericCrafterEntity entity = tile.ent();
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.rect(reg(frameRegions[(int)Mathf.absin(entity.totalProgress, 5f, 2.999f)]), tile.drawx(), tile.drawy());
Draw.color(Color.clear, tile.entity.liquids.current().color, tile.entity.liquids.total() / liquidCapacity);
Draw.color(Color.clear, tile.entity.liquids().current().color, tile.entity.liquids().total() / liquidCapacity);
Draw.rect(reg(liquidRegion), tile.drawx(), tile.drawy());
Draw.color();
Draw.rect(reg(topRegion), tile.drawx(), tile.drawy());
@@ -949,6 +984,12 @@ public class Blocks implements ContentList{
buildCostMultiplier = 3f;
}};
underflowGate = new OverflowGate("underflow-gate"){{
requirements(Category.distribution, ItemStack.with(Items.lead, 2, Items.copper, 4));
buildCostMultiplier = 3f;
invert = true;
}};
massDriver = new MassDriver("mass-driver"){{
requirements(Category.distribution, ItemStack.with(Items.titanium, 125, Items.silicon, 75, Items.lead, 125, Items.thorium, 50));
size = 3;
@@ -1096,13 +1137,13 @@ public class Blocks implements ContentList{
differentialGenerator = new SingleTypeGenerator("differential-generator"){{
requirements(Category.power, ItemStack.with(Items.copper, 70, Items.titanium, 50, Items.lead, 100, Items.silicon, 65, Items.metaglass, 50));
powerProduction = 16f;
itemDuration = 120f;
itemDuration = 140f;
hasLiquids = true;
hasItems = true;
size = 3;
consumes.item(Items.pyratite).optional(true, false);
consumes.liquid(Liquids.cryofluid, 0.18f);
consumes.liquid(Liquids.cryofluid, 0.15f);
}};
rtgGenerator = new DecayGenerator("rtg-generator"){{
@@ -1509,25 +1550,25 @@ public class Blocks implements ContentList{
}
@Override
public void init(mindustry.entities.type.Bullet b){
public void init(Bulletc b){
for(int i = 0; i < rays; i++){
Damage.collideLine(b, b.getTeam(), hitEffect, b.x, b.y, b.rot(), rayLength - Math.abs(i - (rays / 2)) * 20f);
Damage.collideLine(b, b.team(), hitEffect, b.x(), b.y(), b.rotation(), rayLength - Math.abs(i - (rays / 2)) * 20f);
}
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
super.draw(b);
Draw.color(Color.white, Pal.lancerLaser, b.fin());
//Draw.alpha(b.fout());
for(int i = 0; i < 7; i++){
Tmp.v1.trns(b.rot(), i * 8f);
Tmp.v1.trns(b.rotation(), i * 8f);
float sl = Mathf.clamp(b.fout() - 0.5f) * (80f - i * 10);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, 4f, sl, b.rot() + 90);
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, 4f, sl, b.rot() - 90);
Drawf.tri(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, 4f, sl, b.rotation() + 90);
Drawf.tri(b.x() + Tmp.v1.x, b.y() + Tmp.v1.y, 4f, sl, b.rotation() - 90);
}
Drawf.tri(b.x, b.y, 20f * b.fout(), (rayLength + 50), b.rot());
Drawf.tri(b.x, b.y, 20f * b.fout(), 10f, b.rot() + 180f);
Drawf.tri(b.x(), b.y(), 20f * b.fout(), (rayLength + 50), b.rotation());
Drawf.tri(b.x(), b.y(), 20f * b.fout(), 10f, b.rotation() + 180f);
Draw.reset();
}
});
@@ -1681,6 +1722,7 @@ public class Blocks implements ContentList{
unitType = UnitTypes.ghoul;
produceTime = 1150;
size = 3;
maxSpawn = 2;
consumes.power(1.2f);
consumes.items(new ItemStack(Items.silicon, 15), new ItemStack(Items.titanium, 10));
}};
@@ -1690,6 +1732,7 @@ public class Blocks implements ContentList{
unitType = UnitTypes.revenant;
produceTime = 2000;
size = 4;
maxSpawn = 2;
consumes.power(3f);
consumes.items(new ItemStack(Items.silicon, 40), new ItemStack(Items.titanium, 30));
}};
@@ -1742,51 +1785,58 @@ public class Blocks implements ContentList{
//endregion
//region upgrades
dartPad = new MechPad("dart-mech-pad"){{
dartPad = new MechPad("dart-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 100, Items.graphite, 50, Items.copper, 75));
mech = Mechs.alpha;
mech = UnitTypes.dart;
size = 2;
consumes.power(0.5f);
}};
alphaPad = new MechPad("alpha-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 100, Items.graphite, 50, Items.copper, 75));
mech = UnitTypes.alpha;
size = 2;
consumes.power(0.5f);
}};
deltaPad = new MechPad("delta-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 175, Items.titanium, 175, Items.copper, 200, Items.silicon, 225, Items.thorium, 150));
mech = Mechs.delta;
mech = UnitTypes.delta;
size = 2;
consumes.power(0.7f);
}};
tauPad = new MechPad("tau-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 125, Items.titanium, 125, Items.copper, 125, Items.silicon, 125));
mech = Mechs.tau;
mech = UnitTypes.tau;
size = 2;
consumes.power(1f);
}};
omegaPad = new MechPad("omega-mech-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 225, Items.graphite, 275, Items.silicon, 325, Items.thorium, 300, Items.surgealloy, 120));
mech = Mechs.omega;
mech = UnitTypes.omega;
size = 3;
consumes.power(1.2f);
}};
javelinPad = new MechPad("javelin-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 175, Items.silicon, 225, Items.titanium, 250, Items.plastanium, 200, Items.phasefabric, 100));
mech = Mechs.javelin;
mech = UnitTypes.javelin;
size = 2;
consumes.power(0.8f);
}};
tridentPad = new MechPad("trident-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 125, Items.copper, 125, Items.silicon, 125, Items.titanium, 150, Items.plastanium, 100));
mech = Mechs.trident;
mech = UnitTypes.trident;
size = 2;
consumes.power(1f);
}};
glaivePad = new MechPad("glaive-ship-pad"){{
requirements(Category.upgrade, ItemStack.with(Items.lead, 225, Items.silicon, 325, Items.titanium, 350, Items.plastanium, 300, Items.surgealloy, 100));
mech = Mechs.glaive;
mech = UnitTypes.glaive;
size = 3;
consumes.power(1.2f);
}};

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,6 @@ public class TechTree implements ContentList{
node(conveyor, () -> {
node(junction, () -> {
node(itemBridge);
node(router, () -> {
node(launchPad, () -> {
node(launchPadLarge, () -> {
@@ -34,7 +33,9 @@ public class TechTree implements ContentList{
node(sorter, () -> {
node(invertedSorter);
node(message);
node(overflowGate);
node(overflowGate, () -> {
node(underflowGate);
});
});
node(container, () -> {
node(unloader);
@@ -43,16 +44,18 @@ public class TechTree implements ContentList{
});
});
node(titaniumConveyor, () -> {
node(phaseConveyor, () -> {
node(massDriver, () -> {
node(itemBridge, () -> {
node(titaniumConveyor, () -> {
node(phaseConveyor, () -> {
node(massDriver, () -> {
});
});
node(armoredConveyor, () -> {
});
});
node(armoredConveyor, () -> {
});
});
});
});
@@ -100,23 +103,25 @@ public class TechTree implements ContentList{
node(copperWall, () -> {
node(copperWallLarge);
node(titaniumWall, () -> {
node(door, () -> {
node(doorLarge);
});
node(plastaniumWall, () -> {
node(plastaniumWallLarge, () -> {
node(copperWallLarge, () -> {
node(titaniumWall, () -> {
node(titaniumWallLarge);
node(door, () -> {
node(doorLarge);
});
});
node(titaniumWallLarge);
node(thoriumWall, () -> {
node(thoriumWallLarge);
node(surgeWall, () -> {
node(surgeWallLarge);
node(phaseWall, () -> {
node(phaseWallLarge);
node(plastaniumWall, () -> {
node(plastaniumWallLarge, () -> {
});
});
node(thoriumWall, () -> {
node(thoriumWallLarge);
node(surgeWall, () -> {
node(surgeWallLarge);
node(phaseWall, () -> {
node(phaseWallLarge);
});
});
});
});
@@ -196,6 +201,8 @@ public class TechTree implements ContentList{
node(liquidRouter, () -> {
node(liquidTank);
node(bridgeConduit);
node(pulseConduit, () -> {
node(phaseConduit, () -> {
@@ -212,7 +219,6 @@ public class TechTree implements ContentList{
});
});
});
node(bridgeConduit);
});
});
});
@@ -337,6 +343,7 @@ public class TechTree implements ContentList{
public static class TechNode{
static TechNode context;
public TechNode parent;
public final Block block;
public final ItemStack[] requirements;
public final Array<TechNode> children = new Array<>();
@@ -346,6 +353,7 @@ public class TechTree implements ContentList{
ccontext.children.add(this);
}
this.parent = ccontext;
this.block = block;
this.requirements = requirements;

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
package mindustry.content;
import mindustry.ctype.*;
import mindustry.type.*;
public class Weathers implements ContentList{
public static Weather
rain,
snow;
@Override
public void load(){
}
}

View File

@@ -1,15 +1,14 @@
package mindustry.content;
import mindustry.ctype.ContentList;
import mindustry.game.*;
import mindustry.ctype.*;
import mindustry.game.Objectives.*;
import mindustry.game.*;
import mindustry.maps.generators.*;
import mindustry.maps.generators.MapGenerator.*;
import mindustry.maps.zonegen.*;
import mindustry.type.*;
import static arc.struct.Array.with;
import static mindustry.content.Items.*;
import static mindustry.content.Planets.starter;
import static mindustry.type.ItemStack.list;
public class Zones implements ContentList{
@@ -22,7 +21,7 @@ public class Zones implements ContentList{
@Override
public void load(){
groundZero = new Zone("groundZero", new MapGenerator("groundZero", 1)){{
groundZero = new Zone("groundZero", starter, new FileMapGenerator("groundZero")){{
baseLaunchCost = list(copper, -60);
startingItems = list(copper, 60);
alwaysUnlocked = true;
@@ -31,7 +30,8 @@ public class Zones implements ContentList{
resources = with(copper, scrap, lead);
}};
desertWastes = new Zone("desertWastes", new DesertWastesGenerator(260, 260)){{
//TODO remove
desertWastes = new Zone("desertWastes", starter, new FileMapGenerator("groundZero")){{
startingItems = list(copper, 120);
conditionWave = 20;
launchPeriod = 10;
@@ -82,7 +82,7 @@ public class Zones implements ContentList{
);
}};
saltFlats = new Zone("saltFlats", new MapGenerator("saltFlats")){{
saltFlats = new Zone("saltFlats", starter, new FileMapGenerator("saltFlats")){{
startingItems = list(copper, 200, Items.silicon, 200, lead, 200);
loadout = Loadouts.basicFoundation;
conditionWave = 10;
@@ -98,8 +98,7 @@ public class Zones implements ContentList{
);
}};
frozenForest = new Zone("frozenForest", new MapGenerator("frozenForest", 1)
.decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.02))){{
frozenForest = new Zone("frozenForest", starter, new FileMapGenerator("frozenForest")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 250);
conditionWave = 10;
@@ -111,7 +110,7 @@ public class Zones implements ContentList{
);
}};
craters = new Zone("craters", new MapGenerator("craters", 1).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.004))){{
craters = new Zone("craters", starter, new FileMapGenerator("craters")){{
startingItems = list(copper, 100);
conditionWave = 10;
resources = with(copper, lead, coal, sand, scrap);
@@ -122,7 +121,7 @@ public class Zones implements ContentList{
);
}};
ruinousShores = new Zone("ruinousShores", new MapGenerator("ruinousShores", 1)){{
ruinousShores = new Zone("ruinousShores", starter, new FileMapGenerator("ruinousShores")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 140, lead, 50);
conditionWave = 20;
@@ -138,8 +137,7 @@ public class Zones implements ContentList{
);
}};
stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2)
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
stainedMountains = new Zone("stainedMountains", starter, new FileMapGenerator("stainedMountains")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 200, lead, 50);
conditionWave = 10;
@@ -153,7 +151,7 @@ public class Zones implements ContentList{
);
}};
fungalPass = new Zone("fungalPass", new MapGenerator("fungalPass")){{
fungalPass = new Zone("fungalPass", starter, new FileMapGenerator("fungalPass")){{
startingItems = list(copper, 250, lead, 250, Items.metaglass, 100, Items.graphite, 100);
resources = with(copper, lead, coal, titanium, sand);
configureObjective = new Launched(this);
@@ -166,7 +164,7 @@ public class Zones implements ContentList{
);
}};
overgrowth = new Zone("overgrowth", new MapGenerator("overgrowth")){{
overgrowth = new Zone("overgrowth", starter, new FileMapGenerator("overgrowth")){{
startingItems = list(copper, 1500, lead, 1000, Items.silicon, 500, Items.metaglass, 250);
conditionWave = 12;
launchPeriod = 4;
@@ -183,8 +181,7 @@ public class Zones implements ContentList{
);
}};
tarFields = new Zone("tarFields", new MapGenerator("tarFields")
.decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{
tarFields = new Zone("tarFields", starter, new FileMapGenerator("tarFields")){{
loadout = Loadouts.basicFoundation;
startingItems = list(copper, 250, lead, 100);
conditionWave = 15;
@@ -198,7 +195,7 @@ public class Zones implements ContentList{
);
}};
desolateRift = new Zone("desolateRift", new MapGenerator("desolateRift")){{
desolateRift = new Zone("desolateRift", starter, new FileMapGenerator("desolateRift")){{
loadout = Loadouts.basicNucleus;
startingItems = list(copper, 1000, lead, 1000, Items.graphite, 250, titanium, 250, Items.silicon, 250);
conditionWave = 3;
@@ -223,8 +220,7 @@ public class Zones implements ContentList{
resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
}};*/
nuclearComplex = new Zone("nuclearComplex", new MapGenerator("nuclearProductionComplex", 1)
.decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{
nuclearComplex = new Zone("nuclearComplex", starter, new FileMapGenerator("nuclearProductionComplex")){{
loadout = Loadouts.basicNucleus;
startingItems = list(copper, 1250, lead, 1500, Items.silicon, 400, Items.metaglass, 250);
conditionWave = 30;

View File

@@ -29,21 +29,17 @@ public class ContentLoader{
private @Nullable Content lastAdded;
private ObjectSet<Cons<Content>> initialization = new ObjectSet<>();
private ContentList[] content = {
new Fx(),
new Items(),
new StatusEffects(),
new Liquids(),
new Bullets(),
new Mechs(),
new UnitTypes(),
new Blocks(),
new Loadouts(),
new TechTree(),
new Zones(),
new TypeIDs(),
//these are not really content classes, but this makes initialization easier
new LegacyColorMapper(),
new Weathers(),
new Planets(),
new Zones()
};
public ContentLoader(){
@@ -135,13 +131,15 @@ public class ContentLoader{
if(blocks().size > i){
int color = pixmap.getPixel(i, 0);
if(color == 0) continue;
if(color == 0 || color == 255) continue;
Block block = block(i);
Color.rgba8888ToColor(block.color, color);
Color.rgba8888ToColor(block.mapColor, color);
block.hasColor = true;
}
}
pixmap.dispose();
ColorMapper.load();
}
public void dispose(){
@@ -234,6 +232,10 @@ public class ContentLoader{
return (Block)getByID(ContentType.block, id);
}
public Block block(String name){
return (Block)getByName(ContentType.block, name);
}
public Array<Item> items(){
return getBy(ContentType.item);
}
@@ -265,4 +267,8 @@ public class ContentLoader{
public Array<UnitType> units(){
return getBy(ContentType.unit);
}
public Array<Planet> planets(){
return getBy(ContentType.planet);
}
}

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
package mindustry.core;
import arc.*;
import mindustry.entities.type.*;
import arc.util.ArcAnnotate.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
import static mindustry.Vars.net;
public class GameState{
/** Current wave number, can be anything in non-wave modes. */
@@ -25,8 +27,8 @@ public class GameState{
/** Current game state. */
private State state = State.menu;
public BaseUnit boss(){
return unitGroup.find(u -> u.isBoss() && u.getTeam() == rules.waveTeam);
public Unitc boss(){
return Groups.unit.find(u -> u.isBoss() && u.team() == rules.waveTeam);
}
public void set(State astate){
@@ -34,6 +36,20 @@ public class GameState{
state = astate;
}
/** Note that being in a campaign does not necessarily mean having a sector. */
public boolean isCampaign(){
return rules.sector != null || rules.region != null;
}
public boolean hasSector(){
return rules.sector != null;
}
@Nullable
public Sector getSector(){
return rules.sector;
}
public boolean isEditor(){
return rules.editor;
}

View File

@@ -6,17 +6,14 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import mindustry.world.blocks.power.*;
import java.util.*;
@@ -34,12 +31,9 @@ public class Logic implements ApplicationListener{
public Logic(){
Events.on(WaveEvent.class, event -> {
for(Player p : playerGroup.all()){
p.respawns = state.rules.respawns;
}
if(world.isZone()){
world.getZone().updateWave(state.wave);
if(state.isCampaign()){
//TODO implement
//state.getSector().updateWave(state.wave);
}
});
@@ -63,7 +57,7 @@ public class Logic implements ApplicationListener{
}
}
TeamData data = state.teams.get(tile.getTeam());
TeamData data = state.teams.get(tile.team());
//remove existing blocks that have been placed here.
//painful O(n) iteration + copy
@@ -100,19 +94,20 @@ public class Logic implements ApplicationListener{
}
}
/** Adds starting items, resets wave time, and sets state to playing. */
public void play(){
state.set(State.playing);
state.wavetime = state.rules.waveSpacing * 2; //grace period of 2x wave time before game starts
Events.fire(new PlayEvent());
//add starting items
if(!world.isZone()){
if(!state.isCampaign()){
for(TeamData team : state.teams.getActive()){
if(team.hasCore()){
TileEntity entity = team.core();
entity.items.clear();
Tilec entity = team.core();
entity.items().clear();
for(ItemStack stack : state.rules.loadout){
entity.items.add(stack.item, stack.amount);
entity.items().add(stack.item, stack.amount);
}
}
}
@@ -127,17 +122,15 @@ public class Logic implements ApplicationListener{
state.rules = new Rules();
state.stats = new Stats();
entities.clear();
Groups.all.clear();
Time.clear();
TileEntity.sleepingEntities = 0;
Events.fire(new ResetEvent());
}
public void runWave(){
spawner.spawnEnemies();
state.wave++;
state.wavetime = world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
state.wavetime = state.hasSector() && state.getSector().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
Events.fire(new WaveEvent());
}
@@ -159,7 +152,7 @@ public class Logic implements ApplicationListener{
}
if(alive != null && !state.gameOver){
if(world.isZone() && alive == state.rules.defaultTeam){
if(state.isCampaign() && alive == state.rules.defaultTeam){
//in attack maps, a victorious game over is equivalent to a launch
Call.launchZone();
}else{
@@ -176,21 +169,22 @@ public class Logic implements ApplicationListener{
ui.hudfrag.showLaunch();
}
for(TileEntity tile : state.teams.playerCores()){
Effects.effect(Fx.launch, tile);
for(Tilec tile : state.teams.playerCores()){
Fx.launch.at(tile);
}
if(world.getZone() != null){
world.getZone().setLaunched();
if(state.isCampaign()){
//TODO implement
//state.getSector().setLaunched();
}
Time.runTask(30f, () -> {
for(TileEntity entity : state.teams.playerCores()){
for(Tilec entity : state.teams.playerCores()){
for(Item item : content.items()){
data.addItem(item, entity.items.get(item));
Events.fire(new LaunchItemEvent(item, entity.items.get(item)));
data.addItem(item, entity.items().get(item));
Events.fire(new LaunchItemEvent(item, entity.items().get(item)));
}
entity.tile.remove();
entity.tile().remove();
}
state.launched = true;
state.gameOver = true;
@@ -210,13 +204,17 @@ public class Logic implements ApplicationListener{
@Override
public void update(){
Events.fire(Trigger.update);
universe.updateGlobal();
if(!state.is(State.menu)){
if(!net.client()){
state.enemies = unitGroup.count(b -> b.getTeam() == state.rules.waveTeam && b.countsAsEnemy());
state.enemies = Groups.unit.count(b -> b.team() == state.rules.waveTeam && b.type().isCounted);
}
if(!state.isPaused()){
if(state.isCampaign()){
universe.update();
}
Time.update();
if(state.rules.waves && state.rules.waveTimer && !state.gameOver){
@@ -229,36 +227,7 @@ public class Logic implements ApplicationListener{
runWave();
}
if(!headless){
effectGroup.update();
groundEffectGroup.update();
}
if(!state.isEditor()){
unitGroup.update();
puddleGroup.update();
shieldGroup.update();
bulletGroup.update();
tileGroup.update();
fireGroup.update();
}else{
unitGroup.updateEvents();
collisions.updatePhysics(unitGroup);
}
playerGroup.update();
//effect group only contains item transfers in the headless version, update it!
if(headless){
effectGroup.update();
}
if(!state.isEditor()){
//bulletGroup
collisions.collideGroups(bulletGroup, unitGroup);
collisions.collideGroups(bulletGroup, playerGroup);
}
Groups.update();
}
if(!net.client() && !world.isInvalidMap() && !state.isEditor() && state.rules.canGameOver){

View File

@@ -1,29 +1,24 @@
package mindustry.core;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.math.*;
import arc.util.CommandHandler.*;
import arc.struct.*;
import arc.util.*;
import arc.util.CommandHandler.*;
import arc.util.io.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.GameState.*;
import mindustry.ctype.ContentType;
import mindustry.entities.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.Net.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.type.TypeID;
import mindustry.world.*;
import mindustry.world.modules.*;
@@ -61,7 +56,7 @@ public class NetClient implements ApplicationListener{
net.handleClient(Connect.class, packet -> {
Log.info("Connecting to server: {0}", packet.addressTCP);
player.isAdmin = false;
player.admin(false);
reset();
@@ -76,11 +71,11 @@ public class NetClient implements ApplicationListener{
});
ConnectPacket c = new ConnectPacket();
c.name = player.name;
c.name = player.name();
c.mods = mods.getModStrings();
c.mobile = mobile;
c.versionType = Version.type;
c.color = Color.rgba8888(player.color);
c.color = Color.rgba8888(player.color());
c.usid = getUsid(packet.addressTCP);
c.uuid = platform.getUUID();
@@ -101,6 +96,7 @@ public class NetClient implements ApplicationListener{
state.set(State.menu);
logic.reset();
platform.updateRPC();
player.name(Core.settings.getString("name"));
if(quiet) return;
@@ -127,21 +123,20 @@ public class NetClient implements ApplicationListener{
});
net.handleClient(InvokePacket.class, packet -> {
packet.writeBuffer.position(0);
RemoteReadClient.readPacket(packet.writeBuffer, packet.type);
RemoteReadClient.readPacket(packet.reader(), packet.type);
});
}
//called on all clients
@Remote(targets = Loc.server, variants = Variant.both)
public static void sendMessage(String message, String sender, Player playersender){
public static void sendMessage(String message, String sender, Playerc playersender){
if(Vars.ui != null){
Vars.ui.chatfrag.addMessage(message, sender);
}
if(playersender != null){
playersender.lastText = message;
playersender.textFadeTime = 1f;
playersender.lastText(message);
playersender.textFadeTime(1f);
}
}
@@ -155,7 +150,7 @@ public class NetClient implements ApplicationListener{
//called when a server recieves a chat message from a player
@Remote(called = Loc.server, targets = Loc.client)
public static void sendChatMessage(Player player, String message){
public static void sendChatMessage(Playerc player, String message){
if(message.length() > maxTextLength){
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
@@ -171,15 +166,20 @@ public class NetClient implements ApplicationListener{
return;
}
//special case; graphical server needs to see its message
if(!headless){
sendMessage(message, colorizeName(player.id(), player.name()), player);
}
//server console logging
Log.info("&y{0}: &lb{1}", player.name, message);
Log.info("&y{0}: &lb{1}", player.name(), message);
//invoke event for all clients but also locally
//this is required so other clients get the correct name even if they don't know who's sending it yet
Call.sendMessage(message, colorizeName(player.id, player.name), player);
Call.sendMessage(message, colorizeName(player.id(), player.name()), player);
}else{
//log command to console but with brackets
Log.info("<&y{0}: &lm{1}&lg>", player.name, message);
Log.info("<&y{0}: &lm{1}&lg>", player.name(), message);
//a command was sent, now get the output
if(response.type != ResponseType.valid){
@@ -200,9 +200,9 @@ public class NetClient implements ApplicationListener{
}
public static String colorizeName(int id, String name){
Player player = playerGroup.getByID(id);
Playerc player = Groups.player.getByID(id);
if(name == null || player == null) return null;
return "[#" + player.color.toString().toUpperCase() + "]" + name;
return "[#" + player.color().toString().toUpperCase() + "]" + name;
}
@Remote(called = Loc.client, variants = Variant.one)
@@ -215,8 +215,8 @@ public class NetClient implements ApplicationListener{
}
@Remote(targets = Loc.client)
public static void onPing(Player player, long time){
Call.onPingResponse(player.con, time);
public static void onPing(Playerc player, long time){
Call.onPingResponse(player.con(), time);
}
@Remote(variants = Variant.one)
@@ -225,7 +225,7 @@ public class NetClient implements ApplicationListener{
}
@Remote(variants = Variant.one)
public static void onTraceInfo(Player player, TraceInfo info){
public static void onTraceInfo(Playerc player, TraceInfo info){
if(player != null){
ui.traces.show(player, info);
}
@@ -256,11 +256,65 @@ public class NetClient implements ApplicationListener{
ui.loadfrag.hide();
}
@Remote(variants = Variant.both, unreliable = true)
public static void setHudText(String message){
if(message == null) return;
ui.hudfrag.setHudText(message);
}
@Remote(variants = Variant.both)
public static void hideHudText(){
ui.hudfrag.toggleHudText(false);
}
/** TCP version */
@Remote(variants = Variant.both)
public static void setHudTextReliable(String message){
setHudText(message);
}
@Remote(variants = Variant.both)
public static void onInfoMessage(String message){
if(message == null) return;
ui.showText("", message);
}
@Remote(variants = Variant.both)
public static void onInfoPopup(String message, float duration, int align, int top, int left, int bottom, int right){
if(message == null) return;
ui.showInfoPopup(message, duration, align, top, left, bottom, right);
}
@Remote(variants = Variant.both)
public static void onLabel(String message, float duration, float worldx, float worldy){
if(message == null) return;
ui.showLabel(message, duration, worldx, worldy);
}
/*
@Remote(variants = Variant.both, unreliable = true)
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
if(effect == null) return;
effect.at(x, y, rotation, color);
}
@Remote(variants = Variant.both)
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
onEffect(effect, x, y, rotation, color);
}*/
@Remote(variants = Variant.both)
public static void onInfoToast(String message, float duration){
if(message == null) return;
ui.showInfoToast(message, duration);
}
@Remote(variants = Variant.both)
public static void onSetRules(Rules rules){
state.rules = rules;
@@ -268,11 +322,10 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void onWorldDataBegin(){
entities.clear();
Groups.all.clear();
netClient.removed.clear();
logic.reset();
ui.chatfrag.clearMessages();
net.setClientLoaded(false);
ui.loadfrag.show("$connecting.data");
@@ -287,60 +340,54 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.one)
public static void onPositionSet(float x, float y){
player.x = x;
player.y = y;
player.set(x, y);
}
@Remote
public static void onPlayerDisconnect(int playerid){
playerGroup.removeByID(playerid);
Groups.player.removeByID(playerid);
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onEntitySnapshot(byte groupID, short amount, short dataLen, byte[] data){
public static void onEntitySnapshot(short amount, short dataLen, byte[] data){
try{
netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen));
DataInputStream input = netClient.dataStream;
EntityGroup group = entities.get(groupID);
//go through each entity
for(int j = 0; j < amount; j++){
int id = input.readInt();
byte typeID = input.readByte();
SyncTrait entity = group == null ? null : (SyncTrait)group.getByID(id);
Syncc entity = Groups.sync.getByID(id);
boolean add = false, created = false;
if(entity == null && id == player.id){
if(entity == null && id == player.id()){
entity = player;
add = true;
}
//entity must not be added yet, so create it
if(entity == null){
entity = (SyncTrait)content.<TypeID>getByID(ContentType.typeid, typeID).constructor.get();
entity.resetID(id);
if(!netClient.isEntityUsed(entity.getID())){
entity = (Syncc)EntityMapping.map(typeID).get();
entity.id(id);
if(!netClient.isEntityUsed(entity.id())){
add = true;
}
created = true;
}
//read the entity
entity.read(input);
entity.read(Reads.get(input));
if(created && entity.getInterpolator() != null && entity.getInterpolator().target != null){
if(created && entity.interpolator().target != null){
//set initial starting position
entity.setNet(entity.getInterpolator().target.x, entity.getInterpolator().target.y);
if(entity instanceof Unit && entity.getInterpolator().targets.length > 0){
((Unit)entity).rotation = entity.getInterpolator().targets[0];
}
entity.setNet(entity.interpolator().target.x, entity.interpolator().target.y);
}
if(add){
entity.add();
netClient.addRemovedEntity(entity.getID());
netClient.addRemovedEntity(entity.id());
}
}
}catch(IOException e){
@@ -361,7 +408,7 @@ public class NetClient implements ApplicationListener{
Log.warn("Missing entity at {0}. Skipping block snapshot.", tile);
break;
}
tile.entity.read(input, tile.entity.version());
tile.entity.readAll(Reads.get(input), tile.entity.version());
}
}catch(Exception e){
e.printStackTrace();
@@ -389,9 +436,9 @@ public class NetClient implements ApplicationListener{
Tile tile = world.tile(pos);
if(tile != null && tile.entity != null){
tile.entity.items.read(input);
tile.entity.items().read(Reads.get(input));
}else{
new ItemModule().read(input);
new ItemModule().read(Reads.get(input));
}
}
@@ -434,7 +481,8 @@ public class NetClient implements ApplicationListener{
connecting = false;
ui.join.hide();
net.setClientLoaded(true);
Core.app.post(Call::connectConfirm);
//TODO connect confirm
//Core.app.post(Call::connectConfirm);
Time.runTask(40f, platform::updateRPC);
Core.app.post(() -> ui.loadfrag.hide());
}
@@ -448,7 +496,7 @@ public class NetClient implements ApplicationListener{
quiet = false;
lastSent = 0;
entities.clear();
Groups.all.clear();
ui.chatfrag.clearMessages();
}
@@ -482,22 +530,28 @@ public class NetClient implements ApplicationListener{
}
void sync(){
if(timer.get(0, playerSyncTime)){
BuildRequest[] requests;
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.buildQueue().size, 10);
BuildRequest[] requests = null;
if(player.isBuilder() && control.input.isBuilding){
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.builder().requests().size, 10);
requests = new BuildRequest[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.buildQueue().get(i);
requests = new BuildRequest[usedRequests];
for(int i = 0; i < usedRequests; i++){
requests[i] = player.builder().requests().get(i);
}
}
Call.onClientShapshot(lastSent++, player.x, player.y,
player.pointerX, player.pointerY, player.rotation, player.baseRotation,
player.velocity().x, player.velocity().y,
player.getMineTile(),
player.isBoosting, player.isShooting, ui.chatfrag.shown(), player.isBuilding,
Unitc unit = player.dead() ? Nulls.unit : player.unit();
Call.onClientShapshot(lastSent++,
unit.x(), unit.y(),
player.mouseX(), player.mouseY(),
unit.rotation(),
unit instanceof Legsc ? ((Legsc)unit).baseRotation() : 0,
unit.vel().x, unit.vel().y,
player.miner().mineTile(),
/*player.isBoosting*/false, control.input.isShooting, ui.chatfrag.shown(),
requests,
Core.camera.position.x, Core.camera.position.y,
Core.camera.width * viewScale, Core.camera.height * viewScale);
@@ -509,6 +563,11 @@ public class NetClient implements ApplicationListener{
}
String getUsid(String ip){
//consistently use the latter part of an IP, if possible
if(ip.contains("/")){
ip = ip.substring(ip.indexOf("/") + 1);
}
if(Core.settings.getString("usid-" + ip, null) != null){
return Core.settings.getString("usid-" + ip, null);
}else{

View File

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

View File

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

View File

@@ -2,27 +2,18 @@ package mindustry.core;
import arc.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.effect.*;
import mindustry.entities.effect.GroundEffectEntity.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.ui.*;
import mindustry.world.blocks.defense.ForceProjector.*;
import static arc.Core.*;
import static mindustry.Vars.*;
@@ -34,65 +25,27 @@ public class Renderer implements ApplicationListener{
public final LightRenderer lights = new LightRenderer();
public final Pixelator pixelator = new Pixelator();
public FrameBuffer shieldBuffer = new FrameBuffer(2, 2);
public FrameBuffer effectBuffer = new FrameBuffer(2, 2);
private Bloom bloom;
private Color clearColor;
private float targetscale = Scl.scl(4);
private float camerascale = targetscale;
private float landscale = 0f, landTime;
private float minZoomScl = Scl.scl(0.01f);
private Rect rect = new Rect(), rect2 = new Rect();
private float shakeIntensity, shaketime;
public Renderer(){
camera = new Camera();
Shaders.init();
Effects.setScreenShakeProvider((intensity, duration) -> {
shakeIntensity = Math.max(intensity, shakeIntensity);
shaketime = Math.max(shaketime, duration);
});
Effects.setEffectProvider((effect, color, x, y, rotation, data) -> {
if(effect == Fx.none) return;
if(Core.settings.getBool("effects")){
Rect view = camera.bounds(rect);
Rect pos = rect2.setSize(effect.size).setCenter(x, y);
if(view.overlaps(pos)){
if(!(effect instanceof GroundEffect)){
EffectEntity entity = Pools.obtain(EffectEntity.class, EffectEntity::new);
entity.effect = effect;
entity.color.set(color);
entity.rotation = rotation;
entity.data = data;
entity.id++;
entity.set(x, y);
if(data instanceof Entity){
entity.setParent((Entity)data);
}
effectGroup.add(entity);
}else{
GroundEffectEntity entity = Pools.obtain(GroundEffectEntity.class, GroundEffectEntity::new);
entity.effect = effect;
entity.color.set(color);
entity.rotation = rotation;
entity.id++;
entity.data = data;
entity.set(x, y);
if(data instanceof Entity){
entity.setParent((Entity)data);
}
groundEffectGroup.add(entity);
}
}
}
});
clearColor = new Color(0f, 0f, 0f, 1f);
}
public void shake(float intensity, float duration){
shakeIntensity = Math.max(shakeIntensity, intensity);
shaketime = Math.max(shaketime, duration);
}
@Override
public void init(){
if(settings.getBool("bloom")){
@@ -119,21 +72,6 @@ public class Renderer implements ApplicationListener{
landTime = 0f;
graphics.clear(Color.black);
}else{
Vec2 position = Tmp.v3.set(player);
if(player.isDead()){
TileEntity core = player.getClosestCore();
if(core != null){
if(player.spawner == null){
camera.position.lerpDelta(core.x, core.y, 0.08f);
}else{
camera.position.lerpDelta(position, 0.08f);
}
}
}else if(control.input instanceof DesktopInput && !state.isPaused()){
camera.position.lerpDelta(position, 0.08f);
}
updateShake(0.75f);
if(pixelator.enabled()){
pixelator.drawPixelate();
@@ -150,7 +88,7 @@ public class Renderer implements ApplicationListener{
@Override
public void dispose(){
minimap.dispose();
shieldBuffer.dispose();
effectBuffer.dispose();
blocks.dispose();
if(bloom != null){
bloom.dispose();
@@ -166,6 +104,13 @@ public class Renderer implements ApplicationListener{
}
}
@Override
public void resume(){
if(settings.getBool("bloom") && bloom != null){
bloom.resume();
}
}
void setupBloom(){
try{
if(bloom != null){
@@ -211,26 +156,26 @@ public class Renderer implements ApplicationListener{
camera.update();
if(Float.isNaN(camera.position.x) || Float.isNaN(camera.position.y)){
camera.position.x = player.x;
camera.position.y = player.y;
camera.position.set(player);
}
graphics.clear(clearColor);
if(!graphics.isHidden() && (Core.settings.getBool("animatedwater") || Core.settings.getBool("animatedshields")) && (shieldBuffer.getWidth() != graphics.getWidth() || shieldBuffer.getHeight() != graphics.getHeight())){
shieldBuffer.resize(graphics.getWidth(), graphics.getHeight());
if(!graphics.isHidden() && (Core.settings.getBool("animatedwater") || Core.settings.getBool("animatedshields")) && (effectBuffer.getWidth() != graphics.getWidth() || effectBuffer.getHeight() != graphics.getHeight())){
effectBuffer.resize(graphics.getWidth(), graphics.getHeight());
}
Draw.proj(camera.projection());
drawBackground();
blocks.floor.checkChanges();
blocks.floor.drawFloor();
groundEffectGroup.draw(e -> e instanceof BelowLiquidTrait);
puddleGroup.draw();
groundEffectGroup.draw(e -> !(e instanceof BelowLiquidTrait));
Groups.drawFloor();
Groups.drawFloorOver();
blocks.processBlocks();
blocks.drawShadows();
Draw.color();
@@ -239,7 +184,9 @@ public class Renderer implements ApplicationListener{
blocks.floor.endDraw();
blocks.drawBlocks(Layer.block);
blocks.drawFog();
if(state.rules.drawFog){
blocks.drawFog();
}
blocks.drawDestroyed();
@@ -249,57 +196,42 @@ public class Renderer implements ApplicationListener{
blocks.drawBlocks(Layer.overlay);
drawGroundShadows();
drawAllTeams(false);
Groups.drawGroundShadows();
Groups.drawGroundUnder();
Groups.drawGround();
blocks.drawBlocks(Layer.turret);
drawFlyerShadows();
Groups.drawFlyingShadows();
blocks.drawBlocks(Layer.power);
blocks.drawBlocks(Layer.lights);
drawAllTeams(true);
Groups.drawFlying();
Draw.flush();
if(bloom != null && !pixelator.enabled()){
if(bloom != null){
bloom.capture();
}
bulletGroup.draw();
effectGroup.draw();
Groups.drawBullets();
Groups.drawEffects();
Draw.flush();
if(bloom != null && !pixelator.enabled()){
if(bloom != null){
bloom.render();
}
overlays.drawBottom();
playerGroup.draw(p -> p.isLocal, Player::drawBuildRequests);
if(shieldGroup.countInBounds() > 0){
if(settings.getBool("animatedshields") && Shaders.shield != null){
Draw.flush();
shieldBuffer.begin();
graphics.clear(Color.clear);
shieldGroup.draw();
shieldGroup.draw(shield -> true, ShieldEntity::drawOver);
Draw.flush();
shieldBuffer.end();
Draw.shader(Shaders.shield);
Draw.color(Pal.accent);
Draw.rect(Draw.wrap(shieldBuffer.getTexture()), camera.position.x, camera.position.y, camera.width, -camera.height);
Draw.color();
Draw.shader();
}else{
shieldGroup.draw(shield -> true, ShieldEntity::drawSimple);
}
if(player.isBuilder()){
player.builder().drawBuildRequests();
}
overlays.drawTop();
playerGroup.draw(p -> !p.isDead(), Player::drawName);
if(!pixelator.enabled()){
Groups.drawNames();
}
if(state.rules.lighting){
lights.draw();
@@ -311,70 +243,35 @@ public class Renderer implements ApplicationListener{
Draw.flush();
}
private void drawLanding(){
if(landTime > 0 && player.getClosestCore() != null){
float fract = landTime / Fx.coreLand.lifetime;
TileEntity entity = player.getClosestCore();
private void drawBackground(){
TextureRegion reg = entity.block.icon(Cicon.full);
}
private void drawLanding(){
if(landTime > 0 && player.closestCore() != null){
float fract = landTime / Fx.coreLand.lifetime;
Tilec entity = player.closestCore();
TextureRegion reg = entity.block().icon(Cicon.full);
float scl = Scl.scl(4f) / camerascale;
float s = reg.getWidth() * Draw.scl * scl * 4f * fract;
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.x, entity.y, s, s);
Draw.rect("circle-shadow", entity.getX(), entity.getY(), s, s);
Angles.randLenVectors(1, (1f- fract), 100, 1000f * scl * (1f-fract), (x, y, fin, fout) -> {
Lines.stroke(scl * fin);
Lines.lineAngle(entity.x + x, entity.y + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
Lines.lineAngle(entity.getX() + x, entity.getY() + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.x, entity.y, reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.rect(reg, entity.getX(), entity.getY(), reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.reset();
}
}
private void drawGroundShadows(){
Draw.color(0, 0, 0, 0.4f);
float rad = 1.6f;
Cons<Unit> draw = u -> {
float size = Math.max(u.getIconRegion().getWidth(), u.getIconRegion().getHeight()) * Draw.scl;
Draw.rect("circle-shadow", u.x, u.y, size * rad, size * rad);
};
unitGroup.draw(unit -> !unit.isDead(), draw::get);
if(!playerGroup.isEmpty()){
playerGroup.draw(unit -> !unit.isDead(), draw::get);
}
Draw.color();
}
private void drawFlyerShadows(){
float trnsX = -12, trnsY = -13;
Draw.color(0, 0, 0, 0.22f);
unitGroup.draw(unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY));
playerGroup.draw(unit -> unit.isFlying() && !unit.isDead(), player -> player.drawShadow(trnsX, trnsY));
Draw.color();
}
private void drawAllTeams(boolean flying){
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder);
playerGroup.draw(p -> p.isFlying() == flying && !p.isDead(), Unit::drawUnder);
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawAll);
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawOver);
}
public void scaleCamera(float amount){
targetscale += amount;
clampScale();
@@ -400,7 +297,7 @@ public class Renderer implements ApplicationListener{
}
public void takeMapScreenshot(){
drawGroundShadows();
Groups.drawGroundShadows();
int w = world.width() * tilesize, h = world.height() * tilesize;
int memory = w * h * 4 / 1024 / 1024;
@@ -410,11 +307,6 @@ public class Renderer implements ApplicationListener{
return;
}
boolean hadShields = Core.settings.getBool("animatedshields");
boolean hadWater = Core.settings.getBool("animatedwater");
Core.settings.put("animatedwater", false);
Core.settings.put("animatedshields", false);
FrameBuffer buffer = new FrameBuffer(w, h);
float vpW = camera.width, vpH = camera.height, px = camera.position.x, py = camera.position.y;
@@ -439,16 +331,13 @@ public class Renderer implements ApplicationListener{
}
buffer.end();
Pixmap fullPixmap = new Pixmap(w, h, Pixmap.Format.RGBA8888);
BufferUtils.copy(lines, 0, fullPixmap.getPixels(), lines.length);
Buffers.copy(lines, 0, fullPixmap.getPixels(), lines.length);
Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png");
PixmapIO.writePNG(file, fullPixmap);
fullPixmap.dispose();
ui.showInfoFade(Core.bundle.format("screenshot", file.toString()));
buffer.dispose();
Core.settings.put("animatedwater", hadWater);
Core.settings.put("animatedshields", hadShields);
}
}

View File

@@ -2,29 +2,23 @@ package mindustry.core;
import arc.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.Input.*;
import arc.assets.*;
import arc.assets.loaders.*;
import arc.assets.loaders.resolvers.*;
import arc.struct.*;
import arc.files.*;
import arc.freetype.*;
import arc.freetype.FreeTypeFontGenerator.*;
import arc.freetype.FreetypeFontLoader.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.TextField.*;
import arc.scene.ui.Tooltip.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.editor.*;
@@ -39,6 +33,8 @@ import static arc.scene.actions.Actions.*;
import static mindustry.Vars.*;
public class UI implements ApplicationListener, Loadable{
public static PixmapPacker packer;
public MenuFragment menufrag;
public HudFragment hudfrag;
public ChatFragment chatfrag;
@@ -67,7 +63,7 @@ public class UI implements ApplicationListener, Loadable{
public TraceDialog traces;
public DatabaseDialog database;
public ContentInfoDialog content;
public DeployDialog deploy;
public PlanetDialog planet;
public TechTreeDialog tech;
//public MinimapDialog minimap;
public SchematicsDialog schematics;
@@ -77,7 +73,7 @@ public class UI implements ApplicationListener, Loadable{
public Cursor drillCursor, unloadCursor;
public UI(){
setupFonts();
Fonts.loadFonts();
}
@Override
@@ -99,6 +95,7 @@ public class UI implements ApplicationListener, Loadable{
Icon.load();
Styles.load();
Tex.loadStyles();
Fonts.loadContentIcons();
Dialog.setShowAction(() -> sequence(alpha(0f), fadeIn(0.1f)));
Dialog.setHideAction(() -> sequence(fadeOut(0.1f)));
@@ -116,7 +113,9 @@ public class UI implements ApplicationListener, Loadable{
Colors.put("unlaunched", Color.valueOf("8982ed"));
Colors.put("highlight", Pal.accent.cpy().lerp(Color.white, 0.3f));
Colors.put("stat", Pal.stat);
loadExtraCursors();
drillCursor = Core.graphics.newCursor("drill");
unloadCursor = Core.graphics.newCursor("unload");
}
@Override
@@ -124,64 +123,6 @@ public class UI implements ApplicationListener, Loadable{
return Array.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", BitmapFont.class), new AssetDescriptor<>("default", BitmapFont.class), new AssetDescriptor<>("chat", BitmapFont.class));
}
/** Called from a static context to make the cursor appear immediately upon startup.*/
public static void loadSystemCursors(){
SystemCursor.arrow.set(Core.graphics.newCursor("cursor"));
SystemCursor.hand.set(Core.graphics.newCursor("hand"));
SystemCursor.ibeam.set(Core.graphics.newCursor("ibeam"));
Core.graphics.restoreCursor();
}
/** Called from a static context for use in the loading screen.*/
public static void loadDefaultFont(){
FileHandleResolver resolver = new InternalFileHandleResolver();
Core.assets.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver));
Core.assets.setLoader(BitmapFont.class, null, new FreetypeFontLoader(resolver){
@Override
public BitmapFont loadSync(AssetManager manager, String fileName, Fi file, FreeTypeFontLoaderParameter parameter){
if(fileName.equals("outline")){
parameter.fontParameters.borderWidth = Scl.scl(2f);
parameter.fontParameters.spaceX -= parameter.fontParameters.borderWidth;
}
parameter.fontParameters.magFilter = TextureFilter.Linear;
parameter.fontParameters.minFilter = TextureFilter.Linear;
parameter.fontParameters.size = fontParameter().size;
return super.loadSync(manager, fileName, file, parameter);
}
});
FreeTypeFontParameter param = new FreeTypeFontParameter(){{
borderColor = Color.darkGray;
incremental = true;
}};
Core.assets.load("outline", BitmapFont.class, new FreeTypeFontLoaderParameter("fonts/font.ttf", param)).loaded = t -> Fonts.outline = (BitmapFont)t;
}
void loadExtraCursors(){
drillCursor = Core.graphics.newCursor("drill");
unloadCursor = Core.graphics.newCursor("unload");
}
public void setupFonts(){
String fontName = "fonts/font.ttf";
FreeTypeFontParameter param = fontParameter();
Core.assets.load("default", BitmapFont.class, new FreeTypeFontLoaderParameter(fontName, param)).loaded = f -> Fonts.def = (BitmapFont)f;
Core.assets.load("chat", BitmapFont.class, new FreeTypeFontLoaderParameter(fontName, param)).loaded = f -> Fonts.chat = (BitmapFont)f;
}
static FreeTypeFontParameter fontParameter(){
return new FreeTypeFontParameter(){{
size = (int)(Scl.scl(18f));
shadowColor = Color.darkGray;
shadowOffsetY = 2;
incremental = true;
}};
}
@Override
public void update(){
if(disableUI || Core.scene == null) return;
@@ -235,7 +176,7 @@ public class UI implements ApplicationListener, Loadable{
traces = new TraceDialog();
maps = new MapsDialog();
content = new ContentInfoDialog();
deploy = new DeployDialog();
planet = new PlanetDialog();
tech = new TechTreeDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
@@ -271,7 +212,17 @@ public class UI implements ApplicationListener, Loadable{
@Override
public void dispose(){
//generator.dispose();
if(packer != null){
packer.dispose();
packer = null;
}
}
public TextureRegionDrawable getIcon(String name){
if(Icon.icons.containsKey(name)){
return Icon.icons.get(name);
}
return Core.atlas.getDrawable("error");
}
public void loadAnd(Runnable call){
@@ -339,6 +290,49 @@ public class UI implements ApplicationListener, Loadable{
Core.scene.add(table);
}
/** Shows a fading label at the top of the screen. */
public void showInfoToast(String info, float duration){
Table table = new Table();
table.setFillParent(true);
table.touchable(Touchable.disabled);
table.update(() -> {
if(state.is(State.menu)) table.remove();
});
table.actions(Actions.delay(duration * 0.9f), Actions.fadeOut(duration * 0.1f, Interpolation.fade), Actions.remove());
table.top().table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).padTop(10);
Core.scene.add(table);
}
/** Shows a label at some position on the screen. Does not fade. */
public void showInfoPopup(String info, float duration, int align, int top, int left, int bottom, int right){
Table table = new Table();
table.setFillParent(true);
table.touchable(Touchable.disabled);
table.update(() -> {
if(state.is(State.menu)) table.remove();
});
table.actions(Actions.delay(duration), Actions.remove());
table.align(align).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).pad(top, left, bottom, right);
Core.scene.add(table);
}
/** Shows a label in the world. This label is behind everything. Does not fade. */
public void showLabel(String info, float duration, float worldx, float worldy){
Table table = new Table();
table.setFillParent(true);
table.touchable(Touchable.disabled);
table.update(() -> {
if(state.is(State.menu)) table.remove();
});
table.actions(Actions.delay(duration), Actions.remove());
table.align(Align.center).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).update(t -> {
Vec2 v = Core.camera.project(worldx, worldy);
t.setPosition(v.x, v.y, Align.center);
});
//make sure it's at the back
Core.scene.root.addChildAt(0, table);
}
public void showInfo(String info){
new Dialog(""){{
getCell(cont).growX();
@@ -476,7 +470,6 @@ public class UI implements ApplicationListener, Loadable{
dialog.show();
}
public void showCustomConfirm(String title, String text, String yes, String no, Runnable confirmed, Runnable denied){
FloatingDialog dialog = new FloatingDialog(title);
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
@@ -509,11 +502,11 @@ public class UI implements ApplicationListener, Loadable{
public String formatAmount(int number){
if(number >= 1000000){
return Strings.fixed(number / 1000000f, 1) + "[gray]" + Core.bundle.getOrNull("unit.millions") + "[]";
return Strings.fixed(number / 1000000f, 1) + "[gray]" + Core.bundle.get("unit.millions") + "[]";
}else if(number >= 10000){
return number / 1000 + "[gray]k[]";
return number / 1000 + "[gray]" + Core.bundle.get("unit.thousands") + "[]";
}else if(number >= 1000){
return Strings.fixed(number / 1000f, 1) + "[gray]" + Core.bundle.getOrNull("unit.thousands") + "[]";
return Strings.fixed(number / 1000f, 1) + "[gray]" + Core.bundle.get("unit.thousands") + "[]";
}else{
return number + "";
}

View File

@@ -1,20 +1,22 @@
package mindustry.core;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.maps.filters.*;
import mindustry.maps.filters.GenerateFilter.*;
import mindustry.maps.generators.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -25,7 +27,7 @@ public class World{
public final Context context = new Context();
private Map currentMap;
private Tile[][] tiles;
public @NonNull Tiles tiles = new Tiles(0, 0);
private boolean generating, invalidMap;
@@ -67,11 +69,11 @@ public class World{
}
public int width(){
return tiles == null ? 0 : tiles.length;
return tiles.width;
}
public int height(){
return tiles == null ? 0 : tiles[0].length;
return tiles.height;
}
public int unitWidth(){
@@ -88,11 +90,7 @@ public class World{
}
public @Nullable Tile tile(int x, int y){
if(tiles == null){
return null;
}
if(!Structs.inBounds(x, y, tiles)) return null;
return tiles[x][y];
return tiles.get(x, y);
}
public @Nullable Tile ltile(int x, int y){
@@ -101,8 +99,9 @@ public class World{
return tile.block().linked(tile);
}
@NonNull
public Tile rawTile(int x, int y){
return tiles[x][y];
return tiles.getn(x, y);
}
public @Nullable Tile tileWorld(float x, float y){
@@ -117,16 +116,10 @@ public class World{
return Math.round(coord / tilesize);
}
public Tile[][] getTiles(){
return tiles;
}
private void clearTileEntities(){
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
if(tiles[x][y] != null && tiles[x][y].entity != null){
tiles[x][y].entity.remove();
}
for(Tile tile : tiles){
if(tile != null && tile.entity != null){
tile.entity.remove();
}
}
}
@@ -135,15 +128,11 @@ public class World{
* Resizes the tile array to the specified size and returns the resulting tile array.
* Only use for loading saves!
*/
public Tile[][] createTiles(int width, int height){
if(tiles != null){
clearTileEntities();
public Tiles resize(int width, int height){
clearTileEntities();
if(tiles.length != width || tiles[0].length != height){
tiles = new Tile[width][height];
}
}else{
tiles = new Tile[width][height];
if(tiles.width != width || tiles.height != height){
tiles = new Tiles(width, height);
}
return tiles;
@@ -164,14 +153,11 @@ public class World{
public void endMapLoad(){
prepareTiles(tiles);
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
tile.updateOcclusion();
for(Tile tile : tiles){
tile.updateOcclusion();
if(tile.entity != null){
tile.entity.updateProximity();
}
if(tile.entity != null){
tile.entity.updateProximity();
}
}
@@ -179,7 +165,7 @@ public class World{
addDarkness(tiles);
}
entities.all().each(group -> group.resize(-finalWorldBounds, -finalWorldBounds, tiles.length * tilesize + finalWorldBounds * 2, tiles[0].length * tilesize + finalWorldBounds * 2));
Groups.resize(-finalWorldBounds, -finalWorldBounds, tiles.width * tilesize + finalWorldBounds * 2, tiles.height * tilesize + finalWorldBounds * 2);
generating = false;
Events.fire(new WorldLoadEvent());
@@ -193,23 +179,21 @@ public class World{
return generating;
}
public boolean isZone(){
return getZone() != null;
}
public Zone getZone(){
return state.rules.zone;
}
public void loadGenerator(Generator generator){
public void loadGenerator(int width, int height, Cons<Tiles> generator){
beginMapLoad();
createTiles(generator.width, generator.height);
generator.generate(tiles);
resize(width, height);
generator.get(tiles);
endMapLoad();
}
public void loadSector(Sector sector){
state.rules.sector = sector;
int size = sector.getSize();
loadGenerator(size, size, tiles -> sector.planet.generator.generate(tiles, sector));
}
public void loadMap(Map map){
loadMap(map, new Rules());
}
@@ -297,60 +281,99 @@ public class World{
}
}
public void addDarkness(Tile[][] tiles){
byte[][] dark = new byte[tiles.length][tiles[0].length];
byte[][] writeBuffer = new byte[tiles.length][tiles[0].length];
public void addDarkness(Tiles tiles){
byte[] dark = new byte[tiles.width * tiles.height];
byte[] writeBuffer = new byte[tiles.width * tiles.height];
byte darkIterations = 4;
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.isDarkened()){
dark[x][y] = darkIterations;
}
for(int i = 0; i < dark.length; i++){
Tile tile = tiles.geti(i);
if(tile.isDarkened()){
dark[i] = darkIterations;
}
}
for(int i = 0; i < darkIterations; i++){
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
boolean min = false;
for(Point2 point : Geometry.d4){
int newX = x + point.x, newY = y + point.y;
if(Structs.inBounds(newX, newY, tiles) && dark[newX][newY] < dark[x][y]){
min = true;
break;
}
for(Tile tile : tiles){
int idx = tile.y * tiles.width + tile.x;
boolean min = false;
for(Point2 point : Geometry.d4){
int newX = tile.x + point.x, newY = tile.y + point.y;
int nidx = newY * tiles.width + newX;
if(tiles.in(newX, newY) && dark[nidx] < dark[idx]){
min = true;
break;
}
writeBuffer[x][y] = (byte)Math.max(0, dark[x][y] - Mathf.num(min));
}
writeBuffer[idx] = (byte)Math.max(0, dark[idx] - Mathf.num(min));
}
for(int x = 0; x < tiles.length; x++){
System.arraycopy(writeBuffer[x], 0, dark[x], 0, tiles[0].length);
System.arraycopy(writeBuffer, 0, dark, 0, writeBuffer.length);
}
for(Tile tile : tiles){
int idx = tile.y * tiles.width + tile.x;
if(tile.isDarkened()){
tile.rotation(dark[idx]);
}
if(dark[idx] == 4){
boolean full = true;
for(Point2 p : Geometry.d4){
int px = p.x + tile.x, py = p.y + tile.y;
int nidx = py * tiles.width + px;
if(tiles.in(px, py) && !(tile.isDarkened() && dark[nidx] == 4)){
full = false;
break;
}
}
if(full) tile.rotation(5);
}
}
}
public float getDarkness(int x, int y){
int edgeBlend = 2;
float dark = 0;
int edgeDst = Math.min(x, Math.min(y, Math.min(Math.abs(x - (tiles.width - 1)), Math.abs(y - (tiles.height - 1)))));
if(edgeDst <= edgeBlend){
dark = Math.max((edgeBlend - edgeDst) * (4f / edgeBlend), dark);
}
if(state.hasSector()){
int circleBlend = 14;
//quantized angle
float offset = state.getSector().rect.rotation + 90;
float angle = Angles.angle(x, y, tiles.width/2, tiles.height/2) + offset;
//polygon sides, depends on sector
int sides = state.getSector().tile.corners.length;
float step = 360f / sides;
//prev and next angles of poly
float prev = Mathf.round(angle, step);
float next = prev + step;
//raw line length to be translated
float length = tiles.width/2f;
float rawDst = Intersector.distanceLinePoint(Tmp.v1.trns(prev, length), Tmp.v2.trns(next, length), Tmp.v3.set(x - tiles.width/2, y - tiles.height/2).rotate(offset)) / Mathf.sqrt3 - 1;
//noise
rawDst += Noise.noise(x, y, 11f, 7f) + Noise.noise(x, y, 22f, 15f);
int circleDst = (int)(rawDst - (tiles.width / 2 - circleBlend));
if(circleDst > 0){
dark = Math.max(circleDst / 1f, dark);
}
}
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.isDarkened()){
tiles[x][y].rotation(dark[x][y]);
}
if(dark[x][y] == 4){
boolean full = true;
for(Point2 p : Geometry.d4){
int px = p.x + x, py = p.y + y;
if(Structs.inBounds(px, py, tiles) && !(tiles[px][py].isDarkened() && dark[px][py] == 4)){
full = false;
break;
}
}
if(full) tiles[x][y].rotation(5);
}
}
Tile tile = world.tile(x, y);
if(tile != null && tile.block().solid && tile.block().fillsTile && !tile.block().synthetic()){
dark = Math.max(dark, tile.rotation());
}
return dark;
}
/**
@@ -359,18 +382,13 @@ public class World{
* - updating occlusion<br>
* Usually used before placing structures on a tile array.
*/
public void prepareTiles(Tile[][] tiles){
public void prepareTiles(Tiles tiles){
//find multiblocks
IntArray multiblocks = new IntArray();
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
Tile tile = tiles[x][y];
if(tile.block().isMultiblock()){
multiblocks.add(tile.pos());
}
for(Tile tile : tiles){
if(tile.block().isMultiblock()){
multiblocks.add(tile.pos());
}
}
@@ -380,9 +398,10 @@ public class World{
int x = Pos.x(pos);
int y = Pos.y(pos);
Tile tile = tiles.getn(x, y);
Block result = tiles[x][y].block();
Team team = tiles[x][y].getTeam();
Block result = tile.block();
Team team = tile.team();
int offsetx = -(result.size - 1) / 2;
int offsety = -(result.size - 1) / 2;
@@ -409,17 +428,19 @@ public class World{
private class Context implements WorldContext{
@Override
public Tile tile(int x, int y){
return tiles[x][y];
return tiles.get(x, y);
}
@Override
public void resize(int width, int height){
createTiles(width, height);
World.this.resize(width, height);
}
@Override
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
return (tiles[x][y] = new Tile(x, y, floorID, overlayID, wallID));
Tile tile = new Tile(x, y, floorID, overlayID, wallID);
tiles.set(x, y, tile);
return tile;
}
@Override
@@ -454,23 +475,8 @@ public class World{
GenerateInput input = new GenerateInput();
for(GenerateFilter filter : filters){
input.begin(filter, width(), height(), (x, y) -> tiles[x][y]);
//actually apply the filter
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
Tile tile = rawTile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
tile.setFloor((Floor)input.floor);
tile.setOverlay(input.ore);
if(!tile.block().synthetic() && !input.block.synthetic()){
tile.setBlock(input.block);
}
}
}
input.begin(filter, width(), height(), (x, y) -> tiles.getn(x, y));
filter.apply(tiles, input);
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package mindustry.ctype;
import arc.*;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
@@ -10,10 +11,10 @@ import mindustry.ui.Cicon;
/** Base interface for an unlockable content type. */
public abstract class UnlockableContent extends MappableContent{
/** Localized, formal name. Never null. Set to block name if not found in bundle. */
/** Localized, formal name. Never null. Set to internal name if not found in bundle. */
public String localizedName;
/** Localized description. May be null. */
public String description;
public @Nullable String description;
/** Icons by Cicon ID.*/
protected TextureRegion[] cicons = new TextureRegion[mindustry.ui.Cicon.all.length];
@@ -24,6 +25,10 @@ public abstract class UnlockableContent extends MappableContent{
this.description = Core.bundle.getOrNull(getContentType() + "." + this.name + ".description");
}
public String displayDescription(){
return minfo.mod == null ? description : description + "\n" + Core.bundle.format("mod.display", minfo.mod.meta.displayName());
}
/** Generate any special icons for this content. Called asynchronously.*/
@CallSuper
public void createIcons(MultiPacker packer){
@@ -49,7 +54,7 @@ public abstract class UnlockableContent extends MappableContent{
public void onUnlock(){
}
/** Whether this content is always hidden in the content info dialog. */
/** Whether this content is always hidden in the content database dialog. */
public boolean isHidden(){
return false;
}
@@ -65,7 +70,7 @@ public abstract class UnlockableContent extends MappableContent{
/** @return whether this content is unlocked, or the player is in a custom game. */
public final boolean unlockedCur(){
return Vars.data.isUnlocked(this) || !Vars.world.isZone();
return Vars.data.isUnlocked(this) || !Vars.state.isCampaign();
}
public final boolean locked(){

View File

@@ -65,7 +65,7 @@ public class DrawOperation{
tile.setFloor((Floor)content.block(to));
}else if(type == OpType.block.ordinal()){
Block block = content.block(to);
tile.setBlock(block, tile.getTeam(), tile.rotation());
tile.setBlock(block, tile.team(), tile.rotation());
}else if(type == OpType.rotation.ordinal()){
tile.rotation(to);
}else if(type == OpType.team.ordinal()){

View File

@@ -1,5 +1,6 @@
package mindustry.editor;
import arc.util.ArcAnnotate.*;
import mindustry.content.Blocks;
import mindustry.core.GameState.State;
import mindustry.editor.DrawOperation.OpType;
@@ -13,7 +14,6 @@ import mindustry.world.modules.*;
import static mindustry.Vars.state;
import static mindustry.Vars.ui;
//TODO somehow remove or replace this class with a more flexible solution
public class EditorTile extends Tile{
public EditorTile(int x, int y, int floor, int overlay, int wall){
@@ -21,7 +21,7 @@ public class EditorTile extends Tile{
}
@Override
public void setFloor(Floor type){
public void setFloor(@NonNull Floor type){
if(state.is(State.playing)){
super.setFloor(type);
return;
@@ -140,10 +140,10 @@ public class EditorTile extends Tile{
if(block.hasEntity()){
entity = block.newEntity().init(this, false);
entity.cons = new ConsumeModule(entity);
if(block.hasItems) entity.items = new ItemModule();
if(block.hasLiquids) entity.liquids = new LiquidModule();
if(block.hasPower) entity.power = new PowerModule();
entity.cons(new ConsumeModule(entity));
if(block.hasItems) entity.items(new ItemModule());
if(block.hasLiquids) entity.liquids(new LiquidModule());
if(block.hasPower) entity.power(new PowerModule());
}
}

View File

@@ -138,7 +138,7 @@ public enum EditorTool{
//only fill synthetic blocks, it's meaningless otherwise
if(tile.link().synthetic()){
Team dest = tile.getTeam();
Team dest = tile.team();
if(dest == editor.drawTeam) return;
fill(editor, x, y, false, t -> t.getTeamID() == (int)dest.id && t.link().synthetic(), t -> t.setTeam(editor.drawTeam));
}
@@ -166,33 +166,42 @@ public enum EditorTool{
stack.clear();
stack.add(Pos.get(x, y));
while(stack.size > 0){
int popped = stack.pop();
x = Pos.x(popped);
y = Pos.y(popped);
try{
while(stack.size > 0 && stack.size < width*height){
int popped = stack.pop();
x = Pos.x(popped);
y = Pos.y(popped);
x1 = x;
while(x1 >= 0 && tester.get(editor.tile(x1, y))) x1--;
x1++;
boolean spanAbove = false, spanBelow = false;
while(x1 < width && tester.get(editor.tile(x1, y))){
filler.get(editor.tile(x1, y));
if(!spanAbove && y > 0 && tester.get(editor.tile(x1, y - 1))){
stack.add(Pos.get(x1, y - 1));
spanAbove = true;
}else if(spanAbove && !tester.get(editor.tile(x1, y - 1))){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && tester.get(editor.tile(x1, y + 1))){
stack.add(Pos.get(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && !tester.get(editor.tile(x1, y + 1))){
spanBelow = false;
}
x1 = x;
while(x1 >= 0 && tester.get(editor.tile(x1, y))) x1--;
x1++;
boolean spanAbove = false, spanBelow = false;
while(x1 < width && tester.get(editor.tile(x1, y))){
filler.get(editor.tile(x1, y));
if(!spanAbove && y > 0 && tester.get(editor.tile(x1, y - 1))){
stack.add(Pos.get(x1, y - 1));
spanAbove = true;
}else if(spanAbove && !tester.get(editor.tile(x1, y - 1))){
spanAbove = false;
}
if(!spanBelow && y < height - 1 && tester.get(editor.tile(x1, y + 1))){
stack.add(Pos.get(x1, y + 1));
spanBelow = true;
}else if(spanBelow && y < height - 1 && !tester.get(editor.tile(x1, y + 1))){
spanBelow = false;
}
x1++;
}
}
stack.clear();
}catch(OutOfMemoryError e){
//hack
stack = null;
System.gc();
e.printStackTrace();
stack = new IntArray();
}
}
}

View File

@@ -64,30 +64,28 @@ public class MapEditor{
reset();
createTiles(pixmap.getWidth(), pixmap.getHeight());
load(() -> MapIO.readPixmap(pixmap, tiles()));
load(() -> MapIO.readImage(pixmap, tiles()));
renderer.resize(width(), height());
}
//adds missing blockparts
//TODO remove, may not be necessary with blockpart refactor later
public void checkLinkedTiles(){
Tile[][] tiles = world.getTiles();
Tiles tiles = world.tiles;
//clear block parts first
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
if(tiles[x][y].block() instanceof BlockPart){
tiles[x][y].setBlock(Blocks.air);
}
//clear old parts
for(Tile tile : tiles){
if(tile.block() instanceof BlockPart){
tile.setBlock(Blocks.air);
}
}
//set up missing blockparts
for(int x = 0; x < width(); x++){
for(int y = 0; y < height(); y++){
if(tiles[x][y].block().isMultiblock()){
tiles[x][y].set(tiles[x][y].block(), tiles[x][y].getTeam());
}
//re-add them
for(Tile tile : tiles){
if(tile.block().isMultiblock()){
tile.set(tile.block(), tile.team());
}
}
}
@@ -99,11 +97,11 @@ public class MapEditor{
/** Creates a 2-D array of EditorTiles with stone as the floor block. */
private void createTiles(int width, int height){
Tile[][] tiles = world.createTiles(width, height);
Tiles tiles = world.resize(width, height);
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
tiles.set(x, y, new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0));
}
}
}
@@ -119,8 +117,8 @@ public class MapEditor{
tags = new StringMap();
}
public Tile[][] tiles(){
return world.getTiles();
public Tiles tiles(){
return world.tiles;
}
public Tile tile(int x, int y){
@@ -245,20 +243,20 @@ public class MapEditor{
public void resize(int width, int height){
clearOp();
Tile[][] previous = world.getTiles();
Tiles previous = world.tiles;
int offsetX = -(width - width()) / 2, offsetY = -(height - height()) / 2;
loading = true;
Tile[][] tiles = world.createTiles(width, height);
Tiles tiles = world.resize(width, height);
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
int px = offsetX + x, py = offsetY + y;
if(Structs.inBounds(px, py, previous.length, previous[0].length)){
tiles[x][y] = previous[px][py];
tiles[x][y].x = (short)x;
tiles[x][y].y = (short)y;
if(previous.in(px, py)){
tiles.set(x, y, previous.getn(px, py));
tiles.getn(x, y).x = (short)x;
tiles.getn(x, y).y = (short)y;
}else{
tiles[x][y] = new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0);
tiles.set(x, y, new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0));
}
}
}
@@ -314,12 +312,14 @@ public class MapEditor{
@Override
public void resize(int width, int height){
world.createTiles(width, height);
world.resize(width, height);
}
@Override
public Tile create(int x, int y, int floorID, int overlayID, int wallID){
return (tiles()[x][y] = new EditorTile(x, y, floorID, overlayID, wallID));
Tile tile = new EditorTile(x, y, floorID, overlayID, wallID);
tiles().set(x, y, tile);
return tile;
}
@Override
@@ -337,4 +337,4 @@ public class MapEditor{
world.endMapLoad();
}
}
}
}

View File

@@ -66,89 +66,69 @@ public class MapEditorDialog extends Dialog implements Disposable{
menu.cont.table(t -> {
t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5);
t.addImageTextButton("$editor.savemap", Icon.floppy16Small, this::save);
t.addImageTextButton("$editor.savemap", Icon.save, this::save);
t.addImageTextButton("$editor.mapinfo", Icon.pencilSmall, () -> {
t.addImageTextButton("$editor.mapinfo", Icon.pencil, () -> {
infoDialog.show();
menu.hide();
});
t.row();
t.addImageTextButton("$editor.generate", Icon.editorSmall, () -> {
t.addImageTextButton("$editor.generate", Icon.terrain, () -> {
generateDialog.show(generateDialog::applyToEditor);
menu.hide();
});
t.addImageTextButton("$editor.resize", Icon.resizeSmall, () -> {
t.addImageTextButton("$editor.resize", Icon.resize, () -> {
resizeDialog.show();
menu.hide();
});
t.row();
t.addImageTextButton("$editor.import", Icon.loadMapSmall, () ->
createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.loadMap, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
})),
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
Pixmap pixmap = new Pixmap(file);
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
Log.err(e);
}
})))
);
t.addImageTextButton("$editor.export", Icon.saveMapSmall, () -> {
if(!ios){
platform.showFileChooser(false, mapExtension, file -> {
ui.loadAnd(() -> {
try{
if(!editor.getTags().containsKey("name")){
editor.getTags().put("name", file.nameWithoutExtension());
}
MapIO.writeMap(file, editor.createMap(file));
}catch(Exception e){
ui.showException("$editor.errorsave", e);
Log.err(e);
}
});
});
}else{
ui.loadAnd(() -> {
try{
Fi result = Core.files.local(editor.getTags().get("name", "unknown") + "." + mapExtension);
MapIO.writeMap(result, editor.createMap(result));
platform.shareFile(result);
}catch(Exception e){
ui.showException("$editor.errorsave", e);
Log.err(e);
t.addImageTextButton("$editor.import", Icon.download, () -> createDialog("$editor.import",
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showInfo("$editor.errorimage");
}else{
editor.beginEdit(MapIO.createMap(file, true));
}
});
}
});
})),
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
platform.showFileChooser(true, "png", file ->
ui.loadAnd(() -> {
try{
Pixmap pixmap = new Pixmap(file);
editor.beginEdit(pixmap);
pixmap.dispose();
}catch(Exception e){
ui.showException("$editor.errorload", e);
Log.err(e);
}
})))
);
t.addImageTextButton("$editor.export", Icon.upload, () -> createDialog("$editor.export",
"$editor.exportfile", "$editor.exportfile.description", Icon.file,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
"$editor.exportimage", "$editor.exportimage.description", Icon.fileImage,
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), "png", file -> {
Pixmap out = MapIO.writeImage(editor.tiles());
file.writePNG(out);
out.dispose();
})));
});
menu.cont.row();
if(steam){
menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> {
menu.cont.addImageTextButton("$editor.publish.workshop", Icon.link, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
@@ -176,16 +156,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name()) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();
}
menu.cont.addImageTextButton("$editor.ingame", Icon.arrowSmall, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.addImageTextButton("$editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
menu.cont.row();
menu.cont.addImageTextButton("$quit", Icon.backSmall, () -> {
menu.cont.addImageTextButton("$quit", Icon.exit, () -> {
tryExit();
menu.hide();
}).size(swidth * 2f + 10, 60f);
@@ -266,7 +246,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
state.teams = new Teams();
player.reset();
state.rules = Gamemode.editor.apply(lastSavedRules.copy());
state.rules.zone = null;
state.rules.sector = null;
world.setMap(new Map(StringMap.of(
"name", "Editor Playtesting",
"width", editor.width(),
@@ -274,16 +254,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
)));
world.endMapLoad();
//add entities so they update. is this really needed?
for(int x = 0; x < world.width(); x++){
for(int y = 0; y < world.height(); y++){
Tile tile = world.rawTile(x, y);
if(tile.entity != null){
tile.entity.add();
}
for(Tile tile : world.tiles){
if(tile.entity != null){
tile.entity.add();
}
}
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
player.setDead(false);
//TODO figure out how to kill player
//player.dead(false);
logic.play();
});
}
@@ -295,7 +273,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.getTags().put("rules", JsonIO.write(state.rules));
editor.getTags().remove("width");
editor.getTags().remove("height");
player.dead = true;
//TODO unkill player
//player.dead = true;
Map returned = null;
@@ -427,7 +406,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
Cons<EditorTool> addTool = tool -> {
ImageButton button = new ImageButton(Core.atlas.drawable("icon-" + tool.name() + "-small"), Styles.clearTogglei);
ImageButton button = new ImageButton(ui.getIcon(tool.name()), Styles.clearTogglei);
button.clicked(() -> {
view.setTool(tool);
if(lastTable[0] != null){
@@ -503,16 +482,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
tools.defaults().size(size, size);
tools.addImageButton(Icon.menuLargeSmall, Styles.cleari, menu::show);
tools.addImageButton(Icon.menu, Styles.cleari, menu::show);
ImageButton grid = tools.addImageButton(Icon.gridSmall, Styles.clearTogglei, () -> view.setGrid(!view.isGrid())).get();
ImageButton grid = tools.addImageButton(Icon.grid, Styles.clearTogglei, () -> view.setGrid(!view.isGrid())).get();
addTool.get(EditorTool.zoom);
tools.row();
ImageButton undo = tools.addImageButton(Icon.undoSmall, Styles.cleari, editor::undo).get();
ImageButton redo = tools.addImageButton(Icon.redoSmall, Styles.cleari, editor::redo).get();
ImageButton undo = tools.addImageButton(Icon.undo, Styles.cleari, editor::undo).get();
ImageButton redo = tools.addImageButton(Icon.redo, Styles.cleari, editor::redo).get();
addTool.get(EditorTool.pick);
@@ -534,7 +513,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
addTool.get(EditorTool.fill);
addTool.get(EditorTool.spray);
ImageButton rotate = tools.addImageButton(Icon.arrow16Small, Styles.cleari, () -> editor.rotation = (editor.rotation + 1) % 4).get();
ImageButton rotate = tools.addImageButton(Icon.right, Styles.cleari, () -> editor.rotation = (editor.rotation + 1) % 4).get();
rotate.getImage().update(() -> {
rotate.getImage().setRotation(editor.rotation * 90);
rotate.getImage().setOrigin(Align.center);

View File

@@ -30,7 +30,7 @@ public class MapGenerateDialog extends FloatingDialog{
private final Prov<GenerateFilter>[] filterTypes = new Prov[]{
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
BlendFilter::new, MirrorFilter::new, ClearFilter::new
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new, EnemySpawnFilter::new
};
private final MapEditor editor;
private final boolean applied;
@@ -124,7 +124,7 @@ public class MapGenerateDialog extends FloatingDialog{
Tile tile = editor.tile(x, y);
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
filter.apply(input);
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.getTeam(), tile.rotation());
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.team(), tile.rotation());
}
}
@@ -235,24 +235,24 @@ public class MapGenerateDialog extends FloatingDialog{
t.table(b -> {
ImageButtonStyle style = Styles.cleari;
b.defaults().size(50f);
b.addImageButton(Icon.refreshSmall, style, () -> {
b.addImageButton(Icon.refresh, style, () -> {
filter.randomize();
update();
});
b.addImageButton(Icon.arrowUpSmall, style, () -> {
b.addImageButton(Icon.upOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.max(0, idx - 1));
rebuildFilters();
update();
});
b.addImageButton(Icon.arrowDownSmall, style, () -> {
b.addImageButton(Icon.downOpen, style, () -> {
int idx = filters.indexOf(filter);
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
rebuildFilters();
update();
});
b.addImageButton(Icon.trashSmall, style, () -> {
b.addImageButton(Icon.trash, style, () -> {
filters.remove(filter);
rebuildFilters();
update();
@@ -263,7 +263,7 @@ public class MapGenerateDialog extends FloatingDialog{
//all the options
c.table(f -> {
f.left().top();
for(FilterOption option : filter.options){
for(FilterOption option : filter.options()){
option.changed = this::update;
f.table(t -> {
@@ -292,7 +292,7 @@ public class MapGenerateDialog extends FloatingDialog{
for(Prov<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
if(!applied && filter.buffered) continue;
if((!applied && filter.isBuffered()) || (filter.isPost() && applied)) continue;
selection.cont.addButton(filter.name(), () -> {
filters.add(filter);
@@ -360,21 +360,17 @@ public class MapGenerateDialog extends FloatingDialog{
for(GenerateFilter filter : copy){
input.begin(filter, editor.width(), editor.height(), (x, y) -> buffer1[Mathf.clamp(x / scaling, 0, pixmap.getWidth()-1)][Mathf.clamp(y / scaling, 0, pixmap.getHeight()-1)].tile());
//read from buffer1 and write to buffer2
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
int x = px * scaling, y = py * scaling;
GenTile tile = buffer1[px][py];
input.apply(x, y, content.block(tile.floor), content.block(tile.block), content.block(tile.ore));
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, Team.get(tile.team), tile.rotation);
}
}
for(int px = 0; px < pixmap.getWidth(); px++){
for(int py = 0; py < pixmap.getHeight(); py++){
buffer1[px][py].set(buffer2[px][py]);
}
}
pixmap.each((px, py) -> {
int x = px * scaling, y = py * scaling;
GenTile tile = buffer1[px][py];
input.apply(x, y, content.block(tile.floor), content.block(tile.block), content.block(tile.ore));
filter.apply(input);
buffer2[px][py].set(input.floor, input.block, input.ore, Team.get(tile.team), tile.rotation);
});
pixmap.each((px, py) -> buffer1[px][py].set(buffer2[px][py]));
}
for(int px = 0; px < pixmap.getWidth(); px++){
@@ -428,7 +424,7 @@ public class MapGenerateDialog extends FloatingDialog{
}
public GenTile set(Tile other){
set(other.floor(), other.block(), other.overlay(), other.getTeam(), other.rotation());
set(other.floor(), other.block(), other.overlay(), other.team(), other.rotation());
return this;
}

View File

@@ -109,9 +109,9 @@ public class MapRenderer implements Disposable{
private void render(int wx, int wy){
int x = wx / chunkSize, y = wy / chunkSize;
IndexedRenderer mesh = chunks[x][y];
Tile tile = editor.tiles()[wx][wy];
Tile tile = editor.tiles().getn(wx, wy);
Team team = tile.getTeam();
Team team = tile.team();
Block floor = tile.floor();
Block wall = tile.block();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,285 @@
package mindustry.entities.def;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.modules.*;
import static mindustry.Vars.*;
@EntityDef(value = {Tilec.class}, isFinal = false, genio = false, serialize = false)
@Component
abstract class TileComp implements Posc, Teamc, Healthc, Tilec, Timerc{
static final float timeToSleep = 60f * 1;
static final ObjectSet<Tile> tmpTiles = new ObjectSet<>();
static int sleepingEntities = 0;
transient Tile tile;
transient Block block;
transient Array<Tile> proximity = new Array<>(8);
PowerModule power;
ItemModule items;
LiquidModule liquids;
ConsumeModule cons;
private transient float timeScale = 1f, timeScaleDuration;
private transient @Nullable SoundLoop sound;
private transient boolean sleeping;
private transient float sleepTime;
/** Sets this tile entity data to this tile, and adds it if necessary. */
@Override
public Tilec init(Tile tile, boolean shouldAdd){
this.tile = tile;
this.block = tile.block();
set(tile.drawx(), tile.drawy());
if(block.activeSound != Sounds.none){
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
}
health(block.health);
maxHealth(block.health);
timer(new Interval(block.timers));
if(shouldAdd){
add();
}
return this;
}
public final void writeBase(Writes write){
write.f(health());
write.b(tile.rotation());
write.b(tile.getTeamID());
if(items != null) items.write(write);
if(power != null) power.write(write);
if(liquids != null) liquids.write(write);
if(cons != null) cons.write(write);
}
public final void readBase(Reads read){
health(read.f());
tile.rotation(read.b());
tile.setTeam(Team.get(read.b()));
if(items != null) items.read(read);
if(power != null) power.read(read);
if(liquids != null) liquids.read(read);
if(cons != null) cons.read(read);
}
public void writeAll(Writes write){
writeBase(write);
write(write);
}
public void readAll(Reads read, byte revision){
readBase(read);
read(read, revision);
}
@CallSuper
@Override
public void write(Writes write){
//overriden by subclasses!
}
@CallSuper
@Override
public void read(Reads read, byte revision){
//overriden by subclasses!
}
@Override
public void applyBoost(float intensity, float duration){
timeScale = Math.max(timeScale, intensity);
timeScaleDuration = Math.max(timeScaleDuration, duration);
}
@Override
public float timeScale(){
return timeScale;
}
@Override
public boolean consValid(){
return cons.valid();
}
@Override
public void consume(){
cons.trigger();
}
/** Scaled delta. */
@Override
public float delta(){
return Time.delta() * timeScale;
}
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
@Override
public float efficiency(){
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
/** Call when nothing is happening to the entity. This increments the internal sleep timer. */
@Override
public void sleep(){
sleepTime += Time.delta();
if(!sleeping && sleepTime >= timeToSleep){
remove();
sleeping = true;
sleepingEntities++;
}
}
/** Call when this entity is updating. This wakes it up. */
@Override
public void noSleep(){
sleepTime = 0f;
if(sleeping){
add();
sleeping = false;
sleepingEntities--;
}
}
/** Returns the version of this TileEntity IO code.*/
@Override
public byte version(){
return 0;
}
@Override
public boolean collide(Bulletc other){
return true;
}
@Override
public void collision(Bulletc other){
block.handleBulletHit(this, other);
}
//TODO Implement damage!
@Override
public void removeFromProximity(){
block.onProximityRemoved(tile);
Point2[] nearby = Edges.getEdges(block.size);
for(Point2 point : nearby){
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
//remove this tile from all nearby tile's proximities
if(other != null){
other.block().onProximityUpdate(other);
if(other.entity != null){
other.entity.proximity().remove(tile, true);
}
}
}
}
@Override
public void updateProximity(){
tmpTiles.clear();
proximity.clear();
Point2[] nearby = Edges.getEdges(block.size);
for(Point2 point : nearby){
Tile other = world.ltile(tile.x + point.x, tile.y + point.y);
if(other == null) continue;
if(other.entity == null || !(other.interactable(tile.team()))) continue;
//add this tile to proximity of nearby tiles
if(!other.entity.proximity().contains(tile, true)){
other.entity.proximity().add(tile);
}
tmpTiles.add(other);
}
//using a set to prevent duplicates
for(Tile tile : tmpTiles){
proximity.add(tile);
}
block.onProximityAdded(tile);
block.onProximityUpdate(tile);
for(Tile other : tmpTiles){
other.block().onProximityUpdate(other);
}
}
@Override
public Array<Tile> proximity(){
return proximity;
}
/** Tile configuration. Defaults to 0. Used for block rebuilding. */
@Override
public int config(){
return 0;
}
@Override
public void remove(){
if(sound != null){
sound.stop();
}
}
@Override
public void killed(){
Events.fire(new BlockDestroyEvent(tile));
block.breakSound.at(tile);
block.onDestroyed(tile);
tile.remove();
}
@Override
public void update(){
timeScaleDuration -= Time.delta();
if(timeScaleDuration <= 0f || !block.canOverdrive){
timeScale = 1f;
}
if(sound != null){
sound.update(x(), y(), block.shouldActiveSound(tile));
}
if(block.idleSound != Sounds.none && block.shouldIdleSound(tile)){
loops.play(block.idleSound, this, block.idleSoundVolume);
}
block.update(tile);
if(liquids != null){
liquids.update();
}
if(cons != null){
cons.update();
}
if(power != null){
power.graph.update();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,229 +0,0 @@
package mindustry.entities.effect;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class Fire extends TimedEntity implements SaveTrait, SyncTrait{
private static final IntMap<Fire> map = new IntMap<>();
private static final float baseLifetime = 1000f, spreadChance = 0.05f, fireballChance = 0.07f;
private int loadedPosition = -1;
private Tile tile;
private Block block;
private float baseFlammability = -1, puddleFlammability;
private float lifetime;
/** Deserialization use only! */
public Fire(){
}
@Remote
public static void onRemoveFire(int fid){
fireGroup.removeByID(fid);
}
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
public static void create(Tile tile){
if(net.client() || tile == null) return; //not clientside.
Fire fire = map.get(tile.pos());
if(fire == null){
fire = new Fire();
fire.tile = tile;
fire.lifetime = baseLifetime;
fire.set(tile.worldx(), tile.worldy());
fire.add();
map.put(tile.pos(), fire);
}else{
fire.lifetime = baseLifetime;
fire.time = 0f;
}
}
public static boolean has(int x, int y){
if(!Structs.inBounds(x, y, world.width(), world.height()) || !map.containsKey(Pos.get(x, y))){
return false;
}
Fire fire = map.get(Pos.get(x, y));
return fire.isAdded() && fire.fin() < 1f && fire.tile != null && fire.tile.x == x && fire.tile.y == y;
}
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.pos())){
Fire fire = map.get(tile.pos());
fire.time += intensity * Time.delta();
if(fire.time >= fire.lifetime()){
Events.fire(Trigger.fireExtinguish);
}
}
}
@Override
public TypeID getTypeID(){
return TypeIDs.fire;
}
@Override
public byte version(){
return 0;
}
@Override
public float lifetime(){
return lifetime;
}
@Override
public void update(){
if(Mathf.chance(0.1 * Time.delta())){
Effects.effect(Fx.fire, x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.05 * Time.delta())){
Effects.effect(Fx.fireSmoke, x + Mathf.range(4f), y + Mathf.range(4f));
}
if(Mathf.chance(0.001 * Time.delta())){
Sounds.fire.at(this);
}
time = Mathf.clamp(time + Time.delta(), 0, lifetime());
map.put(tile.pos(), this);
if(net.client()){
return;
}
if(time >= lifetime() || tile == null){
remove();
return;
}
TileEntity entity = tile.link().entity;
boolean damage = entity != null;
float flammability = baseFlammability + puddleFlammability;
if(!damage && flammability <= 0){
time += Time.delta() * 8;
}
if(baseFlammability < 0 || block != tile.block()){
baseFlammability = tile.block().getFlammability(tile);
block = tile.block();
}
if(damage){
lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Time.delta();
}
if(flammability > 1f && Mathf.chance(spreadChance * Time.delta() * Mathf.clamp(flammability / 5f, 0.3f, 2f))){
Point2 p = Geometry.d4[Mathf.random(3)];
Tile other = world.tile(tile.x + p.x, tile.y + p.y);
create(other);
if(Mathf.chance(fireballChance * Time.delta() * Mathf.clamp(flammability / 10f))){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), 1, 1);
}
}
if(Mathf.chance(0.1 * Time.delta())){
Puddle p = Puddle.getPuddle(tile);
if(p != null){
puddleFlammability = p.getFlammability() / 3f;
}else{
puddleFlammability = 0;
}
if(damage){
entity.damage(0.4f);
}
Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f,
unit -> !unit.isFlying() && !unit.isImmune(StatusEffects.burning),
unit -> unit.applyEffect(StatusEffects.burning, 60 * 5));
}
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeInt(tile.pos());
stream.writeFloat(lifetime);
stream.writeFloat(time);
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
this.loadedPosition = stream.readInt();
this.lifetime = stream.readFloat();
this.time = stream.readFloat();
add();
}
@Override
public void write(DataOutput data) throws IOException{
data.writeInt(tile.pos());
data.writeFloat(lifetime);
}
@Override
public void read(DataInput data) throws IOException{
int pos = data.readInt();
this.lifetime = data.readFloat();
x = Pos.x(pos) * tilesize;
y = Pos.y(pos) * tilesize;
tile = world.tile(pos);
}
@Override
public void reset(){
loadedPosition = -1;
tile = null;
baseFlammability = -1;
puddleFlammability = 0f;
incrementID();
}
@Override
public void added(){
if(loadedPosition != -1){
map.put(loadedPosition, this);
tile = world.tile(loadedPosition);
set(tile.worldx(), tile.worldy());
}
}
@Override
public void removed(){
if(tile != null){
Call.onRemoveFire(id);
map.remove(tile.pos());
}
}
@Override
public EntityGroup targetGroup(){
return fireGroup;
}
}

View File

@@ -1,90 +0,0 @@
package mindustry.entities.effect;
import arc.math.Mathf;
import arc.util.Time;
import mindustry.Vars;
import mindustry.entities.Effects;
import mindustry.entities.Effects.Effect;
import mindustry.entities.Effects.EffectRenderer;
import mindustry.entities.type.EffectEntity;
import mindustry.world.Tile;
/**
* A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer.
*/
public class GroundEffectEntity extends EffectEntity{
private boolean once;
@Override
public void update(){
GroundEffect effect = (GroundEffect)this.effect;
if(effect.isStatic){
time += Time.delta();
time = Mathf.clamp(time, 0, effect.staticLife);
if(!once && time >= lifetime()){
once = true;
time = 0f;
Tile tile = Vars.world.tileWorld(x, y);
if(tile != null && tile.floor().isLiquid){
remove();
}
}else if(once && time >= effect.staticLife){
remove();
}
}else{
super.update();
}
}
@Override
public void draw(){
GroundEffect effect = (GroundEffect)this.effect;
if(once && effect.isStatic)
Effects.renderEffect(id, effect, color, lifetime(), rotation, x, y, data);
else
Effects.renderEffect(id, effect, color, time, rotation, x, y, data);
}
@Override
public void reset(){
super.reset();
once = false;
}
/**
* An effect that is rendered on the ground layer as opposed to the top layer.
*/
public static class GroundEffect extends Effect{
/**
* How long this effect stays on the ground when static.
*/
public final float staticLife;
/**
* If true, this effect will stop and lie on the ground for a specific duration,
* after its initial lifetime is over.
*/
public final boolean isStatic;
public GroundEffect(float life, float staticLife, EffectRenderer draw){
super(life, draw);
this.staticLife = staticLife;
this.isStatic = true;
}
public GroundEffect(boolean isStatic, float life, EffectRenderer draw){
super(life, draw);
this.staticLife = 0f;
this.isStatic = isStatic;
}
public GroundEffect(float life, EffectRenderer draw){
super(life, draw);
this.staticLife = 0f;
this.isStatic = false;
}
}
}

View File

@@ -1,119 +0,0 @@
package mindustry.entities.effect;
import mindustry.annotations.Annotations.Loc;
import mindustry.annotations.Annotations.Remote;
import arc.graphics.g2d.*;
import arc.math.Interpolation;
import arc.math.Mathf;
import arc.math.geom.Position;
import arc.math.geom.Vec2;
import arc.util.Time;
import arc.util.pooling.Pools;
import mindustry.entities.*;
import mindustry.entities.type.TimedEntity;
import mindustry.entities.traits.DrawTrait;
import mindustry.entities.type.Unit;
import mindustry.graphics.Pal;
import mindustry.type.Item;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public class ItemTransfer extends TimedEntity implements DrawTrait{
private Vec2 from = new Vec2();
private Vec2 current = new Vec2();
private Vec2 tovec = new Vec2();
private Item item;
private float seed;
private Position to;
private Runnable done;
public ItemTransfer(){
}
@Remote(called = Loc.server, unreliable = true)
public static void transferItemEffect(Item item, float x, float y, Unit to){
if(to == null) return;
create(item, x, y, to, () -> {
});
}
@Remote(called = Loc.server, unreliable = true)
public static void transferItemToUnit(Item item, float x, float y, Unit to){
if(to == null) return;
create(item, x, y, to, () -> to.addItem(item));
}
@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++){
Time.run(i * 3, () -> create(item, x, y, tile, () -> {}));
}
tile.entity.items.add(item, amount);
}
public static void create(Item item, float fromx, float fromy, Position to, Runnable done){
ItemTransfer tr = Pools.obtain(ItemTransfer.class, ItemTransfer::new);
tr.item = item;
tr.from.set(fromx, fromy);
tr.to = to;
tr.done = done;
tr.seed = Mathf.range(1f);
tr.add();
}
@Override
public float lifetime(){
return 60;
}
@Override
public void reset(){
super.reset();
item = null;
to = null;
done = null;
from.setZero();
current.setZero();
tovec.setZero();
}
@Override
public void removed(){
if(done != null){
done.run();
}
Pools.free(this);
}
@Override
public void update(){
if(to == null){
remove();
return;
}
super.update();
current.set(from).interpolate(tovec.set(to.getX(), to.getY()), fin(), Interpolation.pow3);
current.add(tovec.set(to.getX(), to.getY()).sub(from).nor().rotate90(1).scl(seed * fslope() * 10f));
set(current.x, current.y);
}
@Override
public void draw(){
Lines.stroke(fslope() * 2f, Pal.accent);
Lines.circle(x, y, fslope() * 2f);
Draw.color(item.color);
Fill.circle(x, y, fslope() * 1.5f);
Draw.reset();
}
@Override
public EntityGroup targetGroup(){
return effectGroup;
}
}

View File

@@ -1,160 +0,0 @@
package mindustry.entities.effect;
import mindustry.annotations.Annotations.Loc;
import mindustry.annotations.Annotations.Remote;
import arc.struct.Array;
import arc.struct.IntSet;
import arc.graphics.Color;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.pooling.Pools;
import mindustry.content.Bullets;
import mindustry.entities.EntityGroup;
import mindustry.entities.Units;
import mindustry.entities.type.Bullet;
import mindustry.entities.type.TimedEntity;
import mindustry.entities.traits.DrawTrait;
import mindustry.entities.traits.TimeTrait;
import mindustry.entities.type.Unit;
import mindustry.game.Team;
import mindustry.gen.Call;
import mindustry.graphics.Pal;
import mindustry.world.Tile;
import static mindustry.Vars.*;
public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
public static final float lifetime = 10f;
private static final Rand random = new Rand();
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;
private static final float hitRange = 30f;
private static int lastSeed = 0;
private Array<Vec2> lines = new Array<>();
private Color color = Pal.lancerLaser;
/** For pooling use only. Do not call directly! */
public Lightning(){
}
/** Create a lighting branch at a location. Use Team.none to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
Call.createLighting(nextSeed(), team, color, damage, x, y, targetAngle, length);
}
public static int nextSeed(){
return lastSeed++;
}
/** Do not invoke! */
@Remote(called = Loc.server, unreliable = true)
public static void createLighting(int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
Lightning l = Pools.obtain(Lightning.class, Lightning::new);
Float dmg = damage;
l.x = x;
l.y = y;
l.color = color;
l.add();
random.setSeed(seed);
hit.clear();
boolean[] bhit = {false};
for(int i = 0; i < length / 2; i++){
Bullet.create(Bullets.damageLightning, l, team, x, y, 0f, 1f, 1f, dmg);
l.lines.add(new Vec2(x + Mathf.range(3f), y + Mathf.range(3f)));
if(l.lines.size > 1){
bhit[0] = false;
Position from = l.lines.get(l.lines.size - 2);
Position to = l.lines.get(l.lines.size - 1);
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
Tile tile = world.ltile(wx, wy);
if(tile != null && tile.block().insulated){
bhit[0] = true;
//snap it instead of removing
l.lines.get(l.lines.size -1).set(wx * tilesize, wy * tilesize);
return true;
}
return false;
});
if(bhit[0]) break;
}
rect.setSize(hitRange).setCenter(x, y);
entities.clear();
if(hit.size < maxChain){
Units.nearbyEnemies(team, rect, u -> {
if(!hit.contains(u.getID())){
entities.add(u);
}
});
}
Unit furthest = Geometry.findFurthest(x, y, entities);
if(furthest != null){
hit.add(furthest.getID());
x = furthest.x;
y = furthest.y;
}else{
rotation += random.range(20f);
x += Angles.trnsx(rotation, hitRange / 2f);
y += Angles.trnsy(rotation, hitRange / 2f);
}
}
}
@Override
public float lifetime(){
return lifetime;
}
@Override
public void reset(){
super.reset();
color = Pal.lancerLaser;
lines.clear();
}
@Override
public void removed(){
super.removed();
Pools.free(this);
}
@Override
public void draw(){
Lines.stroke(3f * fout());
Draw.color(color, Color.white, fin());
Lines.beginLine();
Lines.linePoint(x, y);
for(Position p : lines){
Lines.linePoint(p.getX(), p.getY());
}
Lines.endLine();
int i = 0;
for(Position p : lines){
Fill.square(p.getX(), p.getY(), (5f - (float)i++ / lines.size * 2f) * fout(), 45);
}
Draw.reset();
}
@Override
public EntityGroup targetGroup(){
return bulletGroup;
}
}

View File

@@ -1,322 +0,0 @@
package mindustry.entities.effect;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.pooling.Pool.*;
import arc.util.pooling.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import java.io.*;
import static mindustry.Vars.*;
public class Puddle extends SolidEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait{
private static final IntMap<Puddle> map = new IntMap<>();
private static final float maxLiquid = 70f;
private static final int maxGeneration = 2;
private static final Color tmp = new Color();
private static final Rect rect = new Rect();
private static final Rect rect2 = new Rect();
private static int seeds;
private int loadedPosition = -1;
private float updateTime;
private float lastRipple;
private Tile tile;
private Liquid liquid;
private float amount, targetAmount;
private float accepting;
private byte generation;
/** Deserialization use only! */
public Puddle(){
}
/** Deposists a puddle between tile and source. */
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
deposit(tile, source, liquid, amount, 0);
}
/** Deposists a puddle at a tile. */
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
}
/** Returns the puddle on the specified tile. May return null. */
public static Puddle getPuddle(Tile tile){
return map.get(tile.pos());
}
private static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){
if(tile == null) return;
if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){
reactPuddle(tile.floor().liquidDrop, liquid, amount, tile,
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
Puddle p = map.get(tile.pos());
if(generation == 0 && p != null && p.lastRipple <= Time.time() - 40f){
Effects.effect(Fx.ripple, tile.floor().liquidDrop.color,
(tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
p.lastRipple = Time.time();
}
return;
}
Puddle p = map.get(tile.pos());
if(p == null){
if(net.client()) return; //not clientside.
Puddle puddle = Pools.obtain(Puddle.class, Puddle::new);
puddle.tile = tile;
puddle.liquid = liquid;
puddle.amount = amount;
puddle.generation = (byte)generation;
puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
puddle.add();
map.put(tile.pos(), puddle);
}else if(p.liquid == liquid){
p.accepting = Math.max(amount, p.accepting);
if(generation == 0 && p.lastRipple <= Time.time() - 40f && p.amount >= maxLiquid / 2f){
Effects.effect(Fx.ripple, p.liquid.color, (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
p.lastRipple = Time.time();
}
}else{
p.amount += reactPuddle(p.liquid, liquid, amount, p.tile, p.x, p.y);
}
}
/**
* Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.
*/
private static boolean canStayOn(Liquid liquid, Liquid other){
return liquid == Liquids.oil && other == Liquids.water;
}
/** Reacts two liquids together at a location. */
private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){
if((dest.flammability > 0.3f && liquid.temperature > 0.7f) ||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
Fire.create(tile);
if(Mathf.chance(0.006 * amount)){
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), 1f, 1f);
}
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot puddle
if(Mathf.chance(0.5f * amount)){
Effects.effect(Fx.steam, x, y);
}
return -0.1f * amount;
}else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold puddle
if(Mathf.chance(0.8f * amount)){
Effects.effect(Fx.steam, x, y);
}
return -0.4f * amount;
}
return 0f;
}
@Remote(called = Loc.server)
public static void onPuddleRemoved(int puddleid){
puddleGroup.removeByID(puddleid);
}
public float getFlammability(){
return liquid.flammability * amount;
}
@Override
public TypeID getTypeID(){
return TypeIDs.puddle;
}
@Override
public byte version(){
return 0;
}
@Override
public void hitbox(Rect rect){
rect.setCenter(x, y).setSize(tilesize);
}
@Override
public void hitboxTile(Rect rect){
rect.setCenter(x, y).setSize(0f);
}
@Override
public void update(){
//no updating happens clientside
if(net.client()){
amount = Mathf.lerpDelta(amount, targetAmount, 0.15f);
}else{
//update code
float addSpeed = accepting > 0 ? 3f : 0f;
amount -= Time.delta() * (1f - liquid.viscosity) / (5f + addSpeed);
amount += accepting;
accepting = 0f;
if(amount >= maxLiquid / 1.5f && generation < maxGeneration){
float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Time.delta();
for(Point2 point : Geometry.d4){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if(other != null && other.block() == Blocks.air){
deposit(other, tile, liquid, deposited, generation + 1);
amount -= deposited / 2f; //tweak to speed up/slow down puddle propagation
}
}
}
amount = Mathf.clamp(amount, 0, maxLiquid);
if(amount <= 0f){
Call.onPuddleRemoved(getID());
}
}
//effects-only code
if(amount >= maxLiquid / 2f && updateTime <= 0f){
Units.nearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> {
if(unit.isFlying()) return;
unit.hitbox(rect2);
if(!rect.overlaps(rect2)) return;
unit.applyEffect(liquid.effect, 60 * 2);
if(unit.velocity().len() > 0.1){
Effects.effect(Fx.ripple, liquid.color, unit.x, unit.y);
}
});
if(liquid.temperature > 0.7f && (tile.link().entity != null) && Mathf.chance(0.3 * Time.delta())){
Fire.create(tile);
}
updateTime = 20f;
}
updateTime -= Time.delta();
}
@Override
public void draw(){
seeds = id;
boolean onLiquid = tile.floor().isLiquid;
float f = Mathf.clamp(amount / (maxLiquid / 1.5f));
float smag = onLiquid ? 0.8f : 0f;
float sscl = 20f;
Draw.color(tmp.set(liquid.color).shiftValue(-0.05f));
Fill.circle(x + Mathf.sin(Time.time() + seeds * 532, sscl, smag), y + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 8f);
Angles.randLenVectors(id, 3, f * 6f, (ex, ey) -> {
Fill.circle(x + ex + Mathf.sin(Time.time() + seeds * 532, sscl, smag),
y + ey + Mathf.sin(Time.time() + seeds * 53, sscl, smag), f * 5f);
seeds++;
});
Draw.color();
if(liquid.lightColor.a > 0.001f && f > 0){
Color color = liquid.lightColor;
float opacity = color.a * f;
renderer.lights.add(tile.drawx(), tile.drawy(), 30f * f, color, opacity * 0.8f);
}
}
@Override
public float drawSize(){
return 20;
}
@Override
public void writeSave(DataOutput stream) throws IOException{
stream.writeInt(tile.pos());
stream.writeFloat(x);
stream.writeFloat(y);
stream.writeByte(liquid.id);
stream.writeFloat(amount);
stream.writeByte(generation);
}
@Override
public void readSave(DataInput stream, byte version) throws IOException{
this.loadedPosition = stream.readInt();
this.x = stream.readFloat();
this.y = stream.readFloat();
this.liquid = content.liquid(stream.readByte());
this.amount = stream.readFloat();
this.generation = stream.readByte();
add();
}
@Override
public void reset(){
loadedPosition = -1;
tile = null;
liquid = null;
amount = 0;
generation = 0;
accepting = 0;
}
@Override
public void added(){
if(loadedPosition != -1){
map.put(loadedPosition, this);
tile = world.tile(loadedPosition);
}
}
@Override
public void removed(){
if(tile != null){
map.remove(tile.pos());
}
reset();
}
@Override
public void write(DataOutput data) throws IOException{
data.writeFloat(x);
data.writeFloat(y);
data.writeByte(liquid.id);
data.writeShort((short)(amount * 4));
data.writeInt(tile.pos());
}
@Override
public void read(DataInput data) throws IOException{
x = data.readFloat();
y = data.readFloat();
liquid = content.liquid(data.readByte());
targetAmount = data.readShort() / 4f;
int pos = data.readInt();
tile = world.tile(pos);
map.put(pos, this);
}
@Override
public EntityGroup targetGroup(){
return puddleGroup;
}
}

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