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

This commit is contained in:
Anuken
2021-09-13 20:52:41 -04:00
82 changed files with 1517 additions and 1114 deletions

View File

@@ -56,6 +56,9 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Log.info("[GL] Using @ context.", gl30 != null ? "OpenGL 3" : "OpenGL 2");
if(maxTextureSize < 4096) Log.warn("[GL] Your maximum texture size is below the recommended minimum of 4096. This will cause severe performance issues.");
Log.info("[JAVA] Version: @", OS.javaVersion);
long ram = Runtime.getRuntime().maxMemory();
boolean gb = ram >= 1024 * 1024 * 1024;
Log.info("[RAM] Available: @ @", Strings.fixed(gb ? ram / 1024f / 1024 / 1024f : ram / 1024f / 1024f, 1), gb ? "GB" : "MB");
Time.setDeltaProvider(() -> {
float result = Core.graphics.getDeltaTime() * 60f;

View File

@@ -12,6 +12,7 @@ import mindustry.game.*;
import mindustry.game.Schematic.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.maps.generators.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.defense.*;
@@ -25,7 +26,6 @@ import static mindustry.Vars.*;
public class BaseAI{
private static final Vec2 axis = new Vec2(), rotator = new Vec2();
private static final float correctPercent = 0.5f;
private static final int attempts = 4;
private static final float emptyChance = 0.01f;
private static final int timerStep = 0, timerSpawn = 1, timerRefreshPath = 2;
@@ -46,6 +46,7 @@ public class BaseAI{
boolean calculating, startedCalculating;
int calcCount = 0;
int totalCalcs = 0;
Block wallType;
public BaseAI(TeamData data){
this.data = data;
@@ -53,6 +54,10 @@ public class BaseAI{
public void update(){
if(wallType == null){
wallType = BaseGenerator.getDifficultyWall(1, data.team.rules().aiTier / 0.8f);
}
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);
@@ -271,7 +276,7 @@ public class BaseAI{
}
private void tryWalls(){
Block wall = Blocks.copperWall;
Block wall = wallType;
Building spawnt = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core() : data.team.core();
Tile spawn = spawnt == null ? null : spawnt.tile;

View File

@@ -50,10 +50,12 @@ public class BlockIndexer{
clearFlags();
Events.on(TilePreChangeEvent.class, event -> {
if(state.isEditor()) return;
removeIndex(event.tile);
});
Events.on(TileChangeEvent.class, event -> {
if(state.isEditor()) return;
addIndex(event.tile);
});
@@ -109,7 +111,7 @@ public class BlockIndexer{
}
}
//update the unit cap when building is remove
//update the unit cap when building is removed
data.unitCap -= tile.block().unitCapModifier;
//unregister building from building quadtree

View File

@@ -139,7 +139,7 @@ public class Pathfinder implements Runnable{
/** Starts or restarts the pathfinding thread. */
private void start(){
stop();
thread = Threads.daemon(this);
thread = Threads.daemon("Pathfinder", this);
}
/** Stops the pathfinding thread. */

View File

@@ -56,7 +56,7 @@ public class WaveSpawner{
public void spawnEnemies(){
spawning = true;
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
eachGroundSpawn(-1, (spawnX, spawnY, doShockwave) -> {
if(doShockwave){
doShockwave(spawnX, spawnY);
}
@@ -70,7 +70,7 @@ public class WaveSpawner{
if(group.type.flying){
float spread = margin / 1.5f;
eachFlyerSpawn((spawnX, spawnY) -> {
eachFlyerSpawn(group.spawn, (spawnX, spawnY) -> {
for(int i = 0; i < spawned; i++){
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
@@ -80,7 +80,7 @@ public class WaveSpawner{
}else{
float spread = tilesize * 2;
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
eachGroundSpawn(group.spawn, (spawnX, spawnY, doShockwave) -> {
for(int i = 0; i < spawned; i++){
Tmp.v1.rnd(spread);
@@ -102,12 +102,14 @@ public class WaveSpawner{
}
public void eachGroundSpawn(Intc2 cons){
eachGroundSpawn((x, y, shock) -> cons.get(World.toTile(x), World.toTile(y)));
eachGroundSpawn(-1, (x, y, shock) -> cons.get(World.toTile(x), World.toTile(y)));
}
private void eachGroundSpawn(SpawnConsumer cons){
private void eachGroundSpawn(int filterPos, SpawnConsumer cons){
if(state.hasSpawns()){
for(Tile spawn : spawns){
if(filterPos != -1 && filterPos != spawn.pos()) continue;
cons.accept(spawn.worldx(), spawn.worldy(), true);
}
}
@@ -115,6 +117,8 @@ public class WaveSpawner{
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
Building firstCore = state.teams.playerCores().first();
for(Building core : state.rules.waveTeam.cores()){
if(filterPos != -1 && filterPos != core.pos()) continue;
Tmp.v1.set(firstCore).sub(core).limit(coreMargin + core.block.size * tilesize /2f * Mathf.sqrt2);
boolean valid = false;
@@ -147,8 +151,10 @@ public class WaveSpawner{
}
}
private void eachFlyerSpawn(Floatc2 cons){
private void eachFlyerSpawn(int filterPos, Floatc2 cons){
for(Tile tile : spawns){
if(filterPos != -1 && filterPos != tile.pos()) continue;
float angle = Angles.angle(world.width() / 2f, world.height() / 2f, tile.x, tile.y);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
@@ -158,6 +164,8 @@ public class WaveSpawner{
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam)){
for(Building core : state.rules.waveTeam.data().cores){
if(filterPos != -1 && filterPos != core.pos()) continue;
cons.get(core.x, core.y);
}
}
@@ -171,7 +179,7 @@ public class WaveSpawner{
public int countFlyerSpawns(){
tmpCount = 0;
eachFlyerSpawn((x, y) -> tmpCount ++);
eachFlyerSpawn(-1, (x, y) -> tmpCount ++);
return tmpCount;
}

View File

@@ -179,7 +179,7 @@ public class SoundControl{
float avol = Core.settings.getInt("ambientvol", 100) / 100f;
sounds.each((sound, data) -> {
data.curVolume = Mathf.lerpDelta(data.curVolume, data.volume * avol, 0.2f);
data.curVolume = Mathf.lerpDelta(data.curVolume, data.volume * avol, 0.11f);
boolean play = data.curVolume > 0.01f;
float pan = Mathf.zero(data.total, 0.0001f) ? 0f : sound.calcPan(data.sum.x / data.total, data.sum.y / data.total);

View File

@@ -1199,6 +1199,7 @@ public class Blocks implements ContentList{
distributor = new Router("distributor"){{
requirements(Category.distribution, with(Items.lead, 4, Items.copper, 4));
buildCostMultiplier = 3f;
size = 2;
}};

View File

@@ -32,7 +32,9 @@ public class Fx{
//lifetime is how many frames it takes to fade out the trail
e.lifetime = trail.length * 1.4f;
trail.shorten();
if(!state.isPaused()){
trail.shorten();
}
trail.drawCap(e.color, e.rotation);
trail.draw(e.color, e.rotation);
}),

View File

@@ -10,7 +10,8 @@ public class SectorPresets implements ContentList{
groundZero,
craters, biomassFacility, frozenForest, ruinousShores, windsweptIslands, stainedMountains, tarFields,
fungalPass, extractionOutpost, saltFlats, overgrowth,
impact0078, desolateRift, nuclearComplex, planetaryTerminal;
impact0078, desolateRift, nuclearComplex, planetaryTerminal,
coastline, navalFortress;
@Override
public void load(){
@@ -64,6 +65,15 @@ public class SectorPresets implements ContentList{
useAI = false;
}};
coastline = new SectorPreset("coastline", serpulo, 108){{
captureWave = 30;
difficulty = 5;
}};
navalFortress = new SectorPreset("navalFortress", serpulo, 216){{
difficulty = 9;
}};
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
difficulty = 4;
useAI = false;

View File

@@ -421,11 +421,11 @@ public class TechTree implements ContentList{
});
});
node(retusa, () -> {
node(oxynoe, () -> {
node(retusa, Seq.with(new SectorComplete(windsweptIslands)), () -> {
node(oxynoe, Seq.with(new SectorComplete(coastline)), () -> {
node(cyerce, () -> {
node(aegires, () -> {
node(navanax, () -> {
node(navanax, Seq.with(new SectorComplete(navalFortress)), () -> {
});
});
@@ -531,7 +531,22 @@ public class TechTree implements ContentList{
new Research(airFactory),
new Research(door)
), () -> {
node(coastline, Seq.with(
new SectorComplete(windsweptIslands),
new Research(navalFactory),
new Research(payloadConveyor)
), () -> {
node(navalFortress, Seq.with(
new SectorComplete(coastline),
new SectorComplete(extractionOutpost),
new Research(oxynoe),
new Research(minke),
new Research(cyclone),
new Research(ripple)
), () -> {
});
});
});
});
});

View File

@@ -8,8 +8,9 @@ import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.entities.bullet.*;
import mindustry.game.EventType.*;
import mindustry.io.*;
import mindustry.mod.Mods.*;
import mindustry.type.*;
import mindustry.world.*;
@@ -206,10 +207,16 @@ public class ContentLoader{
}
public <T extends MappableContent> T getByName(ContentType type, String name){
if(contentNameMap[type.ordinal()] == null){
return null;
var map = contentNameMap[type.ordinal()];
if(map == null) return null;
//load fallbacks
if(type == ContentType.block){
name = SaveVersion.modContentNameMap.get(name, name);
}
return (T)contentNameMap[type.ordinal()].get(name);
return (T)map.get(name);
}
public <T extends Content> T getByID(ContentType type, int id){

View File

@@ -218,6 +218,8 @@ public class NetClient implements ApplicationListener{
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
message = message.replace("\n", "");
Events.fire(new PlayerChatEvent(player, message));
//log commands before they are handled

View File

@@ -397,7 +397,9 @@ public class NetServer implements ApplicationListener{
}
if(found != null){
if(found.admin){
if(found == player){
player.sendMessage("[scarlet]You can't vote to kick yourself.");
}else if(found.admin){
player.sendMessage("[scarlet]Did you really expect to be able to kick an admin?");
}else if(found.isLocal()){
player.sendMessage("[scarlet]Local players cannot be kicked.");

View File

@@ -114,7 +114,7 @@ public class Renderer implements ApplicationListener{
public void update(){
Color.white.set(1f, 1f, 1f, 1f);
float dest = Mathf.round(targetscale, 0.5f);
float dest = Mathf.clamp(Mathf.round(targetscale, 0.5f), minScale(), maxScale());
camerascale = Mathf.lerpDelta(camerascale, dest, 0.1f);
if(Mathf.equal(camerascale, dest, 0.001f)) camerascale = dest;
laserOpacity = settings.getInt("lasersopacity") / 100f;
@@ -504,6 +504,8 @@ public class Renderer implements ApplicationListener{
public void showLaunch(CoreBlock coreType){
Vars.ui.hudfrag.showLaunch();
Vars.control.input.frag.config.hideConfig();
Vars.control.input.frag.inv.hide();
launchCoreType = coreType;
launching = true;
landCore = player.team().core();

View File

@@ -18,7 +18,7 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class MapEditor{
public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15, 20};
public static final float[] brushSizes = {1, 1.5f, 2, 3, 4, 5, 9, 15, 20};
public StringMap tags = new StringMap();
public MapRenderer renderer = new MapRenderer();
@@ -28,7 +28,7 @@ public class MapEditor{
private DrawOperation currentOp;
private boolean loading;
public int brushSize = 1;
public float brushSize = 1;
public int rotation;
public Block drawBlock = Blocks.stone;
public Team drawTeam = Team.sharded;
@@ -227,8 +227,9 @@ public class MapEditor{
}
public void drawCircle(int x, int y, Cons<Tile> drawer){
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
int clamped = (int)brushSize;
for(int rx = -clamped; rx <= clamped; rx++){
for(int ry = -clamped; ry <= clamped; ry++){
if(Mathf.within(rx, ry, brushSize - 0.5f + 0.0001f)){
int wx = x + rx, wy = y + ry;
@@ -243,8 +244,9 @@ public class MapEditor{
}
public void drawSquare(int x, int y, Cons<Tile> drawer){
for(int rx = -brushSize; rx <= brushSize; rx++){
for(int ry = -brushSize; ry <= brushSize; ry++){
int clamped = (int)brushSize;
for(int rx = -clamped; rx <= clamped; rx++){
for(int ry = -clamped; ry <= clamped; ry++){
int wx = x + rx, wy = y + ry;
if(wx < 0 || wy < 0 || wx >= width() || wy >= height()){

View File

@@ -576,13 +576,11 @@ public class MapEditorDialog extends Dialog implements Disposable{
}).growX().top();
}
if(experimental){
mid.row();
mid.row();
mid.table(t -> {
t.button("Cliffs", Icon.terrain, Styles.cleart, editor::addCliffs).growX().margin(9f);
}).growX().top();
}
mid.table(t -> {
t.button("@editor.cliffs", Icon.terrain, Styles.cleart, editor::addCliffs).growX().margin(9f);
}).growX().top();
}).margin(0).left().growY();

View File

@@ -10,6 +10,7 @@ import mindustry.content.*;
import mindustry.game.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
@@ -132,6 +133,11 @@ public class MapRenderer implements Disposable{
wall.editorVariantRegions()[Mathf.randomSeed(idxWall, 0, wall.editorVariantRegions().length - 1)] :
wall.editorIcon();
if(wall == Blocks.cliff){
mesh.setColor(Tmp.c1.set(floor.mapColor).mul(1.6f));
region = ((Cliff)Blocks.cliff).editorCliffs[tile.data & 0xff];
}
offsetX = tilesize / 2f - region.width / 2f * Draw.scl;
offsetY = tilesize / 2f - region.height / 2f * Draw.scl;
}else if(wall == Blocks.air && !tile.overlay().isAir()){

View File

@@ -38,7 +38,8 @@ public class MapView extends Element implements GestureListener{
for(int i = 0; i < MapEditor.brushSizes.length; i++){
float size = MapEditor.brushSizes[i];
brushPolygons[i] = Geometry.pixelCircle(size, (index, x, y) -> Mathf.dst(x, y, index, index) <= index - 0.5f);
float mod = size % 1f;
brushPolygons[i] = Geometry.pixelCircle(size, (index, x, y) -> Mathf.dst(x, y, index - mod, index - mod) <= size - 0.5f);
}
Core.input.getInputProcessors().insert(0, new GestureDetector(20, 0.5f, 2, 0.15f, this));

View File

@@ -38,8 +38,14 @@ public class WaveGraph extends Table{
lay.setText(font, "1");
int maxY = switch(mode){
case counts -> nextStep(max);
case health -> nextStep((int)maxHealth);
case totals -> nextStep(maxTotal);
};
float fh = lay.height;
float offsetX = Scl.scl(30f), offsetY = Scl.scl(22f) + fh + Scl.scl(5f);
float offsetX = Scl.scl(lay.width * (maxY + "").length() * 2), offsetY = Scl.scl(22f) + fh + Scl.scl(5f);
float graphX = x + offsetX, graphY = y + offsetY, graphW = width - offsetX, graphH = height - offsetY;
float spacing = graphW / (values.length - 1);
@@ -53,7 +59,7 @@ public class WaveGraph extends Table{
for(int i = 0; i < values.length; i++){
int val = values[i][type.id];
float cx = graphX + i*spacing, cy = graphY + val * graphH / max;
float cx = graphX + i * spacing, cy = graphY + val * graphH / maxY;
Lines.linePoint(cx, cy);
}
@@ -69,7 +75,7 @@ public class WaveGraph extends Table{
sum += values[i][type.id];
}
float cx = graphX + i*spacing, cy = graphY + sum * graphH / maxTotal;
float cx = graphX + i * spacing, cy = graphY + sum * graphH / maxY;
Lines.linePoint(cx, cy);
}
@@ -84,7 +90,7 @@ public class WaveGraph extends Table{
sum += (type.health) * values[i][type.id];
}
float cx = graphX + i*spacing, cy = graphY + sum * graphH / maxHealth;
float cx = graphX + i * spacing, cy = graphY + sum * graphH / maxY;
Lines.linePoint(cx, cy);
}
@@ -92,19 +98,23 @@ public class WaveGraph extends Table{
}
//how many numbers can fit here
float totalMarks = (graphH - getMarginBottom() *2f) / (lay.height * 2);
float totalMarks = Mathf.clamp(maxY, 1, 10);
int markSpace = Math.max(1, Mathf.ceil(max / totalMarks));
int markSpace = Math.max(1, Mathf.ceil(maxY / totalMarks));
Draw.color(Color.lightGray);
for(int i = 0; i < max; i += markSpace){
float cy = graphY + i * graphH / max, cx = graphX;
//Lines.line(cx, cy, cx + len, cy);
Draw.alpha(0.1f);
for(int i = 0; i < maxY; i += markSpace){
float cy = graphY + i * graphH / maxY, cx = graphX;
Lines.line(cx, cy, cx + graphW, cy);
lay.setText(font, "" + i);
font.draw("" + i, cx, cy + lay.height/2f, Align.right);
font.draw("" + i, cx, cy + lay.height / 2f, Align.right);
}
Draw.alpha(1f);
float len = Scl.scl(4f);
font.setColor(Color.lightGray);
@@ -113,7 +123,7 @@ public class WaveGraph extends Table{
float cy = y + fh, cx = graphX + graphW / (values.length - 1) * i;
Lines.line(cx, cy, cx, cy + len);
if(i == values.length/2){
if(i == values.length / 2){
font.draw("" + (i + from + 1), cx, cy - Scl.scl(2f), Align.center);
}
}
@@ -164,13 +174,24 @@ public class WaveGraph extends Table{
sum += spawned;
}
maxTotal = Math.max(maxTotal, sum);
maxHealth = Math.max(maxHealth,healthsum);
maxHealth = Math.max(maxHealth, healthsum);
}
ObjectSet<UnitType> usedCopy = new ObjectSet<>(used);
colors.clear();
colors.left();
colors.button("@waves.units.hide", Styles.cleart, () -> {
if(hidden.size == usedCopy.size){
hidden.clear();
}else{
hidden.addAll(usedCopy);
}
used.clear();
used.addAll(usedCopy);
for(UnitType o : hidden) used.remove(o);
}).update(b -> b.setText(hidden.size == usedCopy.size ? "@waves.units.show" : "@waves.units.hide")).height(32f).width(130f);
colors.pane(t -> {
t.left();
for(UnitType type : used){
@@ -200,6 +221,23 @@ public class WaveGraph extends Table{
return Tmp.c1.fromHsv(type.id / (float)Vars.content.units().size * 360f, 0.7f, 1f);
}
int nextStep(float value){
int order = 1;
while(order < value){
if(order * 2 > value){
return order * 2;
}
if(order * 5 > value){
return order * 5;
}
if(order * 10 > value){
return order * 10;
}
order *= 10;
}
return order;
}
enum Mode{
counts, totals, health;

View File

@@ -1,9 +1,9 @@
package mindustry.editor;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
@@ -20,6 +20,8 @@ import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import java.util.*;
import static mindustry.Vars.*;
import static mindustry.game.SpawnGroup.*;
@@ -68,41 +70,46 @@ public class WaveInfoDialog extends BaseDialog{
addCloseButton();
buttons.button("@waves.edit", () -> {
buttons.button("@waves.edit", Icon.pencil, () -> {
BaseDialog dialog = new BaseDialog("@waves.edit");
dialog.addCloseButton();
dialog.setFillParent(false);
dialog.cont.defaults().size(210f, 64f);
dialog.cont.button("@waves.copy", () -> {
ui.showInfoFade("@waves.copied");
Core.app.setClipboardText(maps.writeWaves(groups));
dialog.hide();
}).disabled(b -> groups == null);
dialog.cont.row();
dialog.cont.button("@waves.load", () -> {
try{
groups = maps.readWaves(Core.app.getClipboardText());
dialog.cont.table(Tex.button, t -> {
var style = Styles.cleart;
t.defaults().size(210f, 58f);
t.button("@waves.copy", Icon.copy, style, () -> {
ui.showInfoFade("@waves.copied");
Core.app.setClipboardText(maps.writeWaves(groups));
dialog.hide();
}).disabled(b -> groups == null).marginLeft(12f).row();
t.button("@waves.load", Icon.download, style, () -> {
try{
groups = maps.readWaves(Core.app.getClipboardText());
buildGroups();
}catch(Exception e){
e.printStackTrace();
ui.showErrorMessage("@waves.invalid");
}
dialog.hide();
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty()).row();
t.button("@settings.reset", Icon.upload, style, () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
groups = JsonIO.copy(waves.get());
buildGroups();
}catch(Exception e){
e.printStackTrace();
ui.showErrorMessage("@waves.invalid");
}
dialog.hide();
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
dialog.cont.row();
dialog.cont.button("@settings.reset", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
groups = JsonIO.copy(waves.get());
buildGroups();
dialog.hide();
}));
dialog.cont.row();
dialog.cont.button("@clear", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
groups.clear();
buildGroups();
dialog.hide();
}));
dialog.hide();
})).marginLeft(12f).row();
t.button("@clear", Icon.cancel, style, () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
groups.clear();
buildGroups();
dialog.hide();
})).marginLeft(12f);
});
dialog.show();
}).size(270f, 64f);
}).size(250f, 64f);
buttons.defaults().width(60f);
@@ -205,6 +212,13 @@ public class WaveInfoDialog extends BaseDialog{
b.label(() -> (group.begin + 1) + "").color(Color.lightGray).minWidth(45f).labelAlign(Align.left).left();
b.button(Icon.copySmall, Styles.emptyi, () -> {
SpawnGroup newGroup = group.copy();
expandedGroup = newGroup;
groups.add(newGroup);
buildGroups();
}).pad(-6).size(46f);
b.button(group.effect != null && group.effect != StatusEffects.none ?
new TextureRegionDrawable(group.effect.uiIcon) :
Icon.logicSmall,
@@ -215,7 +229,7 @@ public class WaveInfoDialog extends BaseDialog{
groups.remove(group);
table.getCell(t).pad(0f);
t.remove();
updateWaves();
buildGroups();
}).pad(-6).size(46f).padRight(-12f);
}, () -> {
expandedGroup = expandedGroup == group ? null : group;
@@ -303,7 +317,30 @@ public class WaveInfoDialog extends BaseDialog{
t.check("@waves.guardian", b -> {
group.effect = (b ? StatusEffects.boss : null);
buildGroups();
}).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
}).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f).row();
//spawn positions are clunky and thus experimental for now
if(experimental){
t.table(a -> {
a.add("spawn at ");
a.field(group.spawn == -1 ? "" : Point2.x(group.spawn) + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
group.spawn = Point2.pack(Strings.parseInt(text), Point2.y(group.spawn));
Log.info(group.spawn);
}
}).width(70f);
a.add(",");
a.field(group.spawn == -1 ? "" : Point2.y(group.spawn) + "", TextFieldFilter.digitsOnly, text -> {
if(Strings.canParsePositiveInt(text)){
group.spawn = Point2.pack(Point2.x(group.spawn), Strings.parseInt(text));
Log.info(group.spawn);
}
}).width(70f);
}).padBottom(8f).padTop(-8f).row();
}
}
}).width(340f).pad(8);
@@ -374,15 +411,15 @@ public class WaveInfoDialog extends BaseDialog{
}
enum Sort{
begin(g -> g.begin),
health(g -> g.type.health),
type(g -> g.type.id);
begin(Structs.comps(Structs.comparingFloat(g -> g.begin), Structs.comparingFloat(g -> g.type.id))),
health(Structs.comps(Structs.comparingFloat(g -> g.type.health), Structs.comparingFloat(g -> g.begin))),
type(Structs.comps(Structs.comparingFloat(g -> g.type.id), Structs.comparingFloat(g -> g.begin)));
static final Sort[] all = values();
final Floatf<SpawnGroup> sort;
final Comparator<SpawnGroup> sort;
Sort(Floatf<SpawnGroup> sort){
Sort(Comparator<SpawnGroup> sort){
this.sort = sort;
}
}

View File

@@ -26,7 +26,7 @@ public class EnergyFieldAbility extends Ability{
public Sound shootSound = Sounds.spark;
public float statusDuration = 60f * 6f;
public float x, y;
public boolean hitBuildings = true;
public boolean targetGround = true, targetAir = true, hitBuildings = true, hitUnits = true;
public int maxTargets = 25;
public float healPercent = 2.5f;
@@ -99,13 +99,15 @@ public class EnergyFieldAbility extends Ability{
all.clear();
Units.nearby(null, rx, ry, range, other -> {
if(other != unit){
all.add(other);
}
});
if(hitUnits){
Units.nearby(null, rx, ry, range, other -> {
if(other != unit && (other.isFlying() ? targetAir : targetGround)){
all.add(other);
}
});
}
if(hitBuildings){
if(hitBuildings && targetGround){
Units.nearbyBuildings(rx, ry, range, b -> {
if(b.team != Team.derelict || state.rules.coreCapture){
all.add(b);

View File

@@ -346,13 +346,7 @@ public class BulletType extends Content implements Cloneable{
}
public void update(Bullet b){
if(!headless && trailLength > 0){
if(b.trail == null){
b.trail = new Trail(trailLength);
}
b.trail.length = trailLength;
b.trail.update(b.x, b.y, trailInterp.apply(b.fin()));
}
updateTrail(b);
if(homingPower > 0.0001f && b.time >= homingDelay){
Teamc target;
@@ -386,6 +380,16 @@ public class BulletType extends Content implements Cloneable{
}
}
}
public void updateTrail(Bullet b){
if(!headless && trailLength > 0){
if(b.trail == null){
b.trail = new Trail(trailLength);
}
b.trail.length = trailLength;
b.trail.update(b.x, b.y, trailInterp.apply(b.fin()));
}
}
@Override
public void init(){

View File

@@ -94,6 +94,10 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
if(!within(tile, finalPlaceDst)) return;
if(!headless){
Vars.control.sound.loop(Sounds.build, tile, 0.51f);
}
if(!(tile.build instanceof ConstructBuild cb)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));

View File

@@ -1509,6 +1509,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
if(cons != null){
cons.update();
}
if(enabled || !block.noUpdateDisabled){
updateTile();
}
@@ -1521,10 +1525,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
liquids.update(updateFlow);
}
if(cons != null){
cons.update();
}
if(power != null){
power.graph.update();
}

View File

@@ -11,7 +11,6 @@ import arc.util.*;
import mindustry.ai.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.entities.*;
@@ -486,12 +485,13 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
if(!spawnedByCore){
Damage.dynamicExplosion(x, y, flammability, explosiveness, power, bounds() / 2f, state.rules.damageExplosions, item().flammability > 1, team, type.deathExplosionEffect);
}else{
type.deathExplosionEffect.at(x, y, bounds() / 2f / 8f);
}
float shake = hitSize / 3f;
Effect.scorch(x, y, (int)(hitSize / 5));
Fx.explosion.at(this);
Effect.shake(shake, shake, this);
type.deathSound.at(this);
@@ -503,7 +503,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){
Damage.damage(team,x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f, true, false, true);
Damage.damage(team, x, y, Mathf.pow(hitSize, 0.94f) * 1.25f, Mathf.pow(hitSize, 0.75f) * type.crashDamageMultiplier * 5f, true, false, true);
}
if(!headless){

View File

@@ -18,6 +18,7 @@ public class MultiEffect extends Effect{
@Override
public void init(){
for(Effect f : effects){
f.init();
clip = Math.max(clip, f.clip);
lifetime = Math.max(lifetime, f.lifetime);
}

View File

@@ -20,6 +20,8 @@ public class ParticleEffect extends Effect{
public float cone = 180f, length = 20f, baseLength = 0f;
/** Particle size/length/radius interpolation. */
public Interp interp = Interp.linear;
/** Particle size interpolation. Null to use interp. */
public @Nullable Interp sizeInterp = null;
public float offsetX, offsetY;
public float lightScl = 2f, lightOpacity = 0.6f;
public @Nullable Color lightColor;
@@ -44,6 +46,7 @@ public class ParticleEffect extends Effect{
@Override
public void init(){
clip = Math.max(clip, length + Math.max(sizeFrom, sizeTo));
if(sizeInterp == null) sizeInterp = interp;
}
@Override
@@ -52,15 +55,15 @@ public class ParticleEffect extends Effect{
float rawfin = e.fin();
float fin = e.fin(interp);
float rad = interp.apply(sizeFrom, sizeTo, rawfin) * 2;
float rad = sizeInterp.apply(sizeFrom, sizeTo, rawfin) * 2;
float ox = e.x + Angles.trnsx(e.rotation, offsetX, offsetY), oy = e.y + Angles.trnsy(e.rotation, offsetX, offsetY);
Draw.color(colorFrom, colorTo, fin);
Color lightColor = this.lightColor == null ? Draw.getColor() : this.lightColor;
if(line){
Lines.stroke(interp.apply(strokeFrom, strokeTo, rawfin));
float len = interp.apply(lenFrom, lenTo, rawfin);
Lines.stroke(sizeInterp.apply(strokeFrom, strokeTo, rawfin));
float len = sizeInterp.apply(lenFrom, lenTo, rawfin);
rand.setSeed(e.id);
for(int i = 0; i < particles; i++){

View File

@@ -291,16 +291,22 @@ public class Schematics implements Loadable{
/** Checks a schematic for deployment validity and adds it to the cache. */
private void checkLoadout(Schematic s, boolean validate){
Stile core = s.tiles.find(t -> t.block instanceof CoreBlock);
if(core == null) return;
int cores = s.tiles.count(t -> t.block instanceof CoreBlock);
int maxSize = getMaxLaunchSize(core.block);
//make sure a core exists, and that the schematic is small enough.
if(core == null || (validate && (s.width > core.block.size + maxLoadoutSchematicPad *2 || s.height > core.block.size + maxLoadoutSchematicPad *2
if((validate && (s.width > maxSize || s.height > maxSize
|| s.tiles.contains(t -> t.block.buildVisibility == BuildVisibility.sandboxOnly || !t.block.unlocked()) || cores > 1))) return;
//place in the cache
loadouts.get((CoreBlock)core.block, Seq::new).add(s);
}
public int getMaxLaunchSize(Block block){
return block.size + maxLoadoutSchematicPad*2;
}
/** Adds a schematic to the list, also copying it into the files.*/
public void add(Schematic schematic){
all.add(schematic);

View File

@@ -19,7 +19,7 @@ import static mindustry.Vars.*;
* weapon equipped, ammo used, and status effects.
* Each spawn group can have multiple sub-groups spawned in different areas of the map.
*/
public class SpawnGroup implements JsonSerializable{
public class SpawnGroup implements JsonSerializable, Cloneable{
public static final int never = Integer.MAX_VALUE;
/** The unit type spawned */
@@ -36,10 +36,12 @@ public class SpawnGroup implements JsonSerializable{
public float unitScaling = never;
/** Shield points that this unit has. */
public float shields = 0f;
/** How much shields get increased per wave. */
/** How much shields get increased by per wave. */
public float shieldScaling = 0f;
/** Amount of enemies spawned initially, with no scaling */
public int unitAmount = 1;
/** If not -1, the unit will only spawn in spawnpoints with these packed coordinates. */
public int spawn = -1;
/** Seq of payloads that this unit will spawn with. */
public @Nullable Seq<UnitType> payloads;
/** Status effect applied to the spawned unit. Null to disable. */
@@ -55,6 +57,10 @@ public class SpawnGroup implements JsonSerializable{
//serialization use only
}
public boolean canSpawn(int position){
return spawn == -1 || spawn == position;
}
/** @return amount of units spawned on a specific wave. */
public int getSpawned(int wave){
if(spacing == 0) spacing = 1;
@@ -111,6 +117,7 @@ public class SpawnGroup implements JsonSerializable{
if(shieldScaling != 0) json.writeValue("shieldScaling", shieldScaling);
if(unitAmount != 1) json.writeValue("amount", unitAmount);
if(effect != null) json.writeValue("effect", effect.name);
if(spawn != -1) json.writeValue("spawn", spawn);
if(payloads != null && payloads.size > 0){
json.writeValue("payloads", payloads.map(u -> u.name).toArray(String.class));
}
@@ -130,6 +137,7 @@ public class SpawnGroup implements JsonSerializable{
shields = data.getFloat("shields", 0);
shieldScaling = data.getFloat("shieldScaling", 0);
unitAmount = data.getInt("amount", 1);
spawn = data.getInt("spawn", -1);
if(data.has("payloads")){
payloads = Seq.with(json.readValue(String[].class, data.get("payloads"))).map(s -> content.getByName(ContentType.unit, s));
}
@@ -157,6 +165,14 @@ public class SpawnGroup implements JsonSerializable{
'}';
}
public SpawnGroup copy(){
try{
return (SpawnGroup)clone();
}catch(CloneNotSupportedException how){
throw new RuntimeException("If you see this, what did you even do?", how);
}
}
@Override
public boolean equals(Object o){
if(this == o) return true;

View File

@@ -350,8 +350,8 @@ public class Waves{
step += (int)(rand.random(15, 30) * Mathf.lerp(1f, 0.5f, difficulty));
}
int bossWave = (int)(rand.random(50, 70) * Mathf.lerp(1f, 0.7f, difficulty));
int bossSpacing = (int)(rand.random(25, 40) * Mathf.lerp(1f, 0.6f, difficulty));
int bossWave = (int)(rand.random(50, 70) * Mathf.lerp(1f, 0.5f, difficulty));
int bossSpacing = (int)(rand.random(25, 40) * Mathf.lerp(1f, 0.5f, difficulty));
int bossTier = difficulty < 0.6 ? 3 : 4;

View File

@@ -150,7 +150,7 @@ public class BlockRenderer{
public void checkChanges(){
darkEvents.each(pos -> {
var tile = world.tile(pos);
if(tile != null){
if(tile != null && tile.block().fillsTile){
tile.data = world.getWallDarkness(tile);
}
});

View File

@@ -967,7 +967,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//consume tap event if necessary
if(build.interactable(player.team()) && build.block.consumesTap){
consumed = true;
}else if(build.interactable(player.team()) && build.block.synthetic() && !consumed){
}else if(build.interactable(player.team()) && build.block.synthetic() && (!consumed || build.block.allowConfigInventory)){
if(build.block.hasItems && build.items.total() > 0){
frag.inv.showFor(build);
consumed = true;

View File

@@ -60,6 +60,13 @@ public abstract class SaveFileReader{
"block-forge", "constructor"
);
public static final ObjectMap<String, String> modContentNameMap = ObjectMap.of(
"craters", "crater-stone",
"deepwater", "deep-water",
"water", "shallow-water",
"slag", "molten-slag"
);
protected final ReusableByteOutStream byteOutput = new ReusableByteOutStream();
protected final DataOutputStream dataBytes = new DataOutputStream(byteOutput);
protected final ReusableByteOutStream byteOutputSmall = new ReusableByteOutStream();

View File

@@ -272,16 +272,22 @@ public class SectorDamage{
float e = build.efficiency();
if(e > 0.08f){
if(build.team == state.rules.defaultTeam && build instanceof Ranged ranged && sparse.contains(t -> t.within(build, ranged.range() + 4*tilesize))){
//TODO make sure power turret network supports the turrets?
if(build.block instanceof Turret t && build instanceof TurretBuild b && b.hasAmmo()){
sumDps += t.shots / t.reloadTime * 60f * b.peekAmmo().estimateDPS() * e;
sumDps += t.shots / t.reloadTime * 60f * b.peekAmmo().estimateDPS() * e * build.timeScale;
}
if(build.block instanceof MendProjector m){
sumRps += m.healPercent / m.reload * avgHealth * 60f / 100f * e;
sumRps += m.healPercent / m.reload * avgHealth * 60f / 100f * e * build.timeScale;
}
//point defense turrets act as flat health right now
if(build.block instanceof PointDefenseTurret && build.consValid()){
sumHealth += 150f * build.timeScale;
}
if(build.block instanceof ForceProjector f){
sumHealth += f.shieldHealth * e;
sumHealth += f.shieldHealth * e * build.timeScale;
sumRps += e;
}
}
@@ -315,14 +321,20 @@ public class SectorDamage{
var reg = new LinearRegression();
SpawnGroup bossGroup = null;
Seq<Vec2> waveDps = new Seq<>(), waveHealth = new Seq<>();
int groundSpawns = Math.max(spawner.countFlyerSpawns(), 1), airSpawns = Math.max(spawner.countGroundSpawns(), 1);
for(int wave = state.wave; wave < state.wave + 10; wave ++){
float sumWaveDps = 0f, sumWaveHealth = 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;
float healthMult = 1f + Mathf.clamp(group.type.armor / 20f);
StatusEffect effect = (group.effect == null ? StatusEffects.none : group.effect);
int spawned = group.getSpawned(wave);
int spawned = group.getSpawned(wave) * spawnCount;
//save the boss group
if(group.effect == StatusEffects.boss){
bossGroup = group;

View File

@@ -8,7 +8,7 @@ import static mindustry.maps.filters.FilterOption.*;
public class BlendFilter extends GenerateFilter{
float radius = 2f;
Block block = Blocks.stone, floor = Blocks.ice, ignore = Blocks.air;
Block block = Blocks.sand, floor = Blocks.sandWater, ignore = Blocks.air;
@Override
public FilterOption[] options(){
@@ -16,7 +16,7 @@ public class BlendFilter extends GenerateFilter{
new SliderOption("radius", () -> radius, f -> radius = f, 1f, 10f),
new BlockOption("block", () -> block, b -> block = b, anyOptional),
new BlockOption("floor", () -> floor, b -> floor = b, anyOptional),
new BlockOption("ignore", () -> ignore, b -> ignore = b, floorsOptional)
new BlockOption("ignore", () -> ignore, b -> ignore = b, anyOptional)
};
}

View File

@@ -27,7 +27,7 @@ public class DistortFilter extends GenerateFilter{
@Override
public void apply(GenerateInput in){
Tile tile = in.tile(in.x + noise(in, scl, mag) - mag / 2f, in.y + noise(in, scl, mag) - mag / 2f);
Tile tile = in.tile(in.x + noise(in, scl, mag) - mag / 2f, in.y + noise(1, in, scl, mag) - mag / 2f);
in.floor = tile.floor();
if(!tile.block().synthetic() && !in.block.synthetic()) in.block = tile.block();

View File

@@ -99,6 +99,10 @@ public abstract class GenerateFilter{
//utility generation functions
protected float noise(int seedOffset, GenerateInput in, float scl, float mag){
return Simplex.noise2d(seedOffset + seed, 1f, 0f, 1f / scl, in.x, in.y) * mag;
}
protected float noise(GenerateInput in, float scl, float mag){
return Simplex.noise2d(seed, 1f, 0f, 1f / scl, in.x, in.y) * mag;
}

View File

@@ -8,7 +8,7 @@ import static mindustry.maps.filters.FilterOption.*;
public class RiverNoiseFilter extends GenerateFilter{
float scl = 40, threshold = 0f, threshold2 = 0.1f, octaves = 1, falloff = 0.5f;
Block floor = Blocks.water, floor2 = Blocks.deepwater, block = Blocks.sandWall;
Block floor = Blocks.water, floor2 = Blocks.deepwater, block = Blocks.sandWall, target = Blocks.air;
@Override
public FilterOption[] options(){
@@ -18,6 +18,7 @@ public class RiverNoiseFilter extends GenerateFilter{
new SliderOption("threshold2", () -> threshold2, f -> threshold2 = f, -1f, 1f),
new SliderOption("octaves", () -> octaves, f -> octaves = f, 1f, 10f),
new SliderOption("falloff", () -> falloff, f -> falloff = f, 0f, 1f),
new BlockOption("target", () -> target, b -> target = b, anyOptional),
new BlockOption("block", () -> block, b -> block = b, wallsOnly),
new BlockOption("floor", () -> floor, b -> floor = b, floorsOnly),
new BlockOption("floor2", () -> floor2, b -> floor2 = b, floorsOnly)
@@ -26,14 +27,14 @@ public class RiverNoiseFilter extends GenerateFilter{
@Override
public char icon(){
return Iconc.blockWater;
return Iconc.blockShallowWater;
}
@Override
public void apply(GenerateInput in){
float noise = rnoise(in.x, in.y, (int)octaves, scl, falloff, 1f);
if(noise >= threshold){
if(noise >= threshold && (target == Blocks.air || in.floor == target || in.block == target)){
in.floor = floor;
if(in.block.solid){

View File

@@ -30,6 +30,12 @@ public class BaseGenerator{
private Tiles tiles;
private Seq<Tile> cores;
public static Block getDifficultyWall(int size, float difficulty){
Seq<Block> wallsSmall = content.blocks().select(b -> b instanceof Wall && b.size == size && !b.insulated && b.buildVisibility == BuildVisibility.shown && !(b instanceof Door));
wallsSmall.sort(b -> b.buildCost);
return wallsSmall.getFrac(difficulty * 0.91f);
}
public void generate(Tiles tiles, Seq<Tile> cores, Tile spawn, Team team, Sector sector, float difficulty){
this.tiles = tiles;
this.cores = cores;
@@ -39,22 +45,15 @@ public class BaseGenerator{
Mathf.rand.setSeed(sector.id);
Seq<Block> wallsSmall = content.blocks().select(b -> b instanceof Wall && b.size == 1 && !b.insulated && b.buildVisibility == BuildVisibility.shown && !(b instanceof Door));
Seq<Block> wallsLarge = content.blocks().select(b -> b instanceof Wall && b.size == 2 && !b.insulated && b.buildVisibility == BuildVisibility.shown && !(b instanceof Door));
//sort by cost for correct fraction
wallsSmall.sort(b -> b.buildCost);
wallsLarge.sort(b -> b.buildCost);
float bracketRange = 0.2f;
float baseChance = Mathf.lerp(0.7f, 1.9f, difficulty);
float bracketRange = 0.17f;
float baseChance = Mathf.lerp(0.7f, 2.1f, difficulty);
int wallAngle = 70; //180 for full coverage
double resourceChance = 0.5 * baseChance;
double nonResourceChance = 0.0005 * baseChance;
BasePart coreschem = bases.cores.getFrac(difficulty);
int passes = difficulty < 0.4 ? 1 : difficulty < 0.8 ? 2 : 3;
Block wall = wallsSmall.getFrac(difficulty * 0.91f), wallLarge = wallsLarge.getFrac(difficulty * 0.91f);
Block wall = getDifficultyWall(1, difficulty), wallLarge = getDifficultyWall(2, difficulty);
for(Tile tile : cores){
tile.clearOverlay();

View File

@@ -517,7 +517,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
float waveTimeDec = 0.4f;
state.rules.waveSpacing = Mathf.lerp(60 * 65 * 2, 60f * 60f * 1f, Math.max(difficulty - waveTimeDec, 0f) / 0.8f);
state.rules.waveSpacing = Mathf.lerp(60 * 65 * 2, 60f * 60f * 1f, Math.max(difficulty - waveTimeDec, 0f));
state.rules.waves = sector.info.waves = true;
state.rules.enemyCoreBuildRadius = 600f;

View File

@@ -80,6 +80,8 @@ public class ContentParser{
if(data.isString()){
StatusEffect result = locate(ContentType.status, data.asString());
if(result != null) return result;
result = (StatusEffect)fieldOpt(StatusEffects.class, data);
if(result != null) return result;
throw new IllegalArgumentException("Unknown status effect: '" + data.asString() + "'");
}
StatusEffect effect = new StatusEffect(currentMod.name + "-" + data.getString("name"));

View File

@@ -177,7 +177,7 @@ public class Mods implements Loadable{
}
private void packSprites(Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<AsyncResult<Runnable>> tasks){
boolean linear = Core.settings.getBool("linear");
boolean linear = Core.settings.getBool("linear", true);
for(Fi file : sprites){
//read and bleed pixmaps in parallel
@@ -186,7 +186,7 @@ public class Mods implements Loadable{
Pixmap pix = new Pixmap(file.readBytes());
//only bleeds when linear filtering is on at startup
if(linear){
Pixmaps.bleed(pix);
Pixmaps.bleed(pix, 2);
}
//this returns a *runnable* which actually packs the resulting pixmap; this has to be done synchronously outside the method
return () -> {
@@ -269,7 +269,7 @@ public class Mods implements Loadable{
}
};
TextureFilter filter = Core.settings.getBool("linear") ? TextureFilter.linear : TextureFilter.nearest;
TextureFilter filter = Core.settings.getBool("linear", true) ? TextureFilter.linear : TextureFilter.nearest;
Time.mark();
//generate new icons

View File

@@ -334,6 +334,9 @@ public class UnitType extends UnlockableContent{
canDrown = false;
omniMovement = false;
immunities.add(StatusEffects.wet);
if(visualElevation < 0f){
visualElevation = 0.11f;
}
}
if(flying){
@@ -473,7 +476,7 @@ public class UnitType extends UnlockableContent{
if(!packer.has(name + "-outline")){
PixmapRegion base = Core.atlas.getPixmap(region);
var result = Pixmaps.outline(base, outlineColor, outlineRadius);
if(Core.settings.getBool("linear")){
if(Core.settings.getBool("linear", true)){
Pixmaps.bleed(result);
}
packer.add(PageType.main, name + "-outline", result);
@@ -709,7 +712,7 @@ public class UnitType extends UnlockableContent{
Draw.color(0, 0, 0, 0.4f * alpha);
float rad = 1.6f;
float size = Math.max(region.width, region.height) * Draw.scl;
Draw.rect(softShadowRegion, unit, size * rad * Draw.xscl, size * rad * Draw.yscl);
Draw.rect(softShadowRegion, unit, size * rad * Draw.xscl, size * rad * Draw.yscl, unit.rotation - 90);
Draw.color();
}

View File

@@ -36,7 +36,7 @@ public class DatabaseDialog extends BaseDialog{
cont.table(s -> {
s.image(Icon.zoom).padRight(8);
search = s.field(null, text -> rebuild()).growX().get();
search.setMessageText(Core.bundle.get("players.search"));
search.setMessageText("@players.search");
}).fillX().padBottom(4).row();
cont.pane(all);

View File

@@ -763,7 +763,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
c.defaults().padBottom(5);
if(sector.preset != null && sector.preset.description != null){
c.add(sector.preset.displayDescription()).left().row();
c.add(sector.preset.displayDescription()).width(420f).wrap().left().row();
}
c.add(Core.bundle.get("sectors.time") + " [accent]" + sector.save.getPlayTime()).left().row();

View File

@@ -230,8 +230,12 @@ public class SettingsMenuDialog extends Dialog{
platform.shareFile(logs);
}else{
platform.showFileChooser(false, "txt", file -> {
file.writeString(getLogs());
app.post(() -> ui.showInfo("@crash.exported"));
try{
file.writeBytes(getLogs().getBytes(Strings.utf8));
app.post(() -> ui.showInfo("@crash.exported"));
}catch(Throwable e){
ui.showException(e);
}
});
}
}

View File

@@ -181,6 +181,11 @@ public class HintsFragment extends Fragment{
&& SectorPresets.frozenForest.unlocked()
&& SectorPresets.frozenForest.sector.save == null,
() -> state.isCampaign() && state.getSector().preset == SectorPresets.frozenForest),
presetDifficulty(() -> state.isCampaign()
&& state.getSector().preset == null
&& state.getSector().threat >= 0.5f
&& !SectorPresets.tarFields.sector.isCaptured(), //appear only when the player hasn't progressed much in the game yet
() -> state.isCampaign() && state.getSector().preset != null),
coreIncinerate(() -> state.isCampaign() && state.rules.defaultTeam.core() != null && state.rules.defaultTeam.core().items.get(Items.copper) >= state.rules.defaultTeam.core().storageCapacity - 10, () -> false),
coopCampaign(() -> net.client() && state.isCampaign() && SectorPresets.groundZero.sector.hasBase(), () -> false),
;

View File

@@ -162,6 +162,8 @@ public class Block extends UnlockableContent{
public int unitCapModifier = 0;
/** Whether the block can be tapped and selected to configure. */
public boolean configurable;
/** If true, the building inventory can be shown with the config. */
public boolean allowConfigInventory = true;
/** If true, this block can be configured by logic. */
public boolean logicConfigurable = false;
/** Whether this block consumes touchDown events when tapped. */
@@ -848,7 +850,8 @@ public class Block extends UnlockableContent{
}
if(!outputsPower && consumes.hasPower() && consumes.getPower().buffered){
throw new IllegalArgumentException("Consumer using buffered power: " + name);
Log.warn("Consumer using buffered power: @. Disabling buffered power.", name);
consumes.getPower().buffered = false;
}
if(buildVisibility == BuildVisibility.sandboxOnly){
@@ -912,12 +915,17 @@ public class Block extends UnlockableContent{
for(int x = 0; x < base.width; x++){
for(int y = 0; y < base.height; y++){
int color = base.get(x, y);
int index = color == 0xffffffff ? 0 : color == 0xdcc6c6ff ? 1 : color == 0x9d7f7fff ? 2 : -1;
int index = switch(color){
case 0xffffffff -> 0;
case 0xdcc6c6ff, 0xdbc5c5ff -> 1;
case 0x9d7f7fff, 0x9e8080ff -> 2;
default -> -1;
};
out.setRaw(x, y, index == -1 ? base.get(x, y) : team.palettei[index]);
}
}
if(Core.settings.getBool("linear")){
if(Core.settings.getBool("linear", true)){
Pixmaps.bleed(out);
}
@@ -938,7 +946,7 @@ public class Block extends UnlockableContent{
if(outlineIcon){
PixmapRegion region = Core.atlas.getPixmap(gen[outlinedIcon >= 0 ? outlinedIcon : gen.length -1]);
Pixmap out = last = Pixmaps.outline(region, outlineColor, outlineRadius);
if(Core.settings.getBool("linear")){
if(Core.settings.getBool("linear", true)){
Pixmaps.bleed(out);
}
packer.add(PageType.main, name, out);

View File

@@ -470,15 +470,15 @@ public class Tile implements Position, QuadTreeObject, Displayable{
getHitbox(rect);
}
public Tile nearby(Point2 relative){
public @Nullable Tile nearby(Point2 relative){
return world.tile(x + relative.x, y + relative.y);
}
public Tile nearby(int dx, int dy){
public @Nullable Tile nearby(int dx, int dy){
return world.tile(x + dx, y + dy);
}
public Tile nearby(int rotation){
public @Nullable Tile nearby(int rotation){
if(rotation == 0) return world.tile(x + 1, y);
if(rotation == 1) return world.tile(x, y + 1);
if(rotation == 2) return world.tile(x - 1, y);
@@ -486,7 +486,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
return null;
}
public Building nearbyBuild(int rotation){
public @Nullable Building nearbyBuild(int rotation){
if(rotation == 0) return world.build(x + 1, y);
if(rotation == 1) return world.build(x, y + 1);
if(rotation == 2) return world.build(x - 1, y);

View File

@@ -75,7 +75,9 @@ public class Wall extends Block{
Draw.blend();
Draw.reset();
hit = Mathf.clamp(hit - Time.delta / 10f);
if(!state.isPaused()){
hit = Mathf.clamp(hit - Time.delta / 10f);
}
}
}
@@ -96,7 +98,7 @@ public class Wall extends Block{
//deflect bullets if necessary
if(chanceDeflect > 0f){
//slow bullets are not deflected
if(bullet.vel().len() <= 0.1f || !bullet.type.reflectable) return true;
if(bullet.vel.len() <= 0.1f || !bullet.type.reflectable) return true;
//bullet reflection chance depends on bullet damage
if(!Mathf.chance(chanceDeflect / bullet.damage())) return true;
@@ -115,9 +117,9 @@ public class Wall extends Block{
bullet.vel.y *= -1;
}
bullet.owner(this);
bullet.team(team);
bullet.time(bullet.time() + 1f);
bullet.owner = this;
bullet.team = team;
bullet.time += 1f;
//disable bullet collision by returning false
return false;

View File

@@ -273,6 +273,7 @@ public class Turret extends ReloadTurret{
}
if(hasAmmo()){
if(Float.isNaN(reload)) rotation = 0;
if(timer(timerTarget, targetInterval)){
findTarget();
@@ -289,9 +290,7 @@ public class Turret extends ReloadTurret{
}else{ //default AI behavior
targetPosition(target);
if(Float.isNaN(rotation)){
rotation = 0;
}
if(Float.isNaN(rotation)) rotation = 0;
}
float targetRot = angleTo(targetPos);

View File

@@ -211,7 +211,7 @@ public class DuctBridge extends Block{
public DuctBridgeBuild findLink(){
for(int i = 1; i <= range; i++){
Tile other = tile.nearby(Geometry.d4x(rotation) * i, Geometry.d4y(rotation) * i);
if(other.build instanceof DuctBridgeBuild build && build.team == team){
if(other != null && other.build instanceof DuctBridgeBuild build && build.team == team){
return build;
}
}

View File

@@ -51,6 +51,8 @@ public class ItemBridge extends Block{
group = BlockGroup.transportation;
noUpdateDisabled = true;
copyConfig = false;
//disabled as to not be annoying
allowConfigInventory = false;
//point2 config is relative
config(Point2.class, (ItemBridgeBuild tile, Point2 i) -> tile.link = Point2.pack(i.x + tile.tileX(), i.y + tile.tileY()));

View File

@@ -9,6 +9,7 @@ import mindustry.world.*;
public class Cliff extends Block{
public float size = 11f;
public @Load(value = "cliffmask#", length = 256) TextureRegion[] cliffs;
public @Load(value = "editor-cliffmask#", length = 256) TextureRegion[] editorCliffs;
public Cliff(String name){
super(name);

View File

@@ -40,10 +40,10 @@ public class BurnerGenerator extends ItemLiquidGenerator{
Draw.rect(turbineRegions[1], x, y, -totalTime * turbineSpeed);
Draw.rect(capRegion, x, y);
}
if(hasLiquids){
Drawf.liquid(liquidRegion, x, y, liquids.total() / liquidCapacity, liquids.current().color);
}
if(hasLiquids && liquidRegion.found()){
Drawf.liquid(liquidRegion, x, y, liquids.total() / liquidCapacity, liquids.current().color);
}
}
}

View File

@@ -13,10 +13,11 @@ public class PowerGraph{
private static final Seq<Building> outArray2 = new Seq<>();
private static final IntSet closedSet = new IntSet();
private final Seq<Building> producers = new Seq<>(false);
private final Seq<Building> consumers = new Seq<>(false);
private final Seq<Building> batteries = new Seq<>(false);
private final Seq<Building> all = new Seq<>(false);
//do not modify any of these unless you know what you're doing!
public final Seq<Building> producers = new Seq<>(false);
public final Seq<Building> consumers = new Seq<>(false);
public final Seq<Building> batteries = new Seq<>(false);
public final Seq<Building> all = new Seq<>(false);
private final WindowedMean powerBalance = new WindowedMean(60);
private float lastPowerProduced, lastPowerNeeded, lastPowerStored;

View File

@@ -8,11 +8,11 @@ import mindustry.world.meta.*;
/** Consumer class for blocks which consume power while being connected to a power graph. */
public class ConsumePower extends Consume{
/** The maximum amount of power which can be processed per tick. This might influence efficiency or load a buffer. */
public final float usage;
public float usage;
/** The maximum power capacity in power units. */
public final float capacity;
public float capacity;
/** True if the module can store power. */
public final boolean buffered;
public boolean buffered;
public ConsumePower(float usage, float capacity, boolean buffered){
this.usage = usage;

View File

@@ -38,13 +38,15 @@ public class DrawSmelter extends DrawBlock{
float cr = Mathf.random(0.1f);
Draw.z(Layer.block + 0.01f);
Draw.alpha(build.warmup);
Draw.rect(top, build.x, build.y);
Draw.alpha(((1f - g) + Mathf.absin(Time.time, 8f, g) + Mathf.random(r) - r) * build.warmup);
Draw.tint(flameColor);
Fill.circle(build.x, build.y, flameRadius + Mathf.absin(Time.time, flameRadiusScl, flameRadiusMag) + cr);
Draw.color(1f, 1f, 1f, build.warmup);
Draw.rect(top, build.x, build.y);
Fill.circle(build.x, build.y, flameRadiusIn + Mathf.absin(Time.time, flameRadiusScl, flameRadiusInMag) + cr);
Draw.color();