This commit is contained in:
Anuken
2020-02-09 12:16:12 -05:00
465 changed files with 16568 additions and 16421 deletions

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,8 +21,6 @@ import mindustry.maps.*;
import mindustry.mod.*;
import mindustry.net.Net;
import mindustry.net.*;
import mindustry.type.Weather.*;
import mindustry.world.blocks.defense.ForceProjector.*;
import java.io.*;
import java.nio.charset.*;
@@ -33,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;
@@ -61,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();
@@ -79,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 */
@@ -180,19 +178,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<WeatherEntity> weatherGroup;
public static EntityGroup<BaseUnit> unitGroup;
public static Player player;
public static Playerc player;
@Override
public void loadAsync(){
@@ -202,6 +188,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){
@@ -237,26 +224,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();
weatherGroup = entities.add(WeatherEntity.class);
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<>();
@@ -121,7 +122,7 @@ public class BlockIndexer{
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){
if(tile.team() == team){
int quadrantX = tile.x / quadrantSize;
int quadrantY = tile.y / quadrantSize;
structQuadrant(team).set(quadrantX, quadrantY);
@@ -145,7 +146,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 +163,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 +212,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 +234,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 +254,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 +305,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 +316,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 +362,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 +374,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;
}

View File

@@ -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 -> {

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 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,9 +49,10 @@ public class WaveSpawner{
eachFlyerSpawn((spawnX, spawnY) -> {
for(int i = 0; i < spawned; i++){
BaseUnit unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
unit.add();
//TODO
//Unitc unit = group.createUnit(state.rules.waveTeam);
//unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
//unit.add();
}
});
}else{
@@ -66,10 +63,11 @@ public class WaveSpawner{
for(int i = 0; i < spawned; i++){
Tmp.v1.rnd(spread);
BaseUnit unit = group.createUnit(state.rules.waveTeam);
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
//TODO
//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));
//Time.run(Math.min(i * 5, 60 * 2), () -> spawnEffect(unit));
}
});
}
@@ -77,7 +75,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 +89,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 +106,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());
}
}
}
@@ -141,11 +139,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.*;
@@ -57,7 +56,7 @@ 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
mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit,
@@ -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(){
@@ -269,13 +268,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);
}};
@@ -409,12 +408,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
@@ -573,7 +597,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;
@@ -664,13 +688,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());
@@ -956,6 +979,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;
@@ -1516,25 +1545,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();
}
});
@@ -1688,6 +1717,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));
}};
@@ -1697,6 +1727,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));
}};
@@ -1749,51 +1780,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,33 @@ 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);
//TODO implement
//Fire.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 +452,7 @@ public class Bullets implements ContentList{
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;
status = StatusEffects.burning;
keepVelocity = false;
}
@Override
@@ -460,7 +461,7 @@ public class Bullets implements ContentList{
}
@Override
public void draw(Bullet b){
public void draw(Bulletc b){
}
};
@@ -479,50 +480,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 +510,33 @@ 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)));
//TODO implement
// Fire.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 +582,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 +636,13 @@ 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);
//TODO implement
//Puddle.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

@@ -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);
});
});
});

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,23 +1,59 @@
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{
public static UnitType
public static UnitDef
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}) UnitDef dagger;
public static @EntityDef({Unitc.class, WaterMovec.class}) UnitDef vanguard;
public static UnitDef alpha, delta, tau, omega, dart, javelin, trident, glaive;
public static UnitDef starter;
@Override
public void load(){
draug = new UnitType("draug", MinerDrone::new){{
dagger = new UnitDef("dagger"){{
speed = 1f;
drag = 0.3f;
hitsize = 8f;
mass = 1.75f;
health = 130;
weapons.add(new Weapon("chain-blaster"){{
reload = 28f;
x = 4f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}});
}};
vanguard = new UnitDef("vanguard"){{
speed = 1.3f;
drag = 0.1f;
hitsize = 8f;
mass = 1.75f;
health = 130;
weapons.add(new Weapon("chain-blaster"){{
reload = 10f;
x = 1.25f;
alternate = true;
rotate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardCopper;
}});
}};
/*
draug = new UnitDef("draug", MinerDrone::new){{
flying = true;
drag = 0.01f;
speed = 0.3f;
@@ -27,12 +63,9 @@ public class UnitTypes implements ContentList{
minePower = 0.9f;
engineSize = 1.8f;
engineOffset = 5.7f;
weapon = new Weapon("you have incurred my wrath. prepare to die."){{
bullet = Bullets.lancerLaser;
}};
}};
spirit = new UnitType("spirit", RepairDrone::new){{
spirit = new UnitDef("spirit", RepairDrone::new){{
flying = true;
drag = 0.01f;
speed = 0.42f;
@@ -53,7 +86,7 @@ public class UnitTypes implements ContentList{
}};
}};
phantom = new UnitType("phantom", BuilderDrone::new){{
phantom = new UnitDef("phantom", BuilderDrone::new){{
flying = true;
drag = 0.01f;
mass = 2f;
@@ -76,7 +109,7 @@ public class UnitTypes implements ContentList{
}};
}};
dagger = new UnitType("dagger", GroundUnit::new){{
dagger = new UnitDef("dagger", GroundUnit::new){{
maxVelocity = 1.1f;
speed = 0.2f;
drag = 0.4f;
@@ -92,7 +125,7 @@ public class UnitTypes implements ContentList{
}};
}};
crawler = new UnitType("crawler", GroundUnit::new){{
crawler = new UnitDef("crawler", GroundUnit::new){{
maxVelocity = 1.27f;
speed = 0.285f;
drag = 0.4f;
@@ -103,27 +136,19 @@ public class UnitTypes implements ContentList{
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){{
titan = new UnitDef("titan", GroundUnit::new){{
maxVelocity = 0.8f;
speed = 0.22f;
drag = 0.4f;
@@ -137,7 +162,6 @@ public class UnitTypes implements ContentList{
shootSound = Sounds.flame;
length = 1f;
reload = 14f;
range = 30f;
alternate = true;
recoil = 1f;
ejectEffect = Fx.none;
@@ -145,7 +169,7 @@ public class UnitTypes implements ContentList{
}};
}};
fortress = new UnitType("fortress", GroundUnit::new){{
fortress = new UnitDef("fortress", GroundUnit::new){{
maxVelocity = 0.78f;
speed = 0.15f;
drag = 0.4f;
@@ -167,7 +191,7 @@ public class UnitTypes implements ContentList{
}};
}};
eruptor = new UnitType("eruptor", GroundUnit::new){{
eruptor = new UnitDef("eruptor", GroundUnit::new){{
maxVelocity = 0.81f;
speed = 0.16f;
drag = 0.4f;
@@ -189,7 +213,7 @@ public class UnitTypes implements ContentList{
}};
}};
chaosArray = new UnitType("chaos-array", GroundUnit::new){{
chaosArray = new UnitDef("chaos-array", GroundUnit::new){{
maxVelocity = 0.68f;
speed = 0.12f;
drag = 0.4f;
@@ -213,7 +237,7 @@ public class UnitTypes implements ContentList{
}};
}};
eradicator = new UnitType("eradicator", GroundUnit::new){{
eradicator = new UnitDef("eradicator", GroundUnit::new){{
maxVelocity = 0.68f;
speed = 0.12f;
drag = 0.4f;
@@ -238,7 +262,7 @@ public class UnitTypes implements ContentList{
}};
}};
wraith = new UnitType("wraith", FlyingUnit::new){{
wraith = new UnitDef("wraith", FlyingUnit::new){{
speed = 0.3f;
maxVelocity = 1.9f;
drag = 0.01f;
@@ -257,7 +281,7 @@ public class UnitTypes implements ContentList{
}};
}};
ghoul = new UnitType("ghoul", FlyingUnit::new){{
ghoul = new UnitDef("ghoul", FlyingUnit::new){{
health = 220;
speed = 0.2f;
maxVelocity = 1.4f;
@@ -281,7 +305,7 @@ public class UnitTypes implements ContentList{
}};
}};
revenant = new UnitType("revenant", HoverUnit::new){{
revenant = new UnitDef("revenant", HoverUnit::new){{
health = 1000;
mass = 5f;
hitsize = 20f;
@@ -312,7 +336,7 @@ public class UnitTypes implements ContentList{
}};
}};
lich = new UnitType("lich", HoverUnit::new){{
lich = new UnitDef("lich", HoverUnit::new){{
health = 6000;
mass = 20f;
hitsize = 40f;
@@ -345,7 +369,7 @@ public class UnitTypes implements ContentList{
}};
}};
reaper = new UnitType("reaper", HoverUnit::new){{
reaper = new UnitDef("reaper", HoverUnit::new){{
health = 11000;
mass = 30f;
hitsize = 56f;
@@ -386,5 +410,440 @@ public class UnitTypes implements ContentList{
shootSound = Sounds.shootBig;
}};
}};
/*
vanguard = new UnitDef("vanguard-ship"){
float healRange = 60f;
float healReload = 200f;
float healPercent = 10f;
{
flying = true;
drillTier = 1;
minePower = 4f;
speed = 0.49f;
drag = 0.09f;
health = 200f;
weaponOffsetX = -1;
engineSize = 2.3f;
weaponOffsetY = -1;
engineColor = Pal.lightTrail;
cellTrnsY = 1f;
buildPower = 1.2f;
weapon = 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 UnitDef("alpha-mech", false){
{
drillTier = -1;
speed = 0.5f;
boostSpeed = 0.95f;
itemCapacity = 15;
mass = 0.9f;
health = 150f;
buildPower = 0.9f;
weaponOffsetX = 1;
weaponOffsetY = -1;
engineColor = Pal.heal;
weapon = 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 UnitDef("delta-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;
weaponOffsetX = 4f;
weapon = 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 UnitDef("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 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 UnitDef("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(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 UnitDef("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;
buildPower = 1.1f;
weapon = 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 UnitDef("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;
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(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 UnitDef("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;
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(Playerc player){
return player.vel().len() > 1.2f;
}
};
glaive = new UnitDef("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;
buildPower = 1.2f;
weapon = new Weapon("bomber"){{
length = 1.5f;
reload = 13f;
alternate = true;
ejectEffect = Fx.shellEjectSmall;
bullet = Bullets.standardGlaive;
shootSound = Sounds.shootSnap;
}};
}
};
starter = vanguard;*/
}
}

View File

@@ -29,12 +29,10 @@ 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(),
@@ -42,7 +40,6 @@ public class ContentLoader{
new Weathers(),
new Planets(),
new Zones(),
new TypeIDs(),
//these are not really content classes, but this makes initialization easier
new LegacyColorMapper(),
@@ -264,7 +261,7 @@ public class ContentLoader{
return getBy(ContentType.zone);
}
public Array<UnitType> units(){
public Array<UnitDef> units(){
return getBy(ContentType.unit);
}
}

View File

@@ -3,17 +3,16 @@ 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.gen.*;
@@ -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);
}
}));
});
@@ -118,7 +115,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 +126,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 +136,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 +160,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(world.isZone()){
data.unlockContent(e.unit.type());
}
});
}
@@ -204,11 +201,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();
@@ -286,7 +281,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();
}
});
@@ -297,13 +292,13 @@ public class Control implements ApplicationListener, Loadable{
zone.rules.get(state.rules);
state.rules.zone = zone;
for(TileEntity core : state.teams.playerCores()){
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;
@@ -426,9 +421,9 @@ public class Control implements ApplicationListener, Loadable{
input.update();
if(world.isZone()){
for(TileEntity tile : state.teams.cores(player.getTeam())){
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

@@ -1,9 +1,9 @@
package mindustry.core;
import arc.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -25,8 +25,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){

View File

@@ -6,12 +6,10 @@ import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.gen.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -33,10 +31,6 @@ 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);
}
@@ -62,7 +56,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
@@ -109,10 +103,10 @@ public class Logic implements ApplicationListener{
if(!world.isZone()){
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,10 +121,8 @@ 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());
}
@@ -176,8 +168,8 @@ 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){
@@ -185,12 +177,12 @@ public class Logic implements ApplicationListener{
}
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;
@@ -213,7 +205,8 @@ public class Logic implements ApplicationListener{
if(!state.is(State.menu)){
if(!net.client()){
state.enemies = unitGroup.count(b -> b.getTeam() == state.rules.waveTeam && b.countsAsEnemy());
//TODO
//state.enemies = Groups.unit.count(b -> b.team() == state.rules.waveTeam && b.countsAsEnemy());
}
if(!state.isPaused()){
@@ -229,13 +222,17 @@ public class Logic implements ApplicationListener{
runWave();
}
Groups.update();
//TODO update groups
/*
if(!headless){
effectGroup.update();
groundEffectGroup.update();
}
if(!state.isEditor()){
unitGroup.update();
Groups.unit.update();
puddleGroup.update();
shieldGroup.update();
bulletGroup.update();
@@ -243,12 +240,12 @@ public class Logic implements ApplicationListener{
fireGroup.update();
weatherGroup.update();
}else{
unitGroup.updateEvents();
collisions.updatePhysics(unitGroup);
Groups.unit.updateEvents();
collisions.updatePhysics(Groups.unit);
}
playerGroup.update();
Groups.player.update();
//effect group only contains item transfers in the headless version, update it!
if(headless){
@@ -257,9 +254,8 @@ public class Logic implements ApplicationListener{
if(!state.isEditor()){
//bulletGroup
collisions.collideGroups(bulletGroup, unitGroup);
collisions.collideGroups(bulletGroup, playerGroup);
}
collisions.collideGroups(bulletGroup, Groups.unit);
}*/
}
if(!net.client() && !world.isInvalidMap() && !state.isEditor() && state.rules.canGameOver){

View File

@@ -1,29 +1,23 @@
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.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 +55,7 @@ public class NetClient implements ApplicationListener{
net.handleClient(Connect.class, packet -> {
Log.info("Connecting to server: {0}", packet.addressTCP);
player.isAdmin = false;
player.admin(false);
reset();
@@ -76,11 +70,11 @@ public class NetClient implements ApplicationListener{
});
ConnectPacket c = new ConnectPacket();
c.name = player.name;
c.name = player.name();
c.mods = mods.getModStrings();
c.mobile = mobile;
c.versionType = Version.type;
c.color = Color.rgba8888(player.color);
c.color = Color.rgba8888(player.color());
c.usid = getUsid(packet.addressTCP);
c.uuid = platform.getUUID();
@@ -134,14 +128,14 @@ public class NetClient implements ApplicationListener{
//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 +149,7 @@ public class NetClient implements ApplicationListener{
//called when a server recieves a chat message from a player
@Remote(called = Loc.server, targets = Loc.client)
public static void sendChatMessage(Player player, String message){
public static void sendChatMessage(Playerc player, String message){
if(message.length() > maxTextLength){
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
@@ -172,19 +166,19 @@ public class NetClient implements ApplicationListener{
}
//special case; graphical server needs to see its message
if(!headless && player == Vars.player){
Vars.ui.chatfrag.addMessage(message, colorizeName(player.id, player.name));
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){
@@ -205,9 +199,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)
@@ -220,8 +214,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)
@@ -230,7 +224,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);
}
@@ -266,6 +260,32 @@ public class NetClient implements ApplicationListener{
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){
ui.showInfoPopup(message, duration, align, top, left, bottom, right);
}
@Remote(variants = Variant.both)
public static void onLabel(String info, float duration, float worldx, float worldy){
ui.showLabel(info, duration, worldx, worldy);
}
/*
@Remote(variants = Variant.both, unreliable = true)
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
effect.at(x, y, rotation, color);
}
@Remote(variants = Variant.both)
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
effect.at(x, y, rotation, color);
}*/
@Remote(variants = Variant.both)
public static void onInfoToast(String message, float duration){
ui.showInfoToast(message, duration);
}
@Remote(variants = Variant.both)
public static void onSetRules(Rules rules){
state.rules = rules;
@@ -273,7 +293,7 @@ public class NetClient implements ApplicationListener{
@Remote(variants = Variant.both)
public static void onWorldDataBegin(){
entities.clear();
Groups.all.clear();
netClient.removed.clear();
logic.reset();
@@ -291,41 +311,38 @@ 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;
@@ -334,17 +351,14 @@ public class NetClient implements ApplicationListener{
//read the entity
entity.read(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){
@@ -365,7 +379,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.read(input);
}
}catch(Exception e){
e.printStackTrace();
@@ -393,7 +407,7 @@ 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(input);
}else{
new ItemModule().read(input);
}
@@ -438,7 +452,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());
}
@@ -452,7 +467,7 @@ public class NetClient implements ApplicationListener{
quiet = false;
lastSent = 0;
entities.clear();
Groups.all.clear();
ui.chatfrag.clearMessages();
}
@@ -486,21 +501,22 @@ public class NetClient implements ApplicationListener{
}
void sync(){
//TODO implement
/*
if(timer.get(0, playerSyncTime)){
BuildRequest[] requests;
//limit to 10 to prevent buffer overflows
int usedRequests = Math.min(player.buildQueue().size, 10);
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[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.vel().x, player.vel().y,
player.miner().mineTile(),
player.isBoosting, player.isShooting, ui.chatfrag.shown(), player.isBuilding,
requests,
Core.camera.position.x, Core.camera.position.y,
@@ -509,7 +525,7 @@ public class NetClient implements ApplicationListener{
if(timer.get(1, 60)){
Call.onPing(Time.millis());
}
}*/
}
String getUsid(String ip){

View File

@@ -9,13 +9,8 @@ import arc.util.*;
import arc.util.CommandHandler.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
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.*;
@@ -49,8 +44,8 @@ public class NetServer implements ApplicationListener{
//find team with minimum amount of players and auto-assign player to that.
TeamData re = state.teams.getActive().min(data -> {
int count = 0;
for(Player other : players){
if(other.getTeam() == data.team && other != player){
for(Playerc other : players){
if(other.team() == data.team && other != player){
count++;
}
}
@@ -121,7 +116,7 @@ public class NetServer implements ApplicationListener{
return;
}
if(admins.getPlayerLimit() > 0 && playerGroup.size() >= admins.getPlayerLimit()){
if(admins.getPlayerLimit() > 0 && Groups.player.size() >= admins.getPlayerLimit()){
con.kick(KickReason.playerLimit);
return;
}
@@ -162,16 +157,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,17 +188,14 @@ 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);
try{
writeBuffer.position(0);
@@ -219,7 +209,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);
@@ -234,6 +224,13 @@ public class NetServer implements ApplicationListener{
RemoteReadServer.readPacket(packet.writeBuffer, 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 +243,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 +254,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 +269,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()));
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 +320,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 +343,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 +381,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 +412,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 +422,75 @@ 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;
//TODO this entire thing needs to be rewritten.
/*
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;
@@ -500,14 +504,14 @@ public class NetServer implements ApplicationListener{
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.pointerX = pointerX;
player.pointerY = pointerY;
player.setMineTile(mining);
player.mouseX(pointerX);
player.mouseY(pointerY);
player.miner().mineTile(mining);
player.isTyping = chatting;
player.isBoosting = boosting;
player.isShooting = shooting;
player.isBuilding = building;
player.buildQueue().clear();
player.builder().requests().clear();
for(BuildRequest req : requests){
if(req == null) continue;
@@ -526,11 +530,11 @@ public class NetServer implements ApplicationListener{
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);
Call.removeQueueBlock(player.con(), req.x, req.y, req.breaking);
connection.rejectedRequests.add(req);
continue;
}
player.buildQueue().addLast(req);
player.builder().requests().addLast(req);
}
connection.rejectedRequests.clear();
@@ -554,7 +558,7 @@ public class NetServer implements ApplicationListener{
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
Call.onPositionSet(player.con(), newx, newy); //teleport and correct position when necessary
}
//reset player to previous synced position so it gets interpolated
@@ -563,23 +567,23 @@ public class NetServer implements ApplicationListener{
//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
player.vel().set(xVelocity, yVelocity); //only for visual calculation purposes, doesn't actually update the player
connection.lastRecievedClientSnapshot = snapshotID;
connection.lastRecievedClientTime = Time.millis();
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 +592,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 +631,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,11 +683,11 @@ 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());
dataStream.writeInt(entity.tile().pos());
entity.write(dataStream);
if(syncStream.size() > maxSnapshotSize){
@@ -700,65 +706,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(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(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 +808,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 +835,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

@@ -1,8 +1,6 @@
package mindustry.core;
import arc.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
@@ -10,19 +8,12 @@ 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,7 +25,7 @@ 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);
@@ -48,51 +39,14 @@ public class Renderer implements ApplicationListener{
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")){
@@ -121,8 +75,10 @@ public class Renderer implements ApplicationListener{
}else{
Vec2 position = Tmp.v3.set(player);
if(player.isDead()){
TileEntity core = player.getClosestCore();
//TODO camera controls should be in input
/*
if(player.dead()){
Tilec core = player.closestCore();
if(core != null){
if(player.spawner == null){
camera.position.lerpDelta(core.x, core.y, 0.08f);
@@ -132,7 +88,7 @@ public class Renderer implements ApplicationListener{
}
}else if(control.input instanceof DesktopInput && !state.isPaused()){
camera.position.lerpDelta(position, 0.08f);
}
}*/
updateShake(0.75f);
if(pixelator.enabled()){
@@ -150,7 +106,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 +122,13 @@ public class Renderer implements ApplicationListener{
}
}
@Override
public void resume(){
if(settings.getBool("bloom") && bloom != null){
bloom.resume();
}
}
void setupBloom(){
try{
if(bloom != null){
@@ -207,6 +170,117 @@ public class Renderer implements ApplicationListener{
}
}
public void draw(){
camera.update();
if(Float.isNaN(camera.position.x) || Float.isNaN(camera.position.y)){
camera.position.set(player);
}
graphics.clear(clearColor);
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());
blocks.floor.drawFloor();
Groups.drawFloor();
Groups.drawFloorOver();
blocks.processBlocks();
blocks.drawShadows();
Draw.color();
blocks.floor.beginDraw();
blocks.floor.drawLayer(CacheLayer.walls);
blocks.floor.endDraw();
blocks.drawBlocks(Layer.block);
blocks.drawFog();
blocks.drawDestroyed();
Draw.shader(Shaders.blockbuild, true);
blocks.drawBlocks(Layer.placement);
Draw.shader();
blocks.drawBlocks(Layer.overlay);
Groups.drawGroundShadows();
Groups.drawGroundUnder();
Groups.drawGround();
blocks.drawBlocks(Layer.turret);
Groups.drawFlyingShadows();
blocks.drawBlocks(Layer.power);
blocks.drawBlocks(Layer.lights);
Groups.drawFlying();
Draw.flush();
if(bloom != null && !pixelator.enabled()){
bloom.capture();
}
Groups.drawBullets();
Groups.drawEffects();
Draw.flush();
if(bloom != null && !pixelator.enabled()){
bloom.render();
}
overlays.drawBottom();
if(player.isBuilder()){
player.builder().drawBuildRequests();
}
overlays.drawTop();
if(!pixelator.enabled()){
Groups.drawNames();
}
if(state.rules.lighting){
lights.draw();
}
drawLanding();
Draw.color();
Draw.flush();
}
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.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.getX() + x, entity.getY() + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.getX(), entity.getY(), reg.getWidth() * Draw.scl * scl, reg.getHeight() * Draw.scl * scl, fract * 135f);
Draw.reset();
}
}
/*
public void draw(){
camera.update();
@@ -217,8 +291,8 @@ public class Renderer implements ApplicationListener{
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());
@@ -276,20 +350,20 @@ public class Renderer implements ApplicationListener{
}
overlays.drawBottom();
playerGroup.draw(p -> p.isLocal, Player::drawBuildRequests);
Groups.player.draw(p -> p.isLocal, Playerc::drawBuildRequests);
if(shieldGroup.countInBounds() > 0){
if(settings.getBool("animatedshields") && Shaders.shield != null){
Draw.flush();
shieldBuffer.begin();
effectBuffer.begin();
graphics.clear(Color.clear);
shieldGroup.draw();
shieldGroup.draw(shield -> true, ShieldEntity::drawOver);
Draw.flush();
shieldBuffer.end();
effectBuffer.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.rect(Draw.wrap(effectBuffer.getTexture()), camera.position.x, camera.position.y, camera.width, -camera.height);
Draw.color();
Draw.shader();
}else{
@@ -299,7 +373,7 @@ public class Renderer implements ApplicationListener{
overlays.drawTop();
playerGroup.draw(p -> !p.isDead(), Player::drawName);
Groups.player.draw(p -> !p.isDead(), Playerc::drawName);
if(state.rules.lighting){
lights.draw();
@@ -312,25 +386,25 @@ public class Renderer implements ApplicationListener{
}
private void drawLanding(){
if(landTime > 0 && player.getClosestCore() != null){
if(landTime > 0 && player.closestCore() != null){
float fract = landTime / Fx.coreLand.lifetime;
TileEntity entity = player.getClosestCore();
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();
}
@@ -340,15 +414,15 @@ public class Renderer implements ApplicationListener{
Draw.color(0, 0, 0, 0.4f);
float rad = 1.6f;
Cons<Unit> draw = u -> {
Cons<Unitc> 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);
Groups.unit.draw(unit -> !unit.isDead(), draw::get);
if(!playerGroup.isEmpty()){
playerGroup.draw(unit -> !unit.isDead(), draw::get);
if(!Groups.player.isEmpty()){
Groups.player.draw(unit -> !unit.isDead(), draw::get);
}
Draw.color();
@@ -358,22 +432,22 @@ public class Renderer implements ApplicationListener{
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));
Groups.unit.draw(unit -> unit.isFlying() && !unit.isDead(), baseUnit -> baseUnit.drawShadow(trnsX, trnsY));
Groups.player.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);
Groups.unit.draw(u -> u.isFlying() == flying && !u.isDead(), Unitc::drawUnder);
Groups.player.draw(p -> p.isFlying() == flying && !p.isDead(), Unitc::drawUnder);
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawAll);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawAll);
Groups.unit.draw(u -> u.isFlying() == flying && !u.isDead(), Unitc::drawAll);
Groups.player.draw(p -> p.isFlying() == flying, Unitc::drawAll);
unitGroup.draw(u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver);
playerGroup.draw(p -> p.isFlying() == flying, Unit::drawOver);
}
Groups.unit.draw(u -> u.isFlying() == flying && !u.isDead(), Unitc::drawOver);
Groups.player.draw(p -> p.isFlying() == flying, Unitc::drawOver);
}*/
public void scaleCamera(float amount){
targetscale += amount;
@@ -400,6 +474,8 @@ public class Renderer implements ApplicationListener{
}
public void takeMapScreenshot(){
//TODO uncomment
/*
drawGroundShadows();
int w = world.width() * tilesize, h = world.height() * tilesize;
@@ -448,7 +524,7 @@ public class Renderer implements ApplicationListener{
buffer.dispose();
Core.settings.put("animatedwater", hadWater);
Core.settings.put("animatedshields", hadShields);
Core.settings.put("animatedshields", hadShields);*/
}
}

View File

@@ -9,6 +9,7 @@ import arc.graphics.*;
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.*;
@@ -289,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();
@@ -426,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);
@@ -459,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

@@ -12,6 +12,7 @@ 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.*;
@@ -163,7 +164,7 @@ public class World{
addDarkness(tiles);
}
entities.all().each(group -> group.resize(-finalWorldBounds, -finalWorldBounds, tiles.width() * tilesize + finalWorldBounds * 2, tiles.height() * tilesize + finalWorldBounds * 2));
Groups.resize(-finalWorldBounds, -finalWorldBounds, tiles.width() * tilesize + finalWorldBounds * 2, tiles.height() * tilesize + finalWorldBounds * 2);
generating = false;
Events.fire(new WorldLoadEvent());
@@ -377,7 +378,7 @@ public class World{
Tile tile = tiles.getn(x, y);
Block result = tile.block();
Team team = tile.getTeam();
Team team = tile.team();
int offsetx = -(result.size - 1) / 2;
int offsety = -(result.size - 1) / 2;

View File

@@ -4,16 +4,16 @@ package mindustry.ctype;
public enum ContentType{
item,
block,
mech,
mech_UNUSED,
bullet,
liquid,
status,
unit,
weather,
effect,
effect_UNUSED,
zone,
loadout,
typeid,
loadout_UNUSED,
typeid_UNUSED,
error,
planet;

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){

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

@@ -139,10 +139,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

@@ -83,7 +83,7 @@ public class MapEditor{
//re-add them
for(Tile tile : tiles){
if(tile.block().isMultiblock()){
tile.set(tile.block(), tile.getTeam());
tile.set(tile.block(), tile.team());
}
}

View File

@@ -176,7 +176,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name()) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();
}
@@ -283,7 +283,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
}
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 +296,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.getTags().put("rules", JsonIO.write(state.rules));
editor.getTags().remove("width");
editor.getTags().remove("height");
player.dead = true;
//TODO unkill player
//player.dead = true;
Map returned = null;

View File

@@ -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());
}
}
@@ -424,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

@@ -111,7 +111,7 @@ public class MapRenderer implements Disposable{
IndexedRenderer mesh = chunks[x][y];
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

@@ -30,7 +30,7 @@ public class WaveInfoDialog extends FloatingDialog{
private Table table, preview;
private int start = 0;
private UnitType lastType = UnitTypes.dagger;
private UnitDef lastType = UnitTypes.dagger;
private float updateTimer, updatePeriod = 1f;
public WaveInfoDialog(MapEditor editor){
@@ -221,7 +221,7 @@ public class WaveInfoDialog extends FloatingDialog{
dialog.setFillParent(true);
dialog.cont.pane(p -> {
int i = 0;
for(UnitType type : content.units()){
for(UnitDef type : content.units()){
p.addButton(t -> {
t.left();
t.addImage(type.icon(mindustry.ui.Cicon.medium)).size(40f).padRight(2f);
@@ -256,7 +256,7 @@ public class WaveInfoDialog extends FloatingDialog{
for(int j = 0; j < spawned.length; j++){
if(spawned[j] > 0){
UnitType type = content.getByID(ContentType.unit, j);
UnitDef type = content.getByID(ContentType.unit, j);
table.addImage(type.icon(Cicon.medium)).size(8f * 4f).padRight(4);
table.add(spawned[j] + "x").color(Color.lightGray).padRight(6);
table.row();

View File

@@ -0,0 +1,56 @@
package mindustry.entities;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
class AllEntities{
@EntityDef(value = {Bulletc.class, Velc.class, Timedc.class}, pooled = true)
class BulletDef{}
@EntityDef(value = {Tilec.class}, isFinal = false)
class TileDef{}
@EntityDef(value = {StandardEffectc.class, Childc.class}, pooled = true)
class EffectDef{}
@EntityDef(value = {GroundEffectc.class, Childc.class}, pooled = true)
class GroundEffectDef{}
@EntityDef(value = {Decalc.class}, pooled = true)
class DecalDef{}
@EntityDef({Playerc.class})
class PlayerDef{}
@EntityDef({Unitc.class})
class GenericUnitDef{}
@EntityDef(value = {Firec.class}, pooled = true)
class FireDef{}
@GroupDef(Entityc.class)
void all(){
}
@GroupDef(Playerc.class)
void player(){
}
@GroupDef(value = Unitc.class, spatial = true)
void unit(){
}
@GroupDef(Tilec.class)
void tile(){
}
@GroupDef(Syncc.class)
void 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,13 @@ public class Damage{
float cy = y + Mathf.range(range);
Tile tile = world.tileWorld(cx, cy);
if(tile != null){
Fire.create(tile);
//TODO uncomment
//Fire.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 +80,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 +88,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 +122,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 +133,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 +143,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 +175,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 +233,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 +256,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,24 @@
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 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 +34,52 @@ 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() : EffectEntity.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;
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;
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,13 @@
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 arc.util.*;
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 +23,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 +48,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 +58,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 +108,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 +206,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 +219,16 @@ 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);
}
}
}
});
}
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,78 @@ 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 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,16 +94,6 @@ 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;
}
}
}
}
@@ -175,93 +114,73 @@ public class EntityGroup<T extends Entity> implements Iterable<T>{
}
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 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,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

@@ -1,11 +1,9 @@
package mindustry.entities;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.entities.traits.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -13,13 +11,13 @@ import static mindustry.Vars.*;
/** Utility class for unit and team interactions.*/
public class Units{
private static Rect hitrect = new Rect();
private static Unit result;
private static Unitc result;
private static float cdist;
private static boolean boolResult;
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
public static boolean canInteract(Player player, Tile tile){
return player == null || tile == null || tile.interactable(player.getTeam());
public static boolean canInteract(Playerc player, Tile tile){
return player == null || tile == null || tile.interactable(player.team());
}
/**
@@ -31,18 +29,18 @@ public class Units{
* @param range The maximum distance from the target X/Y the targeter can be for it to be valid
* @return whether the target is invalid
*/
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range){
return target == null || (range != Float.MAX_VALUE && !target.withinDst(x, y, range)) || target.getTeam() == team || !target.isValid();
public static boolean invalidateTarget(Teamc target, Team team, float x, float y, float range){
return target == null || (range != Float.MAX_VALUE && !target.withinDst(x, y, range)) || 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(Teamc, Team, float, float, float)} */
public static boolean invalidateTarget(Teamc 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(Teamc, 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;
@@ -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

@@ -4,7 +4,7 @@ import arc.math.geom.Rect;
import arc.util.Time;
import mindustry.content.Fx;
import mindustry.entities.Units;
import mindustry.entities.type.Bullet;
import mindustry.gen.*;
public class FlakBulletType extends BasicBulletType{
protected static Rect rect = new Rect();
@@ -24,18 +24,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(), rect.setSize(explodeRange * 2f).setCenter(b.x(), b.y()), unit -> {
if(b.data() instanceof Float) 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

@@ -5,9 +5,7 @@ import arc.graphics.g2d.*;
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,36 +43,38 @@ 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);
b.remove();
hit(b);
}
//TODO implement
Tile tile = world.tileWorld(b.x(), b.y());
//if(tile != null && Fire.has(tile.x, tile.y)){
//Fire.extinguish(tile, 100f);
// b.remove();
// hit(b);
//}
}
}
@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);
//TODO implement
// Puddle.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);
//Fire.extinguish(world.tileWorld(hitx, hity), intensity);
for(Point2 p : Geometry.d4){
Fire.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
// Fire.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;
transient float x, y;
transient 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,229 @@
package mindustry.entities.def;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.struct.Queue;
import arc.util.*;
import arc.util.ArcAnnotate.*;
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{
static final Vec2[] tmptr = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
transient float x, y, rotation;
Queue<BuildRequest> requests = new Queue<>();
float buildSpeed = 1f;
//boolean building;
void updateBuilding(){
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;
}
}
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();
}
void drawOver(){
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,115 @@
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.*;
@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();
}
@Override
public void collision(Hitboxc other, float x, float y){
if(!type.pierce) remove();
type.hit(this, x, y);
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);
}
}
@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{
transient 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,29 @@
package mindustry.entities.def;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class DecalComp implements Drawc, Timedc, Rotc, Posc, DrawLayerFloorc{
transient 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{
transient 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,59 @@
package mindustry.entities.def;
import arc.func.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.gen.*;
import java.io.*;
import static mindustry.Vars.player;
@Component
@BaseComponent
abstract class EntityComp{
private boolean added;
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();
void read(DataInput input) throws IOException{
//TODO dynamic io
}
void write(DataOutput output) throws IOException{
//TODO dynamic io
}
}

View File

@@ -0,0 +1,8 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class FireComp implements Timedc{
}

View File

@@ -0,0 +1,73 @@
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{
transient float x, y;
transient Vec2 vel;
float elevation;
float drownTime;
float splashTimer;
boolean isGrounded(){
return elevation < 0.001f;
}
boolean isFlying(){
return elevation >= 0.001f;
}
boolean canDrown(){
return isGrounded();
}
void moveAt(Vec2 vector){
Vec2 t = Tmp.v3.set(vector).scl(floorSpeedMultiplier()); //target vector
float mag = Tmp.v3.len();
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(isGrounded() && floor.isLiquid){
if((splashTimer += Mathf.dst(deltaX(), deltaY())) >= 7f){
floor.walkEffect.at(x, y, 0, floor.color);
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.color);
}
//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,13 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class GroundEffectComp implements Effectc, DrawLayerFloorOverc{
@Override
public void drawFloorOver(){
draw();
}
}

View File

@@ -0,0 +1,83 @@
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, maxHealth = 1f, hitTime;
boolean dead;
boolean isValid(){
return !dead && isAdded();
}
float healthf(){
return health / maxHealth;
}
@Override
public void update(){
hitTime -= Time.delta() / hitDuration;
}
float hitAlpha(){
return hitTime / 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.0001f;
}
void damage(float amount){
health -= amount;
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,51 @@
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{
transient float x, y;
float hitSize;
float lastX, lastY;
@Override
public void update(){
}
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 Intersector.overlapsRect(x - hitSize/2f, y - hitSize/2f, hitSize, hitSize,
other.x() - other.hitSize()/2f, other.y() - other.hitSize()/2f, other.hitSize(), other.hitSize());
}
@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();
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,25 @@
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{
transient float x, y;
float baseRotation, 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{
float mass = 1f;
public void impulse(float x, float y){
vel().add(x / mass, y / mass);
}
}

View File

@@ -0,0 +1,104 @@
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{
transient float x, y, rotation;
float mineTimer;
@Nullable Tile mineTile;
abstract boolean canMine(Item item);
abstract float miningSpeed();
abstract boolean offloadImmediately();
boolean mining(){
return mineTile != null;
}
void updateMining(){
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
|| 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);
}
}
}
void drawOver(){
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.*;
@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;
@Nullable 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;
lastText = null;
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){
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,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,13 @@
package mindustry.entities.def;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class StandardEffectComp implements Effectc, DrawLayerEffectsc{
@Override
public void drawEffects(){
draw();
}
}

View File

@@ -1,34 +1,38 @@
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 float speedMultiplier;
@ReadOnly float damageMultiplier;
@ReadOnly float 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
@@ -38,12 +42,12 @@ public class Statuses implements Saveable{
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 +62,68 @@ 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();
for(StatusEntry entry : statuses){
statuses.eachFilter(entry -> {
entry.time = Math.max(entry.time - Time.delta(), 0);
applied.set(entry.effect.id);
if(entry.time <= 0){
Pools.free(entry);
removals.add(entry);
return true;
}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);
}
return false;
});
}
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 +131,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 +147,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{
transient 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{
transient float x, y;
Team team = Team.sharded;
public @Nullable
Tilec closestCore(){
return state.teams.closestCore(x, y, team);
}
}

View File

@@ -0,0 +1,241 @@
package mindustry.entities.def;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.modules.*;
import static mindustry.Vars.*;
@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;
Tile tile;
Block block;
Array<Tile> proximity = new Array<>(8);
PowerModule power;
ItemModule items;
LiquidModule liquids;
ConsumeModule cons;
private float timeScale = 1f, timeScaleDuration;
private @Nullable SoundLoop sound;
private boolean sleeping;
private 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;
}
@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,167 @@
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,
DrawLayerGroundc, DrawLayerFlyingc, DrawLayerGroundShadowsc, DrawLayerFlyingShadowsc{
transient float x, y, rotation;
private UnitController controller;
private UnitDef 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(UnitDef def, UnitController controller){
type(type);
controller(controller);
}
@Override
public void type(UnitDef type){
this.type = type;
maxHealth(type.health);
heal();
drag(type.drag);
hitSize(type.hitsize);
controller(type.createController());
setupWeapons(type);
}
@Override
public UnitDef type(){
return type;
}
public void lookAt(float angle){
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed);
}
public void lookAt(Position pos){
lookAt(angleTo(pos));
}
@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);
}
}
}
@Override
public boolean isImmune(StatusEffect effect){
return type.immunities.contains(effect);
}
@Override
public void draw(){
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 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);
//TODO cleanup
//ScorchDecal.create(getX(), getY());
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));
//TODO implement suicide bomb trigger
//if(explosiveness > 7f && this == player){
// Events.fire(Trigger.suicideBomb);
//}
}
}

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{
transient float x, y;
final Vec2 vel = new Vec2();
float drag = 0f;
//velocity needs to be called first, as it affects delta and lastPosition
@MethodPriority(-1)
@Override
public void update(){
move(vel.x, vel.y);
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{
transient 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,137 @@
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{
transient 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(UnitDef 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.rotation = Angles.moveToward(mount.rotation, Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation(), weapon.rotateSpeed);
}
if(mount.shoot){
float rotation = this.rotation - 90;
//shoot if applicable
//TODO only shoot if angle is reached, don't shoot inaccurately
if(mount.reload <= 0.0001f){
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);
shoot(weapon, shootX, shootY, shootAngle);
}
mount.side = !mount.side;
mount.reload = weapon.reload;
}
}
}
}
private void shoot(Weapon weapon, float x, float y, float rotation){
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);
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;
}
}

View File

@@ -1,41 +0,0 @@
package mindustry.entities.effect;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Mathf;
import static mindustry.Vars.headless;
public class RubbleDecal extends Decal{
private TextureRegion region;
/** Creates a rubble effect at a position. Provide a block size to use. */
public static void create(float x, float y, int size){
if(headless) return;
RubbleDecal decal = new RubbleDecal();
decal.region = Core.atlas.find("rubble-" + size + "-" + Mathf.randomSeed(decal.id, 0, 1));
if(!Core.atlas.isFound(decal.region)){
return;
}
decal.set(x, y);
decal.add();
}
@Override
public float lifetime(){
return 8200f;
}
@Override
public void drawDecal(){
if(!Core.atlas.isFound(region)){
remove();
return;
}
Draw.rect(region, x, y, Mathf.randomSeed(id, 0, 4) * 90);
}
}

View File

@@ -1,49 +0,0 @@
package mindustry.entities.effect;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.graphics.g2d.TextureRegion;
import arc.math.Angles;
import arc.math.Mathf;
import mindustry.world.Tile;
import static mindustry.Vars.headless;
import static mindustry.Vars.world;
public class ScorchDecal extends Decal{
private static final int scorches = 5;
private static final TextureRegion[] regions = new TextureRegion[scorches];
public static void create(float x, float y){
if(headless) return;
if(regions[0] == null || regions[0].getTexture().isDisposed()){
for(int i = 0; i < regions.length; i++){
regions[i] = Core.atlas.find("scorch" + (i + 1));
}
}
Tile tile = world.tileWorld(x, y);
if(tile == null || tile.floor().liquidDrop != null) return;
ScorchDecal decal = new ScorchDecal();
decal.set(x, y);
decal.add();
}
@Override
public void drawDecal(){
for(int i = 0; i < 3; i++){
TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches - 1)];
float rotation = Mathf.randomSeed(id + i, 0, 360);
float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20) / 10f;
Draw.rect(region,
x + Angles.trnsx(rotation, space),
y + Angles.trnsy(rotation, space) + region.getHeight() / 2f * Draw.scl,
region.getWidth() * Draw.scl,
region.getHeight() * Draw.scl,
region.getWidth() / 2f * Draw.scl, 0, rotation - 90);
}
}
}

View File

@@ -1,13 +0,0 @@
package mindustry.entities.traits;
public interface AbsorbTrait extends Entity, TeamTrait, DamageTrait{
void absorb();
default boolean canBeAbsorbed(){
return true;
}
default float getShieldDamage(){
return damage();
}
}

View File

@@ -1,7 +0,0 @@
package mindustry.entities.traits;
/**
* A flag interface for marking an effect as appearing below liquids.
*/
public interface BelowLiquidTrait{
}

View File

@@ -1,22 +0,0 @@
package mindustry.entities.traits;
/** A class for gracefully merging mining and building traits.*/
public interface BuilderMinerTrait extends MinerTrait, BuilderTrait{
default void updateMechanics(){
updateBuilding();
//mine only when not building
if(buildRequest() == null){
updateMining();
}
}
default void drawMechanics(){
if(isBuilding()){
drawBuilding();
}else{
drawMining();
}
}
}

View File

@@ -1,402 +0,0 @@
package mindustry.entities.traits;
import arc.*;
import arc.struct.Queue;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.BuildBlock.*;
import java.io.*;
import java.util.*;
import static mindustry.Vars.*;
import static mindustry.entities.traits.BuilderTrait.BuildDataStatic.*;
/** Interface for units that build things.*/
public interface BuilderTrait extends Entity, TeamTrait{
//these are not instance variables!
float placeDistance = 220f;
float mineDistance = 70f;
/** Updates building mechanism for this unit.*/
default void updateBuilding(){
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : placeDistance;
Unit unit = (Unit)this;
Iterator<BuildRequest> it = buildQueue().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();
}
}
TileEntity core = unit.getClosestCore();
//nothing to build.
if(buildRequest() == null) return;
//find the next build request
if(buildQueue().size > 1){
int total = 0;
BuildRequest req;
while((dst((req = buildRequest()).tile()) > finalPlaceDst || shouldSkip(req, core)) && total < buildQueue().size){
buildQueue().removeFirst();
buildQueue().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 && canCreateBlocks() && !current.breaking && Build.validPlace(getTeam(), current.x, current.y, current.block, current.rotation)){
Build.beginPlace(getTeam(), current.x, current.y, current.block, current.rotation);
}else if(!current.initialized && canCreateBlocks() && current.breaking && Build.validBreak(getTeam(), current.x, current.y)){
Build.beginBreak(getTeam(), current.x, current.y);
}else{
buildQueue().removeFirst();
return;
}
}
if(tile.entity instanceof BuildEntity && !current.initialized){
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, unit.getTeam(), 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(unit.dst(tile) <= finalPlaceDst){
unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f);
}
if(current.breaking){
entity.deconstruct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * 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;
}
/** @return whether this request should be skipped, in favor of the next one. */
default boolean shouldSkip(BuildRequest request, @Nullable TileEntity 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);
}
default void removeRequest(int x, int y, boolean breaking){
//remove matching request
int idx = player.buildQueue().indexOf(req -> req.breaking == breaking && req.x == x && req.y == y);
if(idx != -1){
player.buildQueue().removeIndex(idx);
}
}
/** Returns the queue for storing build requests. */
Queue<BuildRequest> buildQueue();
/** Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all. */
float getBuildPower(Tile tile);
/** Whether this type of builder can begin creating new blocks. */
default boolean canCreateBlocks(){
return true;
}
default void writeBuilding(DataOutput output) throws IOException{
BuildRequest request = buildRequest();
if(request != null && (request.block != null || request.breaking)){
output.writeByte(request.breaking ? 1 : 0);
output.writeInt(Pos.get(request.x, request.y));
output.writeFloat(request.progress);
if(!request.breaking){
output.writeShort(request.block.id);
output.writeByte(request.rotation);
}
}else{
output.writeByte(-1);
}
}
default void readBuilding(DataInput input) throws IOException{
readBuilding(input, true);
}
default void readBuilding(DataInput input, boolean applyChanges) throws IOException{
if(applyChanges) buildQueue().clear();
byte type = input.readByte();
if(type != -1){
int position = input.readInt();
float progress = input.readFloat();
BuildRequest request;
if(type == 1){ //remove
request = new BuildRequest(Pos.x(position), Pos.y(position));
}else{ //place
short block = input.readShort();
byte rotation = input.readByte();
request = new BuildRequest(Pos.x(position), Pos.y(position), rotation, content.block(block));
}
request.progress = progress;
if(applyChanges){
buildQueue().addLast(request);
}else if(isBuilding()){
BuildRequest last = buildRequest();
last.progress = progress;
if(last.tile() != null && last.tile().entity instanceof BuildEntity){
((BuildEntity)last.tile().entity).progress = progress;
}
}
}
}
/** Return whether this builder's place queue contains items. */
default boolean isBuilding(){
return buildQueue().size != 0;
}
/** Clears the placement queue. */
default void clearBuilding(){
buildQueue().clear();
}
/** Add another build requests to the tail of the queue, if it doesn't exist there yet. */
default void addBuildRequest(BuildRequest place){
addBuildRequest(place, true);
}
/** Add another build requests to the queue, if it doesn't exist there yet. */
default void addBuildRequest(BuildRequest place, boolean tail){
BuildRequest replace = null;
for(BuildRequest request : buildQueue()){
if(request.x == place.x && request.y == place.y){
replace = request;
break;
}
}
if(replace != null){
buildQueue().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){
buildQueue().addLast(place);
}else{
buildQueue().addFirst(place);
}
}
/**
* Return the build requests currently active, or the one at the top of the queue.
* May return null.
*/
default @Nullable
BuildRequest buildRequest(){
return buildQueue().size == 0 ? null : buildQueue().first();
}
//due to iOS weirdness, this is apparently required
class BuildDataStatic{
static Vec2[] tmptr = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
}
/** Draw placement effects for an entity. */
default void drawBuilding(){
if(!isBuilding()) return;
Unit unit = (Unit)this;
BuildRequest request = buildRequest();
Tile tile = world.tile(request.x, request.y);
if(dst(tile) > placeDistance && !state.isEditor()){
return;
}
Lines.stroke(1f, Pal.accent);
float focusLen = 3.8f + Mathf.absin(Time.time(), 1.1f, 0.6f);
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
float sz = Vars.tilesize * tile.block().size / 2f;
float ang = unit.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, (a, b) -> -Float.compare(Angles.angleDist(Angles.angle(unit.x, unit.y, a.x, a.y), ang),
Angles.angleDist(Angles.angle(unit.x, unit.y, b.x, b.y), 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();
}
/** Class for storing build requests. Can be either a place or remove request. */
class BuildRequest{
/** Position and rotation of this request. */
public int x, y, rotation;
/** Block being placed. If null, this is a breaking request.*/
public @Nullable Block block;
/** Whether this is a break request.*/
public boolean breaking;
/** Whether this request comes with a config int. If yes, any blocks placed with this request will not call playerPlaced.*/
public boolean hasConfig;
/** Config int. Not used unless hasConfig is true.*/
public int config;
/** Original position, only used in schematics.*/
public int originalX, originalY, originalWidth, originalHeight;
/** Last progress.*/
public float progress;
/** Whether construction has started for this request, and other special variables.*/
public boolean initialized, worldContext = true, stuck;
/** Visual scale. Used only for rendering.*/
public float animScale = 0f;
/** This creates a build request. */
public BuildRequest(int x, int y, int rotation, Block block){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
}
/** This creates a remove request. */
public BuildRequest(int x, int y){
this.x = x;
this.y = y;
this.rotation = -1;
this.block = world.tile(x, y).block();
this.breaking = true;
}
public BuildRequest(){
}
public BuildRequest copy(){
BuildRequest copy = new BuildRequest();
copy.x = x;
copy.y = y;
copy.rotation = rotation;
copy.block = block;
copy.breaking = breaking;
copy.hasConfig = hasConfig;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
copy.progress = progress;
copy.initialized = initialized;
copy.animScale = animScale;
return copy;
}
public BuildRequest original(int x, int y, int originalWidth, int originalHeight){
originalX = x;
originalY = y;
this.originalWidth = originalWidth;
this.originalHeight = originalHeight;
return this;
}
public Rect bounds(Rect rect){
if(breaking){
return rect.set(-100f, -100f, 0f, 0f);
}else{
return block.bounds(x, y, rect);
}
}
public BuildRequest set(int x, int y, int rotation, Block block){
this.x = x;
this.y = y;
this.rotation = rotation;
this.block = block;
this.breaking = false;
return this;
}
public float drawx(){
return x*tilesize + block.offset();
}
public float drawy(){
return y*tilesize + block.offset();
}
public BuildRequest configure(int config){
this.config = config;
this.hasConfig = true;
return this;
}
public @Nullable Tile tile(){
return world.tile(x, y);
}
@Override
public String toString(){
return "BuildRequest{" +
"x=" + x +
", y=" + y +
", rotation=" + rotation +
", recipe=" + block +
", breaking=" + breaking +
", progress=" + progress +
", initialized=" + initialized +
'}';
}
}
}

View File

@@ -1,9 +0,0 @@
package mindustry.entities.traits;
public interface DamageTrait{
float damage();
default void killed(Entity other){
}
}

View File

@@ -1,10 +0,0 @@
package mindustry.entities.traits;
public interface DrawTrait extends Entity{
default float drawSize(){
return 20f;
}
void draw();
}

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