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

 Conflicts:
	core/src/mindustry/ui/dialogs/PlanetDialog.java
This commit is contained in:
Anuken
2021-08-18 15:04:30 -04:00
86 changed files with 942 additions and 740 deletions

View File

@@ -87,7 +87,7 @@ public class Vars implements Loadable{
public static final int maxNameLength = 40;
/** displayed item size when ingame. */
public static final float itemSize = 5f;
/** units outside of this bound will die instantly */
/** units outside this bound will die instantly */
public static final float finalWorldBounds = 250;
/** range for building */
public static final float buildingRange = 220f;

View File

@@ -52,6 +52,7 @@ public class BaseAI{
}
public void update(){
if(data.team.rules().aiCoreSpawn && timer.get(timerSpawn, 60 * 2.5f) && data.hasCore()){
CoreBlock block = (CoreBlock)data.core().block;
int coreUnits = Groups.unit.count(u -> u.team == data.team && u.type == block.unitType);
@@ -90,49 +91,51 @@ public class BaseAI{
}else{
var field = pathfinder.getField(state.rules.waveTeam, Pathfinder.costGround, Pathfinder.fieldCore);
int[][] weights = field.weights;
for(int i = 0; i < pathStep; i++){
int minCost = Integer.MAX_VALUE;
int cx = calcTile.x, cy = calcTile.y;
boolean foundAny = false;
for(Point2 p : Geometry.d4){
int nx = cx + p.x, ny = cy + p.y;
if(field.weights != null){
int[][] weights = field.weights;
for(int i = 0; i < pathStep; i++){
int minCost = Integer.MAX_VALUE;
int cx = calcTile.x, cy = calcTile.y;
boolean foundAny = false;
for(Point2 p : Geometry.d4){
int nx = cx + p.x, ny = cy + p.y;
Tile other = world.tile(nx, ny);
if(other != null && weights[nx][ny] < minCost && weights[nx][ny] != -1){
minCost = weights[nx][ny];
calcTile = other;
foundAny = true;
Tile other = world.tile(nx, ny);
if(other != null && weights[nx][ny] < minCost && weights[nx][ny] != -1){
minCost = weights[nx][ny];
calcTile = other;
foundAny = true;
}
}
//didn't find anything, break out of loop, this will trigger a clear later
if(!foundAny){
calcCount = Integer.MAX_VALUE;
break;
}
calcPath.add(calcTile.pos());
for(Point2 p : Geometry.d8){
calcPath.add(Point2.pack(p.x + calcTile.x, p.y + calcTile.y));
}
//found the end.
if(calcTile.build instanceof CoreBuild b && b.team == state.rules.defaultTeam){
//clean up calculations and flush results
calculating = false;
calcCount = 0;
path.clear();
path.addAll(calcPath);
calcPath.clear();
calcTile = null;
totalCalcs ++;
foundPath = true;
break;
}
calcCount ++;
}
//didn't find anything, break out of loop, this will trigger a clear later
if(!foundAny){
calcCount = Integer.MAX_VALUE;
break;
}
calcPath.add(calcTile.pos());
for(Point2 p : Geometry.d8){
calcPath.add(Point2.pack(p.x + calcTile.x, p.y + calcTile.y));
}
//found the end.
if(calcTile.build instanceof CoreBuild b && b.team == state.rules.defaultTeam){
//clean up calculations and flush results
calculating = false;
calcCount = 0;
path.clear();
path.addAll(calcPath);
calcPath.clear();
calcTile = null;
totalCalcs ++;
foundPath = true;
break;
}
calcCount ++;
}
}
}

View File

@@ -21,6 +21,7 @@ import static mindustry.Vars.*;
public class WaveSpawner{
private static final float margin = 40f, coreMargin = tilesize * 2f, maxSteps = 30;
private int tmpCount;
private Seq<Tile> spawns = new Seq<>();
private boolean spawning = false;
private boolean any = false;
@@ -162,6 +163,18 @@ public class WaveSpawner{
}
}
public int countGroundSpawns(){
tmpCount = 0;
eachGroundSpawn((x, y) -> tmpCount ++);
return tmpCount;
}
public int countFlyerSpawns(){
tmpCount = 0;
eachFlyerSpawn((x, y) -> tmpCount ++);
return tmpCount;
}
public boolean isSpawning(){
return spawning && !net.client();
}

View File

@@ -5,21 +5,17 @@ import arc.math.geom.*;
import mindustry.ai.formations.*;
public class CircleFormation extends FormationPattern{
/** Angle offset. */
public float angleOffset = 0;
@Override
public Vec3 calculateSlotLocation(Vec3 outLocation, int slotNumber){
if(slots > 1){
float angle = (360f * slotNumber) / slots;
float angle = (360f * slotNumber) / slots + (slots == 8 ? 22.5f : 0);
float radius = spacing / (float)Math.sin(180f / slots * Mathf.degRad);
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
}else{
outLocation.set(0, spacing * 1.1f, 360f * slotNumber);
}
outLocation.z += angleOffset;
return outLocation;
}

View File

@@ -15,11 +15,12 @@ import static mindustry.Vars.*;
public class BuilderAI extends AIController{
public static float buildRadius = 1500, retreatDst = 110f, fleeRange = 370f, retreatDelay = Time.toSeconds * 2f;
public @Nullable Unit following;
public @Nullable Teamc enemy;
public @Nullable BlockPlan lastPlan;
boolean found = false;
@Nullable Unit following;
@Nullable Teamc enemy;
float retreatTimer;
@Nullable BlockPlan lastPlan;
@Override
public void updateMovement(){

View File

@@ -21,12 +21,12 @@ public class DefenderAI extends AIController{
}
@Override
protected void updateTargeting(){
public void updateTargeting(){
if(retarget()) target = findTarget(unit.x, unit.y, unit.range(), true, true);
}
@Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
//find unit to follow if not in rally mode
if(command() != UnitCommand.rally){

View File

@@ -32,7 +32,7 @@ public class FlyingAI extends AIController{
}
@Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
var result = findMainTarget(x, y, range, air, ground);
//if the main target is in range, use it, otherwise target whatever is closest
@@ -40,7 +40,7 @@ public class FlyingAI extends AIController{
}
@Override
protected Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
public Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
var core = targetFlag(x, y, BlockFlag.core, true);
if(core != null && Mathf.within(x, y, core.getX(), core.getY(), range)){

View File

@@ -44,7 +44,7 @@ public class LogicAI extends AIController{
private ObjectSet<Object> radars = new ObjectSet<>();
@Override
protected void updateMovement(){
public void updateMovement(){
if(itemTimer >= 0) itemTimer -= Time.delta;
if(payTimer >= 0) payTimer -= Time.delta;
@@ -114,7 +114,7 @@ public class LogicAI extends AIController{
}
@Override
protected void moveTo(Position target, float circleLength, float smooth){
public void moveTo(Position target, float circleLength, float smooth){
if(target == null) return;
vec.set(target).sub(unit);
@@ -141,29 +141,29 @@ public class LogicAI extends AIController{
}
@Override
protected boolean checkTarget(Teamc target, float x, float y, float range){
public boolean checkTarget(Teamc target, float x, float y, float range){
return false;
}
//always retarget
@Override
protected boolean retarget(){
public boolean retarget(){
return true;
}
@Override
protected boolean invalid(Teamc target){
public boolean invalid(Teamc target){
return false;
}
@Override
protected boolean shouldShoot(){
public boolean shouldShoot(){
return shoot && !(unit.type.canBoost && boost);
}
//always aim for the main target
@Override
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
public Teamc target(float x, float y, float range, boolean air, boolean ground){
return switch(aimControl){
case target -> posTarget;
case targetp -> mainTarget;

View File

@@ -9,12 +9,12 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class MinerAI extends AIController{
boolean mining = true;
Item targetItem;
Tile ore;
public boolean mining = true;
public Item targetItem;
public Tile ore;
@Override
protected void updateMovement(){
public void updateMovement(){
Building core = unit.closestCore();
if(!(unit.canMine()) || core == null) return;

View File

@@ -13,7 +13,7 @@ public class RepairAI extends AIController{
float retreatTimer;
@Override
protected void updateMovement(){
public void updateMovement(){
if(target instanceof Building){
boolean shoot = false;
@@ -56,7 +56,7 @@ public class RepairAI extends AIController{
}
@Override
protected void updateTargeting(){
public void updateTargeting(){
Building target = Units.findDamagedTile(unit.team, unit.x, unit.y);
if(target instanceof ConstructBuild) target = null;

View File

@@ -111,7 +111,7 @@ public class SuicideAI extends GroundAI{
}
@Override
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
public Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground &&
!(t.block instanceof Conveyor || t.block instanceof Conduit)); //do not target conveyors/conduits
}

View File

@@ -393,12 +393,6 @@ public class Fx{
Lines.circle(e.x, e.y, 2f + e.finpow() * 7f);
}),
healDenamic = new Effect(11, e -> {
color(Pal.heal);
stroke(e.fout() * 2f);
Lines.circle(e.x, e.y, 2f + e.finpow() * e.rotation);
}),
shieldWave = new Effect(22, e -> {
color(e.color, 0.7f);
stroke(e.fout() * 2f);
@@ -1000,7 +994,9 @@ public class Fx{
float length = 20f * e.finpow();
float size = 7f * e.fout();
rect(((Item)e.data).fullIcon, e.x + trnsx(e.rotation, length), e.y + trnsy(e.rotation, length), size, size);
if(!(e.data instanceof Item item)) return;
rect(item.fullIcon, e.x + trnsx(e.rotation, length), e.y + trnsy(e.rotation, length), size, size);
}),
shockwave = new Effect(10f, 80f, e -> {

View File

@@ -4,6 +4,7 @@ import arc.*;
import arc.graphics.*;
import arc.math.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.type.*;
import mindustry.graphics.*;
@@ -163,7 +164,7 @@ public class StatusEffects implements ContentList{
}};
boss = new StatusEffect("boss"){{
color = Pal.health;
color = Team.sharded.color;
permanent = true;
damageMultiplier = 1.3f;
healthMultiplier = 1.5f;

View File

@@ -1789,7 +1789,7 @@ public class UnitTypes implements ContentList{
shootY = 6f;
beamWidth = 0.8f;
mirror = false;
repairSpeed = 0.7f;
repairSpeed = 0.75f;
bullet = new BulletType(){{
maxRange = 120f;
@@ -1835,8 +1835,8 @@ public class UnitTypes implements ContentList{
speed = 0f;
splashDamage = 50f;
splashDamageRadius = 40f;
splashDamage = 55f;
splashDamageRadius = 45f;
}};
}});
}};

View File

@@ -383,6 +383,8 @@ public class Control implements ApplicationListener, Loadable{
Groups.fire.clear();
Groups.puddle.clear();
//reset to 0, so replaced cores don't count
state.rules.defaultTeam.data().unitCap = 0;
Schematics.placeLaunchLoadout(spawn.x, spawn.y);
//set up camera/player locations

View File

@@ -105,8 +105,8 @@ public class Renderer implements ApplicationListener{
for(int i = 0; i < splashes.length; i++) splashes[i] = atlas.find("splash-" + i);
assets.load("sprites/clouds.png", Texture.class).loaded = t -> {
((Texture)t).setWrap(TextureWrap.repeat);
((Texture)t).setFilter(TextureFilter.linear);
t.setWrap(TextureWrap.repeat);
t.setFilter(TextureFilter.linear);
};
}

View File

@@ -261,6 +261,7 @@ public class UI implements ApplicationListener, Loadable{
this.numeric = inumeric;
this.maxLength = textLength;
this.accepted = confirmed;
this.allowEmpty = false;
}});
}else{
new Dialog(titleText){{

View File

@@ -358,30 +358,23 @@ public class World{
}
public void raycastEach(int x0f, int y0f, int x1, int y1, Raycaster cons){
int x0 = x0f;
int y0 = y0f;
int dx = Math.abs(x1 - x0);
int dy = Math.abs(y1 - y0);
int x0 = x0f, dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int y0 = y0f, dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int e2, err = dx - dy;
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx - dy;
int e2;
while(true){
if(cons.accept(x0, y0)) break;
if(x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if(e2 > -dy){
err = err - dy;
x0 = x0 + sx;
err -= dy;
x0 += sx;
}
if(e2 < dx){
err = err + dx;
y0 = y0 + sy;
err += dx;
y0 += sy;
}
}
}

View File

@@ -232,7 +232,7 @@ public class MapGenerateDialog extends BaseDialog{
}else{
Core.scene.setScrollFocus(null);
}
}).grow().uniformX().get().setScrollingDisabled(true, false);
}).grow().uniformX().scrollX(false);
}).grow();
buffer1 = create();
@@ -350,7 +350,7 @@ public class MapGenerateDialog extends BaseDialog{
update();
selection.hide();
}).with(Table::left).get().getLabelCell().growX().left().padLeft(5).labelAlign(Align.left);
}).get().setScrollingDisabled(true, false);
}).scrollX(false);
selection.addCloseButton();
selection.show();

View File

@@ -38,7 +38,7 @@ public class MapInfoDialog extends BaseDialog{
TextField name = t.field(tags.get("name", ""), text -> {
tags.put("name", text);
}).size(400, 55f).addInputDialog(50).get();
}).size(400, 55f).maxTextLength(50).get();
name.setMessageText("@unknown");
t.row();
@@ -46,7 +46,7 @@ public class MapInfoDialog extends BaseDialog{
TextArea description = t.area(tags.get("description", ""), Styles.areaField, text -> {
tags.put("description", text);
}).size(400f, 140f).addInputDialog(1000).get();
}).size(400f, 140f).maxTextLength(1000).get();
t.row();
t.add("@editor.author").padRight(8).left();
@@ -54,7 +54,7 @@ public class MapInfoDialog extends BaseDialog{
TextField author = t.field(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
tags.put("author", text);
Core.settings.put("mapAuthor", text);
}).size(400, 55f).addInputDialog(50).get();
}).size(400, 55f).maxTextLength(50).get();
author.setMessageText("@unknown");
t.row();

View File

@@ -29,7 +29,7 @@ public class MapResizeDialog extends BaseDialog{
table.field((w ? width : height) + "", TextFieldFilter.digitsOnly, value -> {
int val = Integer.parseInt(value);
if(w) width = val; else height = val;
}).valid(value -> Strings.canParsePositiveInt(value) && Integer.parseInt(value) <= maxSize && Integer.parseInt(value) >= minSize).addInputDialog(3);
}).valid(value -> Strings.canParsePositiveInt(value) && Integer.parseInt(value) <= maxSize && Integer.parseInt(value) >= minSize).maxTextLength(3);
table.row();
}

View File

@@ -189,7 +189,7 @@ public class WaveGraph extends Table{
for(UnitType o : hidden) used.remove(o);
}).update(b -> b.setChecked(hidden.contains(type)));
}
}).get().setScrollingDisabled(false, true);
}).scrollY(false);
for(UnitType type : hidden){
used.remove(type);

View File

@@ -128,7 +128,7 @@ public class WaveInfoDialog extends BaseDialog{
cont.clear();
cont.stack(new Table(Tex.clear, main -> {
main.pane(t -> table = t).growX().growY().padRight(8f).get().setScrollingDisabled(true, false);
main.pane(t -> table = t).growX().growY().padRight(8f).scrollX(false);
main.row();
main.button("@add", () -> {
if(groups == null) groups = new Seq<>();

View File

@@ -19,7 +19,8 @@ import static mindustry.Vars.*;
public class Effect{
private static final float shakeFalloff = 10000f;
private static final EffectContainer container = new EffectContainer();
private static final Seq<Effect> all = new Seq<>();
public static final Seq<Effect> all = new Seq<>();
private boolean initialized;

View File

@@ -1,5 +1,6 @@
package mindustry.entities;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
@@ -23,7 +24,9 @@ public class EntityCollisions{
private Rect r2 = new Rect();
//entity collisions
private Seq<Hitboxc> arrOut = new Seq<>();
private Seq<Hitboxc> arrOut = new Seq<>(Hitboxc.class);
private Cons<Hitboxc> hitCons = this::updateCollision;
private Cons<QuadTree> treeCons = tree -> tree.intersect(r2, arrOut);
public void moveCheck(Hitboxc entity, float deltax, float deltay, SolidPred solidCheck){
if(!solidCheck.solid(entity.tileX(), entity.tileY())){
@@ -36,7 +39,7 @@ public class EntityCollisions{
}
public void move(Hitboxc entity, float deltax, float deltay, SolidPred solidCheck){
if(Math.abs(deltax) < 0.0001f & Math.abs(deltay) < 0.0001f) return;
if(Math.abs(deltax) < 0.0001f & Math.abs(deltay) < 0.0001f) return;
boolean movedx = false;
@@ -213,28 +216,34 @@ public class EntityCollisions{
@SuppressWarnings("unchecked")
public <T extends Hitboxc> void collide(EntityGroup<T> groupa){
groupa.each(solid -> {
solid.hitbox(r1);
r1.x += (solid.lastX() - solid.getX());
r1.y += (solid.lastY() - solid.getY());
groupa.each((Cons<T>)hitCons);
}
solid.hitbox(r2);
r2.merge(r1);
private void updateCollision(Hitboxc solid){
solid.hitbox(r1);
r1.x += (solid.lastX() - solid.getX());
r1.y += (solid.lastY() - solid.getY());
arrOut.clear();
solid.hitbox(r2);
r2.merge(r1);
//get all targets based on what entity wants to collide with
solid.getCollisions(tree -> tree.intersect(r2, arrOut));
arrOut.clear();
for(Hitboxc sc : arrOut){
sc.hitbox(r1);
if(r2.overlaps(r1)){
checkCollide(solid, sc);
//break out of loop when this object hits something
if(!solid.isAdded()) return;
}
//get all targets based on what entity wants to collide with
solid.getCollisions(treeCons);
var items = arrOut.items;
int size = arrOut.size;
for(int i = 0; i < size; i++){
Hitboxc sc = items[i];
sc.hitbox(r1);
if(r2.overlaps(r1)){
checkCollide(solid, sc);
//break out of loop when this object hits something
if(!solid.isAdded()) return;
}
});
}
}
public interface SolidPred{

View File

@@ -10,6 +10,7 @@ import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -105,7 +106,11 @@ public class EnergyFieldAbility extends Ability{
});
if(hitBuildings){
Units.nearbyBuildings(rx, ry, range, all::add);
Units.nearbyBuildings(rx, ry, range, b -> {
if(b.team != Team.derelict || state.rules.coreCapture){
all.add(b);
}
});
}
all.sort(h -> h.dst2(rx, ry));

View File

@@ -118,36 +118,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
type.update(self());
if(type.collidesTiles && type.collides && type.collidesGround){
world.raycastEach(World.toTile(lastX()), World.toTile(lastY()), tileX(), tileY(), (x, y) -> {
Building build = world.build(x, y);
if(build == null || !isAdded()) return false;
if(build.collide(self()) && type.testCollision(self(), build) && !build.dead() && (type.collidesTeam || build.team != team) && !(type.pierceBuilding && hasCollided(build.id))){
boolean remove = false;
float health = build.health;
if(build.team != team){
remove = build.collision(self());
}
if(remove || type.collidesTeam){
if(!type.pierceBuilding){
hit = true;
remove();
}else{
collided.add(build.id);
}
}
type.hitTile(self(), build, health, true);
return !type.pierceBuilding;
}
return false;
});
tileRaycast(World.toTile(lastX()), World.toTile(lastY()), tileX(), tileY());
}
if(type.pierceCap != -1 && collided.size >= type.pierceCap){
@@ -156,6 +127,55 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
}
}
//copy-paste of World#raycastEach, inlined for lambda capture performance.
@Override
public void tileRaycast(int x0f, int y0f, int x1, int y1){
int x = x0f, dx = Math.abs(x1 - x), sx = x < x1 ? 1 : -1;
int y = y0f, dy = Math.abs(y1 - y), sy = y < y1 ? 1 : -1;
int e2, err = dx - dy;
while(true){
Building build = world.build(x, y);
if(build != null && isAdded() && build.collide(self()) && type.testCollision(self(), build)
&& !build.dead() && (type.collidesTeam || build.team != team) && !(type.pierceBuilding && hasCollided(build.id))){
boolean remove = false;
float health = build.health;
if(build.team != team){
remove = build.collision(self());
}
if(remove || type.collidesTeam){
if(!type.pierceBuilding){
hit = true;
remove();
}else{
collided.add(build.id);
}
}
type.hitTile(self(), build, health, true);
//stop raycasting when building is hit
if(type.pierceBuilding) return;
}
if(x == x1 && y == y1) break;
e2 = 2 * err;
if(e2 > -dy){
err -= dy;
x += sx;
}
if(e2 < dx){
err += dx;
y += sy;
}
}
}
@Override
public void draw(){
Draw.z(type.layer);

View File

@@ -45,6 +45,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
transient String lastText = "";
transient float textFadeTime;
transient private Unit lastReadUnit = Nulls.unit;
transient private int wrongReadUnits;
transient @Nullable Unit justSwitchFrom, justSwitchTo;
public boolean isBuilder(){
@@ -104,9 +105,15 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
//reason: we know the server is lying here, essentially skip the unit snapshot because we know the client's information is more recent
if(isLocal() && unit == justSwitchFrom && justSwitchFrom != null && justSwitchTo != null){
unit = justSwitchTo;
//if several snapshots have passed and this unit is still incorrect, something's wrong
if(++wrongReadUnits >= 2){
justSwitchFrom = null;
wrongReadUnits = 0;
}
}else{
justSwitchFrom = null;
justSwitchTo = null;
wrongReadUnits = 0;
}
//simulate a unit change after sync

View File

@@ -40,8 +40,8 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
@Import int id;
@Import float x, y;
@Import boolean added;
transient private boolean mismatch = false;
transient float accepting, updateTime, lastRipple = Time.time + Mathf.random(40f);
float amount;
Tile tile;
@@ -79,9 +79,12 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
return;
}
if(Puddles.get(tile) != self()){
mismatch = true;
remove();
if(Puddles.get(tile) != self() && added){
//force removal without pool free
Groups.all.remove(self());
Groups.draw.remove(self());
Groups.puddle.remove(self());
added = false;
return;
}
@@ -134,9 +137,7 @@ abstract class PuddleComp implements Posc, Puddlec, Drawc{
@Override
public void remove(){
if(!mismatch){
Puddles.remove(tile);
}
Puddles.remove(tile);
}
@Override

View File

@@ -46,19 +46,19 @@ public class AIController implements UnitController{
}
@Nullable
protected AIController fallback(){
public AIController fallback(){
return null;
}
protected boolean useFallback(){
public boolean useFallback(){
return false;
}
protected UnitCommand command(){
public UnitCommand command(){
return unit.team.data().command;
}
protected void updateVisuals(){
public void updateVisuals(){
if(unit.isFlying()){
unit.wobble();
@@ -66,21 +66,21 @@ public class AIController implements UnitController{
}
}
protected void updateMovement(){
public void updateMovement(){
}
protected void updateTargeting(){
public void updateTargeting(){
if(unit.hasWeapons()){
updateWeapons();
}
}
protected boolean invalid(Teamc target){
public boolean invalid(Teamc target){
return Units.invalidateTarget(target, unit.team, unit.x, unit.y);
}
protected void pathfind(int pathTarget){
public void pathfind(int pathTarget){
int costType = unit.pathType();
Tile tile = unit.tileOn();
@@ -92,7 +92,7 @@ public class AIController implements UnitController{
unit.moveAt(vec.trns(unit.angleTo(targetTile.worldx(), targetTile.worldy()), unit.speed()));
}
protected void updateWeapons(){
public void updateWeapons(){
float rotation = unit.rotation - 90;
boolean ret = retarget();
@@ -146,45 +146,45 @@ public class AIController implements UnitController{
}
}
protected boolean checkTarget(Teamc target, float x, float y, float range){
public boolean checkTarget(Teamc target, float x, float y, float range){
return Units.invalidateTarget(target, unit.team, x, y, range);
}
protected boolean shouldShoot(){
public boolean shouldShoot(){
return true;
}
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
public Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
if(unit.team == Team.derelict) return null;
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
return target == null ? null : target.build;
}
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
public Teamc target(float x, float y, float range, boolean air, boolean ground){
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground);
}
protected boolean retarget(){
public boolean retarget(){
return timer.get(timerTarget, target == null ? 40 : 90);
}
protected Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
public Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
return findTarget(x, y, range, air, ground);
}
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
public Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
return target(x, y, range, air, ground);
}
protected void init(){
public void init(){
}
protected @Nullable Tile getClosestSpawner(){
public @Nullable Tile getClosestSpawner(){
return Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns());
}
protected void unloadPayloads(){
public void unloadPayloads(){
if(unit instanceof Payloadc pay && pay.hasPayload() && target instanceof Building && pay.payloads().peek() instanceof UnitPayload){
if(target.within(unit, Math.max(unit.type().range + 1f, 75f))){
pay.dropLastPayload();
@@ -192,11 +192,11 @@ public class AIController implements UnitController{
}
}
protected void circle(Position target, float circleLength){
public void circle(Position target, float circleLength){
circle(target, circleLength, unit.speed());
}
protected void circle(Position target, float circleLength, float speed){
public void circle(Position target, float circleLength, float speed){
if(target == null) return;
vec.set(target).sub(unit);
@@ -210,11 +210,11 @@ public class AIController implements UnitController{
unit.moveAt(vec);
}
protected void moveTo(Position target, float circleLength){
public void moveTo(Position target, float circleLength){
moveTo(target, circleLength, 100f);
}
protected void moveTo(Position target, float circleLength, float smooth){
public void moveTo(Position target, float circleLength, float smooth){
if(target == null) return;
vec.set(target).sub(unit);

View File

@@ -5,8 +5,6 @@ import arc.func.*;
import arc.util.*;
import mindustry.maps.*;
import static mindustry.Vars.*;
/** Defines preset rule sets. */
public enum Gamemode{
survival(rules -> {
@@ -25,7 +23,7 @@ public enum Gamemode{
rules.waveSpacing = 2f * Time.toMinutes;
rules.teams.get(rules.waveTeam).infiniteResources = true;
}, map -> map.teams.contains(state.rules.waveTeam.id)),
}, map -> map.teams.size > 1),
pvp(rules -> {
rules.pvp = true;
rules.enemyCoreBuildRadius = 600f;

View File

@@ -33,6 +33,7 @@ import mindustry.world.blocks.power.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import mindustry.world.meta.*;
import java.io.*;
@@ -450,6 +451,10 @@ public class Schematics implements Loadable{
if(st.block instanceof Drill){
tile.getLinkedTiles(t -> t.setOverlay(resource));
}
if(tile.build instanceof CoreBuild cb){
state.teams.registerCore(cb);
}
});
}

View File

@@ -48,11 +48,11 @@ public class Teams{
return Geometry.findClosest(x, y, get(team).cores);
}
public boolean eachEnemyCore(Team team, Boolf<CoreBuild> ret){
public boolean anyEnemyCoresWithin(Team team, float x, float y, float radius){
for(TeamData data : active){
if(team != data.team){
for(CoreBuild tile : data.cores){
if(ret.get(tile)){
if(tile.within(x, y, radius)){
return true;
}
}

View File

@@ -261,6 +261,10 @@ public class Waves{
}
public static Seq<SpawnGroup> generate(float difficulty, Rand rand, boolean attack){
return generate(difficulty, rand, attack, false);
}
public static Seq<SpawnGroup> generate(float difficulty, Rand rand, boolean attack, boolean airOnly){
UnitType[][] species = {
{dagger, mace, fortress, scepter, reign},
{nova, pulsar, quasar, vela, corvus},
@@ -268,6 +272,12 @@ public class Waves{
{flare, horizon, zenith, rand.chance(0.5) ? quad : antumbra, rand.chance(0.1) ? quad : eclipse}
};
if(airOnly){
species = Structs.filter(UnitType[].class, species, v -> v[0].flying);
}
UnitType[][] fspec = species;
//required progression:
//- extra periodic patterns
@@ -281,7 +291,7 @@ public class Waves{
Intc createProgression = start -> {
//main sequence
UnitType[] curSpecies = Structs.random(species);
UnitType[] curSpecies = Structs.random(fspec);
int curTier = 0;
for(int i = start; i < cap;){
@@ -326,7 +336,7 @@ public class Waves{
//small chance to switch species
if(rand.chance(0.3)){
curSpecies = Structs.random(species);
curSpecies = Structs.random(fspec);
}
}
};

View File

@@ -235,7 +235,7 @@ public class Shaders{
super(frag);
Core.assets.load("sprites/space.png", Texture.class).loaded = t -> {
texture = (Texture)t;
texture = t;
texture.setFilter(TextureFilter.linear);
texture.setWrap(TextureWrap.mirroredRepeat);
};
@@ -274,8 +274,8 @@ public class Shaders{
public void loadNoise(){
Core.assets.load("sprites/" + textureName() + ".png", Texture.class).loaded = t -> {
((Texture)t).setFilter(TextureFilter.linear);
((Texture)t).setWrap(TextureWrap.repeat);
t.setFilter(TextureFilter.linear);
t.setWrap(TextureWrap.repeat);
};
}

View File

@@ -136,8 +136,12 @@ public class PlanetRenderer implements Disposable{
public void renderPlanet(Planet planet){
if(!planet.visible()) return;
//render planet at offsetted position in the world
planet.draw(cam.combined, planet.getTransform(mat));
cam.update();
if(cam.frustum.containsSphere(planet.position, planet.clipRadius)){
//render planet at offsetted position in the world
planet.draw(cam.combined, planet.getTransform(mat));
}
for(Planet child : planet.children){
renderPlanet(child);
@@ -151,7 +155,7 @@ public class PlanetRenderer implements Disposable{
renderSectors(planet);
}
if(planet.parent != null && planet.hasAtmosphere && Core.settings.getBool("atmosphere")){
if(cam.frustum.containsSphere(planet.position, planet.clipRadius) && planet.parent != null && planet.hasAtmosphere && Core.settings.getBool("atmosphere")){
Gl.depthMask(false);
Blending.additive.apply();

View File

@@ -379,15 +379,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
throw new ValidateException(player, "Player cannot control a unit.");
}
//TODO problem:
//1. server send snapshot
//2. client requests to control unit, becomes unit locally
//3. snapshot arrives, client now thinks they're in the old unit (!!!)
//4. server gets packet that player is in the right unit
//5. server sends snapshot
//6. client gets snapshot, realizes that they are actually in the unit they selected
//7. client gets switched to new unit -> rubberbanding (!!!)
//clear player unit when they possess a core
if(unit == null){ //just clear the unit (is this used?)
player.clearUnit();
@@ -404,6 +395,9 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(!player.dead()){
Fx.unitSpirit.at(player.x, player.y, 0f, unit);
}
}else if(net.server()){
//reject forwarding the packet if the unit was dead, AI or team
throw new ValidateException(player, "Player attempted to control invalid unit.");
}
Events.fire(new UnitControlEvent(player, unit));
@@ -462,7 +456,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
player.unit().updateBuilding(isBuilding);
}
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && player.unit().ammo <= 0){
if(player.shooting && !wasShooting && player.unit().hasWeapons() && state.rules.unitAmmo && !player.team().rules().infiniteAmmo && player.unit().ammo <= 0){
player.unit().type.weapons.first().noAmmoSound.at(player.unit());
}

View File

@@ -86,7 +86,7 @@ public abstract class LStatement{
protected Cell<TextField> field(Table table, String value, Cons<String> setter){
return table.field(value, Styles.nodeField, s -> setter.get(sanitize(s)))
.size(144f, 40f).pad(2f).color(table.color).maxTextLength(LAssembler.maxTokenLength).addInputDialog();
.size(144f, 40f).pad(2f).color(table.color).maxTextLength(LAssembler.maxTokenLength);
}
protected Cell<TextField> fields(Table table, String desc, String value, Cons<String> setter){
@@ -175,7 +175,7 @@ public abstract class LStatement{
t.top().pane(inner -> {
inner.top();
hideCons.get(inner, hide);
}).pad(0f).top().get().setScrollingDisabled(true, false);
}).pad(0f).top().scrollX(false);
t.pack();
}

View File

@@ -381,7 +381,7 @@ public class Maps{
//try to load preview
if(map.previewFile().exists()){
//this may fail, but calls queueNewPreview
Core.assets.load(new AssetDescriptor<>(map.previewFile().path() + "." + mapExtension, Texture.class, new MapPreviewParameter(map))).loaded = t -> map.texture = (Texture)t;
Core.assets.load(new AssetDescriptor<>(map.previewFile().path() + "." + mapExtension, Texture.class, new MapPreviewParameter(map))).loaded = t -> map.texture = t;
try{
readCache(map);

View File

@@ -490,7 +490,8 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
state.rules.waves = sector.info.waves = true;
state.rules.enemyCoreBuildRadius = 600f;
state.rules.spawns = Waves.generate(difficulty, new Rand(), state.rules.attackMode);
//spawn air only when spawn is blocked
state.rules.spawns = Waves.generate(difficulty, new Rand(sector.id), state.rules.attackMode, state.rules.attackMode && spawner.countGroundSpawns() == 0);
}
@Override

View File

@@ -77,9 +77,10 @@ public class ContentParser{
}
});
put(StatusEffect.class, (type, data) -> {
Object result = fieldOpt(StatusEffects.class, data);
if(result != null){
return result;
if(data.isString()){
StatusEffect result = locate(ContentType.status, data.asString());
if(result != null) return result;
throw new IllegalArgumentException("Unknown status effect: '" + data.asString() + "'");
}
StatusEffect effect = new StatusEffect(currentMod.name + "-" + data.getString("name"));
readFields(effect, data);
@@ -99,7 +100,7 @@ public class ContentParser{
put(AmmoType.class, (type, data) -> {
//string -> item
//if liquid ammo support is added, this should scan for liquids as well
if(data.isString()) return find(ContentType.item, data.asString());
if(data.isString()) return new ItemAmmoType(find(ContentType.item, data.asString()));
//number -> power
if(data.isNumber()) return new PowerAmmoType(data.asFloat());

View File

@@ -267,6 +267,7 @@ public class Mods implements Loadable{
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.linear : TextureFilter.nearest;
Time.mark();
//generate new icons
for(Seq<Content> arr : content.getContentMap()){
arr.each(c -> {
@@ -277,6 +278,7 @@ public class Mods implements Loadable{
}
});
}
Log.debug("Time to generate icons: @", Time.elapsed());
//dispose old atlas data
Core.atlas = packer.flush(filter, new TextureAtlas());
@@ -287,7 +289,7 @@ public class Mods implements Loadable{
packer.dispose();
packer = null;
Log.debug("Time to update textures: @", Time.elapsed());
Log.debug("Total time to generate & flush textures synchronously: @", Time.elapsed());
}
private PageType getPage(AtlasRegion region){

View File

@@ -21,10 +21,10 @@ import java.net.*;
import java.util.regex.*;
public class Scripts implements Disposable{
private final Context context;
private final Scriptable scope;
private boolean errored;
public final Context context;
public final Scriptable scope;
private boolean errored;
LoadedMod currentMod = null;
public Scripts(){

View File

@@ -342,24 +342,9 @@ public class ArcNetProvider implements NetProvider{
//for debugging total read/write speeds
private static final boolean debug = false;
ThreadLocal<ByteBuffer> decompressBuffer = new ThreadLocal<>(){
@Override
protected ByteBuffer initialValue(){
return ByteBuffer.allocate(32768);
}
};
ThreadLocal<Reads> reads = new ThreadLocal<>(){
@Override
protected Reads initialValue(){
return new Reads(new ByteBufferInput(decompressBuffer.get()));
}
};
ThreadLocal<Writes> writes = new ThreadLocal<>(){
@Override
protected Writes initialValue(){
return new Writes(new ByteBufferOutput(decompressBuffer.get()));
}
};
ThreadLocal<ByteBuffer> decompressBuffer = Threads.local(() -> ByteBuffer.allocate(32768));
ThreadLocal<Reads> reads = Threads.local(() -> new Reads(new ByteBufferInput(decompressBuffer.get())));
ThreadLocal<Writes> writes = Threads.local(() -> new Writes(new ByteBufferOutput(decompressBuffer.get())));
//for debugging network write counts
static WindowedMean upload = new WindowedMean(5), download = new WindowedMean(5);

View File

@@ -274,6 +274,7 @@ public class Net{
builder.add(c.data);
ui.loadfrag.setProgress(builder.progress());
ui.loadfrag.snapProgress();
netClient.resetTimeout();
if(builder.isDone()){

View File

@@ -41,6 +41,8 @@ public class Planet extends UnlockableContent{
public boolean drawOrbit = true;
/** Atmosphere radius adjustment parameters. */
public float atmosphereRadIn = 0, atmosphereRadOut = 0.3f;
/** Frustrum sphere clip radius. */
public float clipRadius = -1f;
/** Orbital radius around the sun. Do not change unless you know exactly what you are doing.*/
public float orbitRadius;
/** Total radius of this planet and all its children. */
@@ -55,7 +57,7 @@ public class Planet extends UnlockableContent{
public float sectorApproxRadius;
/** Whether this planet is tidally locked relative to its parent - see https://en.wikipedia.org/wiki/Tidal_locking */
public boolean tidalLock = false;
/** Whether or not this planet is listed in the planet access UI. **/
/** Whether this planet is listed in the planet access UI. **/
public boolean accessible = true;
/** If true, a day/night cycle is simulated. */
public boolean updateLighting = true;
@@ -252,6 +254,7 @@ public class Planet extends UnlockableContent{
updateBaseCoverage();
}
clipRadius = Math.max(clipRadius, radius + atmosphereRadOut + 0.5f);
}
/** Gets a sector a tile position. */

View File

@@ -623,9 +623,9 @@ public class UnitType extends UnlockableContent{
Draw.reset();
a.draw(unit);
}
Draw.reset();
}
Draw.reset();
}
public <T extends Unit & Payloadc> void drawPayload(T unit){

View File

@@ -62,6 +62,10 @@ public class Bar extends Element{
update(() -> this.name = name.get());
}
public void snap(){
lastValue = value = fraction.get();
}
public Bar outline(Color color, float stroke){
outlineColor.set(color);
outlineRadius = Scl.scl(stroke);

View File

@@ -74,19 +74,19 @@ public class Fonts{
largeIcons.clear();
FreeTypeFontParameter param = fontParameter();
Core.assets.load("default", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.def = (Font)f;
Core.assets.load("default", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.def = f;
Core.assets.load("icon", Font.class, new FreeTypeFontLoaderParameter("fonts/icon.ttf", new FreeTypeFontParameter(){{
size = 30;
incremental = true;
characters = "\0";
}})).loaded = f -> Fonts.icon = (Font)f;
}})).loaded = f -> Fonts.icon = f;
Core.assets.load("iconLarge", Font.class, new FreeTypeFontLoaderParameter("fonts/icon.ttf", new FreeTypeFontParameter(){{
size = 48;
incremental = false;
characters = "\0" + Iconc.all;
borderWidth = 5f;
borderColor = Color.darkGray;
}})).loaded = f -> Fonts.iconLarge = (Font)f;
}})).loaded = f -> Fonts.iconLarge = f;
}
public static TextureRegion getLargeIcon(String name){

View File

@@ -50,7 +50,7 @@ public class ItemsDisplay extends Table{
label.actions(Actions.color(Color.white, 0.75f, Interp.fade));
}
}
}).get().setScrollingDisabled(true, false), false).setDuration(0.3f);
}).scrollX(false), false).setDuration(0.3f);
c.button("@globalitems", Icon.downOpen, Styles.clearTogglet, col::toggle).update(t -> {
t.setChecked(col.isCollapsed());

View File

@@ -1,59 +1,6 @@
package mindustry.ui;
import arc.func.*;
import arc.graphics.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import mindustry.gen.*;
//TODO remove, unlikely to be used anywhere else.
public class SearchBar{
public static <T> Table add(Table parent, Seq<T> list, Func<String, String> queryf,
Func<T, String> namef, Cons2<Table, T> itemc, boolean show){
Table[] pane = {null};
Cons<String> rebuild = str -> {
String query = queryf.get(str);
pane[0].clear();
boolean any = false;
for(T item : list){
if(query.isEmpty() || matches(query, namef.get(item))){
any = true;
itemc.get(pane[0], item);
}
}
if(!any){
pane[0].add("@none.found").color(Color.lightGray).pad(4);
}
};
if(show){
parent.table(search -> {
search.image(Icon.zoom).padRight(8f);
search.field("", rebuild).growX();
}).fillX().padBottom(4);
}
parent.row();
parent.pane(table -> {
pane[0] = table;
rebuild.get("");
}).get().setScrollingDisabled(true, false);
return pane[0];
}
public static <T> Table add(Table parent, Seq<T> list, Func<String, String> queryf, Func<T, String> namef, Cons2<Table, T> itemc){
return add(parent, list, queryf, namef, itemc, true);
}
public static <T> Table add(Table parent, Seq<T> list, Func<T, String> namef, Cons2<Table, T> itemc, boolean show){
return add(parent, list, String::toLowerCase, namef, itemc, show);
}
/** Match a list item with the search query, case insensitive */
public static boolean matches(String query, String name){
return name != null && !name.isEmpty() && name.toLowerCase().contains(query);
}
}

View File

@@ -126,7 +126,7 @@ public class CustomRulesDialog extends BaseDialog{
void setup(){
cont.clear();
cont.pane(m -> main = m).get().setScrollingDisabled(true, false);
cont.pane(m -> main = m).scrollX(false);
main.margin(10f);
main.button("@settings.reset", () -> {
rules = resetter.get();
@@ -273,7 +273,7 @@ public class CustomRulesDialog extends BaseDialog{
t.add(text).left().padRight(5);
t.field((prov.get()) + "", s -> cons.get(Strings.parseInt(s)))
.padRight(100f)
.valid(f -> Strings.parseInt(f) >= min && Strings.parseInt(f) <= max).width(120f).left().addInputDialog();
.valid(f -> Strings.parseInt(f) >= min && Strings.parseInt(f) <= max).width(120f).left();
}).padTop(0).row();
}
@@ -285,7 +285,7 @@ public class CustomRulesDialog extends BaseDialog{
t.field((integer ? (int)prov.get() : prov.get()) + "", s -> cons.get(Strings.parseFloat(s)))
.padRight(100f)
.update(a -> a.setDisabled(!condition.get()))
.valid(f -> Strings.canParsePositiveFloat(f) && Strings.parseFloat(f) >= min && Strings.parseFloat(f) <= max).width(120f).left().addInputDialog();
.valid(f -> Strings.canParsePositiveFloat(f) && Strings.parseFloat(f) >= min && Strings.parseFloat(f) <= max).width(120f).left();
}).padTop(0);
main.row();
}
@@ -309,7 +309,7 @@ public class CustomRulesDialog extends BaseDialog{
Cell<TextField> field(Table table, float value, Floatc setter){
return table.field(Strings.autoFixed(value, 2), v -> setter.get(Strings.parseFloat(v)))
.valid(Strings::canParsePositiveFloat)
.size(90f, 40f).pad(2f).addInputDialog();
.size(90f, 40f).pad(2f);
}
void weatherDialog(){

View File

@@ -67,7 +67,7 @@ public class JoinDialog extends BaseDialog{
TextField field = add.cont.field(Core.settings.getString("ip"), text -> {
Core.settings.put("ip", text);
}).size(320f, 54f).maxTextLength(100).addInputDialog().get();
}).size(320f, 54f).maxTextLength(100).get();
add.cont.row();
add.buttons.defaults().size(140f, 60f).pad(4f);
@@ -287,7 +287,7 @@ public class JoinDialog extends BaseDialog{
t.field(Core.settings.getString("name"), text -> {
player.name(text);
Core.settings.put("name", text);
}).grow().pad(8).addInputDialog(maxNameLength);
}).grow().pad(8).maxTextLength(maxNameLength);
ImageButton button = t.button(Tex.whiteui, Styles.clearFulli, 40, () -> {
new PaletteDialog().show(color -> {

View File

@@ -131,7 +131,7 @@ public class LaunchLoadoutDialog extends BaseDialog{
}
}).growX().get().setScrollingDisabled(true, false);
}).growX().scrollX(false);
cont.row();
cont.pane(items);

View File

@@ -69,7 +69,7 @@ public class ModsDialog extends BaseDialog{
browser.cont.pane(tablebrow -> {
tablebrow.margin(10f).top();
browserTable = tablebrow;
}).get().setScrollingDisabled(true, false);
}).scrollX(false);
browser.addCloseButton();
browser.onResize(this::rebuildBrowser);
@@ -208,90 +208,113 @@ public class ModsDialog extends BaseDialog{
if(!mods.list().isEmpty()){
boolean[] anyDisabled = {false};
SearchBar.add(cont, mods.list(),
mod -> mod.meta.displayName(),
(table, mod) -> {
if(!mod.enabled() && !anyDisabled[0] && mods.list().size > 0){
anyDisabled[0] = true;
table.row();
table.image().growX().height(4f).pad(6f).color(Pal.gray);
table.row();
}
Table[] pane = {null};
table.button(t -> {
t.top().left();
t.margin(12f);
Cons<String> rebuild = query -> {
pane[0].clear();
boolean any = false;
for(LoadedMod item : mods.list()){
if(Strings.matches(query, item.meta.displayName())){
any = true;
if(!item.enabled() && !anyDisabled[0] && mods.list().size > 0){
anyDisabled[0] = true;
pane[0].row();
pane[0].image().growX().height(4f).pad(6f).color(Pal.gray).row();
}
t.defaults().left().top();
t.table(title -> {
title.left();
pane[0].button(t -> {
t.top().left();
t.margin(12f);
title.add(new BorderImage(){{
if(mod.iconTexture != null){
setDrawable(new TextureRegion(mod.iconTexture));
}else{
setDrawable(Tex.nomap);
}
border(Pal.accent);
}}).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f);
t.defaults().left().top();
t.table(title1 -> {
title1.left();
title.table(text -> {
boolean hideDisabled = !mod.isSupported() || mod.hasUnmetDependencies() || mod.hasContentErrors();
title1.add(new BorderImage(){{
if(item.iconTexture != null){
setDrawable(new TextureRegion(item.iconTexture));
}else{
setDrawable(Tex.nomap);
}
border(Pal.accent);
}}).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f);
text.add("[accent]" + Strings.stripColors(mod.meta.displayName()) + "\n[lightgray]v" + Strings.stripColors(trimText(mod.meta.version)) + (mod.enabled() || hideDisabled ? "" : "\n" + Core.bundle.get("mod.disabled") + ""))
title1.table(text -> {
boolean hideDisabled = !item.isSupported() || item.hasUnmetDependencies() || item.hasContentErrors();
text.add("[accent]" + Strings.stripColors(item.meta.displayName()) + "\n[lightgray]v" + Strings.stripColors(trimText(item.meta.version)) + (item.enabled() || hideDisabled ? "" : "\n" + Core.bundle.get("mod.disabled") + ""))
.wrap().top().width(300f).growX().left();
text.row();
if(mod.isOutdated()){
text.labelWrap("@mod.outdated").growX();
text.row();
}else if(!mod.isSupported()){
text.labelWrap(Core.bundle.format("mod.requiresversion", mod.meta.minGameVersion)).growX();
text.row();
}else if(mod.hasUnmetDependencies()){
text.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row();
}else if(mod.hasContentErrors()){
text.labelWrap("@mod.erroredcontent").growX();
text.row();
}else if(mod.meta.hidden){
text.labelWrap("@mod.multiplayer.compatible").growX();
text.row();
}
}).top().growX();
title.add().growX();
}).growX().growY().left();
if(item.isOutdated()){
text.labelWrap("@mod.outdated").growX();
text.row();
}else if(!item.isSupported()){
text.labelWrap(Core.bundle.format("mod.requiresversion", item.meta.minGameVersion)).growX();
text.row();
}else if(item.hasUnmetDependencies()){
text.labelWrap(Core.bundle.format("mod.missingdependencies", item.missingDependencies.toString(", "))).growX();
t.row();
}else if(item.hasContentErrors()){
text.labelWrap("@mod.erroredcontent").growX();
text.row();
}else if(item.meta.hidden){
text.labelWrap("@mod.multiplayer.compatible").growX();
text.row();
}
}).top().growX();
t.table(right -> {
right.right();
right.button(mod.enabled() ? Icon.downOpen : Icon.upOpen, Styles.clearPartiali, () -> {
mods.setEnabled(mod, !mod.enabled());
setup();
}).size(50f).disabled(!mod.isSupported());
title1.add().growX();
}).growX().growY().left();
right.button(mod.hasSteamID() ? Icon.link : Icon.trash, Styles.clearPartiali, () -> {
if(!mod.hasSteamID()){
ui.showConfirm("@confirm", "@mod.remove.confirm", () -> {
mods.removeMod(mod);
setup();
});
}else{
platform.viewListing(mod);
}
}).size(50f);
t.table(right -> {
right.right();
right.button(item.enabled() ? Icon.downOpen : Icon.upOpen, Styles.clearPartiali, () -> {
mods.setEnabled(item, !item.enabled());
setup();
}).size(50f).disabled(!item.isSupported());
if(steam && !mod.hasSteamID()){
right.row();
right.button(Icon.export, Styles.clearPartiali, () -> {
platform.publish(mod);
right.button(item.hasSteamID() ? Icon.link : Icon.trash, Styles.clearPartiali, () -> {
if(!item.hasSteamID()){
ui.showConfirm("@confirm", "@mod.remove.confirm", () -> {
mods.removeMod(item);
setup();
});
}else{
platform.viewListing(item);
}
}).size(50f);
}
}).growX().right().padRight(-8f).padTop(-8f);
}, Styles.clearPartialt, () -> showMod(mod)).size(w, h).growX().pad(4f);
table.row();
}, !mobile || Core.graphics.isPortrait()).margin(10f).top();
if(steam && !item.hasSteamID()){
right.row();
right.button(Icon.export, Styles.clearPartiali, () -> {
platform.publish(item);
}).size(50f);
}
}).growX().right().padRight(-8f).padTop(-8f);
}, Styles.clearPartialt, () -> showMod(item)).size(w, h).growX().pad(4f);
pane[0].row();
}
}
if(!any){
pane[0].add("@none.found").color(Color.lightGray).pad(4);
}
};
if(!mobile || Core.graphics.isPortrait()){
cont.table(search -> {
search.image(Icon.zoom).padRight(8f);
search.field("", rebuild).growX();
}).fillX().padBottom(4);
}
cont.row();
cont.pane(table1 -> {
pane[0] = table1.margin(10f).top();
rebuild.get("");
}).scrollX(false);
}else{
cont.table(Styles.black6, t -> t.add("@mods.none")).height(80f);
}

View File

@@ -27,7 +27,6 @@ import mindustry.graphics.*;
import mindustry.graphics.g3d.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.input.*;
import mindustry.io.legacy.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.ui.*;
@@ -65,7 +64,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
public boolean showed = false, sectorsShown;
public String searchText = "";
public Table sectorTop = new Table(), notifs;
public Table sectorTop = new Table(), notifs = new Table();
public Label hoverLabel = new Label("");
public PlanetDialog(){
@@ -131,7 +130,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
@Override
public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY){
if(event.targetActor == PlanetDialog.this){
zoom = Mathf.clamp(zoom + y / 10f, planets.planet.minZoom, 2f);
zoom = Mathf.clamp(zoom + amountY / 10f, planets.planet.minZoom, 2f);
}
return true;
}
@@ -166,16 +165,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
return this;
}
//load legacy research
if(Core.settings.has("unlocks") && !Core.settings.has("junction-unlocked")){
Core.app.post(() -> {
ui.showCustomConfirm("@research", "@research.legacy", "@research.load", "@research.discard", () -> {
LegacyIO.readResearch();
Core.settings.remove("unlocks");
}, () -> Core.settings.remove("unlocks"));
});
}
rebuildButtons();
mode = look;
selected = hovered = launchSector = null;
@@ -545,7 +534,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}),
new Table(c -> {
if(!(graphics.isPortrait() && mobile) && planets.planet.sectors.contains(Sector::hasBase)){
c.visible(() -> !(graphics.isPortrait() && mobile));
if(planets.planet.sectors.contains(Sector::hasBase)){
int attacked = planets.planet.sectors.count(Sector::isAttacked);
//sector notifications & search
@@ -571,8 +561,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
})).grow();
}
//TODO
void rebuildList(){
if(notifs == null) return;
notifs.clear();
var all = planets.planet.sectors.select(Sector::hasBase);
@@ -586,7 +577,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
s.field(searchText, t -> {
searchText = t;
readd[0].run();
}).growX().height(50f).addInputDialog();
}).growX().height(50f);
}).growX().row();
Table con = p.table().growX().get();
@@ -632,7 +623,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
};
readd[0].run();
}).grow().get().setScrollingDisabled(true, false);
}).grow().scrollX(false);
}
@Override

View File

@@ -41,7 +41,7 @@ public class SchematicsDialog extends BaseDialog{
public SchematicsDialog(){
super("@schematics");
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> ((Texture)t).setWrap(TextureWrap.repeat);
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> t.setWrap(TextureWrap.repeat);
tags = Core.settings.getJson("schematic-tags", Seq.class, String.class, Seq::new);
@@ -69,7 +69,7 @@ public class SchematicsDialog extends BaseDialog{
searchField = s.field(search, res -> {
search = res;
rebuildPane.run();
}).growX().addInputDialog().get();
}).growX().get();
}).fillX().padBottom(4);
cont.row();
@@ -97,7 +97,7 @@ public class SchematicsDialog extends BaseDialog{
}
};
rebuildTags.run();
}).fillX().height(tagh).get().setScrollingDisabled(false, true);
}).fillX().height(tagh).scrollY(false);
in.button(Icon.pencilSmall, () -> {
showAllTags();
@@ -164,12 +164,12 @@ public class SchematicsDialog extends BaseDialog{
cont.table(tags -> buildTags(s, tags, false)).maxWidth(400f).fillX().left().row();
cont.margin(30).add("@name").padRight(6f);
TextField nameField = cont.field(s.name(), null).size(400f, 55f).addInputDialog().left().get();
TextField nameField = cont.field(s.name(), null).size(400f, 55f).left().get();
cont.row();
cont.margin(30).add("@editor.description").padRight(6f);
TextField descField = cont.area(s.description(), Styles.areaField, t -> {}).size(400f, 140f).left().addInputDialog().get();
TextField descField = cont.area(s.description(), Styles.areaField, t -> {}).size(400f, 140f).left().get();
Runnable accept = () -> {
s.tags.put("name", nameField.getText());
@@ -246,7 +246,7 @@ public class SchematicsDialog extends BaseDialog{
};
rebuildPane.run();
}).grow().get().setScrollingDisabled(true, false);
}).grow().scrollX(false);
}
public void showInfo(Schematic schematic){
@@ -573,7 +573,7 @@ public class SchematicsDialog extends BaseDialog{
});
}
}).fillX().left().height(tagh).get().setScrollingDisabled(false, true);
}).fillX().left().height(tagh).scrollY(false);
t.button(Icon.addSmall, () -> {
var dialog = new BaseDialog("@schematic.addtag");

View File

@@ -48,12 +48,12 @@ public class HintsFragment extends Fragment{
}else if(!current.show()){ //current became hidden
hide();
}
}else if(hints.size > 0 && !renderer.isCutscene()){
}else if(hints.size > 0){
//check one hint each frame to see if it should be shown.
Hint hint = hints.find(Hint::show);
if(hint != null && hint.complete()){
hints.remove(hint);
}else if(hint != null){
}else if(hint != null && !renderer.isCutscene() && state.isGame() && control.saves.getTotalPlaytime() > 8000){
display(hint);
}else{
//moused over a derelict structure
@@ -92,7 +92,7 @@ public class HintsFragment extends Fragment{
hints.sort(Hint::order);
Hint first = hints.find(Hint::show);
if(first != null){
if(first != null && !renderer.isCutscene() && state.isGame()){
hints.remove(first);
display(first);
}

View File

@@ -56,6 +56,10 @@ public class LoadingFragment extends Fragment{
bar.set(() -> ((int)(progress.get() * 100) + "%"), progress, Pal.accent);
}
public void snapProgress(){
bar.snap();
}
public void setProgress(float progress){
progValue = progress;
if(!bar.visible){

View File

@@ -52,7 +52,7 @@ public class PlayerListFragment extends Fragment{
search.setMessageText(Core.bundle.get("players.search"));
pane.row();
pane.pane(content).grow().get().setScrollingDisabled(true, false);
pane.pane(content).grow().scrollX(false);
pane.row();
pane.table(menu -> {

View File

@@ -37,8 +37,11 @@ import java.util.*;
import static mindustry.Vars.*;
public class Block extends UnlockableContent{
/** If true, buildings have an ItemModule. */
public boolean hasItems;
/** If true, buildings have a LiquidModule. */
public boolean hasLiquids;
/** If true, buildings have a PowerModule. */
public boolean hasPower;
public boolean outputsLiquid = false;
@@ -46,8 +49,6 @@ public class Block extends UnlockableContent{
public boolean outputsPower = false;
public boolean outputsPayload = false;
public boolean acceptsPayload = false;
public boolean outputFacing = true;
public boolean noSideBlend = false;
public boolean acceptsItems = false;
public int itemCapacity = 10;
@@ -57,6 +58,11 @@ public class Block extends UnlockableContent{
public final BlockBars bars = new BlockBars();
public final Consumers consumes = new Consumers();
/** If true, this block outputs to its facing direction, when applicable.
* Used for blending calculations. */
public boolean outputFacing = true;
/** if true, this block does not accept input from the sides (used for armored conveyors) */
public boolean noSideBlend = false;
/** whether to display flow rate */
public boolean displayFlow = true;
/** whether this block is visible in the editor */

View File

@@ -151,7 +151,7 @@ public class Build{
if(closest != null && closest.team != team){
return false;
}
}else if(state.teams.eachEnemyCore(team, core -> Mathf.dst(x * tilesize + type.offset, y * tilesize + type.offset, core.x, core.y) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f)){
}else if(state.teams.anyEnemyCoresWithin(team, x * tilesize + type.offset, y * tilesize + type.offset, state.rules.enemyCoreBuildRadius + tilesize)){
return false;
}
}

View File

@@ -122,7 +122,7 @@ public class LaunchPad extends Block{
if(!state.isCampaign()) return;
//increment launchCounter then launch when full and base conditions are met
if((launchCounter += edelta()) >= launchTime && items.total() >= itemCapacity){
if((launchCounter += edelta()) >= launchTime && edelta() >= 0.001f && items.total() >= itemCapacity){
launchSound.at(x, y);
LaunchPayload entity = LaunchPayload.create();
items.each((item, amount) -> entity.stacks.add(new ItemStack(item, amount)));

View File

@@ -38,8 +38,8 @@ public class LaserTurret extends PowerTurret{
}
public class LaserTurretBuild extends PowerTurretBuild{
Bullet bullet;
float bulletLife;
public Bullet bullet;
public float bulletLife;
@Override
protected void updateCooling(){

View File

@@ -43,6 +43,12 @@ public class DuctBridge extends Block{
drawArrow = false;
}
@Override
public void init(){
clipSize = Math.max(clipSize, (range + 0.5f) * 2 * tilesize);
super.init();
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());

View File

@@ -113,7 +113,7 @@ public class PayloadConveyor extends Block{
int ntrns = 1 + size/2;
Tile next = tile.nearby(Geometry.d4(rotation).x * ntrns, Geometry.d4(rotation).y * ntrns);
blocked = (next != null && next.solid() && !next.block().outputsPayload) || (this.next != null && (this.next.rotation + 2)%4 == rotation);
blocked = (next != null && next.solid() && !(next.block().outputsPayload || next.block().acceptsPayload)) || (this.next != null && this.next.block.rotate && (this.next.rotation + 2) % 4 == rotation);
}
@Override

View File

@@ -23,11 +23,9 @@ import mindustry.world.modules.*;
import static mindustry.Vars.*;
public class PowerNode extends PowerBlock{
protected static boolean returnValue = false;
protected static BuildPlan otherReq;
protected final static ObjectSet<PowerGraph> graphs = new ObjectSet<>();
protected static int returnInt = 0;
protected final static ObjectSet<PowerGraph> graphs = new ObjectSet<>();
public @Load("laser") TextureRegion laser;
public @Load("laser-end") TextureRegion laserEnd;
@@ -134,6 +132,13 @@ public class PowerNode extends PowerBlock{
stats.add(Stat.powerConnections, maxNodes, StatUnit.none);
}
@Override
public void init(){
super.init();
clipSize = Math.max(clipSize, laserRange * tilesize);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Tile tile = world.tile(x, y);

View File

@@ -50,7 +50,7 @@ public class SolidPump extends Pump{
bars.add("efficiency", (SolidPumpBuild entity) -> new Bar(() -> Core.bundle.formatFloat("bar.pumpspeed",
entity.lastPump / Time.delta * 60, 1),
() -> Pal.ammo,
() -> entity.warmup));
() -> entity.warmup * entity.efficiency()));
}
@Override
@@ -116,7 +116,7 @@ public class SolidPump extends Pump{
lastPump = 0f;
}
pumpTime += warmup * delta();
pumpTime += warmup * edelta();
dumpLiquid(result);
}

View File

@@ -143,10 +143,15 @@ public class UnitFactory extends UnitBlock{
@Override
public Object senseObject(LAccess sensor){
if(sensor == LAccess.config) return currentPlan == -1 ? null : plans.get(currentPlan).unit;
if(sensor == LAccess.progress) return Mathf.clamp(fraction());
return super.senseObject(sensor);
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(fraction());
return super.sense(sensor);
}
@Override
public void buildConfiguration(Table table){
Seq<UnitType> units = Seq.with(plans).map(u -> u.unit).filter(u -> u.unlockedNow() && !u.isBanned());

View File

@@ -45,6 +45,6 @@ public class DrawAnimation extends DrawBlock{
@Override
public TextureRegion[] icons(Block block){
return new TextureRegion[]{block.region, top};
return top.found() ? new TextureRegion[]{block.region, top} : new TextureRegion[]{block.region};
}
}