Merge branch 'master' of https://github.com/Anuken/Mindustry into 7.0-features

 Conflicts:
	core/assets/icons/icons.properties
	core/assets/logicids.dat
	core/src/mindustry/ui/dialogs/PlanetDialog.java
	core/src/mindustry/world/blocks/distribution/ItemBridge.java
	core/src/mindustry/world/blocks/liquid/LiquidExtendingBridge.java
	core/src/mindustry/world/blocks/storage/StorageBlock.java
	gradle.properties
This commit is contained in:
Anuken
2021-08-10 15:15:07 -04:00
196 changed files with 2485 additions and 1664 deletions

View File

@@ -103,8 +103,8 @@ public class Vars implements Loadable{
public static final float invasionGracePeriod = 20;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.1f;
/** launch animation duration */
public static final float launchDuration = 140f;
/** land/launch animation duration */
public static final float coreLandDuration = 160f;
/** size of tiles in units */
public static final int tilesize = 8;
/** size of one tile payload (^2) */
@@ -142,8 +142,8 @@ public class Vars implements Loadable{
public static boolean clientLoaded = false;
/** max GL texture size */
public static int maxTextureSize = 2048;
/** Whether to show the core landing animation. */
public static boolean showLandAnimation = true;
/** Whether to show sector info upon landing. */
public static boolean showSectorLandInfo = true;
/** Whether to check for memory use before taking screenshots. */
public static boolean checkScreenshotMemory = true;
/** Whether to prompt the user to confirm exiting. */

View File

@@ -13,7 +13,6 @@ import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import java.util.*;
@@ -32,7 +31,7 @@ public class BlockIndexer{
/** Stores all ore quadrants on the map. Maps ID to qX to qY to a list of tiles with that ore. */
private IntSeq[][][] ores;
/** Stores all damaged tile entities by team. */
private ObjectSet<Building>[] damagedTiles = new ObjectSet[Team.all.length];
private Seq<Building>[] damagedTiles = new Seq[Team.all.length];
/** All ores available on this map. */
private ObjectSet<Item> allOres = new ObjectSet<>();
/** Stores teams that are present here as tiles. */
@@ -59,7 +58,7 @@ public class BlockIndexer{
});
Events.on(WorldLoadEvent.class, event -> {
damagedTiles = new ObjectSet[Team.all.length];
damagedTiles = new Seq[Team.all.length];
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
activeTeams = new Seq<>(Team.class);
@@ -74,10 +73,6 @@ public class BlockIndexer{
for(Tile tile : world.tiles){
process(tile);
if(tile.build != null && tile.build.damaged()){
notifyTileDamaged(tile.build);
}
var drop = tile.drop();
if(drop != null){
@@ -104,6 +99,7 @@ public class BlockIndexer{
public void removeIndex(Tile tile){
var team = tile.team();
if(tile.build != null && tile.isCenter()){
var build = tile.build;
var flags = tile.block().flags;
var data = team.data();
@@ -118,7 +114,15 @@ public class BlockIndexer{
//unregister building from building quadtree
if(data.buildings != null){
data.buildings.remove(tile.build);
data.buildings.remove(build);
}
//is no longer registered
build.wasDamaged = false;
//unregister damaged buildings
if(build.damaged() && damagedTiles[team.id] != null){
damagedTiles[team.id].remove(build);
}
}
}
@@ -175,25 +179,12 @@ public class BlockIndexer{
}
/** Returns all damaged tiles by team. */
public ObjectSet<Building> getDamaged(Team team){
breturnArray.clear();
public Seq<Building> getDamaged(Team team){
if(damagedTiles[team.id] == null){
damagedTiles[team.id] = new ObjectSet<>();
return damagedTiles[team.id] = new Seq<>(false);
}
ObjectSet<Building> set = damagedTiles[team.id];
for(Building build : set){
if((!build.isValid() || build.team != team || !build.damaged()) || build.block instanceof ConstructBlock){
breturnArray.add(build);
}
}
for(Building tile : breturnArray){
set.remove(tile);
}
return set;
return damagedTiles[team.id];
}
/** Get all allied blocks with a flag. */
@@ -271,12 +262,22 @@ public class BlockIndexer{
return returnArray;
}
public void notifyTileDamaged(Building entity){
if(damagedTiles[entity.team.id] == null){
damagedTiles[entity.team.id] = new ObjectSet<>();
public void notifyBuildHealed(Building build){
if(build.wasDamaged && !build.damaged() && damagedTiles[build.team.id] != null){
damagedTiles[build.team.id].remove(build);
build.wasDamaged = false;
}
}
public void notifyBuildDamaged(Building build){
if(build.wasDamaged || !build.damaged()) return;
if(damagedTiles[build.team.id] == null){
damagedTiles[build.team.id] = new Seq<>(false);
}
damagedTiles[entity.team.id].add(entity);
damagedTiles[build.team.id].add(build);
build.wasDamaged = true;
}
public void allBuildings(float x, float y, float range, Cons<Building> cons){
@@ -306,7 +307,7 @@ public class BlockIndexer{
for(int i = 0; i < activeTeams.size; i++){
Team enemy = activeTeams.items[i];
if(enemy == team || (team == Team.derelict && !state.rules.coreCapture)) continue;
if(enemy == team || (enemy == Team.derelict && !state.rules.coreCapture)) continue;
Building candidate = indexer.findTile(enemy, x, y, range, pred, true);
if(candidate == null) continue;
@@ -417,6 +418,8 @@ public class BlockIndexer{
data.buildings = new QuadTree<>(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
}
data.buildings.insert(tile.build);
notifyBuildDamaged(tile.build);
}
if(!tile.block().isStatic()){

View File

@@ -175,16 +175,6 @@ public class Formation{
return -1;
}
// debug
public SlotAssignment getSlotAssignmentAt(int index){
return slotAssignments.get(index);
}
// debug
public int getSlotAssignmentCount(){
return slotAssignments.size;
}
/** Writes new slot locations to each member */
public void updateSlots(){
positionOffset.set(anchor);

View File

@@ -33,16 +33,31 @@ public class FlyingAI extends AIController{
@Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
Teamc result = target(x, y, range, air, ground);
if(result != null) return result;
var result = findMainTarget(x, y, range, air, ground);
if(ground) result = targetFlag(x, y, BlockFlag.generator, true);
if(result != null) return result;
//if the main target is in range, use it, otherwise target whatever is closest
return checkTarget(result, x, y, range) ? target(x, y, range, air, ground) : result;
}
if(ground) result = targetFlag(x, y, BlockFlag.core, true);
if(result != null) return result;
@Override
protected Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
var core = targetFlag(x, y, BlockFlag.core, true);
return null;
if(core != null && Mathf.within(x, y, core.getX(), core.getY(), range)){
return core;
}
for(var flag : unit.team.isAI() ? unit.type.targetFlags : unit.type.playerTargetFlags){
if(flag == null){
Teamc result = target(x, y, range, air, ground);
if(result != null) return result;
}else if(ground){
Teamc result = targetFlag(x, y, flag, true);
if(result != null) return result;
}
}
return core;
}
protected void attack(float circleLength){

View File

@@ -102,7 +102,7 @@ public class LogicAI extends AIController{
}
//look where moving if there's nothing to aim at
if(!shoot){
if(!shoot || !unit.type.omniMovement){
unit.lookAt(unit.prefRotation());
}else if(unit.hasWeapons() && unit.mounts.length > 0 && !unit.mounts[0].weapon.ignoreRotation){ //if there is, look at the object
unit.lookAt(unit.mounts[0].aimX, unit.mounts[0].aimY);
@@ -131,7 +131,13 @@ public class LogicAI extends AIController{
//do not move when infinite vectors are used.
if(vec.isNaN() || vec.isInfinite()) return;
unit.approach(vec);
if(unit.type.omniMovement){
unit.approach(vec);
}else{
unit.rotateMove(vec);
}
}
@Override

View File

@@ -2,6 +2,7 @@ package mindustry.async;
import arc.*;
import arc.struct.*;
import arc.util.async.*;
import mindustry.game.EventType.*;
import java.util.concurrent.*;
@@ -49,7 +50,7 @@ public class AsyncCore{
executor = Executors.newFixedThreadPool(processes.size, r -> {
Thread thread = new Thread(r, "AsyncLogic-Thread");
thread.setDaemon(true);
thread.setUncaughtExceptionHandler((t, e) -> Core.app.post(() -> { throw new RuntimeException(e); }));
thread.setUncaughtExceptionHandler((t, e) -> Threads.throwAppException(e));
return thread;
});
}

View File

@@ -42,10 +42,10 @@ public class PhysicsProcess implements AsyncProcess{
if(entity.physref == null){
PhysicsBody body = new PhysicsBody();
body.x = entity.x();
body.y = entity.y();
body.x = entity.x;
body.y = entity.y;
body.mass = entity.mass();
body.radius = entity.hitSize() / 2f;
body.radius = entity.hitSize / 2f;
PhysicRef ref = new PhysicRef(entity, body);
refs.add(ref);

View File

@@ -93,7 +93,7 @@ public class Blocks implements ContentList{
commandCenter,
groundFactory, airFactory, navalFactory,
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
repairPoint, repairTurret, resupplyPoint,
repairPoint, repairTurret,
//payloads
payloadConveyor, payloadRouter, payloadPropulsionTower, deconstructor, constructor, largeConstructor, payloadLoader, payloadUnloader,
@@ -131,7 +131,7 @@ public class Blocks implements ContentList{
new ConstructBlock(i);
}
deepwater = new Floor("deepwater"){{
deepwater = new Floor("deep-water"){{
speedMultiplier = 0.2f;
variants = 0;
liquidDrop = Liquids.water;
@@ -144,7 +144,7 @@ public class Blocks implements ContentList{
albedo = 0.5f;
}};
water = new Floor("water"){{
water = new Floor("shallow-water"){{
speedMultiplier = 0.5f;
variants = 0;
status = StatusEffects.wet;
@@ -198,7 +198,7 @@ public class Blocks implements ContentList{
cacheLayer = CacheLayer.tar;
}};
slag = new Floor("slag"){{
slag = new Floor("molten-slag"){{
drownTime = 150f;
status = StatusEffects.melting;
statusDuration = 240f;
@@ -223,7 +223,7 @@ public class Blocks implements ContentList{
stone = new Floor("stone");
craters = new Floor("craters"){{
craters = new Floor("crater-stone"){{
variants = 3;
blendGroup = stone;
}};
@@ -806,8 +806,8 @@ public class Blocks implements ContentList{
craftTime = 35f;
size = 2;
consumes.power(1f);
consumes.liquid(Liquids.slag, 0.07f);
consumes.power(1.1f);
consumes.liquid(Liquids.slag, 4f / 60f);
}};
disassembler = new Separator("disassembler"){{
@@ -851,7 +851,9 @@ public class Blocks implements ContentList{
craftTime = 40f;
updateEffect = Fx.pulverizeSmall;
hasItems = hasPower = true;
drawer = new DrawRotator();
drawer = new DrawRotator(){{
drawSpinSprite = true;
}};
ambientSound = Sounds.grinding;
ambientSoundVolume = 0.025f;
@@ -1142,15 +1144,20 @@ public class Blocks implements ContentList{
itemBridge = new BufferedItemBridge("bridge-conveyor"){{
requirements(Category.distribution, with(Items.lead, 6, Items.copper, 6));
fadeIn = moveArrows = false;
range = 4;
speed = 74f;
arrowSpacing = 6f;
bufferCapacity = 14;
}};
phaseConveyor = new ItemBridge("phase-conveyor"){{
requirements(Category.distribution, with(Items.phaseFabric, 5, Items.silicon, 7, Items.lead, 10, Items.graphite, 10));
range = 12;
arrowPeriod = 0.9f;
arrowTimeScl = 2.75f;
hasPower = true;
pulse = true;
consumes.power(0.30f);
}};
@@ -1217,7 +1224,7 @@ public class Blocks implements ContentList{
mechanicalPump = new Pump("mechanical-pump"){{
requirements(Category.liquid, with(Items.copper, 15, Items.metaglass, 10));
pumpAmount = 0.11f;
pumpAmount = 7f / 60f;
}};
rotaryPump = new Pump("rotary-pump"){{
@@ -1273,8 +1280,10 @@ public class Blocks implements ContentList{
requirements(Category.liquid, with(Items.graphite, 2, Items.metaglass, 2));
}};
bridgeConduit = new LiquidExtendingBridge("bridge-conduit"){{
bridgeConduit = new LiquidBridge("bridge-conduit"){{
requirements(Category.liquid, with(Items.graphite, 4, Items.metaglass, 8));
fadeIn = moveArrows = false;
arrowSpacing = 6f;
range = 4;
hasPower = false;
}};
@@ -1282,8 +1291,11 @@ public class Blocks implements ContentList{
phaseConduit = new LiquidBridge("phase-conduit"){{
requirements(Category.liquid, with(Items.phaseFabric, 5, Items.silicon, 7, Items.metaglass, 20, Items.titanium, 10));
range = 12;
arrowPeriod = 0.9f;
arrowTimeScl = 2.75f;
hasPower = true;
canOverdrive = false;
pulse = true;
consumes.power(0.30f);
}};
@@ -1463,6 +1475,7 @@ public class Blocks implements ContentList{
drillEffect = Fx.mineHuge;
rotateSpeed = 6f;
warmupSpeed = 0.01f;
itemCapacity = 20;
//more than the laser drill
liquidBoostIntensity = 1.8f;
@@ -1502,14 +1515,13 @@ public class Blocks implements ContentList{
maxBoost = 2f;
consumes.power(80f / 60f);
consumes.liquid(Liquids.water, 20f / 60f);
consumes.liquid(Liquids.water, 18f / 60f);
}};
oilExtractor = new Fracker("oil-extractor"){{
requirements(Category.production, with(Items.copper, 150, Items.graphite, 175, Items.lead, 115, Items.thorium, 115, Items.silicon, 75));
result = Liquids.oil;
updateEffect = Fx.pulverize;
liquidCapacity = 50f;
updateEffectChance = 0.05f;
pumpAmount = 0.25f;
size = 3;
@@ -1563,6 +1575,7 @@ public class Blocks implements ContentList{
health = 3500;
itemCapacity = 9000;
size = 4;
thrusterLength = 34/4f;
unitCapModifier = 16;
researchCostMultiplier = 0.07f;
@@ -1575,6 +1588,7 @@ public class Blocks implements ContentList{
health = 6000;
itemCapacity = 13000;
size = 5;
thrusterLength = 40/4f;
unitCapModifier = 24;
researchCostMultiplier = 0.11f;
@@ -1584,14 +1598,14 @@ public class Blocks implements ContentList{
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
health = size * size * 60;
health = size * size * 55;
}};
container = new StorageBlock("container"){{
requirements(Category.effect, with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
health = size * size * 60;
health = size * size * 55;
}};
unloader = new Unloader("unloader"){{
@@ -1788,7 +1802,7 @@ public class Blocks implements ContentList{
shots = 4;
burstSpacing = 5;
inaccuracy = 10f;
range = 215f;
range = 235f;
xRand = 6f;
size = 2;
health = 300 * size * size;
@@ -2029,13 +2043,13 @@ public class Blocks implements ContentList{
range = 195f;
reloadTime = 90f;
firingMoveFract = 0.5f;
shootDuration = 220f;
shootDuration = 230f;
powerUse = 17f;
shootSound = Sounds.laserbig;
loopSound = Sounds.beam;
loopSoundVolume = 2f;
shootType = new ContinuousLaserBulletType(70){{
shootType = new ContinuousLaserBulletType(75){{
length = 200f;
hitEffect = Fx.hitMeltdown;
hitColor = Pal.meltdownHit;
@@ -2200,17 +2214,6 @@ public class Blocks implements ContentList{
acceptCoolant = true;
}};
resupplyPoint = new ResupplyPoint("resupply-point"){{
requirements(Category.units, BuildVisibility.ammoOnly, with(Items.lead, 20, Items.copper, 15, Items.silicon, 15));
size = 2;
range = 80f;
itemCapacity = 20;
ammoAmount = 5;
consumes.item(Items.copper, 1);
}};
//endregion
//region payloads
@@ -2229,7 +2232,7 @@ public class Blocks implements ContentList{
size = 5;
reloadTime = 140f;
chargeTime = 100f;
range = 500f;
range = 600f;
maxPayloadSize = 3.5f;
consumes.power(6f);
}};

View File

@@ -1,17 +1,9 @@
package mindustry.content;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class Bullets implements ContentList{
public static BulletType
@@ -113,6 +105,7 @@ public class Bullets implements ContentList{
splashDamageRadius = 25f * 0.75f;
splashDamage = 35f;
status = StatusEffects.burning;
statusDuration = 60f * 12f;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
makeFire = true;
@@ -132,7 +125,6 @@ public class Bullets implements ContentList{
frontColor = Pal.missileYellow;
status = StatusEffects.blasted;
statusDuration = 60f;
}};
flakGlassFrag = new BasicBulletType(3f, 5, "bullet"){{
@@ -177,7 +169,7 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 22f * 1.5f;
splashDamage = 25f * 1.5f;
splashDamageRadius = 20f;
fragBullet = flakGlassFrag;
fragBullets = 6;
@@ -342,11 +334,14 @@ public class Bullets implements ContentList{
lifetime = 60f;
}};
standardDenseBig = new BasicBulletType(7f, 55, "bullet"){{
hitSize = 5;
standardDenseBig = new BasicBulletType(7.5f, 50, "bullet"){{
hitSize = 4.8f;
width = 15f;
height = 21f;
shootEffect = Fx.shootBig;
ammoMultiplier = 4;
reloadMultiplier = 1.7f;
knockback = 0.3f;
}};
standardThoriumBig = new BasicBulletType(8f, 80, "bullet"){{
@@ -359,7 +354,7 @@ public class Bullets implements ContentList{
knockback = 0.7f;
}};
standardIncendiaryBig = new BasicBulletType(7f, 60, "bullet"){{
standardIncendiaryBig = new BasicBulletType(7f, 70, "bullet"){{
hitSize = 5;
width = 16f;
height = 21f;
@@ -370,50 +365,13 @@ public class Bullets implements ContentList{
makeFire = true;
pierceCap = 2;
pierceBuilding = true;
knockback = 0.7f;
knockback = 0.7f;
ammoMultiplier = 3;
}};
fireball = new BulletType(1f, 4){
{
pierce = true;
collidesTiles = false;
collides = false;
drag = 0.03f;
hitEffect = despawnEffect = Fx.none;
}
fireball = new FireBulletType(1f, 4);
@Override
public void init(Bullet b){
b.vel.setLength(0.6f + Mathf.random(2f));
}
@Override
public void draw(Bullet b){
Draw.color(Pal.lightFlame, Pal.darkFlame, Color.gray, b.fin());
Fill.circle(b.x, b.y, 3f * b.fout());
Draw.reset();
}
@Override
public void update(Bullet b){
if(Mathf.chance(0.04 * Time.delta)){
Tile tile = world.tileWorld(b.x, b.y);
if(tile != null){
Fires.create(tile);
}
}
if(Mathf.chance(0.1 * Time.delta)){
Fx.fireballsmoke.at(b.x, b.y);
}
if(Mathf.chance(0.1 * Time.delta)){
Fx.ballfire.at(b.x, b.y);
}
}
};
basicFlame = new BulletType(3.35f, 16f){{
basicFlame = new BulletType(3.35f, 17f){{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 18f;
@@ -428,13 +386,13 @@ public class Bullets implements ContentList{
hittable = false;
}};
pyraFlame = new BulletType(3.35f, 25f){{
ammoMultiplier = 4f;
pyraFlame = new BulletType(4f, 50f){{
ammoMultiplier = 6f;
hitSize = 7f;
lifetime = 18f;
pierce = true;
collidesAir = false;
statusDuration = 60f * 6;
statusDuration = 60f * 10;
shootEffect = Fx.shootPyraFlame;
hitEffect = Fx.hitFlameSmall;
despawnEffect = Fx.none;

View File

@@ -130,7 +130,7 @@ public class Fx{
Fill.circle(x, y, e.fslope() * 1.5f * size);
}),
pointBeam = new Effect(25f, e -> {
pointBeam = new Effect(25f, 300f, e -> {
if(!(e.data instanceof Position)) return;
Position pos = e.data();
@@ -184,6 +184,16 @@ public class Fx{
Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f);
}),
coreLaunchConstruct = new Effect(35, e -> {
color(Pal.accent);
stroke(4f - e.fin() * 3f);
Lines.square(e.x, e.y, tilesize / 2f * e.rotation * 1.2f + e.fin() * 5f);
randLenVectors(e.id, 5 + (int)(e.rotation * 5), e.rotation * 3f + (tilesize * e.rotation) * e.finpow() * 1.5f, (x, y) -> {
Lines.lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * (4f + e.rotation));
});
}),
tapBlock = new Effect(12, e -> {
color(Pal.accent);
stroke(3f - e.fin() * 2f);
@@ -1628,7 +1638,7 @@ public class Fx{
coalSmeltsmoke = new Effect(40f, e -> {
randLenVectors(e.id, 0.2f + e.fin(), 4, 6.3f, (x, y, fin, out) -> {
color(Color.darkGray, Pal.coalBlack, e.finpowdown());
Fill.circle(e.x + x, e.y + y, out * 2f + 0.25f);
Fill.circle(e.x + x, e.y + y, out * 2f + 0.35f);
});
}),
@@ -1869,6 +1879,13 @@ public class Fx{
Lines.poly(e.x, e.y, 6, e.rotation + e.fin());
}),
coreLandDust = new Effect(100f, e -> {
color(e.color, e.fout(0.1f));
rand.setSeed(e.id);
Tmp.v1.trns(e.rotation, e.finpow() * 90f * rand.random(0.2f, 1f));
Fill.circle(e.x + Tmp.v1.x, e.y + Tmp.v1.y, 8f * rand.random(0.6f, 1f) * e.fout(0.2f));
}).layer(Layer.block + 1f),
unitShieldBreak = new Effect(35, e -> {
if(!(e.data instanceof Unitc)) return;
@@ -1962,8 +1979,5 @@ public class Fx{
}
Lines.endLine();
}).followParent(false),
coreLand = new Effect(120f, e -> {
});
}).followParent(false);
}

View File

@@ -7,6 +7,7 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.graphics.*;
import mindustry.graphics.g3d.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.maps.planet.*;
@@ -85,6 +86,7 @@ public class Planets implements ContentList{
atmosphereRadOut = 0.3f;
startSector = 15;
alwaysUnlocked = true;
landCloudColor = Pal.spore.cpy().a(0.5f);
}};
makeAsteroid("verlius", sun, Blocks.stoneWall, Blocks.iceWall, 0.5f, 12, 2f, gen -> {

View File

@@ -69,12 +69,12 @@ public class StatusEffects implements ContentList{
transitionDamage = 14;
init(() -> {
affinity(shocked, ((unit, result, time) -> {
affinity(shocked, (unit, result, time) -> {
unit.damagePierce(transitionDamage);
if(unit.team == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
}));
});
opposite(burning, melting);
});
}};
@@ -96,11 +96,11 @@ public class StatusEffects implements ContentList{
init(() -> {
opposite(wet, freezing);
affinity(tarred, ((unit, result, time) -> {
affinity(tarred, (unit, result, time) -> {
unit.damagePierce(8f);
Fx.burning.at(unit.x + Mathf.range(unit.bounds() / 2f), unit.y + Mathf.range(unit.bounds() / 2f));
result.set(melting, Math.min(time + result.time, 200f));
}));
});
});
}};
@@ -133,8 +133,8 @@ public class StatusEffects implements ContentList{
effect = Fx.oily;
init(() -> {
affinity(melting, ((unit, result, time) -> result.set(melting, result.time + time)));
affinity(burning, ((unit, result, time) -> result.set(burning, result.time + time)));
affinity(melting, (unit, result, time) -> result.set(melting, result.time + time));
affinity(burning, (unit, result, time) -> result.set(burning, result.time + time));
});
}};

View File

@@ -14,6 +14,7 @@ import mindustry.entities.effect.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.type.ammo.*;
import mindustry.type.weapons.*;
import mindustry.world.meta.*;
@@ -52,8 +53,8 @@ public class UnitTypes implements ContentList{
//air + payload, legacy
public static @EntityDef(value = {Unitc.class, Payloadc.class}, legacy = true) UnitType quad;
//air + payload + ammo distribution
public static @EntityDef({Unitc.class, Payloadc.class, AmmoDistributec.class}) UnitType oct;
//air + payload + legacy (different branch)
public static @EntityDef(value = {Unitc.class, Payloadc.class}, legacy = true) UnitType oct;
//air, legacy
public static @EntityDef(value = {Unitc.class}, legacy = true) UnitType alpha, beta, gamma;
@@ -89,6 +90,7 @@ public class UnitTypes implements ContentList{
hitSize = 10f;
health = 540;
armor = 4f;
ammoType = new ItemAmmoType(Items.coal);
immunities.add(StatusEffects.burning);
@@ -99,10 +101,10 @@ public class UnitTypes implements ContentList{
reload = 11f;
recoil = 1f;
ejectEffect = Fx.none;
bullet = new BulletType(4.1f, 32f){{
bullet = new BulletType(4.1f, 35f){{
ammoMultiplier = 3f;
hitSize = 7f;
lifetime = 12f;
lifetime = 13f;
pierce = true;
statusDuration = 60f * 4;
shootEffect = Fx.shootSmallFlame;
@@ -123,6 +125,7 @@ public class UnitTypes implements ContentList{
health = 900;
armor = 9f;
mechFrontSway = 0.55f;
ammoType = new ItemAmmoType(Items.graphite);
weapons.add(new Weapon("artillery"){{
top = false;
@@ -156,6 +159,7 @@ public class UnitTypes implements ContentList{
armor = 10f;
canDrown = false;
mechFrontSway = 1f;
ammoType = new ItemAmmoType(Items.thorium);
mechStepParticles = true;
mechStepShake = 0.15f;
@@ -220,6 +224,7 @@ public class UnitTypes implements ContentList{
canDrown = false;
mechFrontSway = 1.9f;
mechSideSway = 0.6f;
ammoType = new ItemAmmoType(Items.thorium);
weapons.add(
new Weapon("reign-weapon"){{
@@ -282,7 +287,7 @@ public class UnitTypes implements ContentList{
commandLimit = 8;
abilities.add(new RepairFieldAbility(10f, 60f * 4, 60f));
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(1000);
weapons.add(new Weapon("heal-weapon"){{
top = false;
@@ -319,7 +324,7 @@ public class UnitTypes implements ContentList{
commandLimit = 9;
abilities.add(new ShieldRegenFieldAbility(20f, 40f, 60f * 5, 60f));
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(1300);
weapons.add(new Weapon("heal-shotgun-weapon"){{
top = false;
@@ -339,7 +344,7 @@ public class UnitTypes implements ContentList{
bullet = new LightningBulletType(){{
lightningColor = hitColor = Pal.heal;
damage = 12f;
damage = 14f;
lightningLength = 7;
lightningLengthRand = 7;
shootEffect = Fx.shootHeal;
@@ -372,7 +377,7 @@ public class UnitTypes implements ContentList{
commandLimit = 10;
mechFrontSway = 0.55f;
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(1500);
speed = 0.4f;
hitSize = 13f;
@@ -408,23 +413,23 @@ public class UnitTypes implements ContentList{
vela = new UnitType("vela"){{
hitSize = 24f;
rotateSpeed = 1.6f;
rotateSpeed = 1.7f;
canDrown = false;
mechFrontSway = 1f;
buildSpeed = 3f;
mechStepParticles = true;
mechStepShake = 0.15f;
ammoType = AmmoTypes.powerHigh;
ammoType = new PowerAmmoType(2500);
speed = 0.39f;
speed = 0.44f;
boostMultiplier = 2.2f;
engineOffset = 12f;
engineSize = 6f;
lowAltitude = true;
riseSpeed = 0.02f;
health = 7500f;
health = 8200f;
armor = 9f;
canBoost = true;
landShake = 4f;
@@ -449,8 +454,8 @@ public class UnitTypes implements ContentList{
cooldownTime = 200f;
bullet = new ContinuousLaserBulletType(){{
damage = 30f;
length = 175f;
damage = 35f;
length = 180f;
hitEffect = Fx.hitMeltHeal;
drawSize = 420f;
lifetime = 160f;
@@ -493,7 +498,7 @@ public class UnitTypes implements ContentList{
hovering = true;
visualElevation = 0.2f;
allowLegStep = true;
ammoType = AmmoTypes.powerHigh;
ammoType = new PowerAmmoType(4000);
groundLayer = Layer.legUnit;
speed = 0.3f;
@@ -558,6 +563,7 @@ public class UnitTypes implements ContentList{
health = 200;
mechSideSway = 0.25f;
range = 40f;
ammoType = new ItemAmmoType(Items.coal);
weapons.add(new Weapon(){{
reload = 24f;
@@ -595,6 +601,7 @@ public class UnitTypes implements ContentList{
legMoveSpace = 1.4f;
hovering = true;
armor = 3f;
ammoType = new ItemAmmoType(Items.coal);
allowLegStep = true;
visualElevation = 0.2f;
@@ -634,7 +641,7 @@ public class UnitTypes implements ContentList{
legBaseOffset = 2f;
hovering = true;
armor = 5f;
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(1000);
buildSpeed = 0.75f;
@@ -706,7 +713,7 @@ public class UnitTypes implements ContentList{
legLengthScl = 0.96f;
rippleScale = 2f;
legSpeed = 0.2f;
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(2000);
buildSpeed = 1f;
legSplashDamage = 32;
@@ -810,7 +817,7 @@ public class UnitTypes implements ContentList{
legLengthScl = 0.93f;
rippleScale = 3f;
legSpeed = 0.19f;
ammoType = AmmoTypes.powerHigh;
ammoType = new ItemAmmoType(Items.graphite, 8);
buildSpeed = 1f;
legSplashDamage = 80;
@@ -928,6 +935,9 @@ public class UnitTypes implements ContentList{
engineOffset = 5.5f;
range = 140f;
targetAir = false;
//as default AI, flares are not very useful in core rushes, they attack nothing in the way
playerTargetFlags = new BlockFlag[]{null};
targetFlags = new BlockFlag[]{BlockFlag.generator, null};
commandLimit = 4;
circleTarget = true;
hitSize = 7;
@@ -961,9 +971,12 @@ public class UnitTypes implements ContentList{
range = 140f;
faceTarget = false;
armor = 3f;
targetFlag = BlockFlag.factory;
//do not rush core, attack closest
playerTargetFlags = new BlockFlag[]{null};
targetFlags = new BlockFlag[]{BlockFlag.factory, null};
commandLimit = 5;
circleTarget = true;
ammoType = new ItemAmmoType(Items.graphite);
weapons.add(new Weapon(){{
minShootVelocity = 0.75f;
@@ -997,10 +1010,13 @@ public class UnitTypes implements ContentList{
range = 140f;
hitSize = 20f;
lowAltitude = true;
forceMultiTarget = true;
armor = 5f;
targetFlags = new BlockFlag[]{BlockFlag.launchPad, BlockFlag.storage, BlockFlag.battery, null};
engineOffset = 12f;
engineSize = 3f;
ammoType = new ItemAmmoType(Items.graphite);
weapons.add(new Weapon("zenith-missiles"){{
reload = 40f;
@@ -1045,7 +1061,8 @@ public class UnitTypes implements ContentList{
engineOffset = 21;
engineSize = 5.3f;
hitSize = 46f;
targetFlag = BlockFlag.battery;
targetFlags = new BlockFlag[]{BlockFlag.generator, BlockFlag.core, null};
ammoType = new ItemAmmoType(Items.thorium);
BulletType missiles = new MissileBulletType(2.7f, 14){{
width = 8f;
@@ -1120,7 +1137,8 @@ public class UnitTypes implements ContentList{
hitSize = 58f;
destructibleWreck = false;
armor = 13f;
targetFlag = BlockFlag.reactor;
targetFlags = new BlockFlag[]{BlockFlag.reactor, BlockFlag.core, null};
ammoType = new ItemAmmoType(Items.thorium);
BulletType fragBullet = new FlakBulletType(4f, 5){{
shootEffect = Fx.shootBig;
@@ -1200,7 +1218,7 @@ public class UnitTypes implements ContentList{
range = 50f;
isCounted = false;
ammoType = AmmoTypes.powerLow;
ammoType = new PowerAmmoType(500);
mineTier = 1;
mineSpeed = 2.5f;
@@ -1221,7 +1239,7 @@ public class UnitTypes implements ContentList{
hitSize = 9f;
lowAltitude = true;
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(900);
mineTier = 2;
mineSpeed = 3.5f;
@@ -1281,7 +1299,7 @@ public class UnitTypes implements ContentList{
buildSpeed = 2.6f;
isCounted = false;
ammoType = AmmoTypes.power;
ammoType = new PowerAmmoType(1100);
weapons.add(
new Weapon("heal-weapon-mount"){{
@@ -1317,7 +1335,7 @@ public class UnitTypes implements ContentList{
quad = new UnitType("quad"){{
armor = 8f;
health = 6000;
speed = 1.4f;
speed = 1.3f;
rotateSpeed = 2f;
accel = 0.05f;
drag = 0.017f;
@@ -1333,9 +1351,9 @@ public class UnitTypes implements ContentList{
buildBeamOffset = 23;
range = 140f;
targetAir = false;
targetFlag = BlockFlag.battery;
targetFlags = new BlockFlag[]{BlockFlag.battery, BlockFlag.factory, null};
ammoType = AmmoTypes.powerHigh;
ammoType = new PowerAmmoType(3000);
weapons.add(
new Weapon(){{
@@ -1405,9 +1423,7 @@ public class UnitTypes implements ContentList{
commandLimit = 6;
lowAltitude = true;
buildBeamOffset = 43;
ammoCapacity = 1300;
ammoResupplyAmount = 20;
ammoCapacity = 1;
abilities.add(new ForceFieldAbility(140f, 4f, 7000f, 60f * 8), new RepairFieldAbility(130f, 60f * 2, 140f));
}};
@@ -1475,6 +1491,7 @@ public class UnitTypes implements ContentList{
accel = 0.3f;
rotateSpeed = 2.6f;
rotateShooting = false;
ammoType = new ItemAmmoType(Items.graphite);
trailLength = 20;
trailX = 5.5f;
@@ -1518,6 +1535,7 @@ public class UnitTypes implements ContentList{
hitSize = 20f;
armor = 7f;
rotateShooting = false;
ammoType = new ItemAmmoType(Items.graphite);
trailLength = 22;
trailX = 7f;
@@ -1581,6 +1599,7 @@ public class UnitTypes implements ContentList{
inaccuracy = 5f;
velocityRnd = 0.1f;
shootSound = Sounds.missile;
ammoType = new ItemAmmoType(Items.thorium);
ejectEffect = Fx.none;
bullet = new MissileBulletType(2.7f, 12){{
@@ -1614,6 +1633,7 @@ public class UnitTypes implements ContentList{
accel = 0.2f;
rotateSpeed = 1.3f;
rotateShooting = false;
ammoType = new ItemAmmoType(Items.thorium);
trailLength = 50;
trailX = 18f;
@@ -1699,6 +1719,7 @@ public class UnitTypes implements ContentList{
accel = 0.19f;
rotateSpeed = 0.9f;
rotateShooting = false;
ammoType = new PowerAmmoType(4000);
float spawnTime = 60f * 15f;
@@ -1756,6 +1777,7 @@ public class UnitTypes implements ContentList{
trailScl = 1.3f;
rotateShooting = false;
range = 100f;
ammoType = new PowerAmmoType(900);
armor = 3f;
@@ -1833,6 +1855,7 @@ public class UnitTypes implements ContentList{
trailX = 5.5f;
trailY = -4f;
trailScl = 1.9f;
ammoType = new ItemAmmoType(Items.coal);
buildSpeed = 2f;
@@ -1901,6 +1924,7 @@ public class UnitTypes implements ContentList{
hitSize = 20f;
armor = 6f;
rotateShooting = false;
ammoType = new ItemAmmoType(Items.graphite);
trailLength = 23;
trailX = 9f;
@@ -2036,6 +2060,8 @@ public class UnitTypes implements ContentList{
accel = 0.2f;
rotateSpeed = 1.4f;
rotateShooting = false;
ammoType = new PowerAmmoType(3500);
ammoCapacity = 40;
//clip size is massive due to energy field
clipSize = 250f;
@@ -2079,6 +2105,7 @@ public class UnitTypes implements ContentList{
accel = 0.2f;
rotateSpeed = 1.1f;
rotateShooting = false;
ammoType = new PowerAmmoType(4500);
trailLength = 70;
trailX = 23f;

View File

@@ -34,7 +34,6 @@ public class ContentLoader{
new StatusEffects(),
new Liquids(),
new Bullets(),
new AmmoTypes(),
new UnitTypes(),
new Blocks(),
new Loadouts(),

View File

@@ -36,7 +36,6 @@ import java.text.*;
import java.util.*;
import static arc.Core.*;
import static mindustry.Vars.net;
import static mindustry.Vars.*;
/**
@@ -195,14 +194,16 @@ public class Control implements ApplicationListener, Loadable{
});
Events.run(Trigger.newGame, () -> {
Building core = player.bestCore();
var core = player.bestCore();
if(core == null) return;
camera.position.set(core);
player.set(core);
if(showLandAnimation){
if(!settings.getBool("skipcoreanimation")){
//delay player respawn so animation can play.
player.deathTimer = -80f;
//TODO this sounds pretty bad due to conflict
if(settings.getInt("musicvol") > 0){
Musics.land.stop();
@@ -211,14 +212,14 @@ public class Control implements ApplicationListener, Loadable{
}
app.post(() -> ui.hudfrag.showLand());
renderer.zoomIn(Fx.coreLand.lifetime);
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
renderer.showLanding();
Time.run(Fx.coreLand.lifetime, () -> {
Time.run(coreLandDuration, () -> {
Fx.launch.at(core);
Effect.shake(5f, 5f, core);
core.thrusterTime = 1f;
if(state.isCampaign()){
if(state.isCampaign() && Vars.showSectorLandInfo){
ui.announce("[accent]" + state.rules.sector.name() + "\n" +
(state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " +
state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5);
@@ -328,6 +329,7 @@ public class Control implements ApplicationListener, Loadable{
slot.load();
slot.setAutosave(true);
state.rules.sector = sector;
state.rules.cloudColor = sector.planet.landCloudColor;
//if there is no base, simulate a new game and place the right loadout at the spawn position
if(state.rules.defaultTeam.cores().isEmpty() || hadNoCore){
@@ -542,7 +544,12 @@ public class Control implements ApplicationListener, Loadable{
core.items.each((i, a) -> i.unlock());
}
if(Core.input.keyTap(Binding.pause) && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
//cannot launch while paused
if(state.is(State.paused) && renderer.isCutscene()){
state.set(State.playing);
}
if(Core.input.keyTap(Binding.pause) && !renderer.isCutscene() && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
state.set(state.is(State.playing) ? State.paused : State.playing);
}

View File

@@ -1,6 +1,7 @@
package mindustry.core;
import arc.*;
import arc.audio.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
@@ -159,8 +160,6 @@ public class NetClient implements ApplicationListener{
clientPacketReliable(type, contents);
}
//TODO enable in build 129
/*
@Remote(variants = Variant.both, unreliable = true)
public static void sound(Sound sound, float volume, float pitch, float pan){
if(sound == null) return;
@@ -173,7 +172,7 @@ public class NetClient implements ApplicationListener{
if(sound == null) return;
sound.at(x, y, pitch, volume);
}*/
}
@Remote(variants = Variant.both, unreliable = true)
public static void effect(Effect effect, float x, float y, float rotation, Color color){

View File

@@ -18,6 +18,7 @@ import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.g3d.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static arc.Core.*;
@@ -27,6 +28,12 @@ public class Renderer implements ApplicationListener{
/** These are global variables, for headless access. Cached. */
public static float laserOpacity = 0.5f, bridgeOpacity = 0.75f;
private static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f;
private static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
private static final float cloudAlpha = 0.81f;
private static final float[] thrusterSizes = {0f, 0f, 0f, 0f, 0.3f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0f};
private static final Interp landInterp = Interp.pow3;
public final BlockRenderer blocks = new BlockRenderer();
public final MinimapRenderer minimap = new MinimapRenderer();
public final OverlayRenderer overlays = new OverlayRenderer();
@@ -37,15 +44,34 @@ public class Renderer implements ApplicationListener{
public @Nullable Bloom bloom;
public FrameBuffer effectBuffer = new FrameBuffer();
public boolean animateShields, drawWeather = true, drawStatus;
public float weatherAlpha;
/** minZoom = zooming out, maxZoom = zooming in */
public float minZoom = 1.5f, maxZoom = 6f;
public Seq<EnvRenderer> envRenderers = new Seq<>();
public TextureRegion[] bubbles = new TextureRegion[16], splashes = new TextureRegion[12];
private @Nullable CoreBuild landCore;
private @Nullable CoreBlock launchCoreType;
private Color clearColor = new Color(0f, 0f, 0f, 1f);
private float targetscale = Scl.scl(4), camerascale = targetscale, landscale, landTime, weatherAlpha, minZoomScl = Scl.scl(0.01f);
private float shakeIntensity, shaketime;
private float
//seed for cloud visuals, 0-1
cloudSeed = 0f,
//target camera scale that is lerp-ed to
targetscale = Scl.scl(4),
//current actual camera scale
camerascale = targetscale,
//minimum camera zoom value for landing/launching; constant TODO make larger?
minZoomScl = Scl.scl(0.02f),
//starts at coreLandDuration, ends at 0. if positive, core is landing.
landTime,
//timer for core landing particles
landPTimer,
//intensity for screen shake
shakeIntensity,
//current duration of screen shake
shakeTime;
//for landTime > 0: if true, core is currently *launching*, otherwise landing.
private boolean launching;
private Vec2 camShakeOffset = new Vec2();
public Renderer(){
@@ -55,7 +81,7 @@ public class Renderer implements ApplicationListener{
public void shake(float intensity, float duration){
shakeIntensity = Math.max(shakeIntensity, intensity);
shaketime = Math.max(shaketime, duration);
shakeTime = Math.max(shakeTime, duration);
}
public void addEnvRenderer(int mask, Runnable render){
@@ -97,12 +123,22 @@ public class Renderer implements ApplicationListener{
drawStatus = Core.settings.getBool("blockstatus");
if(landTime > 0){
if(!state.isPaused()){
updateLandParticles();
}
if(!state.isPaused()){
landTime -= Time.delta;
}
landscale = Interp.pow5In.apply(minZoomScl, Scl.scl(4f), 1f - landTime / Fx.coreLand.lifetime);
camerascale = landscale;
float fin = landTime / coreLandDuration;
if(!launching) fin = 1f - fin;
camerascale = landInterp.apply(minZoomScl, Scl.scl(4f), fin);
weatherAlpha = 0f;
//snap camera to cutscene core regardless of player input
if(landCore != null){
camera.position.set(landCore);
}
}else{
weatherAlpha = Mathf.lerpDelta(weatherAlpha, 1f, 0.08f);
}
@@ -114,12 +150,12 @@ public class Renderer implements ApplicationListener{
landTime = 0f;
graphics.clear(Color.black);
}else{
if(shaketime > 0){
if(shakeTime > 0){
float intensity = shakeIntensity * (settings.getInt("screenshake", 4) / 4f) * 0.75f;
camShakeOffset.setToRandomDirection().scl(Mathf.random(intensity));
camera.position.add(camShakeOffset);
shakeIntensity -= 0.25f * Time.delta;
shaketime -= Time.delta;
shakeTime -= Time.delta;
shakeIntensity = Mathf.clamp(shakeIntensity, 0f, 100f);
}else{
camShakeOffset.setZero();
@@ -136,16 +172,13 @@ public class Renderer implements ApplicationListener{
}
}
public boolean isLanding(){
/** @return whether a launch/land cutscene is playing. */
public boolean isCutscene(){
return landTime > 0;
}
public float weatherAlpha(){
return weatherAlpha;
}
public float landScale(){
return landTime > 0 ? landscale : 1f;
return landTime > 0 ? camerascale : 1f;
}
@Override
@@ -278,48 +311,160 @@ public class Renderer implements ApplicationListener{
}
private void drawBackground(){
//nothing to draw currently
}
private void drawLanding(){
CoreBuild entity = landCore == null ? player.bestCore() : landCore;
//var clouds = assets.get("sprites/clouds.png", Texture.class);
if(landTime > 0 && entity != null){
float fout = landTime / Fx.coreLand.lifetime;
void updateLandParticles(){
float time = launching ? coreLandDuration - landTime : landTime;
float tsize = Mathf.sample(thrusterSizes, (time + 35f) / coreLandDuration);
//TODO clouds
/*
float scaling = 10000f;
float sscl = 1f + fout*1.5f;
float offset = -0.38f;
landPTimer += tsize * Time.delta;
if(landCore != null && landPTimer >= 1f){
landCore.tile.getLinkedTiles(t -> {
if(Mathf.chance(0.4f)){
Fx.coreLandDust.at(t.worldx(), t.worldy(), landCore.angleTo(t) + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.5f + Mathf.range(0.15f)));
}
});
Tmp.tr1.set(clouds);
Tmp.tr1.set((camera.position.x - camera.width/2f * sscl) / scaling, (camera.position.y - camera.height/2f * sscl) / scaling, (camera.position.x + camera.width/2f * sscl) / scaling, (camera.position.y + camera.height/2f * sscl) / scaling);
Draw.alpha(Mathf.slope(Mathf.clamp(((1f - fout) + offset)/(1f + offset))));
Draw.mixcol(Pal.spore, 0.5f);
Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height);
Draw.reset();*/
landPTimer = 0f;
}
}
TextureRegion reg = entity.block.fullIcon;
void drawLanding(){
CoreBuild build = landCore == null ? player.bestCore() : landCore;
var clouds = assets.get("sprites/clouds.png", Texture.class);
if(landTime > 0 && build != null){
float fout = landTime / coreLandDuration;
if(launching) fout = 1f - fout;
float fin = 1f - fout;
//draw core
var block = launching && launchCoreType != null ? launchCoreType : (CoreBlock)build.block;
TextureRegion reg = block.fullIcon;
float scl = Scl.scl(4f) / camerascale;
float s = reg.width * Draw.scl * scl * 4f * fout;
float shake = 0f;
float s = reg.width * Draw.scl * scl * 3.6f * Interp.pow2Out.apply(fout);
float rotation = Interp.pow2In.apply(fout) * 135f, x = build.x + Mathf.range(shake), y = build.y + Mathf.range(shake);
float thrustOpen = 0.25f;
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
float thrusterSize = Mathf.sample(thrusterSizes, fin);
//when launching, thrusters stay out the entire time.
if(launching){
Interp i = Interp.pow2Out;
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
thrusterSize = i.apply(Mathf.clamp(fout*9f));
}
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.x, entity.y, s, s);
//TODO spikier heat
Draw.rect("circle-shadow", x, y, s, s);
Angles.randLenVectors(1, (1f- fout), 100, 1000f * scl * (1f-fout), (x, y, ffin, ffout) -> {
Lines.stroke(scl * ffin);
Lines.lineAngle(entity.x + x, entity.y + y, Mathf.angle(x, y), (ffin * 20 + 1f) * scl);
Draw.color(Pal.lightTrail);
float pfin = Interp.pow3Out.apply(fin), pf = Interp.pow2In.apply(fout);
//draw particles
Angles.randLenVectors(1, pfin, 100, 800f * scl * pfin, (ax, ay, ffin, ffout) -> {
Lines.stroke(scl * ffin * pf * 3f);
Lines.lineAngle(build.x + ax, build.y + ay, Mathf.angle(ax, ay), (ffin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fout);
Draw.rect(reg, entity.x, entity.y, reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fout * 135f);
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
//accent tint indicating that the core was just constructed
if(launching){
float f = Mathf.clamp(1f - fout * 12f);
if(f > 0.001f){
Draw.mixcol(Pal.accent, f);
}
}
Draw.scl(scl);
Draw.alpha(1f);
//draw thruster flame
float strength = (1f + (block.size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
float offset = (block.size - 3) * 3f * scl;
for(int i = 0; i < 4; i++){
Tmp.v1.trns(i * 90 + rotation, 1f);
Tmp.v1.setLength((block.size * tilesize/2f + 1f)*scl + strength*2f + offset);
Draw.color(build.team.color);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
Tmp.v1.setLength((block.size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
Draw.color(Color.white);
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
}
drawThrusters(block, x, y, rotation, thrusterFrame);
Drawf.spinSprite(block.region, x, y, rotation);
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
drawThrusters(block, x, y, rotation, thrusterFrame);
Draw.alpha(1f);
Drawf.spinSprite(block.teamRegions[build.team.id], x, y, rotation);
Draw.scl();
Draw.reset();
//draw clouds
if(state.rules.cloudColor.a > 0.0001f){
float scaling = cloudScaling;
float sscl = Math.max(1f + Mathf.clamp(fin + cfinOffset)* cfinScl, 0f) * camerascale;
Tmp.tr1.set(clouds);
Tmp.tr1.set(
(camera.position.x - camera.width/2f * sscl) / scaling,
(camera.position.y - camera.height/2f * sscl) / scaling,
(camera.position.x + camera.width/2f * sscl) / scaling,
(camera.position.y + camera.height/2f * sscl) / scaling);
Tmp.tr1.scroll(10f * cloudSeed, 10f * cloudSeed);
Draw.alpha(Mathf.sample(cloudAlphas, fin + calphaFinOffset) * cloudAlpha);
Draw.mixcol(state.rules.cloudColor, state.rules.cloudColor.a);
Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height);
Draw.reset();
}
}
}
void drawThrusters(CoreBlock block, float x, float y, float rotation, float frame){
float length = block.thrusterLength * (frame - 1f) - 1f/4f;
float alpha = Draw.getColor().a;
//two passes for consistent lighting
for(int j = 0; j < 2; j++){
for(int i = 0; i < 4; i++){
var reg = i >= 2 ? block.thruster2 : block.thruster1;
float rot = (i * 90) + rotation % 90f;
Tmp.v1.trns(rot, length * Draw.xscl);
//second pass applies extra layer of shading
if(j == 1){
Tmp.v1.rotate(-90f);
Draw.alpha((rotation % 90f) / 90f * alpha);
rot -= 90f;
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}else{
Draw.alpha(alpha);
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
}
}
}
Draw.alpha(1f);
}
public void scaleCamera(float amount){
targetscale *= (amount / 4) + 1;
clampScale();
@@ -350,9 +495,23 @@ public class Renderer implements ApplicationListener{
clampScale();
}
public void zoomIn(float duration){
landscale = minZoomScl;
landTime = duration;
public void showLanding(){
launching = false;
camerascale = minZoomScl;
landTime = coreLandDuration;
cloudSeed = Mathf.random(1f);
}
public void showLaunch(CoreBlock coreType){
Vars.ui.hudfrag.showLaunch();
launchCoreType = coreType;
launching = true;
landCore = player.team().core();
cloudSeed = Mathf.random(1f);
landTime = coreLandDuration;
if(landCore != null){
Fx.coreLaunchConstruct.at(landCore.x, landCore.y, coreType.size);
}
}
public void takeMapScreenshot(){

View File

@@ -289,6 +289,7 @@ public class World{
if(liquid != null) content.add(liquid);
}
state.rules.cloudColor = sector.planet.landCloudColor;
sector.info.resources = content.asArray();
sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
sector.saveInfo();

View File

@@ -16,7 +16,7 @@ public enum ContentType{
typeid_UNUSED,
error,
planet,
ammo;
ammo_UNUSED;
public static final ContentType[] all = values();
}

View File

@@ -16,12 +16,12 @@ public class Puddles{
/** Deposits a Puddle between tile and source. */
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
deposit(tile, source, liquid, amount, 0);
deposit(tile, source, liquid, amount, true);
}
/** Deposits a Puddle at a tile. */
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
deposit(tile, tile, liquid, amount, true);
}
/** Returns the Puddle on the specified tile. May return null. */
@@ -29,7 +29,7 @@ public class Puddles{
return map.get(tile.pos());
}
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount, boolean initial){
if(tile == null) return;
if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){
@@ -38,16 +38,14 @@ public class Puddles{
Puddle p = map.get(tile.pos());
if(generation == 0 && p != null && p.lastRipple <= Time.time - 40f){
if(initial && p != null && p.lastRipple <= Time.time - 40f){
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, 1f, tile.floor().liquidDrop.color);
p.lastRipple = Time.time;
}
return;
}
if(tile.floor().solid){
return;
}
if(tile.floor().solid) return;
Puddle p = map.get(tile.pos());
if(p == null){
@@ -55,14 +53,13 @@ public class Puddles{
puddle.tile = tile;
puddle.liquid = liquid;
puddle.amount = amount;
puddle.generation = generation;
puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f);
puddle.add();
map.put(tile.pos(), puddle);
puddle.add();
}else if(p.liquid == liquid){
p.accepting = Math.max(amount, p.accepting);
if(generation == 0 && p.lastRipple <= Time.time - 40f && p.amount >= maxLiquid / 2f){
if(initial && p.lastRipple <= Time.time - 40f && p.amount >= maxLiquid / 2f){
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, 1f, p.liquid.color);
p.lastRipple = Time.time;
}

View File

@@ -22,6 +22,7 @@ public class Units{
private static float cdist;
private static boolean boolResult;
private static int intResult;
private static Building buildResult;
@Remote(called = Loc.server)
public static void unitCapDeath(Unit unit){
@@ -142,7 +143,7 @@ public class Units{
/** Returns the nearest damaged tile. */
public static Building findDamagedTile(Team team, float x, float y){
return Geometry.findClosest(x, y, indexer.getDamaged(team));
return indexer.getDamaged(team).min(b -> b.dst2(x, y));
}
/** Returns the nearest ally tile in a range. */
@@ -157,6 +158,26 @@ public class Units{
return indexer.findEnemyTile(team, x, y, range, pred);
}
/** @return the closest building of the provided team that matches the predicate. */
public static @Nullable Building closestBuilding(Team team, float wx, float wy, float range, Boolf<Building> pred){
buildResult = null;
cdist = 0f;
var buildings = team.data().buildings;
if(buildings == null) return null;
buildings.intersect(wx - range, wy - range, range*2f, range*2f, b -> {
if(pred.get(b)){
float dst = b.dst(wx, wy) - b.hitSize()/2f;
if(dst <= range && (buildResult == null || dst <= cdist)){
cdist = dst;
buildResult = b;
}
}
});
return buildResult;
}
/** Iterates through all buildings in a range. */
public static void nearbyBuildings(float x, float y, float range, Cons<Building> cons){
indexer.allBuildings(x, y, range, cons);

View File

@@ -14,6 +14,8 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import static mindustry.Vars.*;
public class EnergyFieldAbility extends Ability{
private static final Seq<Healthc> all = new Seq<>();
@@ -31,6 +33,7 @@ public class EnergyFieldAbility extends Ability{
public float effectRadius = 5f, sectorRad = 0.14f, rotateSpeed = 0.5f;
public int sectors = 5;
public Color color = Pal.heal;
public boolean useAmmo = true;
protected float timer, curStroke;
protected boolean anyNearby = false;
@@ -88,8 +91,7 @@ public class EnergyFieldAbility extends Ability{
curStroke = Mathf.lerpDelta(curStroke, anyNearby ? 1 : 0, 0.09f);
if((timer += Time.delta) >= reload){
if((timer += Time.delta) >= reload && (!useAmmo || unit.ammo > 0 || !state.rules.unitAmmo)){
Tmp.v1.trns(unit.rotation - 90, x, y).add(unit.x, unit.y);
float rx = Tmp.v1.x, ry = Tmp.v1.y;
anyNearby = false;
@@ -131,7 +133,11 @@ public class EnergyFieldAbility extends Ability{
}
}else{
anyNearby = true;
other.damage(damage);
if(other instanceof Building b){
b.damage(unit.team, damage);
}else{
other.damage(damage);
}
if(other instanceof Statusc s){
s.apply(status, statusDuration);
}
@@ -143,6 +149,10 @@ public class EnergyFieldAbility extends Ability{
if(anyNearby){
shootSound.at(unit);
if(useAmmo && state.rules.unitAmmo){
unit.ammo --;
}
}
timer = 0f;

View File

@@ -24,6 +24,8 @@ public class MoveLightningAbility extends Ability{
public Color color = Color.valueOf("a9d8ff");
/** Shifts where the lightning spawns along the Y axis */
public float offset = 0f;
/** Offset along the X axis. */
public float width = 0f;
/** Jittering heat sprite like the shield on v5 Javelin */
public String heatRegion = "error";
/** Bullet type that is fired. Can be null */
@@ -33,6 +35,8 @@ public class MoveLightningAbility extends Ability{
public Effect shootEffect = Fx.sparkShoot;
public Sound shootSound = Sounds.spark;
protected float side = 1f;
MoveLightningAbility(){}
@@ -61,7 +65,7 @@ public class MoveLightningAbility extends Ability{
public void update(Unit unit){
float scl = Mathf.clamp((unit.vel().len() - minSpeed) / (maxSpeed - minSpeed));
if(Mathf.chance(Time.delta * chance * scl)){
float x = unit.x + Angles.trnsx(unit.rotation, offset, 0), y = unit.y + Angles.trnsy(unit.rotation, offset, 0);
float x = unit.x + Angles.trnsx(unit.rotation, offset, width * side), y = unit.y + Angles.trnsy(unit.rotation, offset, width * side);
shootEffect.at(x, y, unit.rotation, color);
shootSound.at(unit);
@@ -73,6 +77,8 @@ public class MoveLightningAbility extends Ability{
if(bullet != null){
bullet.create(unit, unit.team, x, y, unit.rotation + bulletAngle + Mathf.range(bulletSpread));
}
side *= -1f;
}
}

View File

@@ -343,15 +343,6 @@ public class BulletType extends Content implements Cloneable{
if(instantDisappear){
b.time = lifetime;
}
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
despawnHit = true;
}
if(lightRadius == -1){
lightRadius = Math.max(18, hitSize * 5f);
}
drawSize = Math.max(drawSize, trailLength * speed * 2f);
}
public void update(Bullet b){
@@ -412,6 +403,15 @@ public class BulletType extends Content implements Cloneable{
if(lightningType == null){
lightningType = !collidesAir ? Bullets.damageLightningGround : Bullets.damageLightning;
}
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
despawnHit = true;
}
if(lightRadius == -1){
lightRadius = Math.max(18, hitSize * 5f);
}
drawSize = Math.max(drawSize, trailLength * speed * 2f);
}
@Override

View File

@@ -0,0 +1,64 @@
package mindustry.entities.bullet;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class FireBulletType extends BulletType{
public Color colorFrom = Pal.lightFlame, colorMid = Pal.darkFlame, colorTo = Color.gray;
public float radius = 3f;
public float velMin = 0.6f, velMax = 2.6f;
public float fireTrailChance = 0.04f;
public Effect trailEffect2 = Fx.ballfire;
public float fireEffectChance = 0.1f, fireEffectChance2 = 0.1f;
{
pierce = true;
collidesTiles = false;
collides = false;
drag = 0.03f;
hitEffect = despawnEffect = Fx.none;
trailEffect = Fx.fireballsmoke;
}
public FireBulletType(float speed, float damage){
super(speed, damage);
}
public FireBulletType(){}
@Override
public void init(Bullet b){
super.init(b);
b.vel.setLength(Mathf.random(velMin, velMax));
}
@Override
public void draw(Bullet b){
Draw.color(colorFrom, colorMid, colorTo, b.fin());
Fill.circle(b.x, b.y, radius * b.fout());
Draw.reset();
}
@Override
public void update(Bullet b){
super.update(b);
if(Mathf.chanceDelta(fireTrailChance)){
Fires.create(b.tileOn());
}
if(Mathf.chanceDelta(fireEffectChance)){
trailEffect.at(b.x, b.y);
}
if(Mathf.chanceDelta(fireEffectChance2)){
trailEffect2.at(b.x, b.y);
}
}
}

View File

@@ -13,7 +13,7 @@ import static mindustry.Vars.*;
public class MassDriverBolt extends BulletType{
public MassDriverBolt(){
super(1f, 50);
super(1f, 75);
collidesTiles = false;
lifetime = 1f;
despawnEffect = Fx.smeltsmoke;

View File

@@ -1,28 +0,0 @@
package mindustry.entities.comp;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.units.*;
@Component
abstract class AmmoDistributeComp implements Unitc{
@Import float x, y;
@Import UnitType type;
@Import Team team;
@Import float ammo;
private transient float ammoCooldown;
@Override
public void update(){
if(ammoCooldown > 0f) ammoCooldown -= Time.delta;
if(ammo > 0 && ammoCooldown <= 0f && ResupplyPoint.resupply(team, x, y, type.ammoResupplyRange, Math.min(type.ammoResupplyAmount, ammo), type.ammoType.color, u -> u != self())){
ammo -= Math.min(type.ammoResupplyAmount, ammo);
ammoCooldown = 5f;
}
}
}

View File

@@ -96,7 +96,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
if(!(tile.build instanceof ConstructBuild cb)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
if(hasAll){
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
@@ -176,7 +176,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
/** @return whether this request should be skipped, in favor of the next one. */
boolean shouldSkip(BuildPlan request, @Nullable Building core){
//requests that you have at least *started* are considered
if(state.rules.infiniteResources || team.rules().infiniteResources || request.breaking || core == null || request.isRotation(team)) return false;
if(state.rules.infiniteResources || team.rules().infiniteResources || request.breaking || core == null || request.isRotation(team) || (isBuilding() && !within(plans.last(), buildingRange))) return false;
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item, Math.min(i.amount, 15)) && Mathf.round(i.amount * state.rules.buildCostMultiplier) > 0) && !request.initialized);
}

View File

@@ -49,6 +49,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
static final float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
static final Seq<Building> tempBuilds = new Seq<>();
static final BuildTeamChangeEvent teamChangeEvent = new BuildTeamChangeEvent();
static int sleepingEntities = 0;
@Import float x, y, health, maxHealth;
@@ -63,6 +64,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
transient boolean enabled = true;
transient float enabledControlTime;
transient String lastAccessed;
transient boolean wasDamaged; //used only by the indexer
PowerModule power;
ItemModule items;
@@ -158,7 +160,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public final void readBase(Reads read){
health = read.f();
//cap health by block health in case of nerfs
health = Math.min(read.f(), block.health);
byte rot = read.b();
team = Team.get(read.b());
@@ -1071,7 +1074,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public String getDisplayName(){
return block.localizedName;
//derelict team icon currently doesn't display
return team == Team.derelict ?
block.localizedName + "\n" + Core.bundle.get("block.derelict") :
block.localizedName + (team == player.team() || team.emoji.isEmpty() ? "" : " " + team.emoji);
}
public TextureRegion getDisplayIcon(){
@@ -1100,7 +1106,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
table.row();
table.table(this::displayConsumption).growX();
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && Core.settings.getBool("flow") && block.displayFlow;
boolean displayFlow = (block.category == Category.distribution || block.category == Category.liquid) && block.displayFlow;
if(displayFlow){
String ps = " " + StatUnit.perSecond.localized();
@@ -1254,9 +1260,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Changes this building's team in a safe manner. */
public void changeTeam(Team next){
Team last = this.team;
indexer.removeIndex(tile);
this.team = next;
indexer.addIndex(tile);
Events.fire(teamChangeEvent.set(last, self()));
}
public boolean canPickup(){
@@ -1342,6 +1350,18 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return tile.build == self() && !dead();
}
@MethodPriority(100)
@Override
public void heal(){
indexer.notifyBuildHealed(self());
}
@MethodPriority(100)
@Override
public void heal(float amount){
indexer.notifyBuildHealed(self());
}
@Override
public float hitSize(){
return tile.block().size * tilesize;

View File

@@ -81,7 +81,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}
public boolean hasCollided(int id){
return collided.size != 0 && !collided.contains(id);
return collided.size != 0 && collided.contains(id);
}
@Replace

View File

@@ -1,5 +1,6 @@
package mindustry.entities.comp;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -20,16 +21,29 @@ import static mindustry.entities.Puddles.*;
@EntityDef(value = {Puddlec.class}, pooled = true)
@Component(base = true)
abstract class PuddleComp implements Posc, Puddlec, Drawc{
private static final int maxGeneration = 2;
private static final Rect rect = new Rect(), rect2 = new Rect();
private static int seeds;
private static Puddle paramPuddle;
private static Cons<Unit> unitCons = unit -> {
if(unit.isGrounded() && !unit.hovering){
unit.hitbox(rect2);
if(rect.overlaps(rect2)){
unit.apply(paramPuddle.liquid.effect, 60 * 2);
if(unit.vel.len2() > 0.1f * 0.1f){
Fx.ripple.at(unit.x, unit.y, unit.type.rippleScale, paramPuddle.liquid.color);
}
}
}
};
@Import int id;
@Import float x, y;
transient float accepting, updateTime, lastRipple;
transient private boolean mismatch = false;
transient float accepting, updateTime, lastRipple = Time.time + Mathf.random(40f);
float amount;
int generation;
Tile tile;
Liquid liquid;
@@ -39,46 +53,45 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
@Override
public void update(){
//update code
float addSpeed = accepting > 0 ? 3f : 0f;
amount -= Time.delta * (1f - liquid.viscosity) / (5f + addSpeed);
amount += accepting;
accepting = 0f;
if(amount >= maxLiquid / 1.5f && generation < maxGeneration){
if(amount >= maxLiquid / 1.5f){
float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Time.delta;
int targets = 0;
for(Point2 point : Geometry.d4){
Tile other = world.tile(tile.x + point.x, tile.y + point.y);
if(other != null && other.block() == Blocks.air){
Puddles.deposit(other, tile, liquid, deposited, generation + 1);
amount -= deposited / 2f; //tweak to speed up/slow down Puddle propagation
targets ++;
Puddles.deposit(other, tile, liquid, deposited, false);
}
}
amount -= deposited * targets;
}
amount = Mathf.clamp(amount, 0, maxLiquid);
if(amount <= 0f){
remove();
return;
}
if(Puddles.get(tile) != self()){
mismatch = true;
remove();
return;
}
//effects-only code
if(amount >= maxLiquid / 2f && updateTime <= 0f){
Units.nearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> {
if(unit.isGrounded() && !unit.hovering){
unit.hitbox(rect2);
if(rect.overlaps(rect2)){
unit.apply(liquid.effect, 60 * 2);
paramPuddle = self();
if(unit.vel.len() > 0.1){
Fx.ripple.at(unit.x, unit.y, unit.type.rippleScale, liquid.color);
}
}
}
});
Units.nearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unitCons);
if(liquid.temperature > 0.7f && (tile.build != null) && Mathf.chance(0.5)){
if(liquid.temperature > 0.7f && tile.build != null && Mathf.chance(0.5)){
Fires.create(tile);
}
@@ -121,7 +134,9 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
@Override
public void remove(){
Puddles.remove(tile);
if(!mismatch){
Puddles.remove(tile);
}
}
@Override

View File

@@ -154,7 +154,7 @@ abstract class StatusComp implements Posc, Flyingc{
public void draw(){
for(StatusEntry e : statuses){
e.effect.draw(self());
e.effect.draw(self(), e.time);
}
}

View File

@@ -59,6 +59,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
vel.approachDelta(vector, type.accel * realSpeed());
}
public void rotateMove(Vec2 vec){
moveAt(Tmp.v2.trns(rotation, vec.len()));
if(!vec.isZero()){
rotation = Angles.moveToward(rotation, vec.angle(), type.rotateSpeed * Math.max(Time.delta, 1));
}
}
public void aimLook(Position pos){
aim(pos);
lookAt(pos);
@@ -126,6 +134,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(isBuilding()){
return state.rules.infiniteResources ? Float.MAX_VALUE : Math.max(type.clipSize, type.region.width) + buildingRange + tilesize*4f;
}
if(mining()){
return type.clipSize + type.miningRange;
}
return type.clipSize;
}

View File

@@ -6,6 +6,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -79,7 +80,6 @@ public class AIController implements UnitController{
return Units.invalidateTarget(target, unit.team, unit.x, unit.y);
}
protected void pathfind(int pathTarget){
int costType = unit.pathType();
@@ -97,7 +97,7 @@ public class AIController implements UnitController{
boolean ret = retarget();
if(ret){
target = findTarget(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
target = findMainTarget(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
}
if(invalid(target)){
@@ -155,6 +155,7 @@ public class AIController implements UnitController{
}
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
if(unit.team == Team.derelict) return null;
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
return target == null ? null : target.build;
}
@@ -167,6 +168,10 @@ public class AIController implements UnitController{
return timer.get(timerTarget, target == null ? 40 : 90);
}
protected Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
return findTarget(x, y, range, air, ground);
}
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
return target(x, y, range, air, ground);
}

View File

@@ -284,6 +284,21 @@ public class EventType{
}
}
/**
* Called after a building's team changes.
* Event object is reused, do not nest!
* */
public static class BuildTeamChangeEvent{
public Team previous;
public Building build;
public BuildTeamChangeEvent set(Team previous, Building build){
this.build = build;
this.previous = previous;
return this;
}
}
/** Called when a core block is placed/removed or its team is changed. */
public static class CoreChangeEvent{
public CoreBuild core;

View File

@@ -23,7 +23,7 @@ public enum Gamemode{
rules.waves = true;
rules.waveTimer = true;
rules.waveSpacing = 60f * Time.toMinutes;
rules.waveSpacing = 2f * Time.toMinutes;
rules.teams.get(rules.waveTeam).infiniteResources = true;
}, map -> map.teams.contains(state.rules.waveTeam.id)),
pvp(rules -> {

View File

@@ -50,7 +50,7 @@ public class Rules{
public boolean unitAmmo = false;
/** Whether cores add to unit limit */
public boolean unitCapVariable = true;
/** How fast unit pads build units. */
/** How fast unit factories build units. */
public float unitBuildSpeedMultiplier = 1f;
/** How much damage any other units deal. */
public float unitDamageMultiplier = 1f;
@@ -94,6 +94,8 @@ public class Rules{
public Seq<WeatherEntry> weather = new Seq<>(1);
/** Blocks that cannot be placed. */
public ObjectSet<Block> bannedBlocks = new ObjectSet<>();
/** Units that cannot be built. */
public ObjectSet<UnitType> bannedUnits = new ObjectSet<>();
/** Reveals blocks normally hidden by build visibility. */
public ObjectSet<Block> revealedBlocks = new ObjectSet<>();
/** Unlocked content names. Only used in multiplayer when the campaign is enabled. */
@@ -109,6 +111,8 @@ public class Rules{
public Team defaultTeam = Team.sharded;
/** team of the enemy in waves/sectors. */
public Team waveTeam = Team.crux;
/** color of clouds that is displayed when the player is landing */
public Color cloudColor = new Color(0f, 0f, 0f, 0f);
/** name of the custom mode that this ruleset describes, or null. */
public @Nullable String modeName;
/** Whether cores incinerate items when full, just like in the campaign. */

View File

@@ -114,6 +114,7 @@ public class Schematics implements Loadable{
target.tiles.addAll(newSchematic.tiles);
target.width = newSchematic.width;
target.height = newSchematic.height;
newSchematic.labels = target.labels;
newSchematic.tags.putAll(target.tags);
newSchematic.file = target.file;
@@ -432,6 +433,11 @@ public class Schematics implements Loadable{
if(seq.contains(t -> !t.block().alwaysReplace && !t.synthetic())){
return;
}
for(var t : seq){
if(t.block() != Blocks.air){
t.remove();
}
}
}
tile.setBlock(st.block, team, st.rotation);

View File

@@ -1,5 +1,6 @@
package mindustry.game;
import arc.func.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
@@ -21,6 +22,7 @@ public class SectorInfo{
private static final int valueWindow = 60;
/** refresh period of export in ticks */
private static final float refreshPeriod = 60;
private static float returnf;
/** Core input statistics. */
public ObjectMap<Item, ExportStat> production = new ObjectMap<>();
@@ -76,6 +78,8 @@ public class SectorInfo{
public int waveVersion = -1;
/** Whether this sector was indicated to the player or not. */
public boolean shown = false;
/** Temporary seq for last imported items. Do not use. */
public transient ItemSeq lastImported = new ItemSeq();
/** Special variables for simulation. */
public float sumHealth, sumRps, sumDps, waveHealthBase, waveHealthSlope, waveDpsBase, waveDpsSlope, bossHealth, bossDps, curEnemyHealth, curEnemyDps;
@@ -265,23 +269,29 @@ public class SectorInfo{
return map;
}
public boolean anyExports(){
if(export.size == 0) return false;
returnf = 0f;
export.each((i, e) -> returnf += e.mean);
return returnf >= 0.01f;
}
/** @return a newly allocated map with import statistics. Use sparingly. */
//TODO this can be a float map
public ObjectMap<Item, ExportStat> importStats(){
public ObjectMap<Item, ExportStat> importStats(Planet planet){
ObjectMap<Item, ExportStat> imports = new ObjectMap<>();
//for all sectors on all planets that have bases and export to this sector
for(Planet planet : content.planets()){
for(Sector sector : planet.sectors){
Sector dest = sector.info.getRealDestination();
if(sector.hasBase() && sector.info != this && dest != null && dest.info == this){
//add their exports to our imports
sector.info.export.each((item, stat) -> {
imports.get(item, ExportStat::new).mean += stat.mean;
});
}
eachImport(planet, sector -> sector.info.export.each((item, stat) -> imports.get(item, ExportStat::new).mean += stat.mean));
return imports;
}
/** Iterates through every sector this one imports from. */
public void eachImport(Planet planet, Cons<Sector> cons){
for(Sector sector : planet.sectors){
Sector dest = sector.info.getRealDestination();
if(sector.hasBase() && sector.info != this && dest != null && dest.info == this){
cons.get(sector);
}
}
return imports;
}
public static class ExportStat{

View File

@@ -17,6 +17,8 @@ public class Team implements Comparable<Team>{
public final int id;
public final Color color;
public final Color[] palette;
public final int[] palettei = new int[3];
public String emoji = "";
public boolean hasPalette;
public String name;
@@ -31,9 +33,9 @@ public class Team implements Comparable<Team>{
Color.valueOf("ffd37f"), Color.valueOf("eab678"), Color.valueOf("d4816b")),
crux = new Team(2, "crux", Color.valueOf("f25555"),
Color.valueOf("fc8e6c"), Color.valueOf("f25555"), Color.valueOf("a04553")),
green = new Team(3, "green", Color.valueOf("54d67d")),
purple = new Team(4, "purple", Color.valueOf("995bb0")),
blue = new Team(5, "blue", Color.valueOf("5a4deb"));
green = new Team(3, "green", Color.valueOf("54d67d"), Color.valueOf("96f58c"), Color.valueOf("54d67d"), Color.valueOf("28785c")),
purple = new Team(4, "purple", Color.valueOf("995bb0"), Color.valueOf("f08dd5"), Color.valueOf("995bb0"), Color.valueOf("312c63")),
blue = new Team(5, "blue", Color.valueOf("554deb"), Color.valueOf("80aaff"), Color.valueOf("554deb"), Color.valueOf("3f207d"));
static{
Mathf.rand.setSeed(8);
@@ -60,6 +62,10 @@ public class Team implements Comparable<Team>{
palette[0] = color;
palette[1] = color.cpy().mul(0.75f);
palette[2] = color.cpy().mul(0.5f);
for(int i = 0; i < 3; i++){
palettei[i] = palette[i].rgba();
}
}
/** Specifies a 3-color team palette. */
@@ -69,6 +75,9 @@ public class Team implements Comparable<Team>{
palette[0] = pal1;
palette[1] = pal2;
palette[2] = pal3;
for(int i = 0; i < 3; i++){
palettei[i] = palette[i].rgba();
}
hasPalette = true;
}
@@ -96,6 +105,11 @@ public class Team implements Comparable<Team>{
return state.teams.isActive(this);
}
/** @return whether this team is solely comprised of AI, with no players. */
public boolean isAI(){
return state.rules.waves && this == state.rules.waveTeam;
}
public boolean isEnemy(Team other){
return this != other;
}

View File

@@ -148,8 +148,35 @@ public class Universe{
//update relevant sectors
for(Planet planet : content.planets()){
//first pass: clear import stats
for(Sector sector : planet.sectors){
if(sector.hasSave() && sector.hasBase()){
if(sector.hasBase() && !sector.isBeingPlayed()){
sector.info.lastImported.clear();
}
}
//second pass: update export & import statistics
for(Sector sector : planet.sectors){
if(sector.hasBase() && !sector.isBeingPlayed()){
//export to another sector
if(sector.info.destination != null){
Sector to = sector.info.destination;
if(to.hasBase()){
ItemSeq items = new ItemSeq();
//calculated exported items to this sector
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * sector.getProductionScale())));
to.addItems(items);
to.info.lastImported.add(items);
}
}
}
}
//third pass: everything else
for(Sector sector : planet.sectors){
if(sector.hasBase()){
//if it is being attacked, capture time is 0; otherwise, increment the timer
if(sector.isAttacked()){
@@ -199,26 +226,16 @@ public class Universe{
float scl = sector.getProductionScale();
//export to another sector
if(sector.info.destination != null){
Sector to = sector.info.destination;
if(to.hasBase()){
ItemSeq items = new ItemSeq();
//calculated exported items to this sector
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * scl)));
to.addItems(items);
}
}
//add production, making sure that it's capped
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * newSecondsPassed * scl), sector.info.storageCapacity - sector.info.items.get(item))));
sector.info.export.each((item, amount) -> {
if(sector.info.items.get(item) <= 0 && sector.info.production.get(item, ExportStat::new).mean < 0){
//disable export when production is negative.
sector.info.export.get(item).mean = 0f;
sector.info.export.each((item, stat) -> {
if(sector.info.items.get(item) <= 0 && sector.info.production.get(item, ExportStat::new).mean < 0 && stat.mean > 0){
//cap export by import when production is negative.
stat.mean = Math.min(sector.info.lastImported.get(item) / (float)newSecondsPassed, stat.mean);
}
});
//add production, making sure that it's capped
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * newSecondsPassed * scl), sector.info.storageCapacity - sector.info.items.get(item))));
//prevent negative values with unloaders
sector.info.items.checkNegative();

View File

@@ -281,10 +281,13 @@ public class FloorRenderer{
for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize && tilex < world.width(); tilex++){
for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize && tiley < world.height(); tiley++){
Tile tile = world.rawTile(tilex, tiley);
boolean wall = tile.block().cacheLayer != CacheLayer.normal;
if(tile.block().cacheLayer != CacheLayer.normal){
if(wall){
used.add(tile.block().cacheLayer);
}else{
}
if(!wall || world.isAccessible(tilex, tiley)){
used.add(tile.floor().cacheLayer);
}
}

View File

@@ -29,7 +29,7 @@ public class MenuRenderer implements Disposable{
private float time = 0f;
private float flyerRot = 45f;
private int flyers = Mathf.chance(0.2) ? Mathf.random(35) : Mathf.random(15);
private UnitType flyerType = content.units().select(u -> u.hitSize <= 20f && u.flying && u.region.found()).random();
private UnitType flyerType = content.units().select(u -> u.hitSize <= 20f && u.flying && u.onTitleScreen && u.region.found()).random();
public MenuRenderer(){
Time.mark();

View File

@@ -33,13 +33,14 @@ public class MinimapRenderer{
updateAll();
});
//make sure to call on the graphics thread
Events.on(TileChangeEvent.class, event -> {
//TODO don't update when the minimap is off?
if(!ui.editor.isShown()){
update(event.tile);
}
});
Events.on(BuildTeamChangeEvent.class, event -> update(event.build.tile));
}
public Pixmap getPixmap(){

View File

@@ -217,8 +217,11 @@ public class PlanetRenderer implements Disposable{
}
public void drawArc(Planet planet, Vec3 a, Vec3 b, Color from, Color to, float length, float timeScale, int pointCount){
//increase curve height when on opposite side of planet, so it doesn't tunnel through
float dot = 1f - (Tmp.v32.set(a).nor().dot(Tmp.v33.set(b).nor()) + 1f)/2f;
Vec3 avg = Tmp.v31.set(b).add(a).scl(0.5f);
avg.setLength(planet.radius*(1f+length));
avg.setLength(planet.radius*(1f+length) + dot * 1.35f);
points.clear();
points.addAll(Tmp.v33.set(b).setLength(outlineRad), Tmp.v31, Tmp.v34.set(a).setLength(outlineRad));

View File

@@ -43,7 +43,7 @@ public class DesktopInput extends InputHandler{
/** Whether player is currently deleting removal requests. */
public boolean deleting = false, shouldShoot = false, panning = false;
/** Mouse pan speed. */
public float panScale = 0.005f, panSpeed = 4.5f, panBoostSpeed = 11f;
public float panScale = 0.005f, panSpeed = 4.5f, panBoostSpeed = 15f;
/** Delta time between consecutive clicks. */
public long selectMillis = 0;
/** Previously selected tile. */
@@ -204,7 +204,6 @@ public class DesktopInput extends InputHandler{
panning = false;
}
//TODO awful UI state checking code
if(((player.dead() || state.isPaused()) && !ui.chatfrag.shown()) && !scene.hasField() && !scene.hasDialog()){
if(input.keyDown(Binding.mouse_move)){
panCam = true;
@@ -237,7 +236,7 @@ public class DesktopInput extends InputHandler{
}
}
if(!player.dead() && !state.isPaused() && !scene.hasField()){
if(!player.dead() && !state.isPaused() && !scene.hasField() && !renderer.isCutscene()){
updateMovement(player.unit());
if(Core.input.keyTap(Binding.respawn)){
@@ -646,6 +645,8 @@ public class DesktopInput extends InputHandler{
if(omni){
unit.moveAt(movement);
}else{
unit.rotateMove(movement);
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
//problem: actual unit rotation is controlled by velocity, but velocity is 1) unpredictable and 2) can be set to 0

View File

@@ -230,7 +230,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Unit unit = player.unit();
if(build != null && build.team == unit.team
if(build != null && state.teams.canInteract(unit.team, build.team)
&& unit.within(build, tilesize * build.block.size * 1.2f + tilesize * 5f)){
//pick up block's payload
@@ -356,7 +356,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Events.fire(new TapEvent(player, tile));
}
@Remote(targets = Loc.both, called = Loc.both, forward = true)
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void buildingControlSelect(Player player, Building build){
if(player == null || build == null || player.dead()) return;
@@ -510,7 +510,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}else{
Building build = world.buildWorld(pay.x(), pay.y());
if(build != null && build.team == unit.team){
if(build != null && state.teams.canInteract(unit.team, build.team)){
Call.requestBuildPayload(player, build);
}
}

View File

@@ -681,7 +681,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
if(!player.dead() && !state.isPaused()){
if(!player.dead() && !state.isPaused() && !renderer.isCutscene()){
updateMovement(player.unit());
}
@@ -822,7 +822,7 @@ public class MobileInput extends InputHandler implements GestureListener{
shiftDeltaX %= tilesize;
shiftDeltaY %= tilesize;
}
}else if(!renderer.isLanding()){
}else{
//pan player
Core.camera.position.x -= deltaX;
Core.camera.position.y -= deltaY;
@@ -922,10 +922,7 @@ public class MobileInput extends InputHandler implements GestureListener{
if(omni){
unit.moveAt(movement);
}else{
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
if(!movement.isZero()){
unit.rotation = Angles.moveToward(unit.rotation, movement.angle(), unit.type.rotateSpeed * Math.max(Time.delta, 1));
}
unit.rotateMove(movement);
}
//update shooting if not building + not mining

View File

@@ -108,7 +108,13 @@ public class Placement{
points.addAll(result);
}
public static boolean isSidePlace(Seq<BuildPlan> plans){
return plans.size > 1 && Mathf.mod(Tile.relativeTo(plans.first().x, plans.first().y, plans.get(1).x, plans.get(1).y) - plans.first().rotation, 2) == 1;
}
public static void calculateBridges(Seq<BuildPlan> plans, ItemBridge bridge){
if(isSidePlace(plans)) return;
//check for orthogonal placement + unlocked state
if(!(plans.first().x == plans.peek().x || plans.first().y == plans.peek().y) || !bridge.unlockedNow()){
return;
@@ -170,6 +176,8 @@ public class Placement{
}
public static void calculateDuctBridges(Seq<BuildPlan> plans, DuctBridge bridge){
if(isSidePlace(plans)) return;
//check for orthogonal placement + unlocked state
if(!(plans.first().x == plans.peek().x || plans.first().y == plans.peek().y) || !bridge.unlockedNow()){
return;
@@ -180,7 +188,6 @@ public class Placement{
var result = plans1.clear();
var team = player.team();
var rot = plans.first().rotation;
outer:
for(int i = 0; i < plans.size;){

View File

@@ -214,7 +214,8 @@ public class JsonIO{
String str = jsonData.asString();
Item item = Vars.content.getByName(ContentType.item, str);
Liquid liquid = Vars.content.getByName(ContentType.liquid, str);
return item != null ? item : liquid;
Block block = Vars.content.getByName(ContentType.block, str);
return item != null ? item : liquid == null ? block : liquid;
}
});

View File

@@ -50,7 +50,11 @@ public abstract class SaveFileReader{
"holostone-wall", "dacite-wall",
"rock", "boulder",
"snowrock", "snow-boulder",
"cliffs", "stone-wall",
"cliffs", "stone-wall",
"craters", "crater-stone",
"deepwater", "deep-water",
"water", "shallow-water",
"slag", "molten-slag",
"cryofluidmixer", "cryofluid-mixer",
"block-forge", "constructor"

View File

@@ -363,7 +363,7 @@ public abstract class SaveVersion extends SaveFileReader{
int amount = stream.readInt();
for(int j = 0; j < amount; j++){
readChunk(stream, true, in -> {
byte typeid = in.readByte();
int typeid = in.readUnsignedByte();
if(mapping[typeid] == null){
in.skipBytes(lastRegionLength - 1);
return;
@@ -408,7 +408,8 @@ public abstract class SaveVersion extends SaveFileReader{
for(int j = 0; j < total; j++){
String name = stream.readUTF();
map[type.ordinal()][j] = content.getByName(type, fallback.get(name, name));
//fallback only for blocks
map[type.ordinal()][j] = content.getByName(type, type == ContentType.block ? fallback.get(name, name) : name);
}
}

View File

@@ -420,6 +420,8 @@ public class Maps{
writeCache(map);
}catch(Exception e){
e.printStackTrace();
}finally{
pix.dispose();
}
});
}catch(Exception e){

View File

@@ -496,7 +496,7 @@ public class SectorDamage{
other.build.addPlan(false);
other.remove();
}else{
indexer.notifyTileDamaged(other.build);
indexer.notifyBuildDamaged(other.build);
}
}else if(other.solid() && !other.synthetic()){ //skip damage propagation through solid blocks

View File

@@ -35,6 +35,7 @@ public class ClassMap{
classes.put("BulletType", mindustry.entities.bullet.BulletType.class);
classes.put("ContinuousLaserBulletType", mindustry.entities.bullet.ContinuousLaserBulletType.class);
classes.put("EmpBulletType", mindustry.entities.bullet.EmpBulletType.class);
classes.put("FireBulletType", mindustry.entities.bullet.FireBulletType.class);
classes.put("FlakBulletType", mindustry.entities.bullet.FlakBulletType.class);
classes.put("LaserBoltBulletType", mindustry.entities.bullet.LaserBoltBulletType.class);
classes.put("LaserBulletType", mindustry.entities.bullet.LaserBulletType.class);
@@ -56,9 +57,6 @@ public class ClassMap{
classes.put("Research", mindustry.game.Objectives.Research.class);
classes.put("SectorComplete", mindustry.game.Objectives.SectorComplete.class);
classes.put("AmmoType", mindustry.type.AmmoType.class);
classes.put("AmmoTypes", mindustry.type.AmmoTypes.class);
classes.put("ItemAmmoType", mindustry.type.AmmoTypes.ItemAmmoType.class);
classes.put("PowerAmmoType", mindustry.type.AmmoTypes.PowerAmmoType.class);
classes.put("Category", mindustry.type.Category.class);
classes.put("ErrorContent", mindustry.type.ErrorContent.class);
classes.put("Item", mindustry.type.Item.class);
@@ -78,6 +76,8 @@ public class ClassMap{
classes.put("Weapon", mindustry.type.Weapon.class);
classes.put("Weather", mindustry.type.Weather.class);
classes.put("WeatherEntry", mindustry.type.Weather.WeatherEntry.class);
classes.put("ItemAmmoType", mindustry.type.ammo.ItemAmmoType.class);
classes.put("PowerAmmoType", mindustry.type.ammo.PowerAmmoType.class);
classes.put("PointDefenseWeapon", mindustry.type.weapons.PointDefenseWeapon.class);
classes.put("RepairBeamWeapon", mindustry.type.weapons.RepairBeamWeapon.class);
classes.put("HealBeamMount", mindustry.type.weapons.RepairBeamWeapon.HealBeamMount.class);
@@ -145,7 +145,7 @@ public class ClassMap{
classes.put("DuctBridge", mindustry.world.blocks.distribution.DuctBridge.class);
classes.put("DuctBridgeBuild", mindustry.world.blocks.distribution.DuctBridge.DuctBridgeBuild.class);
classes.put("DuctRouter", mindustry.world.blocks.distribution.DuctRouter.class);
classes.put("DuctBuild", mindustry.world.blocks.distribution.DuctRouter.DuctBuild.class);
classes.put("DuctRouterBuild", mindustry.world.blocks.distribution.DuctRouter.DuctRouterBuild.class);
classes.put("ExtendingItemBridge", mindustry.world.blocks.distribution.ExtendingItemBridge.class);
classes.put("ExtendingItemBridgeBuild", mindustry.world.blocks.distribution.ExtendingItemBridge.ExtendingItemBridgeBuild.class);
classes.put("ItemBridge", mindustry.world.blocks.distribution.ItemBridge.class);
@@ -330,8 +330,6 @@ public class ClassMap{
classes.put("ReconstructorBuild", mindustry.world.blocks.units.Reconstructor.ReconstructorBuild.class);
classes.put("RepairPoint", mindustry.world.blocks.units.RepairPoint.class);
classes.put("RepairPointBuild", mindustry.world.blocks.units.RepairPoint.RepairPointBuild.class);
classes.put("ResupplyPoint", mindustry.world.blocks.units.ResupplyPoint.class);
classes.put("ResupplyPointBuild", mindustry.world.blocks.units.ResupplyPoint.ResupplyPointBuild.class);
classes.put("UnitBlock", mindustry.world.blocks.units.UnitBlock.class);
classes.put("UnitBuild", mindustry.world.blocks.units.UnitBlock.UnitBuild.class);
classes.put("UnitFactory", mindustry.world.blocks.units.UnitFactory.class);

View File

@@ -29,6 +29,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.mod.Mods.*;
import mindustry.type.*;
import mindustry.type.ammo.*;
import mindustry.type.weather.*;
import mindustry.world.*;
import mindustry.world.blocks.units.*;
@@ -95,6 +96,19 @@ public class ContentParser{
readFields(result, data);
return result;
});
put(AmmoType.class, (type, data) -> {
//string -> item
//if liquid ammo support is added, this should scan for liquids as well
if(data.isString()) return find(ContentType.item, data.asString());
//number -> power
if(data.isNumber()) return new PowerAmmoType(data.asFloat());
var bc = resolve(data.getString("type", ""), ItemAmmoType.class);
data.remove("type");
AmmoType result = make(bc);
readFields(result, data);
return result;
});
put(DrawBlock.class, (type, data) -> {
if(data.isString()){
//try to instantiate
@@ -746,7 +760,7 @@ public class ContentParser{
/** Tries to resolve a class from the class type map. */
<T> Class<T> resolve(String base, Class<T> def){
//no base class specified
if(base.isEmpty() && def != null) return def;
if((base == null || base.isEmpty()) && def != null) return def;
//return mapped class if found in the global map
var out = ClassMap.classes.get(!base.isEmpty() && Character.isLowerCase(base.charAt(0)) ? Strings.capitalize(base) : base);

View File

@@ -626,7 +626,7 @@ public class Mods implements Loadable{
try{
//this binds the content but does not load it entirely
Content loaded = parser.parse(l.mod, l.file.nameWithoutExtension(), l.file.readString("UTF-8"), l.file, l.type);
Log.debug("[@] Loaded '@'.", l.mod.meta.name, (loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded));
Log.debug("[@] Loaded '@'.", l.mod.meta.name, (loaded instanceof UnlockableContent u ? u.localizedName : loaded));
}catch(Throwable e){
if(current != content.getLastAdded() && content.getLastAdded() != null){
parser.markError(content.getLastAdded(), l.mod, l.file, e);
@@ -723,7 +723,7 @@ public class Mods implements Loadable{
if(!metaf.exists()){
Log.warn("Mod @ doesn't have a '[mod/plugin].[h]json' file, skipping.", sourceFile);
throw new IllegalArgumentException("Invalid file: No mod.json found.");
throw new ModLoadException("Invalid file: No mod.json found.");
}
ModMeta meta = json.fromJson(ModMeta.class, Jval.read(metaf.readString()).toString(Jformat.plain));
@@ -750,7 +750,7 @@ public class Mods implements Loadable{
//unload
mods.remove(other);
}else{
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
throw new ModLoadException("A mod with the name '" + baseName + "' is already imported.");
}
}
@@ -779,12 +779,23 @@ public class Mods implements Loadable{
(meta.getMinMajor() >= 105 || headless)
){
if(ios){
throw new IllegalArgumentException("Java class mods are not supported on iOS.");
throw new ModLoadException("Java class mods are not supported on iOS.");
}
loader = platform.loadJar(sourceFile, mainLoader);
mainLoader.addChild(loader);
Class<?> main = Class.forName(mainClass, true, loader);
//detect mods that incorrectly package mindustry in the jar
if((main.getSuperclass().getName().equals("mindustry.mod.Plugin") || main.getSuperclass().getName().equals("mindustry.mod.Mod")) &&
main.getSuperclass().getClassLoader() != Mod.class.getClassLoader()){
throw new ModLoadException(
"This mod/plugin has loaded Mindustry dependencies from its own class loader. " +
"You are incorrectly including Mindustry dependencies in the mod JAR - " +
"make sure Mindustry is declared as `compileOnly` in Gradle, and that the JAR is created with `runtimeClasspath`!"
);
}
metas.put(main, meta);
mainMod = (Mod)main.getDeclaredConstructor().newInstance();
}else{
@@ -1017,6 +1028,12 @@ public class Mods implements Loadable{
}
}
public static class ModLoadException extends RuntimeException{
public ModLoadException(String message){
super(message);
}
}
public enum ModState{
enabled,
contentErrors,

View File

@@ -6,19 +6,14 @@ import arc.func.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import arc.util.serialization.*;
import arc.util.serialization.JsonValue.*;
import arc.util.serialization.JsonWriter.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.gen.*;
import java.io.*;
import java.text.*;
import java.util.*;
import static arc.Core.*;
import static mindustry.Vars.net;
import static mindustry.Vars.*;
public class CrashSender{
@@ -126,6 +121,9 @@ public class CrashSender{
}catch(Throwable ignored){
}
//disabled until further notice.
/*
JsonValue value = new JsonValue(ValueType.object);
boolean fn = netActive, fs = netServer;
@@ -144,7 +142,7 @@ public class CrashSender{
ex(() -> value.addChild("trace", new JsonValue(parseException(exception))));
ex(() -> value.addChild("javaVersion", new JsonValue(OS.javaVersion)));
ex(() -> value.addChild("javaArch", new JsonValue(OS.osArchBits)));
Log.info("Sending crash report.");
//post to crash report URL, exit code indicates send success
@@ -154,7 +152,7 @@ public class CrashSender{
}).block(r -> {
Log.info("Crash sent successfully.");
System.exit(1);
});
});*/
ret();
}catch(Throwable death){

View File

@@ -96,7 +96,7 @@ public class Net{
String type = t.getClass().toString().toLowerCase();
boolean isError = false;
if(e instanceof BufferUnderflowException || e instanceof BufferOverflowException){
if(e instanceof BufferUnderflowException || e instanceof BufferOverflowException || e.getCause() instanceof EOFException){
error = Core.bundle.get("error.io");
}else if(error.equals("mismatch") || e instanceof LZ4Exception || (e instanceof IndexOutOfBoundsException && e.getStackTrace()[0].getClassName().contains("java.nio"))){
error = Core.bundle.get("error.mismatch");

View File

@@ -1,28 +1,12 @@
package mindustry.type;
import arc.graphics.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
/** Type of ammo that a unit uses. */
public class AmmoType extends Content{
public String icon = Iconc.itemCopper + "";
public Color color = Pal.ammo;
public Color barColor = Pal.ammo;
public AmmoType(char icon, Color color){
this.icon = icon + "";
this.color = color;
}
public AmmoType(){
}
public void resupply(Unit unit){}
@Override
public ContentType getContentType(){
return ContentType.ammo;
}
public interface AmmoType{
String icon();
Color color();
Color barColor();
void resupply(Unit unit);
}

View File

@@ -1,85 +0,0 @@
package mindustry.type;
import mindustry.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class AmmoTypes implements ContentList{
public static AmmoType
powerLow,
power,
powerHigh,
copper,
thorium;
@Override
public void load(){
powerLow = new PowerAmmoType(500);
power = new PowerAmmoType(1000);
powerHigh = new PowerAmmoType(2000);
copper = new ItemAmmoType(Items.copper);
thorium = new ItemAmmoType(Items.thorium);
}
public static class PowerAmmoType extends AmmoType{
public float totalPower = 1000;
public PowerAmmoType(){
super(Iconc.power, Pal.powerLight);
barColor = color;
}
public PowerAmmoType(float totalPower){
this();
this.totalPower = totalPower;
}
@Override
public void resupply(Unit unit){
float range = unit.hitSize + 60f;
Tile closest = Vars.indexer.findClosestFlag(unit.x, unit.y, unit.team, BlockFlag.battery);
if(closest != null && closest.build != null && unit.within(closest.build, range) && closest.build.power != null){
var build = closest.build;
if(build.block.consumes.hasPower() && build.block.consumes.getPower().buffered){
float amount = closest.build.power.status * build.block.consumes.getPower().capacity;
float powerPerAmmo = totalPower / unit.type.ammoCapacity;
float ammoRequired = unit.type.ammoCapacity - unit.ammo;
float powerRequired = ammoRequired * powerPerAmmo;
float powerTaken = Math.min(amount, powerRequired);
if(powerTaken > 1){
closest.build.power.status -= powerTaken / build.block.consumes.getPower().capacity;
unit.ammo += powerTaken / powerPerAmmo;
Fx.itemTransfer.at(build.x, build.y, Math.max(powerTaken / 100f, 1f), Pal.power, unit);
}
}
}
}
}
public static class ItemAmmoType extends AmmoType{
public Item item;
public ItemAmmoType(Item item){
this.item = item;
this.color = item.color;
}
public ItemAmmoType(){
}
@Override
public void load(){
if(item != null){
icon = item.emoji();
}
}
}
}

View File

@@ -67,6 +67,8 @@ public class Planet extends UnlockableContent{
public boolean bloom = false;
/** Whether this planet is displayed. */
public boolean visible = true;
/** Tint of clouds displayed when landing. */
public Color landCloudColor = new Color(1f, 1f, 1f, 0.5f);
/** For suns, this is the color that shines on other planets. Does nothing for children. */
public Color lightColor = Color.white.cpy();
/** Atmosphere tint for landable planets. */

View File

@@ -11,6 +11,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.game.Saves.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.ui.*;
import mindustry.world.modules.*;
@@ -131,6 +132,13 @@ public class Sector{
return info.contentIcon != null ? info.contentIcon.uiIcon : info.icon == null ? null : Fonts.getLargeIcon(info.icon);
}
@Nullable
public String iconChar(){
if(info.contentIcon != null) return info.contentIcon.emoji();
if(info.icon != null) return Iconc.codes.get(info.icon) + "";
return null;
}
public boolean isCaptured(){
return save != null && !info.waves;
}

View File

@@ -43,13 +43,13 @@ public class StatusEffect extends UnlockableContent{
public Color color = Color.white.cpy();
/** Effect that happens randomly on top of the affected unit. */
public Effect effect = Fx.none;
/** Affinity & opposite values for stat displays. */
public ObjectSet<StatusEffect> affinities = new ObjectSet<>(), opposites = new ObjectSet<>();
/** Transition handler map. */
protected ObjectMap<StatusEffect, TransitionHandler> transitions = new ObjectMap<>();
/** Called on init. */
protected Runnable initblock = () -> {};
public ObjectSet<StatusEffect> affinities = new ObjectSet<>(), opposites = new ObjectSet<>();
public StatusEffect(String name){
super(name);
}
@@ -152,6 +152,10 @@ public class StatusEffect extends UnlockableContent{
});
}
public void draw(Unit unit, float time){
draw(unit); //Backwards compatibility
}
public void draw(Unit unit){
}

View File

@@ -24,6 +24,7 @@ import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.MultiPacker.*;
import mindustry.type.ammo.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
@@ -41,7 +42,11 @@ public class UnitType extends UnlockableContent{
/** If true, the unit is always at elevation 1. */
public boolean flying;
/** If `flying` and this is true, the unit can appear on the title screen */
public boolean onTitleScreen = true;
/** Creates a new instance of this unit class. */
public Prov<? extends Unit> constructor;
/** The default AI controller to assign on creation. */
public Prov<? extends UnitController> defaultController = () -> !flying ? new GroundAI() : new FlyingAI();
/** Environmental flags that are *all* required for this unit to function. 0 = any environment */
@@ -73,7 +78,10 @@ public class UnitType extends UnlockableContent{
public Effect fallThrusterEffect = Fx.fallSmoke;
public Effect deathExplosionEffect = Fx.dynamicExplosion;
public Seq<Ability> abilities = new Seq<>();
public BlockFlag targetFlag = BlockFlag.generator;
/** Flags to target based on priority. Null indicates that the closest target should be found. The closest enemy core is used as a fallback. */
public BlockFlag[] targetFlags = {null};
/** targetFlags, as an override for "non-AI" teams. By default, units of this type will rush the core. */
public BlockFlag[] playerTargetFlags = {BlockFlag.core, null};
public Color outlineColor = Pal.darkerMetal;
public int outlineRadius = 3;
@@ -84,9 +92,6 @@ public class UnitType extends UnlockableContent{
public float legSplashDamage = 0f, legSplashRange = 5;
public boolean flipBackLegs = true;
public int ammoResupplyAmount = 10;
public float ammoResupplyRange = 100f;
public float mechSideSway = 0.54f, mechFrontSway = 0.1f;
public float mechStride = -1f;
public float mechStepShake = -1f;
@@ -95,7 +100,7 @@ public class UnitType extends UnlockableContent{
public int itemCapacity = -1;
public int ammoCapacity = -1;
public AmmoType ammoType = AmmoTypes.copper;
public AmmoType ammoType = new ItemAmmoType(Items.copper);
public int mineTier = -1;
public float buildSpeed = -1f, mineSpeed = 1f;
public Sound mineSound = Sounds.minebeam;
@@ -118,6 +123,7 @@ public class UnitType extends UnlockableContent{
public boolean canHeal = false;
/** If true, all weapons will attack the same target. */
public boolean singleTarget = false;
public boolean forceMultiTarget = false;
public ObjectSet<StatusEffect> immunities = new ObjectSet<>();
public Sound deathSound = Sounds.bang;
@@ -195,7 +201,7 @@ public class UnitType extends UnlockableContent{
bars.row();
if(state.rules.unitAmmo){
bars.add(new Bar(ammoType.icon + " " + Core.bundle.get("stat.ammo"), ammoType.barColor, () -> unit.ammo / ammoCapacity));
bars.add(new Bar(ammoType.icon() + " " + Core.bundle.get("stat.ammo"), ammoType.barColor(), () -> unit.ammo / ammoCapacity));
bars.row();
}
@@ -232,6 +238,10 @@ public class UnitType extends UnlockableContent{
return (envEnabled & env) != 0 && (envDisabled & env) == 0 && (envRequired == 0 || (envRequired & env) == envRequired);
}
public boolean isBanned(){
return state.rules.bannedUnits.contains(this);
}
@Override
public void getDependencies(Cons<UnlockableContent> cons){
//units require reconstructors being researched
@@ -318,7 +328,7 @@ public class UnitType extends UnlockableContent{
}
clipSize = Math.max(clipSize, lightRadius * 1.1f);
singleTarget = weapons.size <= 1;
singleTarget = weapons.size <= 1 && !forceMultiTarget;
if(itemCapacity < 0){
itemCapacity = Math.max(Mathf.round((int)(hitSize * 4.3), 10), 10);
@@ -388,9 +398,9 @@ public class UnitType extends UnlockableContent{
//dynamically create ammo capacity based on firing rate
if(ammoCapacity < 0){
float shotsPerSecond = weapons.sumf(w -> 60f / w.reload);
float shotsPerSecond = weapons.sumf(w -> w.useAmmo ? 60f / w.reload : 0f);
//duration of continuous fire without reload
float targetSeconds = 30;
float targetSeconds = 35;
ammoCapacity = Math.max(1, (int)(shotsPerSecond * targetSeconds));
}

View File

@@ -344,15 +344,15 @@ public class Weather extends UnlockableContent{
@Override
public void draw(){
if(renderer.weatherAlpha() > 0.0001f && renderer.drawWeather && Core.settings.getBool("showweather")){
if(renderer.weatherAlpha > 0.0001f && renderer.drawWeather && Core.settings.getBool("showweather")){
Draw.draw(Layer.weather, () -> {
Draw.alpha(renderer.weatherAlpha() * opacity * weather.opacityMultiplier);
Draw.alpha(renderer.weatherAlpha * opacity * weather.opacityMultiplier);
weather.drawOver(self());
Draw.reset();
});
Draw.draw(Layer.debris, () -> {
Draw.alpha(renderer.weatherAlpha() * opacity * weather.opacityMultiplier);
Draw.alpha(renderer.weatherAlpha * opacity * weather.opacityMultiplier);
weather.drawUnder(self());
Draw.reset();
});

View File

@@ -0,0 +1,57 @@
package mindustry.type.ammo;
import arc.graphics.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
public class ItemAmmoType implements AmmoType{
public float range = 85f;
public int ammoPerItem = 15;
public Item item;
public ItemAmmoType(Item item){
this.item = item;
}
public ItemAmmoType(Item item, int ammoPerItem){
this.item = item;
this.ammoPerItem = ammoPerItem;
}
public ItemAmmoType(){
}
@Override
public String icon(){
return item.emoji();
}
@Override
public Color color(){
return item.color;
}
@Override
public Color barColor(){
return Pal.ammo;
}
@Override
public void resupply(Unit unit){
//do not resupply when it would waste resources
if(unit.type.ammoCapacity - unit.ammo < ammoPerItem) return;
float range = unit.hitSize + this.range;
Building build = Units.closestBuilding(unit.team, unit.x, unit.y, range, u -> u.block.allowResupply && u.items.has(item));
if(build != null){
Fx.itemTransfer.at(build.x, build.y, ammoPerItem / 2f, item.color, unit);
unit.ammo = Math.min(unit.ammo + ammoPerItem, unit.type.ammoCapacity);
build.items.remove(item, 1);
}
}
}

View File

@@ -0,0 +1,58 @@
package mindustry.type.ammo;
import arc.graphics.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
public class PowerAmmoType implements AmmoType{
public float range = 85f;
public float totalPower = 1000;
public PowerAmmoType(float totalPower){
this.totalPower = totalPower;
}
public PowerAmmoType(){
}
@Override
public String icon(){
return Iconc.power + "";
}
@Override
public Color color(){
return Pal.powerLight;
}
@Override
public Color barColor(){
return Pal.powerLight;
}
@Override
public void resupply(Unit unit){
float range = unit.hitSize + this.range;
Building build = Units.closestBuilding(unit.team, unit.x, unit.y, range, u -> u.block.consumes.hasPower() && u.block.consumes.getPower().buffered);
if(build != null){
float amount = build.power.status * build.block.consumes.getPower().capacity;
float powerPerAmmo = totalPower / unit.type.ammoCapacity;
float ammoRequired = unit.type.ammoCapacity - unit.ammo;
float powerRequired = ammoRequired * powerPerAmmo;
float powerTaken = Math.min(amount, powerRequired);
if(powerTaken > 1){
build.power.status -= powerTaken / build.block.consumes.getPower().capacity;
unit.ammo += powerTaken / powerPerAmmo;
Fx.itemTransfer.at(build.x, build.y, Math.max(powerTaken / 100f, 1f), Pal.power, unit);
}
}
}
}

View File

@@ -68,6 +68,10 @@ public class Bar extends Element{
return this;
}
public void flash(){
blink = 1f;
}
public Bar blink(Color color){
blinkColor.set(color);
return this;
@@ -107,8 +111,10 @@ public class Bar extends Element{
}
Draw.colorl(0.1f);
Draw.alpha(parentAlpha);
bar.draw(x, y, width, height);
Draw.color(color, blinkColor, blink);
Draw.alpha(parentAlpha);
Drawable top = Tex.barTop;
float topWidth = width * value;
@@ -128,7 +134,7 @@ public class Bar extends Element{
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
lay.setText(font, name);
font.setColor(Color.white);
font.setColor(1f, 1f, 1f, parentAlpha);
font.draw(name, x + width / 2f - lay.width / 2f, y + height / 2f + lay.height / 2f + 1);
Pools.free(lay);

View File

@@ -21,6 +21,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.gen.*;
import java.util.*;
@@ -155,6 +156,12 @@ public class Fonts{
}
}
});
for(Team team : Team.baseTeams){
if(Core.atlas.has("team-" + team.name)){
team.emoji = stringIcons.get(team.name, "");
}
}
}
/** Called from a static context for use in the loading screen.*/

View File

@@ -28,87 +28,93 @@ public class CustomRulesDialog extends BaseDialog{
private Table main;
private Prov<Rules> resetter;
private LoadoutDialog loadoutDialog;
private BaseDialog banDialog;
public CustomRulesDialog(){
super("@mode.custom");
loadoutDialog = new LoadoutDialog();
banDialog = new BaseDialog("@bannedblocks");
banDialog.addCloseButton();
banDialog.shown(this::rebuildBanned);
banDialog.buttons.button("@addall", Icon.add, () -> {
rules.bannedBlocks.addAll(content.blocks().select(Block::canBeBuilt));
rebuildBanned();
}).size(180, 64f);
banDialog.buttons.button("@clear", Icon.trash, () -> {
rules.bannedBlocks.clear();
rebuildBanned();
}).size(180, 64f);
setFillParent(true);
shown(this::setup);
addCloseButton();
}
private void rebuildBanned(){
float previousScroll = banDialog.cont.getChildren().isEmpty() ? 0f : ((ScrollPane)banDialog.cont.getChildren().first()).getScrollY();
banDialog.cont.clear();
banDialog.cont.pane(t -> {
t.margin(10f);
private <T extends UnlockableContent> void showBanned(String title, ContentType type, ObjectSet<T> set, Boolf<T> pred){
BaseDialog bd = new BaseDialog(title);
bd.addCloseButton();
if(rules.bannedBlocks.isEmpty()){
t.add("@empty");
}
Runnable[] rebuild = {null};
Seq<Block> array = Seq.with(rules.bannedBlocks);
array.sort();
rebuild[0] = () -> {
float previousScroll = bd.cont.getChildren().isEmpty() ? 0f : ((ScrollPane)bd.cont.getChildren().first()).getScrollY();
bd.cont.clear();
bd.cont.pane(t -> {
t.margin(10f);
int cols = mobile && Core.graphics.isPortrait() ? 1 : mobile ? 2 : 3;
int i = 0;
for(Block block : array){
t.table(Tex.underline, b -> {
b.left().margin(4f);
b.image(block.uiIcon).size(iconMed).padRight(3);
b.add(block.localizedName).color(Color.lightGray).padLeft(3).growX().left().wrap();
b.button(Icon.cancel, Styles.clearPartiali, () -> {
rules.bannedBlocks.remove(block);
rebuildBanned();
}).size(70f).pad(-4f).padLeft(0f);
}).size(300f, 70f).padRight(5);
if(++i % cols == 0){
t.row();
if(set.isEmpty()){
t.add("@empty");
}
}
}).get().setScrollYForce(previousScroll);
banDialog.cont.row();
banDialog.cont.button("@add", Icon.add, () -> {
BaseDialog dialog = new BaseDialog("@add");
dialog.cont.pane(t -> {
t.left().margin(14f);
int[] i = {0};
content.blocks().each(b -> !rules.bannedBlocks.contains(b) && b.canBeBuilt(), b -> {
int cols = mobile && Core.graphics.isPortrait() ? 4 : 12;
t.button(new TextureRegionDrawable(b.uiIcon), Styles.cleari, iconMed, () -> {
rules.bannedBlocks.add(b);
rebuildBanned();
dialog.hide();
}).size(60f);
if(++i[0] % cols == 0){
Seq<T> array = set.asArray();
array.sort();
int cols = mobile && Core.graphics.isPortrait() ? 1 : mobile ? 2 : 3;
int i = 0;
for(T con : array){
t.table(Tex.underline, b -> {
b.left().margin(4f);
b.image(con.uiIcon).size(iconMed).padRight(3);
b.add(con.localizedName).color(Color.lightGray).padLeft(3).growX().left().wrap();
b.button(Icon.cancel, Styles.clearPartiali, () -> {
set.remove(con);
rebuild[0].run();
}).size(70f).pad(-4f).padLeft(0f);
}).size(300f, 70f).padRight(5);
if(++i % cols == 0){
t.row();
}
});
});
}
}).get().setScrollYForce(previousScroll);
bd.cont.row();
bd.cont.button("@add", Icon.add, () -> {
BaseDialog dialog = new BaseDialog("@add");
dialog.cont.pane(t -> {
t.left().margin(14f);
int[] i = {0};
content.<T>getBy(type).each(b -> !set.contains(b) && pred.get(b), b -> {
int cols = mobile && Core.graphics.isPortrait() ? 4 : 12;
t.button(new TextureRegionDrawable(b.uiIcon), Styles.cleari, iconMed, () -> {
set.add(b);
rebuild[0].run();
dialog.hide();
}).size(60f);
dialog.addCloseButton();
dialog.show();
}).size(300f, 64f);
if(++i[0] % cols == 0){
t.row();
}
});
});
dialog.addCloseButton();
dialog.show();
}).size(300f, 64f);
};
bd.shown(rebuild[0]);
bd.buttons.button("@addall", Icon.add, () -> {
set.addAll(content.<T>getBy(type).select(pred));
rebuild[0].run();
}).size(180, 64f);
bd.buttons.button("@clear", Icon.trash, () -> {
set.clear();
rebuild[0].run();
}).size(180, 64f);
bd.show();
}
public void show(Rules rules, Prov<Rules> resetter){
@@ -157,7 +163,7 @@ public class CustomRulesDialog extends BaseDialog{
)).left().width(300f);
main.row();
main.button("@bannedblocks", banDialog::show).left().width(300f);
main.button("@bannedblocks", () -> showBanned("@bannedblocks", ContentType.block, rules.bannedBlocks, Block::canBeBuilt)).left().width(300f);
main.row();
title("@rules.title.unit");
@@ -167,6 +173,9 @@ public class CustomRulesDialog extends BaseDialog{
number("@rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier);
number("@rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier, 0.001f, 50f);
main.button("@bannedunits", () -> showBanned("@bannedunits", ContentType.unit, rules.bannedUnits, u -> !u.isHidden())).left().width(300f);
main.row();
title("@rules.title.enemy");
check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
check("@rules.buildai", b -> rules.teams.get(rules.waveTeam).ai = rules.teams.get(rules.waveTeam).infiniteResources = b, () -> rules.teams.get(rules.waveTeam).ai);

View File

@@ -13,7 +13,9 @@ import mindustry.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -50,14 +52,24 @@ public class DatabaseDialog extends BaseDialog{
table.table(list -> {
list.left();
int cols = Mathf.clamp((Core.graphics.getWidth() - 30) / (32 + 10), 1, 18);
int cols = (int)Mathf.clamp((Core.graphics.getWidth() - Scl.scl(30)) / Scl.scl(32 + 10), 1, 22);
int count = 0;
for(int i = 0; i < array.size; i++){
UnlockableContent unlock = (UnlockableContent)array.get(i);
Image image = unlocked(unlock) ? new Image(unlock.uiIcon).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
list.add(image).size(8 * 4).pad(3);
//banned cross
if(state.isGame() && (unlock instanceof UnitType u && u.isBanned() || unlock instanceof Block b && state.rules.bannedBlocks.contains(b))){
list.stack(image, new Image(Icon.cancel){{
setColor(Color.scarlet);
touchable = Touchable.disabled;
}}).size(8 * 4).pad(3);
}else{
list.add(image).size(8 * 4).pad(3);
}
ClickListener listener = new ClickListener();
image.addListener(listener);
if(!mobile && unlocked(unlock)){

View File

@@ -39,6 +39,7 @@ public class FileChooser extends BaseDialog{
if(!lastDirectory.exists()){
lastDirectory = homeDirectory;
directory = lastDirectory;
}
onResize(() -> {

View File

@@ -271,7 +271,7 @@ public class JoinDialog extends BaseDialog{
hosts.clear();
section("@servers.local", local, false);
section(steam ? "@servers.local.steam" : "@servers.local", local, false);
section("@servers.remote", remote, false);
section("@servers.global", global, true);

View File

@@ -13,6 +13,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import static arc.Core.*;
@@ -72,7 +73,7 @@ public class KeybindDialog extends Dialog{
}
if(sections.length != 1){
TextButton button = new TextButton(bundle.get("section." + section.name + ".name", Strings.capitalize(section.name))/*, "toggle"*/);
TextButton button = new TextButton(bundle.get("section." + section.name + ".name", Strings.capitalize(section.name)));
if(section.equals(this.section))
button.toggle();
@@ -115,9 +116,9 @@ public class KeybindDialog extends Dialog{
}
}).disabled(sectionControls.get(section, 0) + 1 >= devices.size).size(40);
table.add(stable).colspan(4);
//no alternate devices until further notice
//table.add(stable).colspan(4).row();
table.row();
table.add().height(10);
table.row();
if(section.device.type() == DeviceType.controller){
@@ -127,6 +128,7 @@ public class KeybindDialog extends Dialog{
table.row();
String lastCategory = null;
var tstyle = Styles.defaultt;
for(KeyBind keybind : keybinds.getKeybinds()){
if(lastCategory != keybind.category() && keybind.category() != null){
@@ -149,7 +151,7 @@ public class KeybindDialog extends Dialog{
table.add(axt).left().minWidth(90).padRight(20);
}
table.button(bundle.get("settings.rebind", "Rebind"), () -> {
table.button("@settings.rebind", tstyle, () -> {
rebindAxis = true;
rebindMin = true;
openDialog(section, keybind);
@@ -160,13 +162,13 @@ public class KeybindDialog extends Dialog{
table.add(keybinds.get(section, keybind).key.toString(),
style.keyColor).left().minWidth(90).padRight(20);
table.button(bundle.get("settings.rebind", "Rebind"), () -> {
table.button("@settings.rebind", tstyle, () -> {
rebindAxis = false;
rebindMin = false;
openDialog(section, keybind);
}).width(130f);
}
table.button(bundle.get("settings.resetKey", "Reset"), () -> {
table.button("@settings.resetKey", tstyle, () -> {
keybinds.resetToDefault(section, keybind);
setup();
}).width(130f);
@@ -175,7 +177,7 @@ public class KeybindDialog extends Dialog{
table.visible(() -> this.section.equals(section));
table.button(bundle.get("settings.reset", "Reset to Defaults"), () -> {
table.button("@settings.reset", () -> {
keybinds.resetToDefaults();
setup();
}).colspan(4).padTop(4).fill();

View File

@@ -36,6 +36,7 @@ import static mindustry.Vars.*;
public class ModsDialog extends BaseDialog{
private ObjectMap<String, TextureRegion> textureCache = new ObjectMap<>();
private float modImportProgress;
private String searchtxt = "";
private @Nullable Seq<ModListing> modList;
private boolean orderDate = false;
@@ -488,11 +489,16 @@ public class ModsDialog extends BaseDialog{
private void handleMod(String repo, HttpResponse result){
try{
Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip");
Streams.copy(result.getResultAsStream(), file.write(false));
long len = result.getContentLength();
Floatc cons = len <= 0 ? f -> {} : p -> modImportProgress = p;
Streams.copyProgress(result.getResultAsStream(), file.write(false), len, 4096, cons);
var mod = mods.importMod(file);
mod.setRepo(repo);
file.delete();
Core.app.post(() -> {
try{
setup();
ui.loadfrag.hide();
@@ -510,10 +516,13 @@ public class ModsDialog extends BaseDialog{
}
private void githubImportMod(String repo, boolean isJava){
modImportProgress = 0f;
ui.loadfrag.show("@downloading");
ui.loadfrag.setProgress(() -> modImportProgress);
if(isJava){
githubImportJavaMod(repo);
}else{
ui.loadfrag.show();
Http.get(ghApi + "/repos/" + repo, res -> {
var json = Jval.read(res.getResultAsString());
String mainBranch = json.getString("default_branch");

View File

@@ -33,6 +33,7 @@ import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.*;
import static arc.Core.*;
import static mindustry.Vars.*;
import static mindustry.graphics.g3d.PlanetRenderer.*;
import static mindustry.ui.dialogs.PlanetDialog.Mode.*;
@@ -373,6 +374,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
if(selectAlpha > 0.001f){
for(Sector sec : planet.sectors){
if(sec.hasBase()){
for(Sector enemy : sec.near()){
@@ -380,6 +383,18 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
planets.drawArc(planet, enemy.tile.v, sec.tile.v, Team.crux.color.write(Tmp.c2).a(selectAlpha), Color.clear, 0.24f, 110f, 25);
}
}
if(selected != null && selected != sec && selected.hasBase()){
//imports
if(sec.info.getRealDestination() == selected && sec.info.anyExports()){
planets.drawArc(planet, sec.tile.v, selected.tile.v, Color.gray.write(Tmp.c2).a(selectAlpha), Pal.accent.write(Tmp.c3).a(selectAlpha), 0.4f, 90f, 25);
}
//exports
if(selected.info.getRealDestination() == sec && selected.info.anyExports()){
planets.drawArc(planet, selected.tile.v, sec.tile.v, Pal.place.write(Tmp.c2).a(selectAlpha), Pal.accent.write(Tmp.c3).a(selectAlpha), 0.4f, 90f, 25);
}
}
}
}
}
@@ -617,6 +632,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
void displayItems(Table c, float scl, ObjectMap<Item, ExportStat> stats, String name){
displayItems(c, scl, stats, name, t -> {});
}
void displayItems(Table c, float scl, ObjectMap<Item, ExportStat> stats, String name, Cons<Table> builder){
Table t = new Table().left();
int i = 0;
@@ -634,8 +653,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
if(t.getChildren().any()){
c.add(name).left().row();
c.add(t).padLeft(10f).left().row();
c.defaults().left();
c.add(name).row();
builder.get(c);
c.add(t).padLeft(10f).row();
}
}
@@ -645,6 +666,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
dialog.cont.pane(c -> {
c.defaults().padBottom(5);
if(sector.preset != null && sector.preset.description != null){
c.add(sector.preset.displayDescription()).left().row();
}
c.add(Core.bundle.get("sectors.time") + " [accent]" + sector.save.getPlayTime()).left().row();
if(sector.info.waves && sector.hasBase()){
@@ -668,11 +693,21 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
displayItems(c, sector.getProductionScale(), sector.info.production, "@sectors.production");
//export
displayItems(c, sector.getProductionScale(), sector.info.export, "@sectors.export");
displayItems(c, sector.getProductionScale(), sector.info.export, "@sectors.export", t -> {
if(sector.info.destination != null){
String ic = sector.info.destination.iconChar();
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + sector.info.destination.name()).padLeft(10f).row();
}
});
//import
if(sector.hasBase()){
displayItems(c, 1f, sector.info.importStats(), "@sectors.import");
displayItems(c, 1f, sector.info.importStats(sector.planet), "@sectors.import", t -> {
sector.info.eachImport(sector.planet, other -> {
String ic = other.iconChar();
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + other.name()).padLeft(10f).row();
});
});
}
ItemSeq items = sector.items();
@@ -747,10 +782,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
t.button(Icon.none, Styles.clearTogglei, () -> {
sector.info.icon = null;
sector.info.contentIcon = null;
sector.saveInfo();
hide();
updateSelected();
}).checked(sector.info.icon == null);
}).checked(sector.info.icon == null && sector.info.contentIcon == null);
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
@@ -758,7 +794,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
for(var key : defaultIcons){
var value = Icon.icons.get(key);
t.button(value, Styles.cleari, () -> {
t.button(value, Styles.clearTogglei, () -> {
sector.info.icon = key;
sector.info.contentIcon = null;
sector.saveInfo();
@@ -777,7 +813,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
i = 0;
for(UnlockableContent u : content.getBy(ctype).<UnlockableContent>as()){
if(!u.isHidden() && u.unlocked()){
t.button(new TextureRegionDrawable(u.uiIcon), Styles.cleari, iconMed, () -> {
t.button(new TextureRegionDrawable(u.uiIcon), Styles.clearTogglei, iconMed, () -> {
sector.info.icon = null;
sector.info.contentIcon = u;
sector.saveInfo();
@@ -931,14 +967,37 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
CoreBlock block = from.info.bestCoreType instanceof CoreBlock b ? b : (CoreBlock)Blocks.coreShard;
loadouts.show(block, from, () -> {
var schemCore = universe.getLastLoadout().findCore();
from.removeItems(universe.getLastLoadout().requirements());
from.removeItems(universe.getLaunchResources());
launching = true;
zoom = planets.planet.minZoom;
if(settings.getBool("skipcoreanimation")){
//just... go there
control.playSector(from, sector);
//hide only after load screen is shown
Time.runTask(8f, this::hide);
}else{
//hide immediately so launch sector is visible
hide();
ui.hudfrag.showLaunchDirect();
Time.runTask(launchDuration, () -> control.playSector(from, sector));
//allow planet dialog to finish hiding before actually launching
Time.runTask(5f, () -> {
Runnable doLaunch = () -> {
renderer.showLaunch(schemCore);
//run with less delay, as the loading animation is delayed by several frames
Time.runTask(coreLandDuration - 8f, () -> control.playSector(from, sector));
};
//load launchFrom sector right before launching so animation is correct
if(!from.isBeingPlayed()){
//run *after* the loading animation is done
Time.runTask(9f, doLaunch);
control.playSector(from);
}else{
doLaunch.run();
}
});
}
});
}
}else if(mode == select){

View File

@@ -292,13 +292,15 @@ public class SettingsMenuDialog extends Dialog{
if(mobile){
game.checkPref("autotarget", true);
game.checkPref("keyboard", false, val -> {
control.setInput(val ? new DesktopInput() : new MobileInput());
input.setUseKeyboard(val);
});
if(Core.settings.getBool("keyboard")){
control.setInput(new DesktopInput());
input.setUseKeyboard(true);
if(!ios){
game.checkPref("keyboard", false, val -> {
control.setInput(val ? new DesktopInput() : new MobileInput());
input.setUseKeyboard(val);
});
if(Core.settings.getBool("keyboard")){
control.setInput(new DesktopInput());
input.setUseKeyboard(true);
}
}
}
//the issue with touchscreen support on desktop is that:
@@ -354,7 +356,7 @@ public class SettingsMenuDialog extends Dialog{
});
graphics.sliderPref("screenshake", 4, 0, 8, i -> (i / 4f) + "x");
graphics.sliderPref("fpscap", 240, 15, 245, 5, s -> (s > 240 ? Core.bundle.get("setting.fpscap.none") : Core.bundle.format("setting.fpscap.text", s)));
graphics.sliderPref("fpscap", 240, 10, 245, 5, s -> (s > 240 ? Core.bundle.get("setting.fpscap.none") : Core.bundle.format("setting.fpscap.text", s)));
graphics.sliderPref("chatopacity", 100, 0, 100, 5, s -> s + "%");
graphics.sliderPref("lasersopacity", 100, 0, 100, 5, s -> {
if(ui.settings != null){
@@ -367,6 +369,12 @@ public class SettingsMenuDialog extends Dialog{
if(!mobile){
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
graphics.checkPref("fullscreen", false, b -> {
if(b && settings.getBool("borderlesswindow")){
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
settings.put("borderlesswindow", false);
graphics.rebuild();
}
if(b){
Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode());
}else{
@@ -374,15 +382,23 @@ public class SettingsMenuDialog extends Dialog{
}
});
graphics.checkPref("borderlesswindow", false, b -> Core.graphics.setUndecorated(b));
graphics.checkPref("borderlesswindow", false, b -> {
if(b && settings.getBool("fullscreen")){
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
settings.put("fullscreen", false);
graphics.rebuild();
}
Core.graphics.setBorderless(b);
});
Core.graphics.setVSync(Core.settings.getBool("vsync"));
if(Core.settings.getBool("fullscreen")){
Core.app.post(() -> Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode()));
}
if(Core.settings.getBool("borderlesswindow")){
Core.app.post(() -> Core.graphics.setUndecorated(true));
Core.app.post(() -> Core.graphics.setBorderless(true));
}
}else if(!ios){
graphics.checkPref("landscape", false, b -> {
@@ -446,11 +462,11 @@ public class SettingsMenuDialog extends Dialog{
}
}
graphics.checkPref("skipcoreanimation", false);
if(!mobile){
Core.settings.put("swapdiagonal", false);
}
graphics.checkPref("flow", true);
}
public void exportData(Fi file) throws IOException{
@@ -610,7 +626,8 @@ public class SettingsMenuDialog extends Dialog{
Setting(String name){
this.name = name;
title = bundle.get("setting." + name + ".name");
String winkey = "setting." + name + ".name.windows";
title = OS.isWindows && bundle.has(winkey) ? bundle.get(winkey) : bundle.get("setting." + name + ".name");
description = bundle.getOrNull("setting." + name + ".description");
}

View File

@@ -32,7 +32,7 @@ public class ChatFragment extends Table{
private Font font;
private GlyphLayout layout = new GlyphLayout();
private float offsetx = Scl.scl(4), offsety = Scl.scl(4), fontoffsetx = Scl.scl(2), chatspace = Scl.scl(50);
private Color shadowColor = new Color(0, 0, 0, 0.4f);
private Color shadowColor = new Color(0, 0, 0, 0.5f);
private float textspacing = Scl.scl(10);
private Seq<String> history = new Seq<>();
private int historyPos = 0;

View File

@@ -12,6 +12,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.input.*;
@@ -54,6 +55,12 @@ public class HintsFragment extends Fragment{
hints.remove(hint);
}else if(hint != null){
display(hint);
}else{
//moused over a derelict structure
var build = world.buildWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());
if(build != null && build.team == Team.derelict){
events.add("derelictmouse");
}
}
}
});
@@ -157,6 +164,7 @@ public class HintsFragment extends Fragment{
conveyorPathfind(() -> control.input.block == Blocks.titaniumConveyor, () -> Core.input.keyRelease(Binding.diagonal_placement) || (mobile && Core.settings.getBool("swapdiagonal"))),
boost(visibleDesktop, () -> !player.dead() && player.unit().type.canBoost, () -> Core.input.keyDown(Binding.boost)),
blockInfo(() -> true, () -> ui.content.isShown()),
derelict(() -> ui.hints.events.contains("derelictmouse"), () -> false),
command(() -> state.rules.defaultTeam.data().units.size > 3 && !net.active(), () -> player.unit().isCommanding()),
payloadPickup(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().isEmpty(), () -> player.unit() instanceof Payloadc p && p.payloads().any()),
payloadDrop(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().any(), () -> player.unit() instanceof Payloadc p && p.payloads().isEmpty()),

View File

@@ -545,20 +545,17 @@ public class HudFragment extends Fragment{
}
}
public void showLaunchDirect(){
Image image = new Image();
image.color.a = 0f;
image.setFillParent(true);
image.actions(Actions.fadeIn(launchDuration / 60f, Interp.pow2In), Actions.delay(8f / 60f), Actions.remove());
Core.scene.add(image);
}
public void showLaunch(){
float margin = 30f;
Image image = new Image();
image.color.a = 0f;
image.touchable = Touchable.disabled;
image.setFillParent(true);
image.actions(Actions.fadeIn(40f / 60f));
image.actions(Actions.delay((coreLandDuration - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
image.update(() -> {
image.toFront();
ui.loadfrag.toFront();
if(state.isMenu()){
image.remove();
}
@@ -571,9 +568,10 @@ public class HudFragment extends Fragment{
image.color.a = 1f;
image.touchable = Touchable.disabled;
image.setFillParent(true);
image.actions(Actions.fadeOut(0.8f), Actions.remove());
image.actions(Actions.fadeOut(35f / 60f), Actions.remove());
image.update(() -> {
image.toFront();
ui.loadfrag.toFront();
if(state.isMenu()){
image.remove();
}
@@ -726,7 +724,7 @@ public class HudFragment extends Fragment{
t.add(new SideBar(() -> player.unit().healthf(), () -> true, true)).width(bw).growY().padRight(pad);
t.image(() -> player.icon()).scaling(Scaling.bounded).grow().maxWidth(54f);
t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : player.unit().healthf(), () -> !player.displayAmmo(), false)).width(bw).growY().padLeft(pad).update(b -> {
b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color : Pal.health);
b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color() : Pal.health);
});
t.getChildren().get(1).toFront();

View File

@@ -38,13 +38,17 @@ public class LoadingFragment extends Fragment{
text("@loading");
bar = t.add(new Bar()).pad(3).size(500f, 40f).visible(false).get();
bar = t.add(new Bar()).pad(3).padTop(6).size(500f, 40f).visible(false).get();
t.row();
button = t.button("@cancel", () -> {}).pad(20).size(250f, 70f).visible(false).get();
table = t;
});
}
public void toFront(){
table.toFront();
}
public void setProgress(Floatp progress){
bar.reset(0f);
bar.visible = true;

View File

@@ -120,7 +120,6 @@ public class MenuFragment extends Fragment{
table.add(tools);
table.add(mods);
//if(platform.canDonate()) table.add(donate);
if(!ios) table.add(exit);
}).colspan(4);
}else{
@@ -139,7 +138,6 @@ public class MenuFragment extends Fragment{
table.defaults().set(container.defaults());
table.add(mods);
//if(platform.canDonate()) table.add(donate);
if(!ios) table.add(exit);
}).colspan(2);
}

View File

@@ -15,6 +15,7 @@ import mindustry.core.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
@@ -38,6 +39,7 @@ public class PlacementFragment extends Fragment{
Block menuHoverBlock;
Displayable hover;
Object lastDisplayState;
Team lastTeam;
boolean wasHovered;
Table blockTable, toggler, topTable;
ScrollPane blockPane;
@@ -283,13 +285,14 @@ public class PlacementFragment extends Fragment{
//don't refresh unnecessarily
//refresh only when the hover state changes, or the displayed block changes
if(wasHovered == isHovered && lastDisplayState == displayState) return;
if(wasHovered == isHovered && lastDisplayState == displayState && lastTeam == player.team()) return;
topTable.clear();
topTable.top().left().margin(5);
lastDisplayState = displayState;
wasHovered = isHovered;
lastTeam = player.team();
//show details of selected block, with costs
if(displayBlock != null){

View File

@@ -71,8 +71,10 @@ public class Block extends UnlockableContent{
public boolean update;
/** whether this block has health and can be destroyed */
public boolean destructible;
/** whether unloaders work on this block*/
/** whether unloaders work on this block */
public boolean unloadable = true;
/** whether units can resupply by taking items from this block */
public boolean allowResupply = false;
/** whether this is solid */
public boolean solid;
/** whether this block CAN be solid. */
@@ -431,7 +433,7 @@ public class Block extends UnlockableContent{
boolean buffered = cons.buffered;
float capacity = cons.capacity;
bars.add("power", entity -> new Bar(() -> buffered ? Core.bundle.format("bar.poweramount", Float.isNaN(entity.power.status * capacity) ? "<ERROR>" : (int)(entity.power.status * capacity)) :
bars.add("power", entity -> new Bar(() -> buffered ? Core.bundle.format("bar.poweramount", Float.isNaN(entity.power.status * capacity) ? "<ERROR>" : UI.formatAmount((int)(entity.power.status * capacity))) :
Core.bundle.get("bar.power"), () -> Pal.powerBar, () -> Mathf.zero(cons.requestedPower(entity)) && entity.power.graph.getPowerProduced() + entity.power.graph.getBatteryStored() > 0f ? 1f : entity.power.status));
}
@@ -794,6 +796,11 @@ public class Block extends UnlockableContent{
}
clipSize = Math.max(clipSize, size * tilesize);
//only kept to ensure compatibility with v6 mods.
if(expanded){
clipSize += tilesize * 10f;
}
if(emitLight){
clipSize = Math.max(clipSize, lightRadius * 2f);
@@ -882,6 +889,36 @@ public class Block extends UnlockableContent{
}
}
//generate paletted team regions
if(teamRegion != null && teamRegion.found()){
for(Team team : Team.all){
//if there's an override, don't generate anything
if(team.hasPalette && !Core.atlas.has(name + "-team-" + team.name)){
var base = Core.atlas.getPixmap(teamRegion);
Pixmap out = new Pixmap(base.width, base.height);
for(int x = 0; x < base.width; x++){
for(int y = 0; y < base.height; y++){
int color = base.get(x, y);
int index = color == 0xffffffff ? 0 : color == 0xdcc6c6ff ? 1 : color == 0x9d7f7fff ? 2 : -1;
out.setRaw(x, y, index == -1 ? base.get(x, y) : team.palettei[index]);
}
}
if(Core.settings.getBool("linear")){
Pixmaps.bleed(out);
}
packer.add(PageType.main, name + "-team-" + team.name, out);
}
}
teamRegions = new TextureRegion[Team.all.length];
for(Team team : Team.all){
teamRegions[team.id] = teamRegion.found() && team.hasPalette ? Core.atlas.find(name + "-team-" + team.name, teamRegion) : teamRegion;
}
}
Pixmap last = null;
var gen = icons();

View File

@@ -2,6 +2,8 @@ package mindustry.world;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@@ -20,7 +22,7 @@ public class ItemBuffer{
public void accept(Item item, short data){
//if(!accepts()) return;
buffer[index++] = Pack.longInt(Float.floatToIntBits(Time.time), Pack.shortInt(item.id, data));
buffer[index++] = TimeItem.get(data, item.id, Time.time);
}
public void accept(Item item){
@@ -30,10 +32,10 @@ public class ItemBuffer{
public Item poll(float speed){
if(index > 0){
long l = buffer[0];
float time = Float.intBitsToFloat(Pack.leftInt(l));
float time = TimeItem.time(l);
if(Time.time >= time + speed || Time.time < time){
return content.item(Pack.leftShort(Pack.rightInt(l)));
return content.item(TimeItem.item(l));
}
}
return null;
@@ -63,4 +65,11 @@ public class ItemBuffer{
}
index = Math.min(index, length - 1);
}
@Struct
class TimeItemStruct{
short data;
short item;
float time;
}
}

View File

@@ -108,7 +108,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
}
/**
* Returns the flammability of the Used for fire calculations.
* Returns the flammability of the tile. Used for fire calculations.
* Takes flammability of floor liquid into account.
*/
public float getFlammability(){
@@ -675,7 +675,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
build.health = health;
if(build.damaged()){
indexer.notifyTileDamaged(build);
indexer.notifyBuildDamaged(build);
}
}

View File

@@ -84,6 +84,11 @@ public class ConstructBlock extends Block{
if(builder != null && builder.getControllerName() != null){
tile.build.lastAccessed = builder.getControllerName();
}
//make sure block indexer knows it's damaged
if(tile.build.damaged()){
indexer.notifyBuildDamaged(tile.build);
}
}
//last builder was this local client player, call placed()

View File

@@ -22,7 +22,6 @@ import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -42,7 +41,6 @@ public class LaunchPad extends Block{
solid = true;
update = true;
configurable = true;
drawDisabled = false;
flags = EnumSet.of(BlockFlag.launchPad);
}
@@ -73,12 +71,6 @@ public class LaunchPad extends Block{
return !state.isCampaign() || net.client() ? SystemCursor.arrow : super.getCursor();
}
//cannot be disabled
@Override
public float efficiency(){
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
@Override
public boolean shouldConsume(){
return true;
@@ -156,7 +148,7 @@ public class LaunchPad extends Block{
Sector dest = state.rules.sector == null ? null : state.rules.sector.info.getRealDestination();
return Core.bundle.format("launch.destination",
dest == null ? Core.bundle.get("sectors.nonelaunch") :
dest == null || !dest.hasBase() ? Core.bundle.get("sectors.nonelaunch") :
"[accent]" + dest.name());
}).pad(4).wrap().width(200f).left();
}

View File

@@ -21,7 +21,6 @@ import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -61,12 +60,6 @@ public class PayloadLaunchPad extends PayloadBlock{
return !state.isCampaign() || net.client() ? SystemCursor.arrow : super.getCursor();
}
//cannot be disabled
@Override
public float efficiency(){
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
@Override
public boolean shouldConsume(){
return true;

View File

@@ -1,12 +1,11 @@
package mindustry.world.blocks.distribution;
import arc.math.*;
import arc.util.io.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
public class BufferedItemBridge extends ExtendingItemBridge{
public class BufferedItemBridge extends ItemBridge{
public final int timerAccept = timers++;
public float speed = 40f;
@@ -19,7 +18,7 @@ public class BufferedItemBridge extends ExtendingItemBridge{
canOverdrive = true;
}
public class BufferedItemBridgeBuild extends ExtendingItemBridgeBuild{
public class BufferedItemBridgeBuild extends ItemBridgeBuild{
ItemBuffer buffer = new ItemBuffer(bufferCapacity);
@Override
@@ -30,11 +29,9 @@ public class BufferedItemBridge extends ExtendingItemBridge{
Item item = buffer.poll(speed / timeScale);
if(timer(timerAccept, 4 / timeScale) && item != null && other.acceptItem(this, item)){
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 4f, 0.05f);
moved = true;
other.handleItem(this, item);
buffer.remove();
}else{
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 0f, 0.008f);
}
}

View File

@@ -32,6 +32,8 @@ public class Conveyor extends Block implements Autotiler{
public float speed = 0f;
public float displayedSpeed = 0f;
public @Nullable Block junctionReplacement, bridgeReplacement;
public Conveyor(String name){
super(name);
rotate = true;
@@ -55,6 +57,14 @@ public class Conveyor extends Block implements Autotiler{
stats.add(Stat.itemsMoved, displayedSpeed, StatUnit.itemsSecond);
}
@Override
public void init(){
super.init();
if(junctionReplacement == null) junctionReplacement = Blocks.junction;
if(bridgeReplacement == null || !(bridgeReplacement instanceof ItemBridge)) bridgeReplacement = Blocks.itemBridge;
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
int[] bits = getTiling(req, list);
@@ -79,7 +89,9 @@ public class Conveyor extends Block implements Autotiler{
@Override
public void handlePlacementLine(Seq<BuildPlan> plans){
Placement.calculateBridges(plans, (ItemBridge)Blocks.itemBridge);
if(bridgeReplacement == null) return;
Placement.calculateBridges(plans, (ItemBridge)bridgeReplacement);
}
@Override
@@ -94,12 +106,14 @@ public class Conveyor extends Block implements Autotiler{
@Override
public Block getReplacement(BuildPlan req, Seq<BuildPlan> requests){
if(junctionReplacement == null) return this;
Boolf<Point2> cont = p -> requests.contains(o -> o.x == req.x + p.x && o.y == req.y + p.y && (req.block instanceof Conveyor || req.block instanceof Junction));
return cont.get(Geometry.d4(req.rotation)) &&
cont.get(Geometry.d4(req.rotation - 2)) &&
req.tile() != null &&
req.tile().block() instanceof Conveyor &&
Mathf.mod(req.tile().build.rotation - req.rotation, 2) == 1 ? Blocks.junction : this;
Mathf.mod(req.tile().build.rotation - req.rotation, 2) == 1 ? junctionReplacement : this;
}
public class ConveyorBuild extends Building implements ChainedBuilding{

View File

@@ -48,7 +48,7 @@ public class DuctRouter extends Block{
Draw.rect(topRegion, req.drawx(), req.drawy(), req.rotation * 90);
}
public class DuctBuild extends Building{
public class DuctRouterBuild extends Building{
public float progress;
public @Nullable Item current;

View File

@@ -1,70 +1,15 @@
package mindustry.world.blocks.distribution;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import mindustry.core.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
/** @deprecated use ItemBridge instead. */
@Deprecated
public class ExtendingItemBridge extends ItemBridge{
public ExtendingItemBridge(String name){
super(name);
hasItems = true;
}
@Deprecated
public class ExtendingItemBridgeBuild extends ItemBridgeBuild{
@Override
public void draw(){
Draw.rect(region, x, y);
Draw.z(Layer.power);
Tile other = world.tile(link);
if(!linkValid(tile, other)) return;
int i = tile.absoluteRelativeTo(other.x, other.y);
float ex = other.worldx() - x - Geometry.d4(i).x * tilesize / 2f,
ey = other.worldy() - y - Geometry.d4(i).y * tilesize / 2f;
float uptime = state.isEditor() ? 1f : this.uptime;
ex *= uptime;
ey *= uptime;
if(Mathf.zero(Renderer.bridgeOpacity)) return;
Draw.alpha(Renderer.bridgeOpacity);
Lines.stroke(8f);
Lines.line(bridgeRegion,
x + Geometry.d4(i).x * tilesize / 2f,
y + Geometry.d4(i).y * tilesize / 2f,
x + ex,
y + ey, false);
Draw.rect(endRegion, x, y, i * 90 + 90);
Draw.rect(endRegion,
x + ex + Geometry.d4(i).x * tilesize / 2f,
y + ey + Geometry.d4(i).y * tilesize / 2f, i * 90 + 270);
int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y));
int arrows = (dist) * tilesize / 6 - 1;
Draw.color();
for(int a = 0; a < arrows; a++){
Draw.alpha(Mathf.absin(a / (float)arrows - time / 100f, 0.1f, 1f) * uptime * Renderer.bridgeOpacity);
Draw.rect(arrowRegion,
x + Geometry.d4(i).x * (tilesize / 2f + a * 6f + 2) * uptime,
y + Geometry.d4(i).y * (tilesize / 2f + a * 6f + 2) * uptime,
i * 90f);
}
Draw.reset();
}
}
}

View File

@@ -5,7 +5,6 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.struct.IntSet.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
@@ -23,12 +22,20 @@ import static mindustry.Vars.*;
public class ItemBridge extends Block{
private static BuildPlan otherReq;
public final int timerCheckMoved = timers ++;
public int range;
public float transportTime = 2f;
public @Load("@-end") TextureRegion endRegion;
public @Load("@-bridge") TextureRegion bridgeRegion;
public @Load("@-arrow") TextureRegion arrowRegion;
public boolean fadeIn = true;
public boolean moveArrows = true;
public boolean pulse = false;
public float arrowSpacing = 4f, arrowOffset = 2f, arrowPeriod = 0.4f;
public float arrowTimeScl = 6.2f;
//for autolink
public @Nullable ItemBridgeBuild lastBuild;
@@ -169,12 +176,10 @@ public class ItemBridge extends Block{
public class ItemBridgeBuild extends Building{
public int link = -1;
//TODO awful
public IntSet incoming = new IntSet();
public float uptime;
public float time;
public float time2;
public float cycleSpeed = 1f;
public IntSeq incoming = new IntSeq(false, 4);
public float warmup;
public float time = -8f, timeSpeed;
public boolean wasMoved, moved;
public float transportCounter;
@Override
@@ -271,36 +276,44 @@ public class ItemBridge extends Block{
}
public void checkIncoming(){
IntSetIterator it = incoming.iterator();
while(it.hasNext){
int i = it.next();
int idx = 0;
while(idx < incoming.size){
int i = incoming.items[idx];
Tile other = world.tile(i);
if(!linkValid(tile, other, false) || ((ItemBridgeBuild)other.build).link != tile.pos()){
it.remove();
incoming.removeIndex(idx);
idx --;
}
idx ++;
}
}
@Override
public void updateTile(){
time += cycleSpeed * delta();
time2 += (cycleSpeed - 1f) * delta();
if(timer(timerCheckMoved, 30f)){
wasMoved = moved;
moved = false;
}
//smooth out animation, so it doesn't stop/start immediately
timeSpeed = Mathf.approachDelta(timeSpeed, wasMoved ? 1f : 0f, 1f / 60f);
time += timeSpeed * delta();
checkIncoming();
Tile other = world.tile(link);
if(!linkValid(tile, other)){
doDump();
uptime = 0f;
warmup = 0f;
}else{
((ItemBridgeBuild)other.build).incoming.add(tile.pos());
if(consValid() && Mathf.zero(1f - efficiency())){
uptime = Mathf.lerpDelta(uptime, 1f, 0.04f);
}else{
uptime = Mathf.lerpDelta(uptime, 0f, 0.02f);
var inc = ((ItemBridgeBuild)other.build).incoming;
int pos = tile.pos();
if(!inc.contains(pos)){
inc.add(pos);
}
warmup = Mathf.approachDelta(warmup, efficiency(), 1f / 30f);
updateTransport(other.build);
}
}
@@ -311,21 +324,18 @@ public class ItemBridge extends Block{
}
public void updateTransport(Building other){
boolean any = false;
transportCounter += edelta();
while(transportCounter >= transportTime){
Item item = items.take();
if(item != null && other.acceptItem(this, item)){
other.handleItem(this, item);
any = true;
moved = true;
}else if(item != null){
items.add(item, 1);
items.undoFlow(item);
}
transportCounter -= transportTime;
}
cycleSpeed = Mathf.lerpDelta(cycleSpeed, any ? 4f : 1f, any ? 0.05f : 0.01f);
}
@Override
@@ -341,8 +351,13 @@ public class ItemBridge extends Block{
int i = relativeTo(other.x, other.y);
Draw.color(Color.white, Color.black, Mathf.absin(Time.time, 6f, 0.07f));
Draw.alpha(Math.max(uptime, 0.25f) * Renderer.bridgeOpacity);
if(pulse){
Draw.color(Color.white, Color.black, Mathf.absin(Time.time, 6f, 0.07f));
}
float warmup = hasPower ? this.warmup : 1f;
Draw.alpha((fadeIn ? Math.max(warmup, 0.25f) : 1f) * Renderer.bridgeOpacity);
Draw.rect(endRegion, x, y, i * 90 + 90);
Draw.rect(endRegion, other.drawx(), other.drawy(), i * 90 + 270);
@@ -357,40 +372,26 @@ public class ItemBridge extends Block{
other.worldx() - Tmp.v1.x,
other.worldy() - Tmp.v1.y, false);
int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y));
float time = time2 / 1.7f;
int arrows = (dist) * tilesize / 4 - 2;
int dist = Math.max(Math.abs(other.x - tile.x), Math.abs(other.y - tile.y)) - 1;
Draw.color();
int arrows = (int)(dist * tilesize / arrowSpacing), dx = Geometry.d4x(i), dy = Geometry.d4y(i);
for(int a = 0; a < arrows; a++){
Draw.alpha(Mathf.absin(a / (float)arrows - time / 100f, 0.1f, 1f) * uptime * Renderer.bridgeOpacity);
Draw.alpha(Mathf.absin(a - time / arrowTimeScl, arrowPeriod, 1f) * warmup * Renderer.bridgeOpacity);
Draw.rect(arrowRegion,
x + Geometry.d4(i).x * (tilesize / 2f + a * 4f + time % 4f),
y + Geometry.d4(i).y * (tilesize / 2f + a * 4f + time % 4f), i * 90f);
x + dx * (tilesize / 2f + a * arrowSpacing + arrowOffset),
y + dy * (tilesize / 2f + a * arrowSpacing + arrowOffset),
i * 90f);
}
Draw.reset();
}
@Override
public boolean acceptItem(Building source, Item item){
if(team != source.team) return false;
Tile other = world.tile(link);
if(items.total() >= itemCapacity) return false;
if(linked(source)) return true;
if(linkValid(tile, other)){
int rel = relativeTo(other);
int rel2 = relativeTo(Edges.getFacingEdge(source, this));
return rel != rel2;
}
return false;
return hasItems && team == source.team && items.total() < itemCapacity && checkAccept(source, world.tile(link));
}
@Override
@@ -400,16 +401,17 @@ public class ItemBridge extends Block{
@Override
public boolean acceptLiquid(Building source, Liquid liquid){
if(team != source.team || !hasLiquids) return false;
Tile other = world.tile(link);
if(!(liquids.current() == liquid || liquids.get(liquids.current()) < 0.2f)) return false;
return
hasLiquids && team == source.team &&
(liquids.current() == liquid || liquids.get(liquids.current()) < 0.2f) &&
checkAccept(source, world.tile(link));
}
protected boolean checkAccept(Building source, Tile other){
if(tile == null || linked(source)) return true;
if(linkValid(tile, other)){
int rel = relativeTo(other.x, other.y);
int rel = relativeTo(other);
int rel2 = relativeTo(Edges.getFacingEdge(source, this));
return rel != rel2;
@@ -433,10 +435,8 @@ public class ItemBridge extends Block{
Tile edge = Edges.getFacingEdge(to.tile, tile);
int i = relativeTo(edge.x, edge.y);
IntSetIterator it = incoming.iterator();
while(it.hasNext){
int v = it.next();
for(int j = 0; j < incoming.size; j++){
int v = incoming.items[j];
if(relativeTo(Point2.x(v), Point2.y(v)) == i){
return false;
}
@@ -460,29 +460,38 @@ public class ItemBridge extends Block{
return Point2.unpack(link).sub(tile.x, tile.y);
}
@Override
public byte version(){
return 1;
}
@Override
public void write(Writes write){
super.write(write);
write.i(link);
write.f(uptime);
write.f(warmup);
write.b(incoming.size);
IntSetIterator it = incoming.iterator();
while(it.hasNext){
write.i(it.next());
for(int i = 0; i < incoming.size; i++){
write.i(incoming.items[i]);
}
write.bool(wasMoved || moved);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
link = read.i();
uptime = read.f();
warmup = read.f();
byte links = read.b();
for(int i = 0; i < links; i++){
incoming.add(read.i());
}
if(revision >= 1){
wasMoved = moved = read.bool();
}
}
}
}

View File

@@ -27,7 +27,7 @@ public class Floor extends Block{
public float dragMultiplier = 1f;
/** Damage taken per tick on this tile. */
public float damageTaken = 0f;
/** How many ticks it takes to drown on this. */
/** How many ticks it takes to drown on this. 0 to disable. */
public float drownTime = 0f;
/** Effect when walking on this floor. */
public Effect walkEffect = Fx.none;
@@ -41,11 +41,11 @@ public class Floor extends Block{
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect. */
public float statusDuration = 60f;
/** liquids that drop from this block, used for pumps */
/** liquids that drop from this block, used for pumps. */
public @Nullable Liquid liquidDrop = null;
/** Multiplier for pumped liquids, used for deep water. */
public float liquidMultiplier = 1f;
/** whether this block can be drowned in */
/** whether this block is liquid. */
public boolean isLiquid;
/** if true, this block cannot be mined by players. useful for annoying things like sand. */
public boolean playerUnmineable = false;

View File

@@ -31,6 +31,7 @@ public class Conduit extends LiquidBlock implements Autotiler{
public @Load("@-cap") TextureRegion capRegion;
public boolean leaks = true;
public @Nullable Block junctionReplacement, bridgeReplacement;
public Conduit(String name){
super(name);
@@ -41,6 +42,14 @@ public class Conduit extends LiquidBlock implements Autotiler{
noUpdateDisabled = true;
}
@Override
public void init(){
super.init();
if(junctionReplacement == null) junctionReplacement = Blocks.liquidJunction;
if(bridgeReplacement == null || !(bridgeReplacement instanceof ItemBridge)) bridgeReplacement = Blocks.bridgeConduit;
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
int[] bits = getTiling(req, list);
@@ -58,12 +67,14 @@ public class Conduit extends LiquidBlock implements Autotiler{
@Override
public Block getReplacement(BuildPlan req, Seq<BuildPlan> requests){
if(junctionReplacement == null) return this;
Boolf<Point2> cont = p -> requests.contains(o -> o.x == req.x + p.x && o.y == req.y + p.y && o.rotation == req.rotation && (req.block instanceof Conduit || req.block instanceof LiquidJunction));
return cont.get(Geometry.d4(req.rotation)) &&
cont.get(Geometry.d4(req.rotation - 2)) &&
req.tile() != null &&
req.tile().block() instanceof Conduit &&
Mathf.mod(req.build().rotation - req.rotation, 2) == 1 ? Blocks.liquidJunction : this;
Mathf.mod(req.build().rotation - req.rotation, 2) == 1 ? junctionReplacement : this;
}
@Override
@@ -73,7 +84,9 @@ public class Conduit extends LiquidBlock implements Autotiler{
@Override
public void handlePlacementLine(Seq<BuildPlan> plans){
Placement.calculateBridges(plans, (ItemBridge)Blocks.bridgeConduit);
if(bridgeReplacement == null) return;
Placement.calculateBridges(plans, (ItemBridge)bridgeReplacement);
}
@Override

View File

@@ -1,13 +1,9 @@
package mindustry.world.blocks.liquid;
import arc.math.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LiquidBridge extends ItemBridge{
public LiquidBridge(String name){
@@ -21,43 +17,17 @@ public class LiquidBridge extends ItemBridge{
}
public class LiquidBridgeBuild extends ItemBridgeBuild{
@Override
public void updateTile(){
time += cycleSpeed * delta();
time2 += (cycleSpeed - 1f) * delta();
checkIncoming();
Building other = world.build(link);
if(other == null || !linkValid(tile, other.tile())){
dumpLiquid(liquids.current(), 1f);
}else{
((ItemBridgeBuild)other).incoming.add(tile.pos());
if(consValid()){
float alpha = 0.04f;
if(hasPower){
alpha *= efficiency(); // Exceed boot time unless power is at max.
}
uptime = Mathf.lerpDelta(uptime, 1f, alpha);
}else{
uptime = Mathf.lerpDelta(uptime, 0f, 0.02f);
}
if(uptime >= 0.5f){
if(moveLiquid(other, liquids.current()) > 0.1f){
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 4f, 0.05f);
}else{
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 1f, 0.01f);
}
}
public void updateTransport(Building other){
if(warmup >= 0.25f){
moved |= moveLiquid(other, liquids.current()) > 0.05f;
}
}
@Override
public boolean acceptItem(Building source, Item item){
return false;
public void doDump(){
dumpLiquid(liquids.current(), 1f);
}
}
}

View File

@@ -1,57 +1,14 @@
package mindustry.world.blocks.liquid;
import arc.math.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LiquidExtendingBridge extends ExtendingItemBridge{
/** @deprecated use LiquidBridge instead. */
@Deprecated
public class LiquidExtendingBridge extends LiquidBridge{
public LiquidExtendingBridge(String name){
super(name);
hasItems = false;
hasLiquids = true;
outputsLiquid = true;
group = BlockGroup.liquids;
envEnabled = Env.any;
}
public class LiquidExtendingBridgeBuild extends ExtendingItemBridgeBuild{
@Override
public void updateTile(){
time += cycleSpeed * delta();
time2 += (cycleSpeed - 1f) * delta();
checkIncoming();
Building other = world.build(link);
if(other == null || !linkValid(tile, other.tile())){
dumpLiquid(liquids.current(), 1f);
}else{
((ItemBridgeBuild)other).incoming.add(tile.pos());
if(consValid()){
uptime = Mathf.lerpDelta(uptime, 1f, 0.04f);
}else{
uptime = Mathf.lerpDelta(uptime, 0f, 0.02f);
}
if(uptime >= 0.5f){
if(moveLiquid(other, liquids.current()) > 0.1f){
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 4f, 0.05f);
}else{
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 0f, 0.01f);
}
}
}
}
@Override
public boolean acceptItem(Building source, Item item){
return false;
}
@Deprecated
public class LiquidExtendingBridgeBuild extends LiquidBridgeBuild{
}
}

View File

@@ -177,7 +177,7 @@ public class LogicBlock extends Block{
public boolean active = true, valid;
public int x, y;
public String name;
Building lastBuild;
public Building lastBuild;
public LogicLink(int x, int y, String name, boolean valid){
this.x = x;
@@ -400,7 +400,8 @@ public class LogicBlock extends Block{
var cur = world.build(l.x, l.y);
boolean valid = validLink(cur);
if(valid != l.valid || (l.lastBuild != null && l.lastBuild != cur)){
if(l.lastBuild == null) l.lastBuild = cur;
if(valid != l.valid || l.lastBuild != cur){
l.lastBuild = cur;
changed = true;
l.valid = valid;

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