Merge branch 'master' into balancing-payload_system

This commit is contained in:
SomeonesShade
2025-06-30 18:15:08 +08:00
committed by GitHub
138 changed files with 1357 additions and 471 deletions
+2
View File
@@ -51,6 +51,8 @@ public class Vars implements Loadable{
public static final int minModGameVersion = 136;
/** Min game version for java mods specifically - this is higher, as Java mods have more breaking changes. */
public static final int minJavaModGameVersion = 147;
/** If true, a button to view sector submission threads is shown. */
public static boolean showSectorSubmissions = true;
/** If true, the BE server list is always used. */
public static boolean forceBeServers = false;
/** If true, mod code and scripts do not run. For internal testing only. This WILL break mods if enabled. */
+1 -1
View File
@@ -540,7 +540,7 @@ public class Pathfinder implements Runnable{
if(!targets.isEmpty()){
boolean any = false;
for(Building other : targets){
if((other.items != null && other.items.any()) || other.status() != BlockStatus.noInput){
if(((other.items != null && other.items.any()) || other.status() != BlockStatus.noInput) && other.block.targetable){
out.add(other.tile.array());
any = true;
}
+12 -5
View File
@@ -141,6 +141,8 @@ public class RtsAI{
boolean handleSquad(Seq<Unit> units, boolean noDefenders){
if(units.isEmpty()) return false;
boolean naval = units.first() instanceof WaterMovec;
float health = 0f, dps = 0f;
float ax = 0f, ay = 0f;
boolean targetAir = true, targetGround = true;
@@ -165,7 +167,7 @@ public class RtsAI{
boolean defendingCore = false;
//there is something to defend, see if it's worth the time
if(damaged.size > 0){
if(damaged.size > 0 && !naval){
//TODO do the weights matter at all?
//for(var build : damaged){
//float w = estimateStats(ax, ay, dps, health);
@@ -251,7 +253,7 @@ public class RtsAI{
}
}
var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0, units.first().isFlying());
var build = anyDefend ? null : findTarget(ax, ay, units.size, dps, health, units.first().flag == 0, units.first().isFlying(), naval);
if(build != null || anyDefend){
for(var unit : units){
@@ -274,7 +276,7 @@ public class RtsAI{
return anyDefend;
}
@Nullable Building findTarget(float x, float y, int total, float dps, float health, boolean checkWeight, boolean air){
@Nullable Building findTarget(float x, float y, int total, float dps, float health, boolean checkWeight, boolean air, boolean naval){
if(total < data.team.rules().rtsMinSquad) return null;
//flag priority?
@@ -282,8 +284,13 @@ public class RtsAI{
//2. factory
//3. core
targets.clear();
for(var flag : flags){
targets.addAll(Vars.indexer.getEnemy(data.team, flag));
if(naval){
//naval units can only target enemy cores, because those are assumed to always be reachable. other blocks may not be!
targets.addAll(Vars.indexer.getEnemy(data.team, BlockFlag.core));
}else{
for(var flag : flags){
targets.addAll(Vars.indexer.getEnemy(data.team, flag));
}
}
targets.removeAll(b -> assignedTargets.contains(b.id) || invalidTarget.contains(b.pos()));
+1 -1
View File
@@ -158,7 +158,7 @@ public class UnitGroup{
}
private void updateRaycast(int index, Vec2 dest, Vec2 v1){
if(collisionLayer != PhysicsProcess.layerFlying){
if(collisionLayer != PhysicsProcess.layerFlying && originalPositions != null && positions != null){
//coordinates in world space
float
+1 -1
View File
@@ -41,7 +41,7 @@ public class FlyingAI extends AIController{
Building closest = null;
float cdist = 0f;
for(Building t : list){
if((t.items != null && t.items.any()) || t.status() != BlockStatus.noInput){
if(((t.items != null && t.items.any()) || t.status() != BlockStatus.noInput) && t.block.targetable){
float dst = t.dst2(x, y);
if(closest == null || dst < cdist){
closest = t;
+1 -5
View File
@@ -3437,7 +3437,6 @@ public class Blocks{
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
status = StatusEffects.shocked;
statusDuration = 10f;
hittable = false;
lightColor = Color.white;
collidesAir = false;
@@ -3489,7 +3488,6 @@ public class Blocks{
despawnEffect = Fx.blastExplosion;
status = StatusEffects.blasted;
statusDuration = 60f;
hitColor = backColor = trailColor = Pal.blastAmmoBack;
frontColor = Pal.blastAmmoFront;
@@ -3915,7 +3913,6 @@ public class Blocks{
collidesGround = true;
status = StatusEffects.blasted;
statusDuration = 60f;
backColor = hitColor = trailColor = Pal.blastAmmoBack;
frontColor = Pal.blastAmmoFront;
@@ -4385,6 +4382,7 @@ public class Blocks{
targetInterval = 5f;
newTargetInterval = 30f;
targetUnderBlocks = false;
shootY = 8f;
float r = range = 130f;
@@ -4421,7 +4419,6 @@ public class Blocks{
);
scaledHealth = 210;
shootY = 7f;
size = 3;
researchCost = with(Items.tungsten, 400, Items.silicon, 400, Items.oxide, 80, Items.beryllium, 800);
@@ -5455,7 +5452,6 @@ public class Blocks{
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
status = StatusEffects.shocked;
statusDuration = 10f;
hittable = false;
lightColor = Color.white;
buildingDamageMultiplier = 0.25f;
@@ -111,7 +111,7 @@ public class SectorPresets{
}};
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
difficulty = 4;
difficulty = 2;
}};
infestedCanyons = new SectorPreset("infestedCanyons", serpulo, 210){{
+50 -23
View File
@@ -3829,8 +3829,10 @@ public class UnitTypes{
engineSize = 4.8f;
engineOffset = 61 / 4f;
range = 4.3f * 60f * 1.4f;
abilities.add(new SuppressionFieldAbility(){{
reload = 60f * 8f;
orbRadius = 5.3f;
y = 1f;
}});
@@ -3846,36 +3848,59 @@ public class UnitTypes{
recoil = 1f;
rotationLimit = 60f;
bullet = new BulletType(){{
bullet = new BasicBulletType(4.3f, 70f, "missile-large"){{
shootEffect = Fx.shootBig;
smokeEffect = Fx.shootBigSmoke2;
shake = 1f;
speed = 0f;
lifetime = 60 * 0.496f;
rangeOverride = 361.2f;
followAimSpeed = 5f;
width = 12f;
height = 22f;
hitSize = 7f;
hitColor = backColor = trailColor = Pal.sapBulletBack;
trailWidth = 3f;
trailLength = 12;
hitEffect = despawnEffect = Fx.hitBulletColor;
keepVelocity = false;
collidesGround = true;
collidesAir = false;
spawnUnit = new MissileUnitType("quell-missile"){{
targetAir = false;
speed = 4.3f;
maxRange = 6f;
lifetime = 60f * 1.4f;
outlineColor = Pal.darkOutline;
engineColor = trailColor = Pal.sapBulletBack;
engineLayer = Layer.effect;
health = 45;
loopSoundVolume = 0.1f;
//workaround to get the missile to behave like in spawnUnit while still spawning on death
fragRandomSpread = 0;
fragBullets = 1;
fragVelocityMin = 1f;
fragOffsetMax = 1f;
weapons.add(new Weapon(){{
shootSound = Sounds.none;
shootCone = 360f;
mirror = false;
reload = 1f;
shootOnDeath = true;
bullet = new ExplosionBulletType(110f, 25f){{
shootEffect = Fx.massiveExplosion;
collidesAir = false;
}};
}});
fragBullet = new BulletType(){{
speed = 0f;
keepVelocity = false;
collidesAir = false;
spawnUnit = new MissileUnitType("quell-missile"){{
targetAir = false;
speed = 4.3f;
maxRange = 6f;
lifetime = 60f * (1.4f - 0.496f);
outlineColor = Pal.darkOutline;
engineColor = trailColor = Pal.sapBulletBack;
engineLayer = Layer.effect;
health = 45;
loopSoundVolume = 0.1f;
weapons.add(new Weapon() {{
shootSound = Sounds.none;
shootCone = 360f;
mirror = false;
reload = 1f;
shootOnDeath = true;
bullet = new ExplosionBulletType(110f, 25f) {{
shootEffect = Fx.massiveExplosion;
collidesAir = false;
}};
}});
}};
}};
}};
}});
@@ -3909,6 +3934,8 @@ public class UnitTypes{
int parts = 10;
abilities.add(new SuppressionFieldAbility(){{
reload = 60 * 15f;
range = 320f;
orbRadius = orbRad;
particleSize = partRad;
y = 10f;
+11 -1
View File
@@ -156,6 +156,16 @@ public class Logic implements ApplicationListener{
if(!net.client() && e.sector == state.getSector() && e.sector.isBeingPlayed()){
state.rules.waveTeam.data().destroyToDerelict();
}
if(!net.client() && e.sector.planet.generator != null){
e.sector.planet.generator.onSectorCaptured(e.sector);
}
});
Events.on(SectorLoseEvent.class, e -> {
if(!net.client() && e.sector.planet.generator != null){
e.sector.planet.generator.onSectorLost(e.sector);
}
});
Events.on(BlockDestroyEvent.class, e -> {
@@ -462,7 +472,7 @@ public class Logic implements ApplicationListener{
if(rules.fillItems && data.cores.size > 0){
var core = data.cores.first();
content.items().each(i -> {
if(i.isOnPlanet(Vars.state.getPlanet())){
if(i.isOnPlanet(Vars.state.getPlanet()) && !i.isHidden()){
core.items.set(i, core.getMaximumAccepted(i));
}
});
@@ -64,6 +64,9 @@ public class DrawOperation{
Block block = content.block(to);
tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation);
if(tile.build != null){
tile.build.enabled = true;
}
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
}else if(type == OpType.rotation.ordinal()){
+11 -9
View File
@@ -222,38 +222,38 @@ public class Damage{
public static float collideLaser(Bullet b, float length, boolean large, boolean laser, int pierceCap){
float resultLength = findPierceLength(b, pierceCap, laser, length);
collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), resultLength, large, laser, pierceCap);
collideLine(b, b.team, b.x, b.y, b.rotation(), resultLength, large, laser, pierceCap);
b.fdata = resultLength;
return resultLength;
}
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length){
collideLine(hitter, team, effect, x, y, angle, length, false);
public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length){
collideLine(hitter, team, x, y, angle, length, false);
}
/**
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
collideLine(hitter, team, effect, x, y, angle, length, large, true);
public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large){
collideLine(hitter, team, x, y, angle, length, large, true);
}
/**
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser){
collideLine(hitter, team, effect, x, y, angle, length, large, laser, -1);
public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large, boolean laser){
collideLine(hitter, team, x, y, angle, length, large, laser, -1);
}
/**
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){
public static void collideLine(Bullet hitter, Team team, float x, float y, float angle, float length, boolean large, boolean laser, int pierceCap){
length = findLength(hitter, length, laser, pierceCap);
hitter.fdata = length;
@@ -545,8 +545,10 @@ public class Damage{
tileDamage(team, x, y, baseRadius, damage, null);
}
public static void tileDamage(Team team, int x, int y, float baseRadius, float damage, @Nullable Bullet source){
public static void tileDamage(Team team, int tx, int ty, float baseRadius, float damage, @Nullable Bullet source){
Time.run(0f, () -> {
int x = Mathf.clamp(tx, -100, world.width() + 100), y = Mathf.clamp(ty, -100, world.height() + 100);
var in = world.build(x, y);
//spawned inside a multiblock. this means that damage needs to be dealt directly.
//why? because otherwise the building would absorb everything in one cell, which means much less damage than a nearby explosion.
@@ -17,6 +17,7 @@ public class SuppressionFieldAbility extends Ability{
protected static Rand rand = new Rand();
public float reload = 60f * 1.5f;
public float maxDelay = 60f * 1.5f;
public float range = 200f;
public float orbRadius = 4.1f, orbMidScl = 0.33f, orbSinScl = 8f, orbSinMag = 1f;
@@ -55,9 +56,9 @@ public class SuppressionFieldAbility extends Ability{
public void update(Unit unit){
if(!active) return;
if((timer += Time.delta) >= reload){
if((timer += Time.delta) >= maxDelay){
Tmp.v1.set(x, y).rotate(unit.rotation - 90f).add(unit);
Damage.applySuppression(unit.team, Tmp.v1.x, Tmp.v1.y, range, reload, reload, applyParticleChance, unit, effectColor);
Damage.applySuppression(unit.team, Tmp.v1.x, Tmp.v1.y, range, reload, maxDelay, applyParticleChance, unit, effectColor);
timer = 0f;
}
}
@@ -85,7 +85,7 @@ public class ContinuousBulletType extends BulletType{
if(timescaleDamage && b.owner instanceof Building build){
b.damage *= build.timeScale();
}
Damage.collideLine(b, b.team, hitEffect, b.x, b.y, b.rotation(), currentLength(b), largeHit, laserAbsorb, pierceCap);
Damage.collideLine(b, b.team, b.x, b.y, b.rotation(), currentLength(b), largeHit, laserAbsorb, pierceCap);
b.damage = damage;
}
@@ -55,13 +55,13 @@ public class ContinuousLaserBulletType extends ContinuousBulletType{
float ellipseLenScl = Mathf.lerp(1 - i / (float)(colors.length), 1f, pointyScaling);
Lines.stroke(stroke);
Lines.lineAngle(b.x, b.y, rot, realLength - frontLength, false);
Lines.lineAngle(b.x, b.y, rot, Math.max(0, realLength - frontLength), false);
//back ellipse
Drawf.flameFront(b.x, b.y, divisions, rot + 180f, backLength, stroke / 2f);
//front ellipse
Tmp.v1.trnsExact(rot, realLength - frontLength);
Tmp.v1.trnsExact(rot, Math.max(0, realLength - frontLength));
Drawf.flameFront(b.x + Tmp.v1.x, b.y + Tmp.v1.y, divisions, rot, frontLength * ellipseLenScl, stroke / 2f);
}
@@ -4,8 +4,10 @@ import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.blocks.distribution.MassDriver.*;
import static mindustry.Vars.*;
@@ -89,5 +91,17 @@ public class MassDriverBolt extends BasicBulletType{
public void hit(Bullet b, float hitx, float hity){
super.hit(b, hitx, hity);
despawned(b);
if(b.data() instanceof DriverBulletData data){
float explosiveness = 0f;
float flammability = 0f;
float power = 0f;
for(int i = 0; i < data.items.length; i++){
Item item = content.item(i);
explosiveness += item.explosiveness * data.items[i];
flammability += item.flammability * data.items[i];
power += item.charge * Mathf.pow(data.items[i], 1.1f) * 25f;
}
Damage.dynamicExplosion(b.x, b.y, flammability / 10f, explosiveness / 10f, power, 1f, state.rules.damageExplosions);
}
}
}
@@ -59,7 +59,7 @@ public class RailBulletType extends BulletType{
super.init(b);
b.fdata = length;
Damage.collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), length, false, false, pierceCap);
Damage.collideLine(b, b.team, b.x, b.y, b.rotation(), length, false, false, pierceCap);
float resultLen = b.fdata;
Vec2 nor = Tmp.v1.trns(b.rotation(), 1f).nor();
@@ -35,7 +35,7 @@ import static mindustry.logic.GlobalVars.*;
@Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Syncc, Shieldc, Displayable, Ranged, Minerc, Builderc, Senseable, Settable{
private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2();
static final float warpDst = 20f;
static final float warpDst = 8f;
@Import boolean dead, disarmed;
@Import float x, y, rotation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
@@ -643,11 +643,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//repel unit out of bounds
if(x < left) dx += (-(x - left)/warpDst);
if(y < bot) dy += (-(y - bot)/warpDst);
if(x > right) dx -= (x - right)/warpDst;
if(y > top) dy -= (y - top)/warpDst;
if(x > right - tilesize) dx -= (x - (right - tilesize))/warpDst;
if(y > top - tilesize) dy -= (y - (top - tilesize))/warpDst;
velAddNet(dx * Time.delta, dy * Time.delta);
float margin = tilesize * 2f;
float margin = tilesize * 1f;
x = Mathf.clamp(x, left - margin, right - tilesize + margin);
y = Mathf.clamp(y, bot - margin, top - tilesize + margin);
}
@@ -21,8 +21,6 @@ public class BuildPlan implements Position, QuadTreeObject{
public boolean breaking;
/** Config int. Not used unless hasConfig is true.*/
public Object config;
/** Original position, only used in schematics.*/
public int originalX, originalY, originalWidth, originalHeight;
/** Last progress.*/
public float progress;
@@ -65,6 +63,7 @@ public class BuildPlan implements Position, QuadTreeObject{
public BuildPlan(){
}
public boolean placeable(Team team){
return Build.validPlace(block, team, x, y, rotation);
}
@@ -111,22 +110,12 @@ public class BuildPlan implements Position, QuadTreeObject{
copy.block = block;
copy.breaking = breaking;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
copy.progress = progress;
copy.initialized = initialized;
copy.animScale = animScale;
return copy;
}
public BuildPlan original(int x, int y, int originalWidth, int originalHeight){
originalX = x;
originalY = y;
this.originalWidth = originalWidth;
this.originalHeight = originalHeight;
return this;
}
public Rect bounds(Rect rect){
if(breaking){
return rect.set(-100f, -100f, 0f, 0f);
+1 -2
View File
@@ -119,8 +119,7 @@ public final class FogControl implements CustomChunk{
var data = data(team);
if(data == null) return false;
if(x < 0 || y < 0 || x >= ww || y >= wh) return false;
return data.read.get(x + y * ww);
return data.read.get(Mathf.clamp(x, 0, ww - 1) + Mathf.clamp(y, 0, wh - 1) * ww);
}
public void resetFog(){
+31 -44
View File
@@ -3,6 +3,7 @@ package mindustry.game;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
@@ -98,7 +99,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
}
public static void registerLegacyMarker(String name, Prov<? extends ObjectiveMarker> prov) {
public static void registerLegacyMarker(String name, Prov<? extends ObjectiveMarker> prov){
Class<?> type = prov.get().getClass();
markerNameToType.put(name, prov);
@@ -663,7 +664,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
}
/** Marker used for drawing various content to indicate something along with an objective. Mostly used as UI overlay. */
/** Marker used for drawing various content to indicate something along with an objective. Mostly used as UI overlay. */
public static abstract class ObjectiveMarker{
/** Internal use only! Do not access. */
public transient int arrayIndex;
@@ -714,7 +715,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
state.mapLocales.getProperty(key + ".mobile") :
state.mapLocales.containsProperty(key) ?
state.mapLocales.getProperty(key) :
Core.bundle.get(key);
Core.bundle.get(key + ".mobile", Core.bundle.get(key));
}else{
out =
state.mapLocales.containsProperty(key) ?
@@ -822,13 +823,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
switch(type){
case fontSize -> fontSize = (float)p1;
case textHeight -> textHeight = (float)p1;
case labelFlags -> {
if(!Mathf.equal((float)p1, 0f)){
flags |= WorldLabel.flagBackground;
}else{
flags &= ~WorldLabel.flagBackground;
}
}
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
case radius -> radius = (float)p1;
case rotation -> rotation = (float)p1;
case color -> color.fromDouble(p1);
@@ -838,13 +834,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
if(!Double.isNaN(p2)){
switch(type){
case labelFlags -> {
if(!Mathf.equal((float)p2, 0f)){
flags |= WorldLabel.flagOutline;
}else{
flags &= ~WorldLabel.flagOutline;
}
}
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p2, 0f));
}
}
}
@@ -944,7 +934,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
Lines.poly(pos.x, pos.y, sides, (radius + 1f) * scaleFactor, rotation + startAngle, rotation + endAngle);
}else{
Draw.color(color);
if (startAngle < endAngle){
if(startAngle < endAngle){
Fill.arc(pos.x, pos.y, radius * scaleFactor, (endAngle - startAngle) / 360f, rotation + startAngle, sides);
}else{
Fill.arc(pos.x, pos.y, radius * scaleFactor, (startAngle - endAngle) / 360f, rotation + endAngle, sides);
@@ -962,6 +952,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
switch(type){
case radius -> radius = (float)p1;
case stroke -> stroke = (float)p1;
case outline -> outline = !Mathf.equal((float)p1, 0f);
case rotation -> rotation = (float)p1;
case color -> color.fromDouble(p1);
case shape -> sides = (int)p1;
@@ -1025,25 +1016,14 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
if(!Double.isNaN(p1)){
switch(type){
case fontSize -> fontSize = (float)p1;
case labelFlags -> {
if(!Mathf.equal((float)p1, 0f)){
flags |= WorldLabel.flagBackground;
}else{
flags &= ~WorldLabel.flagBackground;
}
}
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
}
}
if(!Double.isNaN(p2)){
switch(type){
case labelFlags -> {
if(!Mathf.equal((float)p2, 0f)){
flags |= WorldLabel.flagOutline;
}else{
flags &= ~WorldLabel.flagOutline;
}
}
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p2, 0f));
}
}
}
@@ -1101,6 +1081,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
case endPos -> endPos.x = (float)p1 * tilesize;
case stroke -> stroke = (float)p1;
case color -> color1.set(color2.fromDouble(p1));
case outline -> outline = !Mathf.equal((float)p1, 0f);
}
}
@@ -1111,7 +1092,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
}
if(!Double.isNaN(p1) && !Double.isNaN(p2)){
switch (type){
switch(type){
case posi -> ((int)p1 == 0 ? pos : (int)p1 == 1 ? endPos : Tmp.v1).x = (float)p2 * tilesize;
case colori -> ((int)p1 == 0 ? color1 : (int)p1 == 1 ? color2 : Tmp.c1).fromDouble(p2);
}
@@ -1199,7 +1180,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
private transient TextureRegion fetchedRegion;
public QuadMarker() {
public QuadMarker(){
for(int i = 0; i < 4; i++){
vertices[i * 6 + 2] = Color.white.toFloatBits();
vertices[i * 6 + 5] = Color.clearFloatBits;
@@ -1250,7 +1231,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
boolean firstUpdate = fetchedRegion == null;
if(fetchedRegion == null) fetchedRegion = new TextureRegion();
if(firstUpdate) fetchedRegion = new TextureRegion();
Tmp.tr1.set(fetchedRegion);
lookupRegion(textureName, fetchedRegion);
@@ -1258,21 +1239,22 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
if(firstUpdate){
if(mapRegion){
mapRegion = false;
// possibly from the editor, we need to clamp the values
for(int i = 0; i < 4; i++){
vertices[i * 6 + 3] = Mathf.map(Mathf.clamp(vertices[i * 6 + 3]), fetchedRegion.u, fetchedRegion.u2);
vertices[i * 6 + 4] = Mathf.map(1 - Mathf.clamp(vertices[i * 6 + 4]), fetchedRegion.v, fetchedRegion.v2);
setUv(i, vertices[i * 6 + 3], vertices[i * 6 + 4]);
}
}
}else{
for(int i = 0; i < 4; i++){
vertices[i * 6 + 3] = Mathf.map(vertices[i * 6 + 3], Tmp.tr1.u, Tmp.tr1.u2, fetchedRegion.u, fetchedRegion.u2);
vertices[i * 6 + 4] = Mathf.map(vertices[i * 6 + 4], Tmp.tr1.v, Tmp.tr1.v2, fetchedRegion.v, fetchedRegion.v2);
setUv(i, unmap(vertices[i * 6 + 3], Tmp.tr1.u, Tmp.tr1.u2), 1 - unmap(vertices[i * 6 + 4], Tmp.tr1.v, Tmp.tr1.v2));
}
}
}
private static float unmap(float x, float from, float to){
if(Mathf.equal(from, to)) return x;
return (x - from) / (to - from);
}
private void setPos(int i, double x, double y){
if(i >= 0 && i < 4){
if(!Double.isNaN(x)) vertices[i * 6] = (float)x * tilesize;
@@ -1290,11 +1272,16 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
if(i >= 0 && i < 4){
if(fetchedRegion == null) setTexture(textureName);
if(!Double.isNaN(u)) vertices[i * 6 + 3] = Mathf.map(Mathf.clamp((float)u), fetchedRegion.u, fetchedRegion.u2);
if(!Double.isNaN(v)) vertices[i * 6 + 4] = Mathf.map(1 - Mathf.clamp((float)v), fetchedRegion.v, fetchedRegion.v2);
if(!Double.isNaN(u)){
boolean clampU = fetchedRegion.texture.getUWrap() != TextureWrap.mirroredRepeat && fetchedRegion.texture.getUWrap() != TextureWrap.repeat;
vertices[i * 6 + 3] = Mathf.map(clampU ? Mathf.clamp((float)u) : (float)u, fetchedRegion.u, fetchedRegion.u2);
}
if(!Double.isNaN(v)){
boolean clampV = fetchedRegion.texture.getVWrap() != TextureWrap.mirroredRepeat && fetchedRegion.texture.getVWrap() != TextureWrap.repeat;
vertices[i * 6 + 4] = Mathf.map(clampV ? 1 - Mathf.clamp((float)v) : 1 - (float)v, fetchedRegion.v, fetchedRegion.v2);
}
}
}
}
private static void lookupRegion(String name, TextureRegion out){
+54 -20
View File
@@ -72,7 +72,32 @@ public class Saves{
lastSectorSave = saves.find(s -> s.isSector() && s.getName().equals(Core.settings.getString("last-sector-save", "<none>")));
ObjectSet<Sector> infoToClear = new ObjectSet<>(), remapped = new ObjectSet<>();
class Remap{
//file in the temp folder
Fi sourceFile;
//slot of source sector to move file for
SaveSlot slot;
Sector sourceSector;
//sector info from source sector to move into
SectorInfo sourceInfo;
//file to copy to
Fi destFile;
//destination sector to move to
Sector destSector;
Remap(SaveSlot slot, Fi sourceFile, Sector sourceSector, SectorInfo sourceInfo, Fi destFile, Sector destSector){
this.slot = slot;
this.sourceFile = sourceFile;
this.sourceSector = sourceSector;
this.sourceInfo = sourceInfo;
this.destFile = destFile;
this.destSector = destSector;
}
}
Seq<Remap> remaps = new Seq<>();
ObjectSet<Sector> remapped = new ObjectSet<>();
//automatically assign sector save slots
for(SaveSlot slot : saves){
@@ -102,22 +127,13 @@ public class Saves{
if(!slot.file.equals(getSectorFile(remapTarget))){
Log.info("Remapping sector: @ -> @ (@)", sector.id, remapTarget.id, remapTarget.preset);
sector.loadInfo();
//overwrite the target sector's info with the save's info
Core.settings.putJson(remapTarget.planet.name + "-s-" + remapTarget.id + "-info", sector.info);
remapTarget.loadInfo();
//queue a clear of the sector that had its data moved
infoToClear.add(sector);
//add to the remapped list (if it was remapped, don't clear it!)
remapped.add(remapTarget);
remapTarget.save = slot;
try{
Fi target = getSectorFile(remapTarget);
//move over save file
slot.file.moveTo(target);
slot.file = target;
SectorInfo info = Core.settings.getJson(sector.planet.name + "-s-" + sector.id + "-info", SectorInfo.class, SectorInfo::new);
Fi tmpRemapFile = saveDirectory.child("remap_" + sector.planet.name + "_" + sector.id + "." + saveExtension);
slot.file.moveTo(tmpRemapFile);
remaps.add(new Remap(slot, tmpRemapFile, sector, info, getSectorFile(remapTarget), remapTarget));
remapped.add(remapTarget);
}catch(Exception e){
Log.err("Failed to move sector files when remapping: " + sector.id + " -> " + remapTarget.id, e);
}
@@ -125,6 +141,7 @@ public class Saves{
remapTarget.save = slot;
slot.meta.rules.sector = remapTarget;
}else{
if(sector.save != null){
Log.warn("Sector @ has two corresponding saves: @ and @", sector, sector.save.file, slot.file);
@@ -134,10 +151,27 @@ public class Saves{
}
}
for(var sector : infoToClear){
if(!remapped.contains(sector)){
sector.clearInfo();
}
//process remaps later to allow swaps of sectors
for(var remap : remaps){
var remapTarget = remap.destSector;
//overwrite the target sector's info with the save's info
Core.settings.putJson(remapTarget.planet.name + "-s-" + remapTarget.id + "-info", remap.sourceInfo);
remapTarget.loadInfo();
remapTarget.save = remap.slot;
try{
//move file from tmp directory back into the correct location
remap.sourceFile.moveTo(remap.destFile);
remap.slot.file = remap.destFile;
}catch(Exception e){
Log.err("Failed to move back sector files when remapping: " + remap.sourceSector.id + " -> " + remapTarget.id, e);
}
//clear the info, assuming it wasn't a sector that got mapped to
if(!remapped.contains(remap.sourceSector)){
remap.sourceSector.clearInfo();
}
}
}
+2 -2
View File
@@ -97,7 +97,7 @@ public class Schematics implements Loadable{
all.sort();
if(shadowBuffer == null){
if(shadowBuffer == null && !headless){
Core.app.post(() -> shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 8, maxSchematicSize + padding + 8));
}
}
@@ -275,7 +275,7 @@ public class Schematics implements Loadable{
/** Creates an array of build plans from a schematic's data, centered on the provided x+y coordinates. */
public Seq<BuildPlan> toPlans(Schematic schem, int x, int y){
return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block, t.config).original(t.x, t.y, schem.width, schem.height))
return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block, t.config))
.removeAll(s -> (!s.block.isVisible() && !(s.block instanceof CoreBlock)) || !s.block.unlockedNow()).sort(Structs.comparingInt(s -> -s.block.schematicPriority));
}
+15
View File
@@ -73,6 +73,8 @@ public class SectorInfo{
public float secondsPassed;
/** How many minutes this sector has been captured. */
public float minutesCaptured;
/** Light coverage in terms of radius. */
public float lightCoverage;
/** Display name. */
public @Nullable String name;
/** Displayed icon. */
@@ -225,6 +227,15 @@ public class SectorInfo{
damage = 0;
hasSpawns = spawner.countSpawns() > 0;
lightCoverage = 0f;
for(var build : state.rules.defaultTeam.data().buildings){
if(build.block.emitLight){
lightCoverage += build.block.lightRadius * build.efficiency;
}
}
lightCoverage += state.rules.defaultTeam.data().units.sumf(u -> u.type.lightRadius/2f);
//cap production at raw production.
production.each((item, stat) -> {
stat.mean = Math.min(stat.mean, rawProduction.get(item, ExportStat::new).mean);
@@ -242,6 +253,10 @@ public class SectorInfo{
if(sector.planet.allowWaveSimulation){
SectorDamage.writeParameters(sector);
}
if(sector.planet.generator != null){
sector.planet.generator.beforeSaveWrite(sector);
}
}
/** Update averages of various stats, updates some special sector logic.
+7
View File
@@ -322,6 +322,13 @@ public class Universe{
return net.client() ? netSeconds : seconds;
}
public void setSeconds(float seconds){
this.seconds = (int)seconds;
this.secondCounter = seconds - this.seconds;
save();
}
public float secondsf(){
return seconds() + secondCounter;
}
@@ -32,7 +32,7 @@ public class LoadRenderer implements Disposable{
private float testprogress = 0f;
private StringBuilder assetText = new StringBuilder();
private Bar[] bars;
private Mesh mesh = MeshBuilder.buildHex(colorRed, 2, true, 1f);
private Mesh mesh = MeshBuilder.buildPlanetGrid(PlanetGrid.create(2), colorRed, 1f);
private Camera3D cam = new Camera3D();
private int lastLength = -1;
private FxProcessor fx;
+2
View File
@@ -109,6 +109,7 @@ public class Shaders{
public Color ambientColor = Color.white.cpy();
public Vec3 camDir = new Vec3();
public Vec3 camPos = new Vec3();
public boolean emissive;
public Planet planet;
public PlanetShader(){
@@ -123,6 +124,7 @@ public class Shaders{
setUniformf("u_ambientColor", ambientColor.r, ambientColor.g, ambientColor.b);
setUniformf("u_camdir", camDir);
setUniformf("u_campos", renderer.planets.cam.position);
setUniformf("u_emissive", emissive ? 1f : 0f);
}
}
@@ -1,7 +1,8 @@
package mindustry.graphics.g3d;
import arc.math.geom.*;
import arc.util.*;
public interface GenericMesh{
public interface GenericMesh extends Disposable{
void render(PlanetParams params, Mat3D projection, Mat3D transform);
}
+3 -2
View File
@@ -8,11 +8,11 @@ import mindustry.type.*;
public class HexMesh extends PlanetMesh{
public HexMesh(Planet planet, int divisions){
super(planet, MeshBuilder.buildHex(planet.generator, divisions, false, planet.radius, 0.2f), Shaders.planet);
super(planet, MeshBuilder.buildHex(planet.generator, divisions, planet.radius, 0.2f), Shaders.planet);
}
public HexMesh(Planet planet, HexMesher mesher, int divisions, Shader shader){
super(planet, MeshBuilder.buildHex(mesher, divisions, false, planet.radius, 0.2f), shader);
super(planet, MeshBuilder.buildHex(mesher, divisions, planet.radius, 0.2f), shader);
}
public HexMesh(){
@@ -21,6 +21,7 @@ public class HexMesh extends PlanetMesh{
@Override
public void preRender(PlanetParams params){
Shaders.planet.planet = planet;
Shaders.planet.emissive = planet.generator != null && planet.generator.isEmissive();
Shaders.planet.lightDir.set(planet.solarSystem.position).sub(planet.position).rotate(Vec3.Y, planet.getRotation()).nor();
Shaders.planet.ambientColor.set(planet.solarSystem.lightColor);
}
+17 -2
View File
@@ -5,8 +5,23 @@ import arc.math.geom.*;
/** Defines color and height for a planet mesh. */
public interface HexMesher{
float getHeight(Vec3 position);
Color getColor(Vec3 position);
default float getHeight(Vec3 position){
return 0f;
}
default void getColor(Vec3 position, Color out){
}
default void getEmissiveColor(Vec3 position, Color out){
}
default boolean isEmissive(){
return false;
}
default boolean skip(Vec3 position){
return false;
}
@@ -21,15 +21,15 @@ public class HexSkyMesh extends PlanetMesh{
}
@Override
public Color getColor(Vec3 position){
return color;
public void getColor(Vec3 position, Color out){
out.set(color);
}
@Override
public boolean skip(Vec3 position){
return Simplex.noise3d(7 + seed, octaves, persistence, scl, position.x, position.y * 3f, position.z) >= thresh;
}
}, divisions, false, planet.radius, radius), Shaders.clouds);
}, divisions, planet.radius, radius), Shaders.clouds);
this.speed = speed;
}
@@ -19,4 +19,9 @@ public class MatMesh implements GenericMesh{
public void render(PlanetParams params, Mat3D projection, Mat3D transform){
mesh.render(params, projection, tmp.set(transform).mul(mat));
}
@Override
public void dispose(){
mesh.dispose();
}
}
+198 -82
View File
@@ -1,56 +1,72 @@
package mindustry.graphics.g3d;
import arc.*;
import arc.graphics.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.maps.generators.*;
public class MeshBuilder{
private static final Vec3 v1 = new Vec3(), v2 = new Vec3(), v3 = new Vec3(), v4 = new Vec3();
private static final float[] floats = new float[3 + 3 + 1];
private static Mesh mesh;
public static Mesh buildIcosphere(int divisions, float radius, Color color){
begin(20 * (2 << (2 * divisions - 1)) * 3);
private static final boolean gl30 = Core.gl30 != null;
private static volatile float[] tmpHeights = new float[14580]; //highest amount of corners in vanilla
/** Note that the resulting icosphere does not have normals or a color. */
public static Mesh buildIcosphere(int divisions, float radius){
MeshResult result = Icosphere.create(divisions);
for(int i = 0; i < result.indices.size; i+= 3){
v1.set(result.vertices.items, result.indices.items[i] * 3).setLength(radius);
v2.set(result.vertices.items, result.indices.items[i + 1] * 3).setLength(radius);
v3.set(result.vertices.items, result.indices.items[i + 2] * 3).setLength(radius);
verts(v1, v3, v2, normal(v1, v2, v3).scl(-1f), color);
Mesh mesh = begin(result.vertices.size / 3, result.indices.size, false, false);
if(result.vertices.size >= 65535) throw new RuntimeException("Due to index size limits, only meshes with a maximum of 65535 vertices are supported. If you want more than that, make your own non-indexed mesh builder.");
float[] items = result.vertices.items;
for(int i = 0; i < result.vertices.size; i ++){
items[i] *= radius;
}
return end();
}
mesh.getVerticesBuffer().put(items, 0, result.vertices.size);
public static Mesh buildIcosphere(int divisions, float radius){
return buildIcosphere(divisions, radius, Color.white);
short[] indices = new short[result.indices.size];
for(int i = 0; i < result.indices.size; i++){
indices[i] = (short)result.indices.items[i];
}
mesh.getIndicesBuffer().put(indices);
return end(mesh);
}
public static Mesh buildPlanetGrid(PlanetGrid grid, Color color, float scale){
int total = 0;
for(Ptile tile : grid.tiles){
total += tile.corners.length * 2;
}
Mesh mesh = begin(grid.tiles.length * 12, 0, false, false);
float col = color.toFloatBits();
float[] floats = new float[8];
begin(total);
for(Ptile tile : grid.tiles){
Corner[] c = tile.corners;
for(int i = 0; i < c.length; i++){
Vec3 a = v1.set(c[i].v).scl(scale);
Vec3 b = v2.set(c[(i + 1) % c.length].v).scl(scale);
vert(a, Vec3.Z, color);
vert(b, Vec3.Z, color);
for(int i = 0; i < c.length; i++){
Vec3 v1 = c[i].v;
Vec3 v2 = c[(i + 1) % c.length].v;
floats[0] = v1.x * scale;
floats[1] = v1.y * scale;
floats[2] = v1.z * scale;
floats[3] = col;
floats[4] = v2.x * scale;
floats[5] = v2.y * scale;
floats[6] = v2.z * scale;
floats[7] = col;
mesh.getVerticesBuffer().put(floats);
}
}
return end();
return end(mesh);
}
public static Mesh buildHex(Color color, int divisions, boolean lines, float radius){
public static Mesh buildHex(Color color, int divisions, float radius){
return buildHex(new HexMesher(){
@Override
public float getHeight(Vec3 position){
@@ -58,20 +74,46 @@ public class MeshBuilder{
}
@Override
public Color getColor(Vec3 position){
return color;
public void getColor(Vec3 position, Color out){
out.set(color);
}
}, divisions, lines, radius, 0);
}, divisions, radius, 0);
}
public static Mesh buildHex(HexMesher mesher, int divisions, boolean lines, float radius, float intensity){
//TODO: in principle this should not be synchronized, but I would rather not realloc tmpHeights every time, and it is unlikely that two planets will be reloading at the same time
public static synchronized Mesh buildHex(HexMesher mesher, int divisions, float radius, float intensity){
PlanetGrid grid = PlanetGrid.create(divisions);
//TODO: this is NOT thread safe, but in practice, it should never cause a problem
if(mesher instanceof PlanetGenerator generator){
generator.seed = generator.baseSeed;
}
begin(grid.tiles.length * 12);
boolean emit = mesher.isEmissive();
if(grid.tiles.length * 6 >= 65535) throw new RuntimeException("Due to index size limits, only meshes with a maximum of 65535 vertices are supported. If you want more than that, make your own non-indexed mesh builder.");
Mesh mesh = begin(grid.tiles.length * 6, grid.tiles.length * 4 * 3, true, emit);
float[] heights;
if(tmpHeights == null || tmpHeights.length < grid.corners.length){
heights = tmpHeights = new float[grid.corners.length];
}else{
heights = tmpHeights;
}
//cache heights in an array to prevent redundant calls to getHeight
for(int i = 0; i < grid.corners.length; i++){
heights[i] = (1f + mesher.getHeight(grid.corners[i].v) * intensity) * radius;
}
int position = 0;
short[] shorts = new short[12];
float[] floats = new float[3 + (gl30 ? 1 : 3) + 1 + (emit ? 1 : 0)];
Vec3 nor = new Vec3();
Color tmpCol = new Color();
for(Ptile tile : grid.tiles){
if(mesher.skip(tile.v)){
@@ -80,81 +122,155 @@ public class MeshBuilder{
Corner[] c = tile.corners;
for(Corner corner : c){
corner.v.setLength((1f + mesher.getHeight(v2.set(corner.v)) * intensity) * radius);
float
h1 = heights[c[0].id],
h2 = heights[c[2].id],
h3 = heights[c[4].id];
Vec3
v1 = c[0].v,
v2 = c[2].v,
v3 = c[4].v;
normal(
v1.x * h1, v1.y * h1, v1.z * h1,
v2.x * h2, v2.y * h2, v2.z * h2,
v3.x * h3, v3.y * h3, v3.z * h3,
nor);
tmpCol.set(1f, 1f, 1f, 1f);
mesher.getColor(tile.v, tmpCol);
float color = tmpCol.toFloatBits();
float emissive = 0f;
if(emit){
tmpCol.set(0f, 0f, 0f, 0f);
mesher.getEmissiveColor(tile.v, tmpCol);
emissive = tmpCol.toFloatBits();
}
Vec3 nor = normal(c[0].v, c[2].v, c[4].v);
Color color = mesher.getColor(v2.set(tile.v));
for(var corner : c){
float height = heights[corner.id];
if(lines){
nor.set(1f, 1f, 1f);
for(int i = 0; i < c.length; i++){
Vec3 v1 = c[i].v;
Vec3 v2 = c[(i + 1) % c.length].v;
vert(v1, nor, color);
vert(v2, nor, color);
}
}else{
verts(c[0].v, c[1].v, c[2].v, nor, color);
verts(c[0].v, c[2].v, c[3].v, nor, color);
verts(c[0].v, c[3].v, c[4].v, nor, color);
if(c.length > 5){
verts(c[0].v, c[4].v, c[5].v, nor, color);
}
vert(mesh, floats, corner.v.x * height, corner.v.y * height, corner.v.z * height, nor, color, emissive);
}
//restore mutated corners
for(Corner corner : c){
corner.v.nor();
shorts[0] = (short)(position);
shorts[1] = (short)(position + 1);
shorts[2] = (short)(position + 2);
shorts[3] = (short)(position);
shorts[4] = (short)(position + 2);
shorts[5] = (short)(position + 3);
shorts[6] = (short)(position);
shorts[7] = (short)(position + 3);
shorts[8] = (short)(position + 4);
if(c.length > 5){
shorts[9] = (short)(position);
shorts[10] = (short)(position + 4);
shorts[11] = (short)(position + 5);
}
mesh.getIndicesBuffer().put(shorts, 0, c.length > 5 ? 12 : 9);
position += c.length;
}
return end();
return end(mesh);
}
private static void begin(int count){
mesh = new Mesh(true, count, 0,
VertexAttribute.position3,
VertexAttribute.normal,
VertexAttribute.color
private static Mesh begin(int vertices, int indices, boolean normal, boolean emissive){
Seq<VertexAttribute> attributes = Seq.with(
VertexAttribute.position3
);
if(normal){
//only GL30 supports GL_INT_2_10_10_10_REV
attributes.add(gl30 ? VertexAttribute.packedNormal : VertexAttribute.normal);
}
attributes.add(VertexAttribute.color);
if(emissive){
attributes.add(new VertexAttribute(4, GL20.GL_UNSIGNED_BYTE, true, "a_emissive"));
}
Mesh mesh = new Mesh(true, vertices, indices, attributes.toArray(VertexAttribute.class));
mesh.getVerticesBuffer().limit(mesh.getVerticesBuffer().capacity());
mesh.getVerticesBuffer().position(0);
if(indices > 0){
mesh.getIndicesBuffer().limit(mesh.getIndicesBuffer().capacity());
mesh.getIndicesBuffer().position(0);
}
return mesh;
}
private static Mesh end(){
Mesh last = mesh;
last.getVerticesBuffer().limit(last.getVerticesBuffer().position());
mesh = null;
return last;
private static Mesh end(Mesh mesh){
mesh.getVerticesBuffer().limit(mesh.getVerticesBuffer().position());
if(mesh.getNumIndices() > 0){
mesh.getIndicesBuffer().limit(mesh.getIndicesBuffer().position());
}
return mesh;
}
private static Vec3 normal(Vec3 v1, Vec3 v2, Vec3 v3){
return v4.set(v2).sub(v1).crs(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z).nor();
private static Vec3 normal(Vec3 v1, Vec3 v2, Vec3 v3, Vec3 out){
return out.set(v2).sub(v1).crs(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z).nor();
}
private static void verts(Vec3 a, Vec3 b, Vec3 c, Vec3 normal, Color color){
vert(a, normal, color);
vert(b, normal, color);
vert(c, normal, color);
private static void normal(float v1x, float v1y, float v1z, float v2x, float v2y, float v2z, float v3x, float v3y, float v3z, Vec3 out){
float
x = v2x - v1x,
y = v2y - v1y,
z = v2z - v1z,
vx = v3x - v1x,
vy = v3y - v1y,
vz = v3z - v1z;
float
cx = y * vz - z * vy,
cy = z * vx - x * vz,
cz = x * vy - y * vx;
out.set(cx, cy, cz).nor();
}
private static void vert(Vec3 a, Vec3 normal, Color color){
floats[0] = a.x;
floats[1] = a.y;
floats[2] = a.z;
private static void vert(Mesh mesh, float[] floats, float x, float y, float z, Vec3 normal, float color, float emissive){
floats[0] = x;
floats[1] = y;
floats[2] = z;
floats[3] = normal.x;
floats[4] = normal.y;
floats[5] = normal.z;
if(gl30){
floats[3] = packNormals(normal.x, normal.y, normal.z);
floats[4] = color;
if(floats.length > 5) floats[5] = emissive;
}else{
floats[3] = normal.x;
floats[4] = normal.x;
floats[5] = normal.x;
floats[6] = color;
if(floats.length > 7) floats[7] = emissive;
}
floats[6] = color.toFloatBits();
mesh.getVerticesBuffer().put(floats);
}
private static float packNormals(float x, float y, float z){
int xs = x < -1f/512f ? 1 : 0;
int ys = y < -1f/512f ? 1 : 0;
int zs = z < -1f/512f ? 1 : 0;
int vi =
zs << 29 | ((int)(z * 511 + (zs << 9)) & 511) << 20 |
ys << 19 | ((int)(y * 511 + (ys << 9)) & 511) << 10 |
xs << 9 | ((int)(x * 511 + (xs << 9)) & 511);
return Float.intBitsToFloat(vi);
}
}
@@ -15,4 +15,11 @@ public class MultiMesh implements GenericMesh{
v.render(params, projection, transform);
}
}
@Override
public void dispose(){
for(var mesh : meshes){
mesh.dispose();
}
}
}
@@ -18,10 +18,10 @@ public class NoiseMesh extends HexMesh{
}
@Override
public Color getColor(Vec3 position){
return color;
public void getColor(Vec3 position, Color out){
out.set(color);
}
}, divisions, false, radius, 0.2f);
}, divisions, radius, 0.2f);
}
/** Two-color variant. */
@@ -35,9 +35,9 @@ public class NoiseMesh extends HexMesh{
}
@Override
public Color getColor(Vec3 position){
return Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1;
public void getColor(Vec3 position, Color out){
out.set(Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1);
}
}, divisions, false, radius, 0.2f);
}, divisions, radius, 0.2f);
}
}
@@ -47,7 +47,7 @@ public class PlanetGrid{
}
}
public static PlanetGrid create(int size){
public static synchronized PlanetGrid create(int size){
//cache grids between calls, since only ~5 different grids total are needed
if(size < cache.length && cache[size] != null){
return cache[size];
@@ -240,6 +240,14 @@ public class PlanetGrid{
corners = new Corner[edgeCount];
edges = new Edge[edgeCount];
}
@Override
public String toString(){
return "Ptile{" +
"id=" + id +
" " + v +
'}';
}
}
public static class Corner{
@@ -26,6 +26,8 @@ public abstract class PlanetMesh implements GenericMesh{
@Override
public void render(PlanetParams params, Mat3D projection, Mat3D transform){
if(mesh.isDisposed()) return;
preRender(params);
shader.bind();
shader.setUniformMatrix4("u_proj", projection.val);
@@ -33,4 +35,9 @@ public abstract class PlanetMesh implements GenericMesh{
shader.apply();
mesh.render(shader, Gl.triangles);
}
@Override
public void dispose(){
mesh.dispose();
}
}
@@ -31,7 +31,7 @@ public class PlanetRenderer implements Disposable{
setThreshold(0.8f);
blurPasses = 6;
}};
public final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, false, 1.5f);
public final Mesh atmosphere = MeshBuilder.buildHex(Color.white, 2, 1.5f);
//seed: 8kmfuix03fw
public final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/"));
+2 -3
View File
@@ -3,7 +3,6 @@ package mindustry.graphics.g3d;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -19,9 +18,9 @@ public class SunMesh extends HexMesh{
}
@Override
public Color getColor(Vec3 position){
public void getColor(Vec3 position, Color out){
double height = Math.pow(Simplex.noise3d(0, octaves, persistence, scl, position.x, position.y, position.z), pow) * mag;
return Tmp.c1.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale);
out.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale);
}
}, divisions, Shaders.unlit);
}
+26 -4
View File
@@ -56,6 +56,11 @@ public class DesktopInput extends InputHandler{
/** Time of most recent control group selection */
public long lastCtrlGroupSelectMillis;
/** Time of most recent payload pickup/drop key press*/
public long lastPayloadKeyTapMillis;
/** Time of most recent payload pickup/drop key hold*/
public long lastPayloadKeyHoldMillis;
private float buildPlanMouseOffsetX, buildPlanMouseOffsetY;
private boolean changedCursor;
@@ -425,10 +430,6 @@ public class DesktopInput extends InputHandler{
}
}
if(Core.input.keyRelease(Binding.select)){
player.shooting = false;
}
if(state.isGame() && !scene.hasDialog() && !scene.hasField()){
if(Core.input.keyTap(Binding.minimap)) ui.minimapfrag.toggle();
if(Core.input.keyTap(Binding.planetMap) && state.isCampaign()) ui.planet.toggle();
@@ -555,6 +556,10 @@ public class DesktopInput extends InputHandler{
changedCursor = false;
}
}
if(Core.input.keyRelease(Binding.select)){
player.shooting = false;
}
}
@Override
@@ -728,6 +733,7 @@ public class DesktopInput extends InputHandler{
mode = none;
}else if(!selectPlans.isEmpty()){
flushPlans(selectPlans);
movedPlan = true;
}else if(isPlacing()){
selectX = cursorX;
selectY = cursorY;
@@ -970,10 +976,26 @@ public class DesktopInput extends InputHandler{
if(unit instanceof Payloadc){
if(Core.input.keyTap(Binding.pickupCargo)){
tryPickupPayload();
lastPayloadKeyTapMillis = Time.millis();
}
if(Core.input.keyDown(Binding.pickupCargo)
&& Time.timeSinceMillis(lastPayloadKeyHoldMillis) > 20
&& Time.timeSinceMillis(lastPayloadKeyTapMillis) > 200){
tryPickupPayload();
lastPayloadKeyHoldMillis = Time.millis();
}
if(Core.input.keyTap(Binding.dropCargo)){
tryDropPayload();
lastPayloadKeyTapMillis = Time.millis();
}
if(Core.input.keyDown(Binding.dropCargo)
&& Time.timeSinceMillis(lastPayloadKeyHoldMillis) > 20
&& Time.timeSinceMillis(lastPayloadKeyTapMillis) > 200){
tryDropPayload();
lastPayloadKeyHoldMillis = Time.millis();
}
}
}
+9 -9
View File
@@ -1331,9 +1331,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
plans.each(plan -> {
if(plan.breaking) return;
float off = plan.block.size % 2 == 0 ? -0.5f : 0f;
plan.pointConfig(p -> {
int cx = p.x, cy = p.y;
int lx = cx;
float cx = p.x + off, cy = p.y + off;
float lx = cx;
if(direction >= 0){
cx = -cy;
@@ -1342,7 +1344,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
cx = cy;
cy = -lx;
}
p.set(cx, cy);
p.set(Mathf.floor(cx - off), Mathf.floor(cy - off));
});
//rotate actual plan, centered on its multiblock position
@@ -1376,14 +1378,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
plan.pointConfig(p -> {
int corigin = x ? plan.originalWidth/2 : plan.originalHeight/2;
int nvalue = -(x ? p.x : p.y);
if(x){
plan.originalX = -(plan.originalX - corigin) + corigin;
p.x = nvalue;
if(plan.block.size % 2 == 0) p.x --;
p.x = -p.x;
}else{
plan.originalY = -(plan.originalY - corigin) + corigin;
p.y = nvalue;
if(plan.block.size % 2 == 0) p.y --;
p.y = -p.y;
}
});
+2 -2
View File
@@ -98,7 +98,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}else{
Building tile = world.buildWorld(x, y);
if((tile != null && player.team() != tile.team && (tile.team != Team.derelict || state.rules.coreCapture)) || (tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged())){
if((tile != null && (player.team() != tile.team && (tile.team != Team.derelict || state.rules.coreCapture)) && player.unit().type.canAttack) || (tile != null && player.unit().type.canHeal && tile.team == player.team() && tile.damaged())){
player.unit().mineTile = null;
target = tile;
}
@@ -1078,7 +1078,7 @@ public class MobileInput extends InputHandler implements GestureListener{
//this may be a bad idea, aiming for a point far in front could work better, test it out
unit.aim(Core.input.mouseWorldX(), Core.input.mouseWorldY());
}else{
Vec2 intercept = Predict.intercept(unit, target, bulletSpeed);
Vec2 intercept = player.unit().type.weapons.contains(w -> w.predictTarget) ? Predict.intercept(unit, target, bulletSpeed) : Tmp.v1.set(target);
player.mouseX = intercept.x;
player.mouseY = intercept.y;
+4 -2
View File
@@ -172,7 +172,7 @@ public class MapIO{
for(Tile tile : tiles){
//while synthetic blocks are possible, most of their data is lost, so in order to avoid questions like
//"why is there air under my drill" and "why are all my conveyors facing right", they are disabled
int color = tile.block().hasColor && !tile.block().synthetic() ? tile.block().mapColor.rgba() : tile.floor().mapColor.rgba();
int color = tile.block().hasColor && !tile.block().hasBuilding() ? tile.block().mapColor.rgba() : tile.floor().mapColor.rgba();
pix.set(tile.x, tiles.height - 1 - tile.y, color);
}
return pix;
@@ -183,6 +183,9 @@ public class MapIO{
int color = pixmap.get(tile.x, pixmap.height - 1 - tile.y);
Block block = ColorMapper.get(color);
//ignore buildings; reading images is only intended for environment tiles
if(block.hasBuilding()) continue;
if(block.isOverlay()){
tile.setOverlay(block.asFloor());
}else if(block.isFloor()){
@@ -194,7 +197,6 @@ public class MapIO{
}
}
//guess at floors by grabbing a random adjacent floor
for(Tile tile : tiles){
//default to stone floor
if(tile.floor() == Blocks.air){
+1 -1
View File
@@ -1104,7 +1104,7 @@ public class TypeIO{
}
}
/** Represents a unit that has not been resolved yet. TODO unimplemented / unused*/
/** Represents a unit that has not been resolved yet. */
public static class UnitBox implements Boxed<Unit>{
public int id;
+1
View File
@@ -41,6 +41,7 @@ public enum LAccess{
displayWidth,
displayHeight,
bufferUsage,
operations,
size,
solid,
dead,
+5
View File
@@ -25,6 +25,7 @@ import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.logic.CanvasBlock.*;
import mindustry.world.blocks.logic.LogicBlock.*;
import mindustry.world.blocks.logic.LogicDisplay.*;
import mindustry.world.blocks.logic.MemoryBlock.*;
@@ -581,6 +582,8 @@ public class LExecutor{
}
}else if(target.isobj && target.objval instanceof CharSequence str){
output.setnum(address < 0 || address >= str.length() ? Double.NaN : (int)str.charAt(address));
}else if(from instanceof CanvasBuild canvas && (exec.privileged || (from.team == exec.team))){
output.setnum(canvas.getPixel(address));
}
}
}
@@ -611,6 +614,8 @@ public class LExecutor{
toVar.numval = value.numval;
toVar.isobj = value.isobj;
}
}else if(from instanceof CanvasBuild canvas && (exec.privileged || (from.team == exec.team))){
canvas.setPixel(address, value.numi());
}
}
}
@@ -11,6 +11,7 @@ public enum LMarkerControl{
color("color"),
radius("radius"),
stroke("stroke"),
outline("outline"),
rotation("rotation"),
shape("sides", "fill", "outline"),
arc("start", "end"),
+1 -1
View File
@@ -221,7 +221,7 @@ public class LogicDialog extends BaseDialog{
update(() -> setColor(typeColor(s, color)));
}}, new Label(() -> " " + typeName(s) + " "){{
setStyle(Styles.outlineLabel);
}});
}}).minWidth(120f);
t.row();
+2
View File
@@ -11,6 +11,7 @@ public enum LogicOp{
div("/", (a, b) -> a / b),
idiv("//", (a, b) -> Math.floor(a / b)),
mod("%", (a, b) -> a % b),
emod("%%", (a, b) -> ((a % b) + b) % b),
pow("^", Math::pow),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0, (a, b) -> Structs.eq(a, b) ? 1 : 0),
@@ -24,6 +25,7 @@ public enum LogicOp{
shl("<<", (a, b) -> (long)a << (long)b),
shr(">>", (a, b) -> (long)a >> (long)b),
ushr(">>>", (a, b) -> (long)a >>> (long)b),
or("or", (a, b) -> (long)a | (long)b),
and("b-and", (a, b) -> (long)a & (long)b),
xor("xor", (a, b) -> (long)a ^ (long)b),
@@ -0,0 +1,73 @@
package mindustry.maps;
import arc.struct.*;
import arc.util.*;
import mindustry.type.*;
/** Class for temporarily (?) storing links to map submissions on Discord. */
public class SectorSubmissions{
private static IntMap<String> hiddenMap = new IntMap<>();
static{
//autogenerated
hiddenMap.put(0, "https://discord.com/channels/391020510269669376/1379926780860698784");
hiddenMap.put(6, "https://discord.com/channels/391020510269669376/1379926782966497322");
hiddenMap.put(13, "https://discord.com/channels/391020510269669376/1379926785164312810");
hiddenMap.put(16, "https://discord.com/channels/391020510269669376/1379926788280680579");
hiddenMap.put(19, "https://discord.com/channels/391020510269669376/1379926792479183019");
hiddenMap.put(20, "https://discord.com/channels/391020510269669376/1379926794114961634");
hiddenMap.put(24, "https://discord.com/channels/391020510269669376/1379926797042581716");
hiddenMap.put(27, "https://discord.com/channels/391020510269669376/1379926798833287289");
hiddenMap.put(30, "https://discord.com/channels/391020510269669376/1379926800854945823");
hiddenMap.put(47, "https://discord.com/channels/391020510269669376/1379926802591645820");
hiddenMap.put(55, "https://discord.com/channels/391020510269669376/1379926823277695189");
hiddenMap.put(66, "https://discord.com/channels/391020510269669376/1379926825941078128");
hiddenMap.put(67, "https://discord.com/channels/391020510269669376/1379926828696866898");
hiddenMap.put(69, "https://discord.com/channels/391020510269669376/1379926831326822610");
hiddenMap.put(76, "https://discord.com/channels/391020510269669376/1379926833411391580");
hiddenMap.put(92, "https://discord.com/channels/391020510269669376/1379926835621527615");
hiddenMap.put(94, "https://discord.com/channels/391020510269669376/1379926838079393802");
hiddenMap.put(103, "https://discord.com/channels/391020510269669376/1379926839559979030");
hiddenMap.put(111, "https://discord.com/channels/391020510269669376/1379926842659569864");
hiddenMap.put(116, "https://discord.com/channels/391020510269669376/1379926845058711734");
hiddenMap.put(127, "https://discord.com/channels/391020510269669376/1379926869465632829");
hiddenMap.put(133, "https://discord.com/channels/391020510269669376/1379926871227240770");
hiddenMap.put(138, "https://discord.com/channels/391020510269669376/1379926873152164004");
hiddenMap.put(150, "https://discord.com/channels/391020510269669376/1379926876457537547");
hiddenMap.put(157, "https://discord.com/channels/391020510269669376/1379926879502598155");
hiddenMap.put(161, "https://discord.com/channels/391020510269669376/1379926882203730024");
hiddenMap.put(162, "https://discord.com/channels/391020510269669376/1379926884606808247");
hiddenMap.put(176, "https://discord.com/channels/391020510269669376/1379926887203213353");
hiddenMap.put(180, "https://discord.com/channels/391020510269669376/1379926889648619580");
hiddenMap.put(185, "https://discord.com/channels/391020510269669376/1379926892181983283");
hiddenMap.put(191, "https://discord.com/channels/391020510269669376/1379926912004001914");
hiddenMap.put(192, "https://discord.com/channels/391020510269669376/1379926914122256449");
hiddenMap.put(197, "https://discord.com/channels/391020510269669376/1379926916911599676");
hiddenMap.put(200, "https://discord.com/channels/391020510269669376/1379926918429806755");
hiddenMap.put(204, "https://discord.com/channels/391020510269669376/1379926921130807447");
hiddenMap.put(207, "https://discord.com/channels/391020510269669376/1379926923370827827");
hiddenMap.put(225, "https://discord.com/channels/391020510269669376/1379926925719376152");
hiddenMap.put(230, "https://discord.com/channels/391020510269669376/1379926927585841163");
hiddenMap.put(237, "https://discord.com/channels/391020510269669376/1379926929636851812");
hiddenMap.put(242, "https://discord.com/channels/391020510269669376/1379926931923013843");
hiddenMap.put(243, "https://discord.com/channels/391020510269669376/1379926955423694978");
hiddenMap.put(244, "https://discord.com/channels/391020510269669376/1379926957738954762");
hiddenMap.put(245, "https://discord.com/channels/391020510269669376/1379926971286290584");
hiddenMap.put(246, "https://discord.com/channels/391020510269669376/1379926973454745600");
hiddenMap.put(247, "https://discord.com/channels/391020510269669376/1379926976361533752");
hiddenMap.put(248, "https://discord.com/channels/391020510269669376/1379926979129774151");
hiddenMap.put(251, "https://discord.com/channels/391020510269669376/1379928042637361382");
hiddenMap.put(254, "https://discord.com/channels/391020510269669376/1379928045577703424");
hiddenMap.put(259, "https://discord.com/channels/391020510269669376/1379928048245280871");
hiddenMap.put(263, "https://discord.com/channels/391020510269669376/1379928050010951694");
hiddenMap.put(265, "https://discord.com/channels/391020510269669376/1379928052921929891");
}
/** @return the link to the Discord discussion thread of the specified hidden sector submission. */
public static @Nullable String getSectorThread(Sector sector){
if(sector.generateEnemyBase){
return hiddenMap.get(sector.id);
}
return null;
}
}
@@ -1,7 +1,5 @@
package mindustry.maps.generators;
import arc.graphics.*;
import arc.math.geom.*;
import mindustry.game.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -9,16 +7,6 @@ import mindustry.world.*;
/** A planet generator that provides no weather, height, color or bases. Override generate().*/
public class BlankPlanetGenerator extends PlanetGenerator{
@Override
public float getHeight(Vec3 position){
return 0;
}
@Override
public Color getColor(Vec3 position){
return Color.white;
}
@Override
public void addWeather(Sector sector, Rules rules){
@@ -25,11 +25,22 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe
protected @Nullable Sector sector;
/** Should generate sector bases for a planet. */
public void generateSector(Sector sector){
}
public void onSectorCaptured(Sector sector){
}
public void onSectorLost(Sector sector){
}
public void beforeSaveWrite(Sector sector){
}
public void getLockedText(Sector hovered, StringBuilder out){
out.append("[gray]").append(Iconc.lock).append(" ").append(Core.bundle.get("locked"));
}
@@ -39,20 +39,17 @@ public class ErekirPlanetGenerator extends PlanetGenerator{
}
@Override
public Color getColor(Vec3 position){
public void getColor(Vec3 position, Color out){
Block block = getBlock(position);
//more obvious color
if(block == Blocks.crystallineStone) block = Blocks.crystalFloor;
//TODO this might be too green
//if(block == Blocks.beryllicStone) block = Blocks.arkyicStone;
return Tmp.c1.set(block.mapColor).a(1f - block.albedo);
out.set(block.mapColor).a(1f - block.albedo);
}
@Override
public float getSizeScl(){
//TODO should sectors be 600, or 500 blocks?
return 2000 * 1.07f * 6f / 5f;
}
@@ -65,17 +62,17 @@ public class ErekirPlanetGenerator extends PlanetGenerator{
}
Block getBlock(Vec3 position){
float ice = rawTemp(position);
Tmp.v32.set(position);
float px = position.x, py = position.y, pz = position.z;
float ice = rawTemp(position);
float height = rawHeight(position);
Tmp.v31.set(position);
height *= 1.2f;
height = Mathf.clamp(height);
Block result = terrain[Mathf.clamp((int)(height * terrain.length), 0, terrain.length - 1)];
if(ice < 0.3 + Math.abs(Ridged.noise3d(seed + crystalSeed, position.x + 4f, position.y + 8f, position.z + 1f, crystalOct, crystalScl)) * crystalMag){
if(ice < 0.3 + Math.abs(Ridged.noise3d(seed + crystalSeed, px + 4f, py + 8f, pz + 1f, crystalOct, crystalScl)) * crystalMag){
return Blocks.crystallineStone;
}
@@ -86,11 +83,9 @@ public class ErekirPlanetGenerator extends PlanetGenerator{
}
}
position = Tmp.v32;
//TODO tweak this to make it more natural
//TODO edge distortion?
if(ice < redThresh - noArkThresh && Ridged.noise3d(seed + arkSeed, position.x + 2f, position.y + 8f, position.z + 1f, arkOct, arkScl) > arkThresh){
if(ice < redThresh - noArkThresh && Ridged.noise3d(seed + arkSeed, px + 2f, py + 8f, pz + 1f, arkOct, arkScl) > arkThresh){
//TODO arkyic in middle
result = Blocks.beryllicStone;
}
@@ -20,13 +20,16 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
public class SerpuloPlanetGenerator extends PlanetGenerator{
//alternate, less direct generation (wip)
public static boolean alt = false;
//alternate, less direct generation
public static boolean indirectPaths = false;
//random water patches
public static boolean genLakes = false;
BaseGenerator basegen = new BaseGenerator();
float heightYOffset = 42.7f;
float scl = 5f;
float waterOffset = 0.05f;
boolean genLakes = false;
float waterOffset = 0.04f;
float heightScl = 1.01f;
Block[][] arr =
{
@@ -58,10 +61,30 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
);
float water = 2f / arr[0].length;
Vec3 basePos = new Vec3(0.9341721, 0.0, 0.3568221);
float rawHeight(Vec3 position){
position = Tmp.v33.set(position).scl(scl);
return (Mathf.pow(Simplex.noise3d(seed, 7, 0.5f, 1f/3f, position.x, position.y, position.z), 2.3f) + waterOffset) / (1f + waterOffset);
return (Mathf.pow(Simplex.noise3d(seed, 7, 0.5f, 1f/3f, position.x * scl, position.y * scl + heightYOffset, position.z * scl) * heightScl, 2.3f) + waterOffset) / (1f + waterOffset);
}
@Override
public void onSectorCaptured(Sector sector){
sector.planet.reloadMeshAsync();
}
@Override
public void onSectorLost(Sector sector){
sector.planet.reloadMeshAsync();
}
@Override
public void beforeSaveWrite(Sector sector){
sector.planet.reloadMeshAsync();
}
@Override
public boolean isEmissive(){
return true;
}
@Override
@@ -86,16 +109,57 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
}
@Override
public Color getColor(Vec3 position){
public void getColor(Vec3 position, Color out){
Block block = getBlock(position);
//replace salt with sand color
if(block == Blocks.salt) return Blocks.sand.mapColor;
return Tmp.c1.set(block.mapColor).a(1f - block.albedo);
if(block == Blocks.salt) block = Blocks.sand;
out.set(block.mapColor).a(1f - block.albedo);
}
@Override
public void getEmissiveColor(Vec3 position, Color out){
float dst = 999f, captureDst = 999f, lightScl = 0f;
Object[] sectors = Planets.serpulo.sectors.items;
int size = Planets.serpulo.sectors.size;
for(int i = 0; i < size; i ++){
var sector = (Sector)sectors[i];
if(sector.hasEnemyBase() && !sector.isCaptured()){
dst = Math.min(dst, position.dst(sector.tile.v) - (sector.preset != null ? sector.preset.difficulty/10f * 0.03f - 0.03f : 0f));
}else if(sector.hasBase()){
float cdst = position.dst(sector.tile.v);
if(cdst < captureDst){
captureDst = cdst;
lightScl = sector.info.lightCoverage;
}
}
}
lightScl = Math.min(lightScl / 50000f, 1.3f);
if(lightScl < 1f) lightScl = Interp.pow5Out.apply(lightScl);
float freq = 0.05f;
if(position.dst(basePos) < 0.55f ?
dst*metalDstScl + Simplex.noise3d(seed + 1, 3, 0.4, 5.5f, position.x, position.y + 200f, position.z)*0.08f + ((basePos.dst(position) + 0.00f) % freq < freq/2f ? 1f : 0f) * 0.07f < 0.08f/* || dst <= 0.0001f*/ :
dst*metalDstScl + Simplex.noise3d(seed, 3, 0.4, 9f, position.x, position.y + 370f, position.z)*0.06f < 0.045){
out.set(Team.crux.color)
.mul(0.8f + Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 99f, position.z) * 0.4f)
.lerp(Team.sharded.color, 0.2f*Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 999f, position.z)).toFloatBits();
}else if(captureDst*metalDstScl + Simplex.noise3d(seed, 3, 0.4, 9f, position.x, position.y + 600f, position.z)*0.07f < 0.05 * lightScl){
out.set(Team.sharded.color).mul(0.7f + Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 99f, position.z) * 0.4f)
.lerp(Team.crux.color, 0.3f*Simplex.noise3d(seed, 1, 1, 9f, position.x, position.y + 999f, position.z)).toFloatBits();
}
}
@Override
public void genTile(Vec3 position, TileGen tile){
tile.floor = getBlock(position);
if(tile.floor == Blocks.darkPanel6) tile.floor = Blocks.darkPanel3;
tile.block = tile.floor.asFloor().wall;
if(Ridged.noise3d(seed + 1, position.x, position.y, position.z, 2, 22) > 0.31){
@@ -103,23 +167,46 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
}
}
static double metalDstScl = 0.25;
Block getBlock(Vec3 position){
float height = rawHeight(position);
Tmp.v31.set(position);
position = Tmp.v33.set(position).scl(scl);
float px = position.x * scl, py = position.y * scl, pz = position.z * scl;
float rad = scl;
float temp = Mathf.clamp(Math.abs(position.y * 2f) / (rad));
float tnoise = Simplex.noise3d(seed, 7, 0.56, 1f/3f, position.x, position.y + 999f, position.z);
float temp = Mathf.clamp(Math.abs(py * 2f) / (rad));
float tnoise = Simplex.noise3d(seed, 7, 0.56, 1f/3f, px, py + 999f - 0.1f, pz);
temp = Mathf.lerp(temp, tnoise, 0.5f);
height *= 1.2f;
height = Mathf.clamp(height);
float tar = Simplex.noise3d(seed, 4, 0.55f, 1f/2f, position.x, position.y + 999f, position.z) * 0.3f + Tmp.v31.dst(0, 0, 1f) * 0.2f;
float tar = Simplex.noise3d(seed, 4, 0.55f, 1f/2f, px, py + 999f, pz) * 0.3f + position.dst(0, 0, 1f) * 0.2f;
Block res = arr[Mathf.clamp((int)(temp * arr.length), 0, arr[0].length - 1)][Mathf.clamp((int)(height * arr[0].length), 0, arr[0].length - 1)];
if(tar > 0.5f){
return tars.get(res, res);
}else{
if(position.within(basePos, 0.65f)){
float dst = 999f;
Object[] sectors = Planets.serpulo.sectors.items;
int size = Planets.serpulo.sectors.size;
for(int i = 0; i < size; i ++){
var sector = (Sector)sectors[i];
if(sector.hasEnemyBase()){
dst = Math.min(dst, position.dst(sector.tile.v));
}
}
float freq = 0.05f, freq2 = 0.07f;
if(dst*0.85f + Simplex.noise3d(seed, 3, 0.4, 5.5f, position.x, position.y + 200f, position.z)*0.015f + ((basePos.dst(position) + 0.00f) % freq < freq/2f ? 1f : 0f) * 0.07f < 0.15f){
return ((basePos.dst(position) + 0.01f) % freq2 < freq2*0.65f) ? Blocks.metalFloor : Blocks.darkPanel6;
}
}
return res;
}
}
@@ -156,7 +243,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
Vec2 midpoint = Tmp.v1.set(to.x, to.y).add(x, y).scl(0.5f);
rand.nextFloat();
if(alt){
if(indirectPaths){
midpoint.add(Tmp.v2.set(1, 0f).setAngle(Angles.angle(to.x, to.y, x, y) + 90f * (rand.chance(0.5) ? 1f : -1f)).scl(Tmp.v1.dst(x, y) * 2f));
}else{
//add randomized offset to avoid straight lines
@@ -14,7 +14,7 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class TantrosPlanetGenerator extends PlanetGenerator{
Color c1 = Color.valueOf("5057a6"), c2 = Color.valueOf("272766"), out = new Color();
Color c1 = Color.valueOf("5057a6"), c2 = Color.valueOf("272766");
Block[][] arr = {
{Blocks.redmat, Blocks.redmat, Blocks.darksand, Blocks.bluemat, Blocks.bluemat}
@@ -30,9 +30,9 @@ public class TantrosPlanetGenerator extends PlanetGenerator{
}
@Override
public Color getColor(Vec3 position){
public void getColor(Vec3 position, Color out){
float depth = Simplex.noise3d(seed, 2, 0.56, 1.7f, position.x, position.y, position.z) / 2f;
return c1.write(out).lerp(c2, Mathf.clamp(Mathf.round(depth, 0.15f))).a(0.2f);
out.set(c1).lerp(c2, Mathf.clamp(Mathf.round(depth, 0.15f))).a(1f - 0.2f).toFloatBits();
}
@Override
+22 -6
View File
@@ -413,11 +413,22 @@ public class Mods implements Loadable{
/** Removes a mod file and marks it for requiring a restart. */
public void removeMod(LoadedMod mod){
if(!android && mod.loader != null){
try{
ClassLoaderCloser.close(mod.loader);
}catch(Exception e){
Log.err(e);
boolean deleted = true;
if(mod.loader != null){
if(android){
//Try to remove cache for Android 14 security problem
Fi cacheDir = new Fi(Core.files.getCachePath()).child("mods");
Fi modCacheDir = cacheDir.child(mod.file.nameWithoutExtension());
if(modCacheDir.exists()){
deleted = modCacheDir.deleteDirectory();
}
}else{
try{
ClassLoaderCloser.close(mod.loader);
}catch(Exception e){
Log.err(e);
}
}
}
@@ -425,7 +436,7 @@ public class Mods implements Loadable{
mod.root.delete();
}
boolean deleted = mod.file.isDirectory() ? mod.file.deleteDirectory() : mod.file.delete();
deleted &= mod.file.isDirectory() ? mod.file.deleteDirectory() : mod.file.delete();
if(!deleted){
ui.showErrorMessage("@mod.delete.error");
@@ -1112,6 +1123,11 @@ public class Mods implements Loadable{
//close the classloader for jar mods
if(!android){
ClassLoaderCloser.close(other.loader);
}else if(other.loader != null){
//Try to remove cache for Android 14 security problem
Fi cacheDir = new Fi(Core.files.getCachePath()).child("mods");
Fi modCacheDir = cacheDir.child(other.file.nameWithoutExtension());
modCacheDir.deleteDirectory();
}
//close zip file
@@ -91,6 +91,10 @@ public class Administration{
dosBlacklist.add(address);
}
public synchronized void unBlacklistDos(String address){
dosBlacklist.remove(address);
}
public synchronized boolean isDosBlacklisted(String address){
return dosBlacklist.contains(address);
}
@@ -113,6 +113,8 @@ public class ArcNetProvider implements NetProvider{
//kill connections above the limit to prevent spam
if((playerLimitCache > 0 && server.getConnections().length > playerLimitCache) || netServer.admins.isDosBlacklisted(ip)){
Log.info("Closing connection @ - IP marked as a potential DOS attack.", ip);
connection.close(DcReason.closed);
return;
}
+3 -1
View File
@@ -28,6 +28,8 @@ public class CrashHandler{
report += "Report this at " + Vars.reportIssueURL + "\n\n";
}
var enabledMods = mods == null ? null : mods.list().select(m -> m.shouldBeEnabled() && m.isSupported());
return report
+ "Version: " + Version.combined() + (Version.buildDate.equals("unknown") ? "" : " (Built " + Version.buildDate + ")") + (Vars.headless ? " (Server)" : "") + "\n"
+ "Date: " + new SimpleDateFormat("MMMM d, yyyy HH:mm:ss a", Locale.getDefault()).format(new Date()) + "\n"
@@ -37,7 +39,7 @@ public class CrashHandler{
+ "Runtime Available Memory: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "mb\n"
+ "Cores: " + OS.cores + "\n"
+ (cause == null ? "" : "Likely Cause: " + cause.meta.displayName + " (" + cause.name + " v" + cause.meta.version + ")\n")
+ (mods == null ? "<no mod init>" : "Mods: " + (!mods.list().contains(LoadedMod::shouldBeEnabled) ? "none (vanilla)" : mods.list().select(LoadedMod::shouldBeEnabled).toString(", ", mod -> mod.name + ":" + mod.meta.version)))
+ (enabledMods == null ? "<no mod init>" : "Mods: " + (enabledMods.isEmpty() ? "none (vanilla)" : enabledMods.toString(", ", mod -> mod.name + ":" + mod.meta.version)))
+ "\n\n" + error;
}
@@ -67,6 +67,7 @@ public enum Achievement{
have10mItems(SStat.totalCampaignItems, 10_000_000),
killEclipseDuo,
killMassDriver,
completeErekir,
completeSerpulo,
@@ -5,6 +5,7 @@ import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.bullet.*;
import mindustry.game.EventType.*;
import mindustry.game.SectorInfo.*;
import mindustry.gen.*;
@@ -394,6 +395,10 @@ public class GameService{
if(e.unit.type == UnitTypes.eclipse && e.bullet.owner instanceof TurretBuild turret && turret.block == Blocks.duo){
killEclipseDuo.complete();
}
if(e.bullet.type instanceof MassDriverBolt){
killMassDriver.complete();
}
}
});
+21 -3
View File
@@ -11,7 +11,6 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.ctype.*;
@@ -348,11 +347,31 @@ public class Planet extends UnlockableContent{
return mat.setToTranslation(position).rotate(Vec3.Y, getRotation());
}
/** Regenerates the planet mesh. For debugging only. */
/** Regenerates the planet mesh. */
public void reloadMesh(){
if(headless) return;
if(mesh != null){
mesh.dispose();
}
mesh = meshLoader.get();
}
public void reloadMeshAsync(){
if(headless) return;
mainExecutor.submit(() -> {
var newMesh = meshLoader.get();
Core.app.post(() -> {
if(mesh != null){
mesh.dispose();
}
mesh = newMesh;
});
});
}
@Override
public void load(){
super.load();
@@ -383,7 +402,6 @@ public class Planet extends UnlockableContent{
}
if(generator != null){
Noise.setSeed(sectorSeed < 0 ? id + 1 : sectorSeed);
for(Sector sector : sectors){
generator.generateSector(sector);
+1 -1
View File
@@ -27,7 +27,7 @@ import java.io.*;
public class Fonts{
private static final String mainFont = "fonts/font.woff";
private static final ObjectSet<String> unscaled = ObjectSet.with("iconLarge");
private static final ObjectSet<String> unscaled = ObjectSet.with("iconLarge", "logic");
private static ObjectIntMap<String> unicodeIcons = new ObjectIntMap<>();
private static IntMap<String> unicodeToName = new IntMap<>();
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
@@ -152,7 +152,6 @@ public class KeybindDialog extends Dialog{
rebindKey = name;
rebindDialog.titleTable.getCells().first().pad(4);
rebindDialog.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
@@ -365,7 +365,7 @@ public class ModsDialog extends BaseDialog{
private @Nullable String getStateDetails(LoadedMod item){
if(item.isOutdated()){
return Core.bundle.format("mod.outdated.details", item.isJava() ? minJavaModGameVersion : minModGameVersion);
return "@mod.incompatiblemod.details";
}else if(item.isBlacklisted()){
return "@mod.blacklisted.details";
}else if(!item.isSupported()){
@@ -652,6 +652,17 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
if(scene.getDialog() == PlanetDialog.this && (scene.getHoverElement() == null || !scene.getHoverElement().isDescendantOf(e -> e instanceof ScrollPane))){
scene.setScrollFocus(PlanetDialog.this);
if(debugSectorAttackEdit){
int timeShift = input.keyDown(KeyCode.rightBracket) ? 1 : input.keyDown(KeyCode.leftBracket) ? -1 : 0;
if(timeShift != 0){
universe.setSeconds(universe.secondsf() + timeShift * Time.delta * 2.5f);
}
if(input.keyTap(KeyCode.r)){
state.planet.reloadMeshAsync();
}
}
if(debugSectorAttackEdit && input.ctrl() && input.keyTap(KeyCode.s)){
try{
PlanetData data = new PlanetData();
@@ -665,10 +676,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
data.presets.put(sector.preset.name, sector.id);
}
}
Log.info("Saving sectors for @: @ presets, @ procedural attack sectors", state.planet.name, data.presets.size, attack.size);
data.attackSectors = attack.toArray();
files.local("planets/" + state.planet.name + ".json").writeString(JsonIO.write(data));
Vars.ui.showInfoFade("@editor.saved");
ui.showInfoFade("@editor.saved");
}catch(Exception e){
Log.err(e);
}
@@ -1137,7 +1149,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
}
void selectSector(Sector sector){
public void selectSector(Sector sector){
selected = sector;
updateSelected();
}
@@ -1262,7 +1274,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
if(sector.isAttacked()){
addSurvivedInfo(sector, stable, false);
}else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(Sector::hasEnemyBase)){
}else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(s -> s.hasEnemyBase() && (s.preset == null || !s.preset.requireUnlock))){
stable.add("@sectors.vulnerable");
stable.row();
}else if(!sector.hasBase() && sector.hasEnemyBase()){
@@ -1287,6 +1299,15 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
if((sector.hasBase() && mode == look) || canSelect(sector) || (sector.preset != null && sector.preset.alwaysUnlocked) || debugSelect){
if(Vars.showSectorSubmissions){
String link = SectorSubmissions.getSectorThread(sector);
if(link != null){
stable.button("@sectors.viewsubmission", Icon.link, () -> {
Core.app.openURI(link);
}).growX().height(54f).minWidth(170f).padTop(2f).row();
}
}
stable.button(
mode == select ? "@sectors.select" :
sector.isBeingPlayed() ? "@sectors.resume" :
@@ -557,7 +557,7 @@ public class SchematicsDialog extends BaseDialog{
next.pack();
float w = next.getWidth() + Scl.scl(9f);
if(w + sum >= Core.graphics.getWidth() * 0.9f){
if(w*2f + sum >= Core.graphics.getWidth() * 0.9f){
p.add(current).row();
current = new Table();
current.left();
@@ -838,8 +838,12 @@ public class HudFragment{
t.add(new SideBar(() -> player.dead() ? 0f : 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);
Boolp playerHasPayloads = () -> player.unit() instanceof Payloadc pay && !pay.payloads().isEmpty();
Floatp playerPayloadCapacityUsed = () -> player.unit() instanceof Payloadc pay ? pay.payloadUsed() / player.unit().type().payloadCapacity : 0f;
t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : playerHasPayloads.get() ? playerPayloadCapacityUsed.get() : player.unit().healthf(), () -> !(player.displayAmmo() || playerHasPayloads.get()), 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() : playerHasPayloads.get() ? Pal.items : Pal.health);
});
t.getChildren().get(1).toFront();
@@ -251,12 +251,16 @@ public class PlacementFragment{
}
if(Core.input.keyTap(Binding.blockInfo)){
var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
Block hovering = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block;
Block displayBlock = menuHoverBlock != null ? menuHoverBlock : input.block != null ? input.block : hovering;
if(displayBlock != null && displayBlock.unlockedNow()){
ui.content.show(displayBlock);
Events.fire(new BlockInfoEvent());
if(hovered() instanceof Unit unit && unit.type.unlockedNow()){
ui.content.show(unit.type());
}else{
var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
Block hovering = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block;
Block displayBlock = menuHoverBlock != null ? menuHoverBlock : input.block != null ? input.block : hovering;
if(displayBlock != null && displayBlock.unlockedNow()){
ui.content.show(displayBlock);
Events.fire(new BlockInfoEvent());
}
}
}
@@ -57,6 +57,8 @@ public class ConstructBlock extends Block{
@Remote(called = Loc.server)
public static void deconstructFinish(Tile tile, Block block, Unit builder){
if(tile == null) return;
Team team = tile.team();
if(!headless && fogControl.isVisibleTile(Vars.player.team(), tile.x, tile.y)){
block.breakEffect.at(tile.drawx(), tile.drawy(), block.size, block.mapColor);
@@ -0,0 +1,23 @@
package mindustry.world.blocks;
public class TileBitmask{
/** Autotile bitmasks for 8-directional sprites (see <a href="https://github.com/GglLfr/tile-gen">tile-gen</a>)*/
public static final int[] values = {
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20,
5, 28, 5, 28, 29, 10, 29, 23, 5, 28, 5, 28, 31, 11, 31, 32,
3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20,
2, 30, 2, 30, 9, 46, 9, 22, 2, 30, 2, 30, 14, 44, 14, 6,
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12,
5, 8, 5, 8, 29, 35, 29, 33, 5, 8, 5, 8, 31, 34, 31, 7,
3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12,
2, 1, 2, 1, 9, 45, 9, 19, 2, 1, 2, 1, 14, 18, 14, 13,
};
}
@@ -223,7 +223,7 @@ public class Accelerator extends Block{
}
public boolean canLaunch(){
return isValid() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f && !launching;
return isValid() && !net.client() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f && !launching;
}
@Override
@@ -383,21 +383,24 @@ public class LandingPad extends Block{
t.background(Styles.black6);
t.button(Icon.downOpen, Styles.clearNonei, 40f, () -> {
if(config != null && state.isCampaign()){
for(Sector sector : state.getPlanet().sectors){
if(sector.hasBase() && sector != state.getSector() && sector.info.destination != state.getSector() && sector.info.hasExport(config)){
sector.info.destination = state.getSector();
sector.saveInfo();
}
}
state.getSector().info.refreshImportRates(state.getPlanet());
if(config == null || !state.isCampaign()) return;
for(Sector sector : state.getPlanet().sectors){
if(!canRedirectExports(sector)) continue;
sector.info.destination = state.getSector();
sector.saveInfo();
}
}).disabled(b -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(s -> s.hasBase() && s.info.hasExport(config) && s.info.destination != state.getSector())))
state.getSector().info.refreshImportRates(state.getPlanet());
}).disabled(button -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(this::canRedirectExports)))
.tooltip("@sectors.redirect").get();
}).fillX().left();
}
}
private boolean canRedirectExports(Sector sector){
return sector.hasBase() && sector != state.getSector() && sector.info.hasExport(config) && sector.info.destination != state.getSector();
}
@Override
public void display(Table table){
super.display(table);
@@ -416,14 +419,13 @@ public class LandingPad extends Block{
int sources = 0;
float perSecond = 0f;
for(var s : state.getPlanet().sectors){
if(s != state.getSector() && s.hasBase() && s.info.destination == state.getSector()){
float amount = s.info.getExport(config);
if(amount > 0){
sources ++;
perSecond += s.info.getExport(config);
}
}
for(var otherSector : state.getPlanet().sectors){
if(otherSector == state.getSector() || !otherSector.hasBase() || otherSector.info.destination != state.getSector()) continue;
float amount = otherSector.info.getExport(config);
if(amount <= 0) continue;
sources ++;
perSecond += amount;
}
String str = Core.bundle.format("landing.sources", sources == 0 ? Core.bundle.get("none") : sources);
@@ -96,8 +96,7 @@ public class DirectionalUnloader extends Block{
front.handleItem(this, item);
back.items.remove(item, 1);
back.itemTaken(item);
offset ++;
offset %= itemc;
offset = item.id + 1;
break;
}
}
@@ -215,7 +215,7 @@ public class MassDriver extends Block{
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(1f - reloadCounter / reload);
if(sensor == LAccess.progress) return Mathf.clamp(1f - reloadCounter);
return super.sense(sensor);
}
@@ -299,13 +299,13 @@ public class MassDriver extends Block{
bullet.create(this, team,
x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation),
angle, -1f, bulletSpeed, bulletLifetime, data);
angle, totalUsed/2f, bulletSpeed, bulletLifetime, data);
shootEffect.at(x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), angle);
smokeEffect.at(x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), angle);
Effect.shake(shake, shake, this);
shootSound.at(tile, Mathf.random(0.9f, 1.1f));
}
@@ -15,6 +15,7 @@ import mindustry.graphics.*;
import mindustry.graphics.MultiPacker.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import java.util.*;
@@ -77,8 +78,11 @@ public class Floor extends Block{
public int blendId = -1;
/** If >0, this floor is drawn as parts of a large texture. */
public int tilingVariants = 0;
/** If true, this floor uses autotiling; variants are not supported. See https://github.com/GglLfr/tile-gen*/
public boolean autotile = false;
protected TextureRegion[][][] tilingRegions;
protected TextureRegion[] autotileRegions;
protected int tilingSize;
protected TextureRegion[][] edges;
protected Seq<Floor> blenders = new Seq<>();
@@ -104,6 +108,10 @@ public class Floor extends Block{
public void load(){
super.load();
if(autotile){
variants = 0;
}
int tsize = (int)(tilesize / Draw.scl);
if(tilingVariants > 0 && !headless){
@@ -132,6 +140,13 @@ public class Floor extends Block{
variantRegions[0] = Core.atlas.find(name);
}
if(autotile){
autotileRegions = new TextureRegion[47];
for(int i = 0; i < 47; i++){
autotileRegions[i] = Core.atlas.find(name + "-" + i);
}
}
if(Core.atlas.has(name + "-edge")){
edges = Core.atlas.find(name + "-edge").split(tsize, tsize);
}
@@ -208,6 +223,17 @@ public class Floor extends Block{
int index = Mathf.randomSeed(Point2.pack(tile.x / tilingSize, tile.y / tilingSize), 0, tilingVariants - 1);
TextureRegion[][] regions = tilingRegions[index];
Draw.rect(regions[tile.x % tilingSize][tilingSize - 1 - tile.y % tilingSize], tile.worldx(), tile.worldy());
}else if(autotile){
int bits = 0;
for(int i = 0; i < 8; i++){
Tile other = tile.nearby(Geometry.d8[i]);
if(other != null && other.floor().blendGroup == blendGroup){
bits |= (1 << i);
}
}
Draw.rect(autotileRegions[TileBitmask.values[bits]], tile.worldx(), tile.worldy());
}else{
Draw.rect(variantRegions[variant(tile.x, tile.y)], tile.worldx(), tile.worldy());
}
@@ -51,7 +51,6 @@ public class SteamVent extends Floor{
parent.drawBase(tile);
if(checkAdjacent(tile)){
Mathf.rand.setSeed(tile.pos());
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx() - tilesize, tile.worldy() - tilesize);
}
}
@@ -17,6 +17,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -35,7 +36,7 @@ public class CanvasBlock extends Block{
public @Load("@-corner1") TextureRegion corner1;
public @Load("@-corner2") TextureRegion corner2;
protected @Nullable Pixmap previewPixmap;
protected @Nullable Pixmap previewPixmap; // please use only for previews
protected @Nullable Texture previewTexture;
protected int tempBlend = 0;
@@ -49,7 +50,7 @@ public class CanvasBlock extends Block{
config(byte[].class, (CanvasBuild build, byte[] bytes) -> {
if(build.data.length == bytes.length){
build.data = bytes;
System.arraycopy(bytes, 0, build.data, 0, bytes.length);
build.updateTexture();
}
});
@@ -65,13 +66,15 @@ public class CanvasBlock extends Block{
bitsPerPixel = Mathf.log2(Mathf.nextPowerOfTwo(palette.length));
clipSize = Math.max(clipSize, size * 8 - padding);
previewPixmap = new Pixmap(canvasSize, canvasSize);
}
@Override
public void drawPlanRegion(BuildPlan plan, Eachable<BuildPlan> list){
//only draw the preview in schematics, as it lags otherwise
if(!plan.worldContext && plan.config instanceof byte[] data){
Pixmap pix = makePixmap(data);
Pixmap pix = makePixmap(data, previewPixmap);
if(previewTexture == null){
previewTexture = new Texture(pix);
@@ -123,20 +126,15 @@ public class CanvasBlock extends Block{
}
}
/** returns the same pixmap instance each time, use with care */
public Pixmap makePixmap(byte[] data){
if(previewPixmap == null){
previewPixmap = new Pixmap(canvasSize, canvasSize);
}
public Pixmap makePixmap(byte[] data, Pixmap target){
int bpp = bitsPerPixel;
int pixels = canvasSize * canvasSize;
for(int i = 0; i < pixels; i++){
int bitOffset = i * bpp;
int pal = getByte(data, bitOffset);
previewPixmap.set(i % canvasSize, i / canvasSize, palette[pal]);
target.set(i % canvasSize, i / canvasSize, palette[pal]);
}
return previewPixmap;
return target;
}
protected int getByte(byte[] data, int bitOffset){
@@ -152,11 +150,41 @@ public class CanvasBlock extends Block{
public @Nullable Texture texture;
public byte[] data = new byte[Mathf.ceil(canvasSize * canvasSize * bitsPerPixel / 8f)];
public int blending;
protected boolean updated = false;
public void setPixel(int pos, int index){
if(pos < canvasSize * canvasSize && pos >= 0 && index >= 0 && index < palette.length){
setByte(data, pos * bitsPerPixel, index);
updated = true;
}
}
public void setPixel(int x, int y, int index){
if(x >= 0 && y >= 0 && x < canvasSize && y < canvasSize && index >= 0 && index < palette.length){
setByte(data, (y * canvasSize + x) * bitsPerPixel, index);
updated = true;
}
}
public double getPixel(int pos){
if(pos >= 0 && pos < canvasSize * canvasSize){
return getByte(data, pos * bitsPerPixel);
}
return Double.NaN;
}
public int getPixel(int x, int y){
if(x >= 0 && y >= 0 && x < canvasSize && y < canvasSize){
return getByte(data, (y * canvasSize + x) * bitsPerPixel);
}
return 0;
}
public void updateTexture(){
if(headless) return;
Pixmap pix = makePixmap(data);
Pixmap pix = makePixmap(data, previewPixmap);
if(texture != null){
texture.draw(pix);
}else{
@@ -214,7 +242,8 @@ public class CanvasBlock extends Block{
super.draw();
}
if(texture == null){
if(texture == null || updated){
updated = false;
updateTexture();
}
Tmp.tr1.set(texture);
@@ -237,6 +266,14 @@ public class CanvasBlock extends Block{
}
}
}
@Override
public double sense(LAccess sensor){
return switch(sensor){
case displayWidth, displayHeight -> canvasSize;
default -> super.sense(sensor);
};
}
@Override
public void remove(){
@@ -252,12 +289,17 @@ public class CanvasBlock extends Block{
table.button(Icon.pencil, Styles.cleari, () -> {
Dialog dialog = new Dialog();
Pixmap pix = makePixmap(data);
Pixmap pix = makePixmap(data, new Pixmap(canvasSize, canvasSize));
Texture texture = new Texture(pix);
int[] curColor = {palette[0]};
boolean[] modified = {false};
boolean[] fill = {false};
dialog.hidden(() -> {
texture.dispose();
pix.dispose();
});
dialog.resized(dialog::hide);
dialog.cont.table(Tex.pane, body -> {
@@ -395,7 +437,6 @@ public class CanvasBlock extends Block{
dialog.buttons.button("@ok", Icon.ok, () -> {
if(modified[0]){
configure(packPixmap(pix));
texture.dispose();
}
dialog.hide();
});
@@ -78,6 +78,7 @@ public class LogicDisplay extends Block{
public float stroke = 1f;
public LongQueue commands = new LongQueue(256);
public @Nullable Mat transform;
public long operations;
@Override
public void draw(){
@@ -111,6 +112,7 @@ public class LogicDisplay extends Block{
return switch(sensor){
case displayWidth, displayHeight -> displaySize;
case bufferUsage -> commands.size;
case operations -> operations;
default -> super.sense(sensor);
};
}
@@ -121,6 +123,8 @@ public class LogicDisplay extends Block{
for(int i = 0; i < added; i++){
commands.addLast(graphicsBuffer.items[i]);
}
operations++;
}
public void processCommands(){
@@ -13,6 +13,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import static mindustry.Vars.*;
@@ -27,25 +28,6 @@ public class TileableLogicDisplay extends LogicDisplay{
public @Load(value = "@-#", length = 47) TextureRegion[] tileRegion;
public @Load("@-back") TextureRegion backRegion;
static final int[] bitmasks = {
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20,
5, 28, 5, 28, 29, 10, 29, 23, 5, 28, 5, 28, 31, 11, 31, 32,
3, 4, 3, 4, 15, 40, 15, 20, 3, 4, 3, 4, 15, 40, 15, 20,
2, 30, 2, 30, 9, 46, 9, 22, 2, 30, 2, 30, 14, 44, 14, 6,
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
39, 36, 39, 36, 27, 16, 27, 24, 39, 36, 39, 36, 27, 16, 27, 24,
38, 37, 38, 37, 17, 41, 17, 43, 38, 37, 38, 37, 26, 21, 26, 25,
3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12,
5, 8, 5, 8, 29, 35, 29, 33, 5, 8, 5, 8, 31, 34, 31, 7,
3, 0, 3, 0, 15, 42, 15, 12, 3, 0, 3, 0, 15, 42, 15, 12,
2, 1, 2, 1, 9, 45, 9, 19, 2, 1, 2, 1, 14, 18, 14, 13,
};
public TileableLogicDisplay(String name){
super(name);
@@ -247,7 +229,7 @@ public class TileableLogicDisplay extends LogicDisplay{
Draw.z(Layer.block + 0.02f);
Draw.rect(tileRegion[bitmasks[bits]], x, y);
Draw.rect(tileRegion[TileBitmask.values[bits]], x, y);
}
@Override
@@ -48,12 +48,12 @@ public class PayloadConveyor extends Block{
public void drawPlace(int x, int y, int rotation, boolean valid){
super.drawPlace(x, y, rotation, valid);
int ntrns = 1 + size/2;
int ntrns = size;
for(int i = 0; i < 4; i++){
Building other = world.build(x + Geometry.d4x[i] * ntrns, y + Geometry.d4y[i] * ntrns);
if(other != null && other.block.outputsPayload && other.block.size == size){
Drawf.selected(other.tileX(), other.tileY(), other.block, other.team.color);
Tile tile = world.tile(x + Geometry.d4x[i] * ntrns, y + Geometry.d4y[i] * ntrns);
if(tile != null && tile.build != null && tile.isCenter() && tile.build.block.outputsPayload && tile.build.block.size == size && (i == rotation || tile.block().rotate && i == (tile.build.rotation + 2) % 4)){
Drawf.selected(tile.x, tile.y, tile.block(), tile.build.team.color);
}
}
}
@@ -1,6 +1,5 @@
package mindustry.world.blocks.power;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
@@ -58,14 +57,30 @@ public class LightBlock extends Block{
Placement.calculateNodes(points, this, rotation, (point, other) -> point.dst2(other) <= placeRadius2);
}
@Override
public int minimapColor(Tile tile){
var build = (LightBuild)tile.build;
//make sure A is 255
return build == null ? 0 : build.color | 0xff;
}
public class LightBuild extends Building{
public int color = Pal.accent.rgba();
public float smoothTime = 1f;
@Override
public void configured(Unit player, Object value){
super.configured(player, value);
if(!headless) renderer.minimap.update(tile);
}
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.color){
color = Tmp.c1.fromDouble(p1).rgba8888();
if(!headless) renderer.minimap.update(tile);
}
super.control(type, p1, p2, p3, p4);
@@ -80,11 +95,9 @@ public class LightBlock extends Block{
@Override
public void draw(){
super.draw();
Draw.blend(Blending.additive);
Draw.color(Tmp.c1.set(color), efficiency * 0.3f);
Draw.color(Tmp.c1.set(color).a(0.4f));
Draw.rect(topRegion, x, y);
Draw.color();
Draw.blend();
}
@Override
@@ -328,6 +328,11 @@ public class PowerNode extends PowerBlock{
}
});
//uncomment for debugging connection translation issues in schematics
//Draw.color(Color.red);
//Lines.line(plan.drawx(), plan.drawy(), px * tilesize, py * tilesize);
//Draw.color();
if(otherReq == null || otherReq.block == null) continue;
drawLaser(plan.drawx(), plan.drawy(), otherReq.drawx(), otherReq.drawy(), size, otherReq.block.size);
@@ -262,7 +262,7 @@ public class BeamDrill extends Block{
time %= drillTime;
}
if(timer(timerDump, dumpTime)){
if(timer(timerDump, dumpTime / timeScale)){
dump();
}
}
@@ -80,7 +80,7 @@ public class BurstDrill extends Drill{
if(invertTime > 0f) invertTime -= delta() / invertedTime;
if(timer(timerDump, dumpTime)){
if(timer(timerDump, dumpTime / timeScale)){
dump(items.has(dominantItem) ? dominantItem : null);
}
@@ -286,7 +286,7 @@ public class Drill extends Block{
@Override
public void updateTile(){
if(timer(timerDump, dumpTime)){
if(timer(timerDump, dumpTime / timeScale)){
dump(dominantItem != null && items.has(dominantItem) ? dominantItem : null);
}
@@ -162,7 +162,7 @@ public class Separator extends Block{
}
}
if(timer(timerDump, dumpTime)){
if(timer(timerDump, dumpTime / timeScale)){
dump();
}
}
@@ -216,7 +216,7 @@ public class WallCrafter extends Block{
totalTime += edelta() * warmup * (eff <= 0f ? 0f : 1f);
if(timer(timerDump, dumpTime)){
if(timer(timerDump, dumpTime / timeScale)){
dump(output);
}
}
@@ -60,6 +60,7 @@ public class ConsumePayloadDynamic extends Consume{
var inv = build.getPayloads();
var pay = payloads.get(build);
table.clear();
table.table(c -> {
int i = 0;
for(var stack : pay){
@@ -16,7 +16,7 @@ public class BuildVisibility{
sandboxOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.infiniteResources),
campaignOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.isCampaign()),
legacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.isCampaign() && Vars.state.getPlanet().campaignRules.legacyLaunchPads) && Blocks.advancedLaunchPad != null && Blocks.advancedLaunchPad.unlocked()),
notLegacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.isCampaign() && !Vars.state.getPlanet().campaignRules.legacyLaunchPads)),
notLegacyLaunchPadOnly = new BuildVisibility(() -> (Vars.state == null || Vars.state.rules.infiniteResources || Vars.state.isCampaign() && !Vars.state.getPlanet().campaignRules.legacyLaunchPads)),
lightingOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.lighting || Vars.state.isCampaign()),
ammoOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.unitAmmo),
fogOnly = new BuildVisibility(() -> Vars.state == null || Vars.state.rules.fog || Vars.state.rules.editor);
@@ -710,7 +710,7 @@ public class StatValues{
if(type.status != StatusEffects.none){
sep(bt, (type.status.hasEmoji() ? type.status.emoji() : "") + "[stat]" + type.status.localizedName + (type.status.reactive ? "" : "[lightgray] ~ [stat]" +
((int)(type.statusDuration / 60f)) + "[lightgray] " + Core.bundle.get("unit.seconds"))).with(c -> withTooltip(c, type.status));
Strings.autoFixed(type.statusDuration / 60f, 1) + "[lightgray] " + Core.bundle.get("unit.seconds"))).with(c -> withTooltip(c, type.status));
}
if(!type.targetMissiles){