Merge branch 'master' of https://github.com/Anuken/Mindustry into adjacent-campaign-sectors

This commit is contained in:
Anuken
2025-05-15 22:09:17 -04:00
50 changed files with 147 additions and 40 deletions

View File

@@ -147,7 +147,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
if(hasAll){
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
if(current.block.instantBuild){
if(!net.client() && current.block.instantBuild){
if(plans.size > 0){
plans.removeFirst();
}
@@ -188,7 +188,7 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
//otherwise, update it.
if(current.breaking){
entity.deconstruct(self(), core, bs);
}else if(entity.current != null && entity.current.unlockedNowHost()){ //only allow building unlocked blocks
}else if(entity.current != null && (state.isEditor() || (state.rules.waves && team == state.rules.waveTeam && entity.current.isVisible()) || (entity.current.unlockedNowHost() && entity.current.environmentBuildable() && entity.current.isPlaceable()))){ //only allow building unlocked blocks
entity.construct(self(), core, bs, current.config);
}

View File

@@ -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 = 30f;
static final float warpDst = 20f;
@Import boolean dead, disarmed;
@Import float x, y, rotation, maxHealth, drag, armor, hitSize, health, shield, ammo, dragMultiplier, armorOverride, speedMultiplier;
@@ -647,6 +647,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(y > top) dy -= (y - top)/warpDst;
velAddNet(dx * Time.delta, dy * Time.delta);
float margin = tilesize * 2f;
x = Mathf.clamp(x, left - margin, right - tilesize + margin);
y = Mathf.clamp(y, bot - margin, top - tilesize + margin);
}
//clamp position if not flying
@@ -771,7 +774,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//move down
elevation -= type.fallSpeed * Time.delta;
if(isGrounded() || health <= -maxHealth){
if(isGrounded() || health <= -maxHealth * type.wreckHealthMultiplier){
Call.unitDestroy(id);
}
}
@@ -866,7 +869,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
//if this unit crash landed (was flying), damage stuff in a radius
if(type.flying && !spawnedByCore && type.createWreck && state.rules.unitCrashDamage(team) > 0){
var shields = indexer.getEnemy(team, BlockFlag.shield);
float crashDamage = Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f * state.rules.unitCrashDamage(team);
float crashDamage = Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 2.5f * state.rules.unitCrashDamage(team);
if(shields.isEmpty() || !shields.contains(b -> b instanceof ExplosionShield s && s.absorbExplosion(x, y, crashDamage))){
Damage.damage(team, x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, crashDamage, true, false, true);
}

View File

@@ -1242,6 +1242,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
@Override
public void setTexture(String textureName){
this.textureName = textureName;
if(headless) return;
boolean firstUpdate = fetchedRegion == null;

View File

@@ -196,7 +196,7 @@ public class SectorInfo{
}
/** Prepare data for writing to a save. */
public void prepare(){
public void prepare(Sector sector){
//update core items
items.clear();
@@ -237,12 +237,10 @@ public class SectorInfo{
export.clear();
}
if(state.rules.sector != null){
state.rules.sector.saveInfo();
}
sector.saveInfo();
if(state.rules.sector != null && state.rules.sector.planet.allowWaveSimulation){
SectorDamage.writeParameters(this);
if(sector.planet.allowWaveSimulation){
SectorDamage.writeParameters(sector);
}
}

View File

@@ -226,7 +226,7 @@ public class Universe{
sector.info.wavesPassed = wavesPassed;
}
float damage = attacked ? SectorDamage.getDamage(sector.info) : 0f;
float damage = attacked ? SectorDamage.getDamage(sector) : 0f;
//damage never goes down until the player visits the sector, so use max
sector.info.damage = Math.max(sector.info.damage, damage);

View File

@@ -118,7 +118,7 @@ public abstract class SaveVersion extends SaveFileReader{
public void writeMeta(DataOutput stream, StringMap tags) throws IOException{
//prepare campaign data for writing
if(state.isCampaign()){
state.rules.sector.info.prepare();
state.rules.sector.info.prepare(state.rules.sector);
state.rules.sector.saveInfo();
}

View File

@@ -1216,8 +1216,7 @@ public class LExecutor{
this.a = a;
}
public PackColorI(){
}
public PackColorI(){}
@Override
public void run(LExecutor exec){
@@ -1225,6 +1224,29 @@ public class LExecutor{
}
}
public static class UnpackColorI implements LInstruction{
public LVar r, g, b, a, value;
public UnpackColorI(LVar r, LVar g, LVar b, LVar a, LVar value){
this.r = r;
this.g = g;
this.b = b;
this.a = a;
this.value = value;
}
public UnpackColorI(){}
@Override
public void run(LExecutor exec){
var color = Tmp.c1.fromDouble(value.num());
r.setnum(color.r);
g.setnum(color.g);
b.setnum(color.b);
a.setnum(color.a);
}
}
public static class CutsceneI implements LInstruction{
public CutsceneAction action = CutsceneAction.stop;
public LVar p1, p2, p3, p4;

View File

@@ -244,6 +244,10 @@ public abstract class LStatement{
}
public String typeName(){
return getClass().getSimpleName().replace("Statement", "");
}
public String name(){
return Strings.insertSpaces(getClass().getSimpleName().replace("Statement", ""));
}

View File

@@ -897,6 +897,35 @@ public class LStatements{
}
}
@RegisterStatement("unpackcolor")
public static class UnpackColorStatement extends LStatement{
public String r = "r", g = "g", b = "b", a = "a", value = "color";
@Override
public void build(Table table){
fields(table, r, str -> r = str);
fields(table, g, str -> g = str);
fields(table, b, str -> b = str);
fields(table, a, str -> a = str);
row(table);
table.add(" = unpack ");
fields(table, value, str -> value = str);
}
@Override
public LInstruction build(LAssembler builder){
return new UnpackColorI(builder.var(r), builder.var(g), builder.var(b), builder.var(a), builder.var(value));
}
@Override
public LCategory category(){
return LCategory.operation;
}
}
@RegisterStatement("end")
public static class EndStatement extends LStatement{
@Override
@@ -1605,6 +1634,8 @@ public class LStatements{
case mapArea -> {
table.add(" = ");
row(table);
fields(table, "x", p1, s -> p1 = s);
fields(table, "y", p2, s -> p2 = s);
row(table);
@@ -1624,7 +1655,7 @@ public class LStatements{
case ban, unban -> {
table.add(" block/unit ");
field(table, value, s -> value = s);
fields(table, value, s -> value = s);
}
default -> {
table.add(" = ");

View File

@@ -295,7 +295,8 @@ public class LogicDialog extends BaseDialog{
for(Prov<LStatement> prov : LogicIO.allStatements){
LStatement example = prov.get();
if(example instanceof InvalidStatement || example.hidden() || (example.privileged() && !privileged) || (example.nonPrivileged() && privileged) || (!text.isEmpty() && !example.name().toLowerCase(Locale.ROOT).contains(text))) continue;
if(example instanceof InvalidStatement || example.hidden() || (example.privileged() && !privileged) || (example.nonPrivileged() && privileged) ||
(!text.isEmpty() && !example.name().toLowerCase(Locale.ROOT).contains(text) && !example.typeName().toLowerCase(Locale.ROOT).contains(text))) continue;
if(matched[0] == null){
matched[0] = prov;

View File

@@ -36,10 +36,12 @@ public enum LogicOp{
len("len", true, (x, y) -> Mathf.dst((float)x, (float)y)),
noise("noise", true, (x, y) -> Simplex.raw2d(0, x, y)),
abs("abs", a -> Math.abs(a)), //not a method reference because it fails to compile for some reason
sign("sign", Math::signum),
log("log", Math::log),
log10("log10", Math::log10),
floor("floor", Math::floor),
ceil("ceil", Math::ceil),
round("round", Math::round),
sqrt("sqrt", Math::sqrt),
rand("rand", d -> GlobalVars.rand.nextDouble() * d),

View File

@@ -239,7 +239,7 @@ public class Map implements Comparable<Map>, Publishable{
int modes = Boolean.compare(Gamemode.pvp.valid(this), Gamemode.pvp.valid(map));
if(modes != 0) return modes;
return name().compareTo(map.name());
return Strings.stripColors(name()).compareTo(Strings.stripColors(map.name()));
}
@Override

View File

@@ -27,23 +27,24 @@ public class SectorDamage{
private static final boolean rubble = true;
/** @return calculated capture progress of the enemy */
public static float getDamage(SectorInfo info){
return getDamage(info, info.wavesPassed);
public static float getDamage(Sector sector){
return getDamage(sector, sector.info.wavesPassed);
}
/** @return calculated capture progress of the enemy */
public static float getDamage(SectorInfo info, int wavesPassed){
return getDamage(info, wavesPassed, false);
public static float getDamage(Sector sector, int wavesPassed){
return getDamage(sector, wavesPassed, false);
}
/** @return maximum waves survived, up to maxRetWave. */
public static int getWavesSurvived(SectorInfo info){
return (int)getDamage(info, maxRetWave, true);
public static int getWavesSurvived(Sector sector){
return (int)getDamage(sector, maxRetWave, true);
}
/** @return calculated capture progress of the enemy if retWave is false, otherwise return the maximum waves survived as int.
* if it survives all the waves, returns maxRetWave. */
public static float getDamage(SectorInfo info, int wavesPassed, boolean retWave){
public static float getDamage(Sector sector, int wavesPassed, boolean retWave){
var info = sector.info;
float health = info.sumHealth;
int wave = info.wave;
float waveSpace = info.waveSpacing;
@@ -64,18 +65,20 @@ public class SectorDamage{
for(int i = waveBegin; i <= waveEnd; i++){
float enemyDps = 0f, enemyHealth = 0f;
for(SpawnGroup group : state.rules.spawns){
//calculate the amount of spawn points used
//if there's a spawn position override, there is only one potential place they spawn
//assume that all overridden positions are valid, should always be true in properly designed campaign maps
int spawnCount = group.spawn != -1 ? 1 : group.type.flying ? airSpawns : groundSpawns;
if(sector.save != null || sector.isBeingPlayed()){
for(SpawnGroup group : (sector.isBeingPlayed() ? state.rules.spawns : sector.save.meta.rules.spawns)){
//calculate the amount of spawn points used
//if there's a spawn position override, there is only one potential place they spawn
//assume that all overridden positions are valid, should always be true in properly designed campaign maps
int spawnCount = group.spawn != -1 ? 1 : group.type.flying ? airSpawns : groundSpawns;
float healthMult = 1f + Mathf.clamp(group.type.armor / 20f);
StatusEffect effect = (group.effect == null ? StatusEffects.none : group.effect);
int spawned = group.getSpawned(i) * spawnCount;
if(spawned <= 0) continue;
enemyHealth += spawned * (group.getShield(i) + group.type.health * effect.healthMultiplier * healthMult);
enemyDps += spawned * group.type.dpsEstimate * effect.damageMultiplier;
float healthMult = 1f + Mathf.clamp(group.type.armor / 20f);
StatusEffect effect = (group.effect == null ? StatusEffects.none : group.effect);
int spawned = group.getSpawned(i) * spawnCount;
if(spawned <= 0) continue;
enemyHealth += spawned * (group.getShield(i) + group.type.health * effect.healthMultiplier * healthMult);
enemyDps += spawned * group.type.dpsEstimate * effect.damageMultiplier;
}
}
float efficiency = health / info.sumHealth;
@@ -106,7 +109,7 @@ public class SectorDamage{
if(timeDestroyEnemy > timeDestroyBase){
health = 0f;
//return current wave if simulating
if(retWave) return i - waveBegin;
if(retWave) return Math.max(i - waveBegin - 1, waveBegin);
break;
}
@@ -132,7 +135,7 @@ public class SectorDamage{
/** Applies wave damage based on sector parameters. */
public static void applyCalculatedDamage(){
//calculate base damage fraction
float damage = getDamage(state.rules.sector.info);
float damage = getDamage(state.rules.sector);
//scaled damage has a power component to make it seem a little more realistic (as systems fail, enemy capturing gets easier and easier)
float scaled = Mathf.pow(damage, 1.2f);
@@ -187,7 +190,8 @@ public class SectorDamage{
}
/** Calculates damage simulation parameters before a game is saved. */
public static void writeParameters(SectorInfo info){
public static void writeParameters(Sector sector){
var info = sector.info;
Building core = state.rules.defaultTeam.core();
Seq<Tile> spawns = new Seq<>();
spawner.eachGroundSpawn((x, y) -> spawns.add(world.tile(x, y)));
@@ -370,7 +374,7 @@ public class SectorDamage{
info.curEnemyDps = curEnemyDps*cmult;
info.curEnemyHealth = curEnemyHealth*cmult;
info.wavesSurvived = getWavesSurvived(info);
info.wavesSurvived = getWavesSurvived(sector);
}
public static void apply(float fraction){

View File

@@ -93,6 +93,8 @@ public class UnitType extends UnlockableContent implements Senseable{
buildRange = Vars.buildingRange,
/** multiplier for damage this (flying) unit deals when crashing on enemy things */
crashDamageMultiplier = 1f,
/** multiplier for health that this flying unit has for its wreck, based on its max health. */
wreckHealthMultiplier = 0.25f,
/** a VERY ROUGH estimate of unit DPS; initialized in init() */
dpsEstimate = -1,
/** graphics clipping size; <0 to calculate automatically */