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

This commit is contained in:
Anuken
2021-08-19 23:52:06 -04:00
32 changed files with 166 additions and 121 deletions

View File

@@ -202,15 +202,20 @@ public class BlockIndexer{
}
public boolean eachBlock(@Nullable Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
breturnArray.clear();
if(team == null){
returnBool = false;
allBuildings(wx, wy, range, b -> {
if(pred.get(b)){
breturnArray.add(b);
returnBool = true;
cons.get(b);
}
});
return returnBool;
}else{
breturnArray.clear();
var buildings = team.data().buildings;
if(buildings == null) return false;
buildings.intersect(wx - range, wy - range, range*2f, range*2f, b -> {

View File

@@ -2,7 +2,6 @@ package mindustry.ai.types;
import arc.math.*;
import mindustry.ai.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.*;
@@ -49,13 +48,6 @@ public class GroundAI extends AIController{
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, unit.type.riseSpeed);
}
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting){
if(unit.type.hasWeapons()){
unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
faceTarget();
}
}

View File

@@ -47,10 +47,10 @@ public class HugAI extends AIController{
})){
if(unit.within(target, (unit.hitSize + (target instanceof Sized s ? s.hitSize() : 1f)) * 0.6f)){
//circle target
unit.moveAt(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
unit.movePref(vec.set(target).sub(unit).rotate(90f).setLength(unit.speed()));
}else{
//move toward target in a straight line
unit.moveAt(vec.set(target).sub(unit).limit(unit.speed()));
unit.movePref(vec.set(target).sub(unit).limit(unit.speed()));
}
}else if(move){
pathfind(Pathfinder.fieldCore);
@@ -69,13 +69,6 @@ public class HugAI extends AIController{
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, unit.type.riseSpeed);
}
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting){
if(unit.type.hasWeapons()){
unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
faceTarget();
}
}

View File

@@ -41,10 +41,6 @@ public class SuicideAI extends GroundAI{
shoot = unit.within(target, unit.type.weapons.first().bullet.range() +
(target instanceof Building b ? b.block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
if(unit.type.hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}
//do not move toward walls or transport blocks
if(!(target instanceof Building build && !(build.block instanceof CoreBlock) && (
build.block.group == BlockGroup.walls ||
@@ -76,7 +72,7 @@ public class SuicideAI extends GroundAI{
if(!blocked){
moveToTarget = true;
//move towards target directly
unit.moveAt(vec.set(target).sub(unit).limit(unit.speed()));
unit.movePref(vec.set(target).sub(unit).limit(unit.speed()));
}
}
}
@@ -103,11 +99,11 @@ public class SuicideAI extends GroundAI{
pathfind(Pathfinder.fieldCore);
}
}
if(unit.moving()) unit.lookAt(unit.vel().angle());
}
unit.controlWeapons(rotate, shoot);
faceTarget();
}
@Override

View File

@@ -164,7 +164,7 @@ public class StatusEffects implements ContentList{
}};
boss = new StatusEffect("boss"){{
color = Team.sharded.color;
color = Team.crux.color;
permanent = true;
damageMultiplier = 1.3f;
healthMultiplier = 1.5f;

View File

@@ -172,13 +172,19 @@ public class Control implements ApplicationListener, Loadable{
Events.on(BlockDestroyEvent.class, e -> {
if(e.tile.team() == player.team()){
state.stats.buildingsDestroyed++;
state.stats.buildingsDestroyed ++;
}
});
Events.on(UnitDestroyEvent.class, e -> {
if(e.unit.team() != player.team()){
state.stats.enemyUnitsDestroyed++;
state.stats.enemyUnitsDestroyed ++;
}
});
Events.on(UnitCreateEvent.class, e -> {
if(e.unit.team == state.rules.defaultTeam){
state.stats.unitsCreated++;
}
});

View File

@@ -149,9 +149,17 @@ public abstract class UnlockableContent extends MappableContent{
}
}
public boolean unlockedNowHost(){
if(!state.isCampaign()) return true;
return net != null && net.client() ?
alwaysUnlocked || state.rules.researched.contains(name) :
unlocked || alwaysUnlocked;
}
public boolean unlocked(){
if(net != null && net.client()) return alwaysUnlocked || state.rules.researched.contains(name);
return unlocked || alwaysUnlocked;
return net != null && net.client() ?
alwaysUnlocked || unlocked || state.rules.researched.contains(name) :
unlocked || alwaysUnlocked;
}
/** Locks this content again. */

View File

@@ -15,6 +15,8 @@ public class MapResizeDialog extends BaseDialog{
public MapResizeDialog(Intc2 cons){
super("@editor.resizemap");
closeOnBack();
shown(() -> {
cont.clear();
width = editor.width();

View File

@@ -170,9 +170,8 @@ public class WaveInfoDialog extends BaseDialog{
t.remove();
updateWaves();
}).pad(-6).size(46f).padRight(-12f);
}, () -> showUpdate(group)).height(46f).pad(-6f).padBottom(0f);
}, () -> showUpdate(group)).height(46f).pad(-6f).padBottom(0f).row();
t.row();
t.table(spawns -> {
spawns.field("" + (group.begin + 1), TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
@@ -190,8 +189,8 @@ public class WaveInfoDialog extends BaseDialog{
updateWaves();
}
}).width(100f).get().setMessageText("");
});
t.row();
}).row();
t.table(p -> {
p.add("@waves.every").padRight(4);
p.field(group.spacing + "", TextFieldFilter.digitsOnly, text -> {
@@ -201,9 +200,8 @@ public class WaveInfoDialog extends BaseDialog{
}
}).width(100f);
p.add("@waves.waves").padLeft(4);
});
}).row();
t.row();
t.table(a -> {
a.field(group.unitAmount + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
@@ -220,8 +218,19 @@ public class WaveInfoDialog extends BaseDialog{
}
}).width(80f);
a.add("@waves.perspawn").padLeft(4);
});
t.row();
}).row();
t.table(a -> {
a.field(group.max + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
group.max = Strings.parseInt(text);
updateWaves();
}
}).width(80f);
a.add("@waves.max").padLeft(5);
}).row();
t.table(a -> {
a.field((int)group.shields + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
@@ -238,9 +247,8 @@ public class WaveInfoDialog extends BaseDialog{
}
}).width(80f);
a.add("@waves.shields").padLeft(4);
});
}).row();
t.row();
t.check("@waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
}).width(340f).pad(8);

View File

@@ -7,6 +7,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -40,6 +41,7 @@ public class UnitSpawnAbility extends Ability{
Unit u = this.unit.create(unit.team);
u.set(x, y);
u.rotation = unit.rotation;
Events.fire(new UnitCreateEvent(u, null, unit));
if(!Vars.net.client()){
u.add();
}

View File

@@ -76,6 +76,17 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
return raw;
}
@Override
@Replace
public void rotateMove(Vec2 vec){
//mechs use baseRotation to rotate, not rotation.
moveAt(Tmp.v2.trns(baseRotation, vec.len()));
if(!vec.isZero()){
baseRotation = Angles.moveToward(baseRotation, vec.angle(), type.rotateSpeed * Math.max(Time.delta, 1));
}
}
@Override
public void moveAt(Vec2 vector, float acceleration){
//mark walking state when moving in a controlled manner

View File

@@ -51,6 +51,15 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
private transient boolean wasPlayer;
private transient boolean wasHealed;
/** Move based on preferred unit movement type. */
public void movePref(Vec2 movement){
if(type.omniMovement){
moveAt(movement);
}else{
rotateMove(movement);
}
}
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
}

View File

@@ -76,6 +76,23 @@ public class AIController implements UnitController{
}
}
/** For ground units: Looks at the target, or the movement position. Does not apply to non-omni units. */
public void faceTarget(){
if(unit.type.omniMovement || unit instanceof Mechc){
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting && unit.type.hasWeapons()){
unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
}
}
public void faceMovement(){
if((unit.type.omniMovement || unit instanceof Mechc) && unit.moving()){
unit.lookAt(unit.vel().angle());
}
}
public boolean invalid(Teamc target){
return Units.invalidateTarget(target, unit.team, unit.x, unit.y);
}
@@ -89,7 +106,7 @@ public class AIController implements UnitController{
if(tile == targetTile || (costType == Pathfinder.costNaval && !targetTile.floor().isLiquid)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile.worldx(), targetTile.worldy()), unit.speed()));
unit.movePref(vec.trns(unit.angleTo(targetTile.worldx(), targetTile.worldy()), unit.speed()));
}
public void updateWeapons(){

View File

@@ -0,0 +1,25 @@
package mindustry.entities.units;
import arc.graphics.*;
import mindustry.graphics.*;
/** A sprite drawn in addition to the base unit sprites. */
public class UnitDecal{
public String region = "error";
public float x, y, rotation;
public float layer = Layer.flyingUnit + 1f;
public float xScale = 1f, yScale = 1f;
public Color color = Color.white;
public UnitDecal(String region, float x, float y, float rotation, float layer, Color color){
this.region = region;
this.x = x;
this.y = y;
this.rotation = rotation;
this.layer = layer;
this.color = color;
}
public UnitDecal(){
}
}

View File

@@ -411,14 +411,20 @@ public class EventType{
}
}
/** Called when a unit is created in a reconstructor or factory. */
/** Called when a unit is created in a reconstructor, factory or other unit. */
public static class UnitCreateEvent{
public final Unit unit;
public final Building spawner;
public final @Nullable Building spawner;
public final @Nullable Unit spawnerUnit;
public UnitCreateEvent(Unit unit, Building spawner){
public UnitCreateEvent(Unit unit, Building spawner, Unit spawnerUnit){
this.unit = unit;
this.spawner = spawner;
this.spawnerUnit = spawnerUnit;
}
public UnitCreateEvent(Unit unit, Building spawner){
this(unit, spawner, null);
}
}

View File

@@ -1,52 +1,16 @@
package mindustry.game;
import arc.math.*;
import arc.struct.*;
import mindustry.type.*;
//TODO more stats:
//- units constructed
public class GameStats{
/** Total items delivered to global resoure counter. Campaign only. */
public ObjectIntMap<Item> itemsDelivered = new ObjectIntMap<>();
/** Enemy (red team) units destroyed. */
public int enemyUnitsDestroyed;
/** Total waves lasted. */
public int wavesLasted;
/** Total (ms) time lasted in this save/zone. */
public long timeLasted;
/** Friendly buildings fully built. */
public int buildingsBuilt;
/** Friendly buildings fully deconstructed. */
public int buildingsDeconstructed;
/** Friendly buildings destroyed. */
public int buildingsDestroyed;
//unused
public RankResult calculateRank(Sector sector){
float score = 0;
int rankIndex = Mathf.clamp((int)score, 0, Rank.all.length - 1);
Rank rank = Rank.all[rankIndex];
String sign = Math.abs((rankIndex + 0.5f) - score) < 0.2f || rank.name().contains("S") ? "" : (rankIndex + 0.5f) < score ? "-" : "+";
return new RankResult(rank, sign);
}
public static class RankResult{
public final Rank rank;
/** + or - */
public final String modifier;
public RankResult(Rank rank, String modifier){
this.rank = rank;
this.modifier = modifier;
}
}
public enum Rank{
F, D, C, B, A, S, SS;
public static final Rank[] all = values();
}
/** Total units created by any means. */
public int unitsCreated;
}

View File

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

View File

@@ -642,11 +642,7 @@ public class DesktopInput extends InputHandler{
unit.lookAt(unit.prefRotation());
}
if(omni){
unit.moveAt(movement);
}else{
unit.rotateMove(movement);
}
unit.movePref(movement);
unit.aim(unit.type.faceTarget ? Core.input.mouseWorld() : Tmp.v1.trns(unit.rotation, Core.input.mouseWorld().dst(unit)).add(unit.x, unit.y));
unit.controlWeapons(true, player.shooting && !boosted);

View File

@@ -919,11 +919,7 @@ public class MobileInput extends InputHandler implements GestureListener{
player.boosting = collisions.overlapsTile(rect) || !unit.within(targetPos, 85f);
if(omni){
unit.moveAt(movement);
}else{
unit.rotateMove(movement);
}
unit.movePref(movement);
//update shooting if not building + not mining
if(!player.unit().activelyBuilding() && player.unit().mineTile == null){

View File

@@ -447,7 +447,7 @@ public class LExecutor{
}
}
case build -> {
if(state.rules.logicUnitBuild && unit.canBuild() && exec.obj(p3) instanceof Block block){
if(state.rules.logicUnitBuild && unit.canBuild() && exec.obj(p3) instanceof Block block && block.canBeBuilt()){
int x = World.toTile(x1 - block.offset/tilesize), y = World.toTile(y1 - block.offset/tilesize);
int rot = exec.numi(p4);

View File

@@ -77,6 +77,8 @@ public class UnitType extends UnlockableContent{
public Effect fallEffect = Fx.fallSmoke;
public Effect fallThrusterEffect = Fx.fallSmoke;
public Effect deathExplosionEffect = Fx.dynamicExplosion;
/** Additional sprites that are drawn with the unit. */
public Seq<UnitDecal> decals = new Seq<>();
public Seq<Ability> abilities = new Seq<>();
/** Flags to target based on priority. Null indicates that the closest target should be found. The closest enemy core is used as a fallback. */
public BlockFlag[] targetFlags = {null};
@@ -618,6 +620,18 @@ public class UnitType extends UnlockableContent{
unit.trns(-legOffset.x, -legOffset.y);
}
if(decals.size > 0){
float base = unit.rotation - 90;
for(var d : decals){
Draw.z(d.layer);
Draw.scl(d.xScale, d.yScale);
Draw.color(d.color);
Draw.rect(d.region, unit.x + Angles.trnsx(base, d.x, d.y), unit.y + Angles.trnsy(base, d.x, d.y), base + d.rotation);
}
Draw.reset();
Draw.z(z);
}
if(unit.abilities.size > 0){
for(Ability a : unit.abilities){
Draw.reset();
@@ -911,4 +925,5 @@ public class UnitType extends UnlockableContent{
}
//endregion
}

View File

@@ -4,7 +4,6 @@ import arc.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@@ -52,6 +51,7 @@ public class GameOverDialog extends BaseDialog{
t.margin(13f);
t.left().defaults().left();
t.add(Core.bundle.format("stat.wave", state.stats.wavesLasted)).row();
t.add(Core.bundle.format("stat.unitsCreated", state.stats.unitsCreated)).row();
t.add(Core.bundle.format("stat.enemiesDestroyed", state.stats.enemyUnitsDestroyed)).row();
t.add(Core.bundle.format("stat.built", state.stats.buildingsBuilt)).row();
t.add(Core.bundle.format("stat.destroyed", state.stats.buildingsDestroyed)).row();
@@ -59,17 +59,6 @@ public class GameOverDialog extends BaseDialog{
if(control.saves.getCurrent() != null){
t.add(Core.bundle.format("stat.playtime", control.saves.getCurrent().getPlayTime())).row();
}
if(state.isCampaign() && !state.stats.itemsDelivered.isEmpty()){
t.add("@stat.delivered").row();
for(Item item : content.items()){
if(state.stats.itemsDelivered.get(item, 0) > 0){
t.table(items -> {
items.add(" [lightgray]" + state.stats.itemsDelivered.get(item, 0));
items.image(item.uiIcon).size(8 * 3).pad(4);
}).left().row();
}
}
}
if(state.isCampaign() && net.client()){
t.add("@gameover.waiting").padTop(20f).row();

View File

@@ -540,7 +540,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
//sector notifications & search
c.top().right();
c.defaults().width(280f);
c.defaults().width(290f);
c.button(bundle.get("sectorlist") +
(attacked == 0 ? "" : "\n[red]⚠[lightgray] " + bundle.format("sectorlist.attacked", "[red]" + attacked + "[]")),
@@ -588,6 +588,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
for(Sector sec : all){
if(sec.hasBase() && (searchText.isEmpty() || sec.name().toLowerCase().contains(searchText.toLowerCase()))){
con.button(t -> {
t.marginRight(10f);
t.left();
t.defaults().growX();

View File

@@ -301,6 +301,8 @@ public class SettingsMenuDialog extends Dialog{
control.setInput(new DesktopInput());
input.setUseKeyboard(true);
}
}else{
Core.settings.put("keyboard", false);
}
}
//the issue with touchscreen support on desktop is that:

View File

@@ -303,7 +303,7 @@ public class ConstructBlock extends Block{
int accumulated = (int)(accumulator[i]); //get amount
if(clampedAmount > 0 && accumulated > 0){ //if it's positive, add it to the core
if(core != null && requirements[i].item.unlockedNow()){ //only accept items that are unlocked
if(core != null && requirements[i].item.unlockedNowHost()){ //only accept items that are unlocked
int accepting = Math.min(accumulated, core.storageCapacity - core.items.get(requirements[i].item));
//transfer items directly, as this is not production.
core.items.add(requirements[i].item, accepting);

View File

@@ -109,13 +109,15 @@ public class PowerNode extends PowerBlock{
Core.bundle.format("bar.powerbalance",
((entity.power.graph.getPowerBalance() >= 0 ? "+" : "") + UI.formatAmount((long)(entity.power.graph.getPowerBalance() * 60)))),
() -> Pal.powerBar,
() -> Mathf.clamp(entity.power.graph.getLastPowerProduced() / entity.power.graph.getLastPowerNeeded())));
() -> Mathf.clamp(entity.power.graph.getLastPowerProduced() / entity.power.graph.getLastPowerNeeded())
));
bars.add("batteries", entity -> new Bar(() ->
Core.bundle.format("bar.powerstored",
(UI.formatAmount((long)entity.power.graph.getLastPowerStored())), UI.formatAmount((long)entity.power.graph.getLastCapacity())),
() -> Pal.powerBar,
() -> Mathf.clamp(entity.power.graph.getLastPowerStored() / entity.power.graph.getLastCapacity())));
() -> Mathf.clamp(entity.power.graph.getLastPowerStored() / entity.power.graph.getLastCapacity())
));
bars.add("connections", entity -> new Bar(() ->
Core.bundle.format("bar.powerlines", entity.power.links.size, maxNodes),

View File

@@ -9,6 +9,7 @@ public class PowerVoid extends PowerBlock{
super(name);
consumes.power(Float.MAX_VALUE);
envEnabled = Env.any;
enableDrawStatus = false;
}
@Override

View File

@@ -126,7 +126,7 @@ public class Reconstructor extends UnitBlock{
var upgrade = upgrade(pay.unit.type);
if(upgrade != null){
if(!upgrade.unlockedNow()){
if(!upgrade.unlockedNowHost()){
//flash "not researched"
pay.showOverlay(Icon.tree);
}
@@ -137,7 +137,7 @@ public class Reconstructor extends UnitBlock{
}
}
return upgrade != null && upgrade.unlockedNow() && !upgrade.isBanned();
return upgrade != null && upgrade.unlockedNowHost() && !upgrade.isBanned();
}
@Override
@@ -232,7 +232,7 @@ public class Reconstructor extends UnitBlock{
if(payload == null) return null;
UnitType t = upgrade(payload.unit.type);
return t != null && t.unlockedNow() ? t : null;
return t != null && t.unlockedNowHost() ? t : null;
}
public boolean constructing(){
@@ -241,7 +241,7 @@ public class Reconstructor extends UnitBlock{
public boolean hasUpgrade(UnitType type){
UnitType t = upgrade(type);
return t != null && t.unlockedNow() && !type.isBanned();
return t != null && t.unlockedNowHost() && !type.isBanned();
}
public UnitType upgrade(UnitType type){

View File

@@ -43,7 +43,6 @@ public class ConsumeItemFilter extends Consume{
@Override
public void update(Building entity){
}
@Override