Merge branch 'master' into port-field

This commit is contained in:
Antsiferov Andrew
2021-01-01 21:55:19 +03:00
committed by GitHub
1246 changed files with 30340 additions and 60705 deletions

View File

@@ -29,11 +29,19 @@ public class Bar extends Element{
public Bar(Prov<String> name, Prov<Color> color, Floatp fraction){
this.fraction = fraction;
lastValue = value = Mathf.clamp(fraction.get());
try{
lastValue = value = Mathf.clamp(fraction.get());
}catch(Exception e){ //getting the fraction may involve referring to invalid data
lastValue = value = 0f;
}
update(() -> {
this.name = name.get();
this.blinkColor.set(color.get());
setColor(color.get());
try{
this.name = name.get();
this.blinkColor.set(color.get());
setColor(color.get());
}catch(Exception e){ //getting the fraction may involve referring to invalid data
this.name = "";
}
});
}
@@ -62,12 +70,25 @@ public class Bar extends Element{
public void draw(){
if(fraction == null) return;
float computed = Mathf.clamp(fraction.get());
float computed;
try{
computed = Mathf.clamp(fraction.get());
}catch(Exception e){ //getting the fraction may involve referring to invalid data
computed = 0f;
}
if(lastValue > computed){
blink = 1f;
lastValue = computed;
}
if(Float.isNaN(lastValue)) lastValue = 0;
if(Float.isInfinite(lastValue)) lastValue = 1f;
if(Float.isNaN(value)) value = 0;
if(Float.isInfinite(value)) value = 1f;
if(Float.isNaN(computed)) computed = 0;
if(Float.isInfinite(computed)) computed = 1f;
blink = Mathf.lerpDelta(blink, 0f, 0.2f);
value = Mathf.lerpDelta(value, computed, 0.15f);

View File

@@ -2,9 +2,9 @@ package mindustry.ui;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.Image;
import arc.scene.ui.layout.Scl;
import mindustry.graphics.Pal;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.graphics.*;
public class BorderImage extends Image{
public float thickness = 4f;

View File

@@ -1,165 +0,0 @@
package mindustry.ui;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class ContentDisplay{
public static void displayBlock(Table table, Block block){
table.table(title -> {
int size = 8 * 6;
title.image(block.icon(Cicon.xlarge)).size(size);
title.add("[accent]" + block.localizedName).padLeft(5);
});
table.row();
table.image().height(3).color(Color.lightGray).pad(8).padLeft(0).padRight(0).fillX();
table.row();
if(block.description != null){
table.add(block.displayDescription()).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.image().height(3).color(Color.lightGray).pad(8).padLeft(0).padRight(0).fillX();
table.row();
}
BlockStats stats = block.stats;
for(StatCategory cat : stats.toMap().keys()){
OrderedMap<BlockStat, Seq<StatValue>> map = stats.toMap().get(cat);
if(map.size == 0) continue;
table.add("@category." + cat.name()).color(Pal.accent).fillX();
table.row();
for(BlockStat stat : map.keys()){
table.table(inset -> {
inset.left();
inset.add("[lightgray]" + stat.localized() + ":[] ").left();
Seq<StatValue> arr = map.get(stat);
for(StatValue value : arr){
value.display(inset);
inset.add().size(10f);
}
}).fillX().padLeft(10);
table.row();
}
}
}
public static void displayItem(Table table, Item item){
table.table(title -> {
title.image(item.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + item.localizedName).padLeft(5);
});
table.row();
table.image().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(item.description != null){
table.add(item.displayDescription()).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.image().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
}
table.left().defaults().fillX();
table.add(Core.bundle.format("item.explosiveness", (int)(item.explosiveness * 100)));
table.row();
table.add(Core.bundle.format("item.flammability", (int)(item.flammability * 100)));
table.row();
table.add(Core.bundle.format("item.radioactivity", (int)(item.radioactivity * 100)));
table.row();
}
public static void displayLiquid(Table table, Liquid liquid){
table.table(title -> {
title.image(liquid.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + liquid.localizedName).padLeft(5);
});
table.row();
table.image().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(liquid.description != null){
table.add(liquid.displayDescription()).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.image().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
}
table.left().defaults().fillX();
table.add(Core.bundle.format("item.explosiveness", (int)(liquid.explosiveness * 100)));
table.row();
table.add(Core.bundle.format("item.flammability", (int)(liquid.flammability * 100)));
table.row();
table.add(Core.bundle.format("liquid.heatcapacity", (int)(liquid.heatCapacity * 100)));
table.row();
table.add(Core.bundle.format("liquid.temperature", (int)(liquid.temperature * 100)));
table.row();
table.add(Core.bundle.format("liquid.viscosity", (int)(liquid.viscosity * 100)));
table.row();
}
public static void displayUnit(Table table, UnitType unit){
table.table(title -> {
title.image(unit.icon(Cicon.xlarge)).size(8 * 6).scaling(Scaling.fit);
title.add("[accent]" + unit.localizedName).padLeft(5);
});
table.row();
table.image().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(unit.description != null){
table.add(unit.displayDescription()).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.image().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
}
table.left().defaults().fillX();
Unit inst = unit.constructor.get();
//TODO more stats
table.add(Core.bundle.format("unit.health", unit.health)).row();
table.add(Core.bundle.format("unit.speed", Strings.fixed(unit.speed, 1))).row();
table.add(Core.bundle.format("unit.itemcapacity", unit.itemCapacity)).row();
if(inst instanceof Minerc) table.add(Core.bundle.format("unit.minespeed", (int)(unit.mineSpeed * 100f))).row();
if(inst instanceof Builderc) table.add(Core.bundle.format("unit.buildspeed", (int)(unit.buildSpeed * 100f))).row();
table.row();
}
}

View File

@@ -16,27 +16,38 @@ import arc.graphics.g2d.*;
import arc.graphics.g2d.Font.*;
import arc.graphics.g2d.PixmapPacker.*;
import arc.graphics.g2d.TextureAtlas.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.style.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import java.util.*;
public class Fonts{
private static final String mainFont = "fonts/font.woff";
private static final ObjectSet<String> unscaled = ObjectSet.with("iconLarge");
private static ObjectIntMap<String> unicodeIcons = new ObjectIntMap<>();
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
private static ObjectMap<String, TextureRegion> largeIcons = new ObjectMap<>();
private static TextureRegion[] iconTable;
private static int lastCid;
public static Font def;
public static Font outline;
public static Font chat;
public static Font icon;
public static Font iconLarge;
public static Font tech;
public static TextureRegion logicIcon(int id){
return iconTable[id];
}
public static int getUnicode(String content){
return unicodeIcons.get(content, 0);
}
@@ -55,10 +66,11 @@ public class Fonts{
}
public static int cursorScale(){
return Math.max(1, Mathf.round(Scl.scl(1f)));
return 1;
}
public static void loadFonts(){
largeIcons.clear();
FreeTypeFontParameter param = fontParameter();
Core.assets.load("default", Font.class, new FreeTypeFontLoaderParameter(mainFont, param)).loaded = f -> Fonts.def = (Font)f;
@@ -68,6 +80,25 @@ public class Fonts{
incremental = true;
characters = "\0";
}})).loaded = f -> Fonts.icon = (Font)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;
}
public static TextureRegion getLargeIcon(String name){
return largeIcons.get(name, () -> {
var region = new TextureRegion();
int code = Iconc.codes.get(name, '\uF8D4');
var glyph = iconLarge.getData().getGlyph((char)code);
if(glyph == null) return Core.atlas.find("error");
region.set(iconLarge.getRegion().texture);
region.set(glyph.u, glyph.v2, glyph.u2, glyph.v);
return region;
});
}
public static void loadContentIcons(){
@@ -97,7 +128,7 @@ public class Fonts{
glyph.srcX = 0;
glyph.srcY = 0;
glyph.width = size;
glyph.height = size;
glyph.height = (int)((float)region.height / region.width * size);
glyph.u = region.u;
glyph.v = region.v2;
glyph.u2 = region.u2;
@@ -111,6 +142,19 @@ public class Fonts{
fonts.each(f -> f.getData().setGlyph(ch, glyph));
}
}
iconTable = new TextureRegion[512];
iconTable[0] = Core.atlas.find("error");
lastCid = 1;
Vars.content.each(c -> {
if(c instanceof UnlockableContent u){
TextureRegion region = Core.atlas.find(u.name + "-icon-logic");
if(region.found()){
iconTable[u.iconId = lastCid++] = region;
}
}
});
}
/** Called from a static context for use in the loading screen.*/
@@ -129,7 +173,8 @@ public class Fonts{
parameter.fontParameters.borderWidth = Scl.scl(2f);
parameter.fontParameters.spaceX -= parameter.fontParameters.borderWidth;
}
if(!scaled.contains(parameter.fontParameters)){
if(!scaled.contains(parameter.fontParameters) && !unscaled.contains(fileName)){
parameter.fontParameters.size = (int)(Scl.scl(parameter.fontParameters.size));
scaled.add(parameter.fontParameters);
}

View File

@@ -1,7 +1,7 @@
package mindustry.ui;
import arc.graphics.g2d.Fill;
import arc.scene.Element;
import arc.graphics.g2d.*;
import arc.scene.*;
public class GridImage extends Element{
private int imageWidth, imageHeight;

View File

@@ -1,8 +1,8 @@
package mindustry.ui;
import arc.Core;
import arc.func.Func;
import arc.*;
import arc.func.*;
/**
* A low-garbage way to format bundle strings.
@@ -10,7 +10,7 @@ import arc.func.Func;
public class IntFormat{
private final StringBuilder builder = new StringBuilder();
private final String text;
private int lastValue = Integer.MIN_VALUE;
private int lastValue = Integer.MIN_VALUE, lastValue2 = Integer.MIN_VALUE;
private Func<Integer, String> converter = String::valueOf;
public IntFormat(String text){
@@ -30,4 +30,14 @@ public class IntFormat{
lastValue = value;
return builder;
}
public CharSequence get(int value1, int value2){
if(lastValue != value1 || lastValue2 != value2){
builder.setLength(0);
builder.append(Core.bundle.format(text, value1, value2));
}
lastValue = value1;
lastValue2 = value2;
return builder;
}
}

View File

@@ -1,8 +1,7 @@
package mindustry.ui;
import arc.scene.ui.layout.Table;
import mindustry.type.Item;
import mindustry.type.ItemStack;
import arc.scene.ui.layout.*;
import mindustry.type.*;
/** An item image with text. */
public class ItemDisplay extends Table{

View File

@@ -3,6 +3,7 @@ package mindustry.ui;
import arc.graphics.g2d.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.core.*;
import mindustry.type.*;
public class ItemImage extends Stack{
@@ -16,7 +17,7 @@ public class ItemImage extends Stack{
add(new Table(t -> {
t.left().bottom();
t.add(amount + "");
t.add(amount > 1000 ? UI.formatAmount(amount) : amount + "");
t.pack();
}));
}
@@ -38,7 +39,7 @@ public class ItemImage extends Stack{
if(stack.amount != 0){
add(new Table(t -> {
t.left().bottom();
t.add(stack.amount + "").style(Styles.outlineLabel);
t.add(stack.amount > 1000 ? UI.formatAmount(stack.amount) : stack.amount + "").style(Styles.outlineLabel);
t.pack();
}));
}

View File

@@ -5,7 +5,7 @@ import arc.math.*;
import arc.scene.actions.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.graphics.*;

View File

@@ -1,28 +1,32 @@
package mindustry.ui;
import arc.Core;
import arc.*;
import arc.graphics.*;
import arc.scene.style.*;
import arc.util.Strings;
import arc.graphics.Color;
import arc.util.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.graphics.*;
import mindustry.mod.Mods.*;
public class Links{
private static LinkEntry[] links;
private static void createLinks(){
links = new LinkEntry[]{
new LinkEntry("discord", "https://discord.gg/mindustry", Icon.discord, Color.valueOf("7289da")),
new LinkEntry("changelog", "https://github.com/Anuken/Mindustry/releases", Icon.list, Pal.accent.cpy()),
new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Icon.trello, Color.valueOf("026aa7")),
new LinkEntry("wiki", "https://mindustrygame.github.io/wiki/", Icon.book, Color.valueOf("0f142f")),
new LinkEntry("suggestions", "https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose/", Icon.add, Color.valueOf("ebebeb")),
new LinkEntry("reddit", "https://www.reddit.com/r/Mindustry/", Icon.redditAlien, Color.valueOf("ee593b")),
new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Icon.itchio, Color.valueOf("fa5c5c")),
new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Icon.googleplay, Color.valueOf("689f38")),
new LinkEntry("f-droid", "https://f-droid.org/packages/io.anuke.mindustry/", Icon.android, Color.valueOf("026aa7")),
new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Icon.github, Color.valueOf("24292e")),
new LinkEntry("dev-builds", "https://github.com/Anuken/MindustryBuilds", Icon.githubSquare, Color.valueOf("fafbfc"))
new LinkEntry("discord", "https://discord.gg/mindustry", Icon.discord, Color.valueOf("7289da")),
new LinkEntry("changelog", "https://github.com/Anuken/Mindustry/releases", Icon.list, Pal.accent.cpy()),
new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Icon.trello, Color.valueOf("026aa7")),
new LinkEntry("wiki", "https://mindustrygame.github.io/wiki/", Icon.book, Color.valueOf("0f142f")),
new LinkEntry("suggestions", "https://github.com/Anuken/Mindustry-Suggestions/issues/new/choose/", Icon.add, Color.valueOf("ebebeb")),
new LinkEntry("reddit", "https://www.reddit.com/r/Mindustry/", Icon.redditAlien, Color.valueOf("ee593b")),
new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Icon.itchio, Color.valueOf("fa5c5c")),
new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Icon.googleplay, Color.valueOf("689f38")),
new LinkEntry("f-droid", "https://f-droid.org/packages/io.anuke.mindustry/", Icon.android, Color.valueOf("026aa7")),
new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Icon.github, Color.valueOf("24292e")),
new LinkEntry("dev-builds", "https://github.com/Anuken/MindustryBuilds", Icon.githubSquare, Color.valueOf("fafbfc")),
new LinkEntry("bug", report(), Icon.wrench, Color.valueOf("cbd97f"))
};
}
@@ -48,4 +52,23 @@ public class Links{
this.title = Core.bundle.get("link." + name + ".title", Strings.capitalize(name.replace("-", " ")));
}
}
}
private static String report(){
return "https://github.com/Anuken/Mindustry/issues/new?assignees=&labels=bug&body=" +
Strings.encode(Strings.format(
"**Platform**: `@`\n" +
"\n**Build**: `@`\n" +
"\n**Issue**: *Explain your issue in detail.*\n" +
"\n**Steps to reproduce**: *How you happened across the issue, and what exactly you did to make the bug happen.*\n" +
"\n**Link(s) to mod(s) used**: `@`\n" +
"\n**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME.*\n" +
"\n**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*\n" +
"\n---\n" +
"\n*Place an X (no spaces) between the brackets to confirm that you have read the line below.*" +
"\n- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**" +
"\n- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**",
OS.isAndroid ? "Android " + Core.app.getVersion() : (System.getProperty("os.name") + (OS.is64Bit ? " x64" : " x32")),
Version.combined(),
Vars.mods.list().any() ? Vars.mods.list().select(LoadedMod::enabled).map(l -> l.meta.author + "/" + l.name + ":" + l.meta.version) : "none"));
}
}

View File

@@ -1,12 +1,11 @@
package mindustry.ui;
import arc.graphics.Color;
import arc.scene.ui.Image;
import arc.scene.ui.layout.Stack;
import arc.scene.ui.layout.Table;
import arc.util.Strings;
import mindustry.type.Liquid;
import mindustry.world.meta.StatUnit;
import arc.graphics.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.type.*;
import mindustry.world.meta.*;
/** An ItemDisplay, but for liquids. */
public class LiquidDisplay extends Table{

View File

@@ -1,9 +1,9 @@
package mindustry.ui;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.input.KeyCode;
import arc.scene.Element;
import arc.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;

View File

@@ -1,8 +1,8 @@
package mindustry.ui;
import arc.scene.style.*;
import arc.scene.ui.ImageButton;
import arc.util.Align;
import arc.scene.ui.*;
import arc.util.*;
public class MobileButton extends ImageButton{

View File

@@ -1,8 +1,8 @@
package mindustry.ui;
import arc.struct.Seq;
import arc.scene.ui.layout.Stack;
import arc.util.Time;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
public class MultiReqImage extends Stack{
private Seq<ReqImage> displays = new Seq<>();

View File

@@ -1,12 +1,11 @@
package mindustry.ui;
import arc.func.Boolp;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.scene.Element;
import arc.scene.ui.Image;
import arc.scene.ui.layout.Stack;
import arc.scene.ui.layout.Scl;
import mindustry.graphics.Pal;
import arc.scene.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.graphics.*;
public class ReqImage extends Stack{
private final Boolp valid;

View File

@@ -0,0 +1,62 @@
package mindustry.ui;
import arc.func.*;
import arc.graphics.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import mindustry.gen.*;
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("");
});
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){
if(name == null || name.isEmpty()){
return false;
}
return name.toLowerCase().contains(query);
}
}

View File

@@ -1,7 +1,6 @@
package mindustry.ui;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.g2d.TextureAtlas.*;
@@ -16,6 +15,7 @@ import arc.scene.ui.ScrollPane.*;
import arc.scene.ui.Slider.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.TextField.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
@@ -23,10 +23,11 @@ import static mindustry.gen.Tex.*;
@StyleDefaults
public class Styles{
//TODO all these names are inconsistent and not descriptive
public static Drawable black, black9, black8, black6, black3, black5, none, flatDown, flatOver;
public static ButtonStyle defaultb, waveb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, logici, geni, colori, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
public static ButtonStyle defaultb, waveb, modsb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, nonet, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt, fullTogglet, logict;
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, logici, geni, colori, accenti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane;
public static KeybindDialogStyle defaultKeybindDialog;
public static SliderStyle defaultSlider, vSlider;
@@ -52,6 +53,12 @@ public class Styles{
over = buttonOver;
disabled = buttonDisabled;
}};
modsb = new ButtonStyle(){{
down = flatOver;
up = underline;
over = underlineWhite;
}};
waveb = new ButtonStyle(){{
up = wavepane;
@@ -85,6 +92,13 @@ public class Styles{
up = buttonOver;
over = buttonDown;
}};
nonet = new TextButtonStyle(){{
font = Fonts.outline;
fontColor = Color.lightGray;
overFontColor = Pal.accent;
disabledFontColor = Color.gray;
up = none;
}};
cleart = new TextButtonStyle(){{
over = flatOver;
font = Fonts.def;
@@ -212,6 +226,11 @@ public class Styles{
//imageDownColor = Pal.accent;
imageUpColor = Color.white;
}};
accenti = new ImageButtonStyle(){{
//imageDownColor = Pal.accent;
imageUpColor = Color.lightGray;
imageDownColor = Color.white;
}};
cleari = new ImageButtonStyle(){{
down = flatOver;
up = black;

View File

@@ -1,10 +1,10 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;

View File

@@ -1,7 +1,6 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.input.*;
import arc.scene.ui.*;
import arc.util.*;
import mindustry.core.GameState.*;
@@ -53,15 +52,15 @@ public class BaseDialog extends Dialog{
});
}
public void addCloseListener(){
closeOnBack();
}
@Override
public void addCloseButton(){
buttons.defaults().size(210f, 64f);
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
keyDown(key -> {
if(key == KeyCode.escape || key == KeyCode.back){
Core.app.post(this::hide);
}
});
addCloseListener();
}
}

View File

@@ -1,8 +1,13 @@
package mindustry.ui.dialogs;
import arc.scene.ui.ScrollPane;
import arc.scene.ui.layout.Table;
import mindustry.ctype.UnlockableContent;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
public class ContentInfoDialog extends BaseDialog{
@@ -18,11 +23,72 @@ public class ContentInfoDialog extends BaseDialog{
Table table = new Table();
table.margin(10);
content.displayInfo(table);
//initialize stats if they haven't been yet
content.checkStats();
table.table(title1 -> {
int size = 8 * 6;
title1.image(content.icon(Cicon.xlarge)).size(size).scaling(Scaling.fit);
title1.add("[accent]" + content.localizedName).padLeft(5);
});
table.row();
if(content.description != null){
var any = content.stats.toMap().size > 0;
if(any){
table.add("@category.purpose").color(Pal.accent).fillX().padTop(10);
table.row();
}
table.add("[lightgray]" + content.displayDescription()).wrap().fillX().padLeft(any ? 10 : 0).width(500f).padTop(any ? 0 : 10).left();
table.row();
if(!content.stats.useCategories && any){
table.add("@category.general").fillX().color(Pal.accent);
table.row();
}
}
Stats stats = content.stats;
for(StatCat cat : stats.toMap().keys()){
OrderedMap<Stat, Seq<StatValue>> map = stats.toMap().get(cat);
if(map.size == 0) continue;
//TODO check
if(stats.useCategories){
table.add("@category." + cat.name()).color(Pal.accent).fillX();
table.row();
}
for(Stat stat : map.keys()){
table.table(inset -> {
inset.left();
inset.add("[lightgray]" + stat.localized() + ":[] ").left();
Seq<StatValue> arr = map.get(stat);
for(StatValue value : arr){
value.display(inset);
inset.add().size(10f);
}
}).fillX().padLeft(10);
table.row();
}
}
if(content.details != null){
table.add("[gray]" + content.details).pad(6).padTop(20).width(400f).wrap().fillX();
table.row();
}
ScrollPane pane = new ScrollPane(table);
cont.add(pane);
show();
}
}

View File

@@ -24,8 +24,8 @@ import static arc.util.Time.*;
import static mindustry.Vars.*;
public class CustomRulesDialog extends BaseDialog{
private Table main;
Rules rules;
private Table main;
private Prov<Rules> resetter;
private LoadoutDialog loadoutDialog;
private BaseDialog banDialog;
@@ -134,15 +134,15 @@ public class CustomRulesDialog extends BaseDialog{
check("@rules.waves", b -> rules.waves = b, () -> rules.waves);
check("@rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer);
check("@rules.waitForWaveToEnd", b -> rules.waitEnemies = b, () -> rules.waitEnemies);
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> true);
number("@rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> true, 1, Float.MAX_VALUE);
number("@rules.dropzoneradius", false, f -> rules.dropZoneRadius = f * tilesize, () -> rules.dropZoneRadius / tilesize, () -> true);
title("@rules.title.resourcesbuilding");
check("@rules.infiniteresources", b -> rules.infiniteResources = b, () -> rules.infiniteResources);
check("@rules.reactorexplosions", b -> rules.reactorExplosions = b, () -> rules.reactorExplosions);
check("@rules.schematic", b-> rules.schematicsAllowed = b, () -> rules.schematicsAllowed);
check("@rules.schematic", b -> rules.schematicsAllowed = b, () -> rules.schematicsAllowed);
number("@rules.buildcostmultiplier", false, f -> rules.buildCostMultiplier = f, () -> rules.buildCostMultiplier, () -> !rules.infiniteResources);
number("@rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier);
number("@rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier, 0.00001f, 10000f);
number("@rules.deconstructrefundmultiplier", false, f -> rules.deconstructRefundMultiplier = f, () -> rules.deconstructRefundMultiplier, () -> !rules.infiniteResources);
number("@rules.blockhealthmultiplier", f -> rules.blockHealthMultiplier = f, () -> rules.blockHealthMultiplier);
number("@rules.blockdamagemultiplier", f -> rules.blockDamageMultiplier = f, () -> rules.blockDamageMultiplier);
@@ -162,17 +162,18 @@ public class CustomRulesDialog extends BaseDialog{
check("@rules.unitammo", b -> rules.unitAmmo = b, () -> rules.unitAmmo);
number("@rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier);
number("@rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier);
number("@rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier);
number("@rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier, 0.00001f, 100f);
title("@rules.title.enemy");
check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
check("@rules.buildai", b -> rules.waveTeam.rules().ai = b, () -> rules.waveTeam.rules().ai);
check("@rules.buildai", b -> rules.teams.get(rules.waveTeam).ai = rules.teams.get(rules.waveTeam).infiniteResources = b, () -> rules.teams.get(rules.waveTeam).ai);
number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200));
title("@rules.title.environment");
check("@rules.explosions", b -> rules.damageExplosions = b, () -> rules.damageExplosions);
check("@rules.fire", b -> rules.fire = b, () -> rules.fire);
check("@rules.lighting", b -> rules.lighting = b, () -> rules.lighting);
check("@rules.enemyLights", b -> rules.enemyLights = b, () -> rules.enemyLights);
main.button(b -> {
b.left();
@@ -185,15 +186,25 @@ public class CustomRulesDialog extends BaseDialog{
}, () -> ui.picker.show(rules.ambientLight, rules.ambientLight::set)).left().width(250f).row();
main.button("@rules.weather", this::weatherDialog).width(250f).left().row();
//TODO add weather patterns
}
void number(String text, Floatc cons, Floatp prov){
number(text, false, cons, prov, () -> true);
number(text, false, cons, prov, () -> true, 0, Float.MAX_VALUE);
}
void number(String text, Floatc cons, Floatp prov, float min, float max){
number(text, false, cons, prov, () -> true, min, max);
}
void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition){
number(text, integer, cons, prov, condition, 0, Float.MAX_VALUE);
}
void number(String text, Floatc cons, Floatp prov, Boolp condition){
number(text, false, cons, prov, condition, 0, Float.MAX_VALUE);
}
void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition, float min, float max){
main.table(t -> {
t.left();
t.add(text).left().padRight(5)
@@ -201,7 +212,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(Strings::canParsePositiveFloat).width(120f).left().addInputDialog();
.valid(f -> Strings.canParsePositiveFloat(f) && Strings.parseFloat(f) >= min && Strings.parseFloat(f) <= max).width(120f).left().addInputDialog();
}).padTop(0);
main.row();
}
@@ -273,19 +284,23 @@ public class CustomRulesDialog extends BaseDialog{
f.defaults().padRight(4).left();
f.add("@rules.weather.duration");
field(f, entry.minDuration / toMinutes, v -> entry.minDuration = v * toMinutes);
field(f, entry.minDuration / toMinutes, v -> entry.minDuration = v * toMinutes).disabled(v -> entry.always);
f.add("@waves.to");
field(f, entry.maxDuration / toMinutes, v -> entry.maxDuration = v * toMinutes);
field(f, entry.maxDuration / toMinutes, v -> entry.maxDuration = v * toMinutes).disabled(v -> entry.always);
f.add("@unit.minutes");
f.row();
f.add("@rules.weather.frequency");
field(f, entry.minFrequency / toMinutes, v -> entry.minFrequency = v * toMinutes);
field(f, entry.minFrequency / toMinutes, v -> entry.minFrequency = v * toMinutes).disabled(v -> entry.always);
f.add("@waves.to");
field(f, entry.maxFrequency / toMinutes, v -> entry.maxFrequency = v * toMinutes);
field(f, entry.maxFrequency / toMinutes, v -> entry.maxFrequency = v * toMinutes).disabled(v -> entry.always);
f.add("@unit.minutes");
f.row();
f.check("@rules.weather.always", val -> entry.always = val).checked(cc -> entry.always).padBottom(4);
//intensity can't currently be customized
}).grow().left().pad(6).top();
@@ -303,8 +318,8 @@ public class CustomRulesDialog extends BaseDialog{
dialog.addCloseButton();
dialog.buttons.button("@add", Icon.add, () -> {
BaseDialog addd = new BaseDialog("@add");
addd.cont.pane(t -> {
BaseDialog add = new BaseDialog("@add");
add.cont.pane(t -> {
t.background(Tex.button);
int i = 0;
for(Weather weather : content.<Weather>getBy(ContentType.weather)){
@@ -313,13 +328,13 @@ public class CustomRulesDialog extends BaseDialog{
rules.weather.add(new WeatherEntry(weather));
rebuild[0].run();
addd.hide();
add.hide();
}).size(140f, 50f);
if(++i % 2 == 0) t.row();
}
});
addd.addCloseButton();
addd.show();
add.addCloseButton();
add.show();
}).width(170f);
//reset cooldown to random number

View File

@@ -40,7 +40,7 @@ public class DatabaseDialog extends BaseDialog{
for(int j = 0; j < allContent.length; j++){
ContentType type = ContentType.all[j];
Seq<Content> array = allContent[j].select(c -> c instanceof UnlockableContent && !((UnlockableContent)c).isHidden());
Seq<Content> array = allContent[j].select(c -> c instanceof UnlockableContent u && (!u.isHidden() || u.node() != null));
if(array.size == 0) continue;
table.add("@content." + type.name() + ".name").growX().left().color(Pal.accent);
@@ -57,7 +57,7 @@ public class DatabaseDialog extends BaseDialog{
UnlockableContent unlock = (UnlockableContent)array.get(i);
Image image = unlocked(unlock) ? new Image(unlock.icon(Cicon.medium)).setScaling(Scaling.fit) : new Image(Icon.lock, Pal.gray);
list.add(image).size(8*4).pad(3);
list.add(image).size(8 * 4).pad(3);
ClickListener listener = new ClickListener();
image.addListener(listener);
if(!Vars.mobile && unlocked(unlock)){

View File

@@ -1,10 +1,10 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.graphics.Color;
import arc.scene.ui.Dialog;
import arc.*;
import arc.graphics.*;
import arc.scene.ui.*;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import mindustry.graphics.*;
import static mindustry.Vars.*;

View File

@@ -4,6 +4,7 @@ import arc.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
@@ -17,7 +18,7 @@ import java.util.*;
public class FileChooser extends BaseDialog{
private static final Fi homeDirectory = Core.files.absolute(Core.files.getExternalStoragePath());
static Fi lastDirectory = homeDirectory;
static Fi lastDirectory = Core.files.absolute(Core.settings.getString("lastDirectory", homeDirectory.absolutePath()));
private Table files;
Fi directory = lastDirectory;
@@ -45,6 +46,12 @@ public class FileChooser extends BaseDialog{
cont.clear();
setupWidgets();
});
keyDown(KeyCode.enter, () -> {
ok.fireClick();
});
addCloseListener();
}
private void setupWidgets(){
@@ -108,7 +115,7 @@ public class FileChooser extends BaseDialog{
ImageButton home = new ImageButton(Icon.home);
home.clicked(() -> {
directory = homeDirectory;
lastDirectory = directory;
setLastDirectory(directory);
updateFiles(true);
});
@@ -187,7 +194,7 @@ public class FileChooser extends BaseDialog{
TextButton upbutton = new TextButton(".." + directory.toString(), Styles.clearTogglet);
upbutton.clicked(() -> {
directory = directory.parent();
lastDirectory = directory;
setLastDirectory(directory);
updateFiles(true);
});
@@ -217,7 +224,7 @@ public class FileChooser extends BaseDialog{
updateFileFieldStatus();
}else{
directory = directory.child(filename);
lastDirectory = directory;
setLastDirectory(directory);
updateFiles(true);
}
});
@@ -242,6 +249,11 @@ public class FileChooser extends BaseDialog{
if(open) filefield.clearText();
}
public static void setLastDirectory(Fi directory){
lastDirectory = directory;
Core.settings.put("lastDirectory", directory.absolutePath());
}
private String shorten(String string){
int max = 30;
if(string.length() <= max){
@@ -269,14 +281,14 @@ public class FileChooser extends BaseDialog{
if(!canBack()) return;
index--;
directory = history.get(index - 1);
lastDirectory = directory;
setLastDirectory(directory);
updateFiles(false);
}
public void forward(){
if(!canForward()) return;
directory = history.get(index);
lastDirectory = directory;
setLastDirectory(directory);
index++;
updateFiles(false);
}

View File

@@ -1,8 +1,8 @@
package mindustry.ui.dialogs;
import arc.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.Stats.*;
import mindustry.game.*;
import mindustry.type.*;
import mindustry.ui.*;
@@ -16,6 +16,8 @@ public class GameOverDialog extends BaseDialog{
super("@gameover");
setFillParent(true);
shown(this::rebuild);
Events.on(ResetEvent.class, e -> hide());
}
public void show(Team winner){
@@ -29,13 +31,13 @@ public class GameOverDialog extends BaseDialog{
}
void rebuild(){
title.setText(state.launched ? "@launch.title" : "@gameover");
title.setText(state.isCampaign() ? "@sector.curlost" : "@gameover");
buttons.clear();
cont.clear();
buttons.margin(10);
if(state.rules.pvp){
if(state.rules.pvp && winner != null){
cont.add(Core.bundle.format("gameover.pvp", winner.localized())).pad(6);
buttons.button("@menu", () -> {
hide();
@@ -50,52 +52,51 @@ public class GameOverDialog extends BaseDialog{
cont.pane(t -> {
t.margin(13f);
t.left().defaults().left();
t.add(Core.bundle.format("stat.wave", state.stats.wavesLasted));
t.row();
t.add(Core.bundle.format("stat.enemiesDestroyed", state.stats.enemyUnitsDestroyed));
t.row();
t.add(Core.bundle.format("stat.built", state.stats.buildingsBuilt));
t.row();
t.add(Core.bundle.format("stat.destroyed", state.stats.buildingsDestroyed));
t.row();
t.add(Core.bundle.format("stat.deconstructed", state.stats.buildingsDeconstructed));
t.row();
t.add(Core.bundle.format("stat.wave", state.stats.wavesLasted)).row();
t.add(Core.bundle.format("stat.enemiesDestroyed", state.stats.enemyUnitsDestroyed)).row();
t.add(Core.bundle.format("stat.built", state.stats.buildingsBuilt)).row();
t.add(Core.bundle.format("stat.destroyed", state.stats.buildingsDestroyed)).row();
t.add(Core.bundle.format("stat.deconstructed", state.stats.buildingsDeconstructed)).row();
if(control.saves.getCurrent() != null){
t.add(Core.bundle.format("stat.playtime", control.saves.getCurrent().getPlayTime()));
t.row();
t.add(Core.bundle.format("stat.playtime", control.saves.getCurrent().getPlayTime())).row();
}
if(state.isCampaign() && !state.stats.itemsDelivered.isEmpty()){
t.add("@stat.delivered");
t.row();
t.add("@stat.delivered").row();
for(Item item : content.items()){
if(state.stats.itemsDelivered.get(item, 0) > 0){
t.table(items -> {
items.add(" [lightgray]" + state.stats.itemsDelivered.get(item, 0));
items.image(item.icon(Cicon.small)).size(8 * 3).pad(4);
}).left();
t.row();
}).left().row();
}
}
}
if(state.hasSector()){
RankResult result = state.stats.calculateRank(state.getSector(), state.launched);
t.add(Core.bundle.format("stat.rank", result.rank + result.modifier));
t.row();
if(state.isCampaign() && net.client()){
t.add("@gameover.waiting").padTop(20f).row();
}
}).pad(12);
if(state.isCampaign()){
buttons.button("@continue", () -> {
hide();
logic.reset();
ui.planet.show();
}).size(130f, 60f);
if(net.client()){
buttons.button("@gameover.disconnect", () -> {
logic.reset();
net.reset();
hide();
state.set(State.menu);
}).size(170f, 60f);
}else{
buttons.button("@continue", () -> {
hide();
ui.planet.show();
}).size(170f, 60f);
}
}else{
buttons.button("@menu", () -> {
hide();
logic.reset();
}).size(130f, 60f);
}).size(140f, 60f);
}
}
}

View File

@@ -85,22 +85,29 @@ public class HostDialog extends BaseDialog{
player.admin(true);
if(steam){
Core.app.post(() -> Core.settings.getBoolOnce("steampublic2", () -> {
Core.app.post(() -> Core.settings.getBoolOnce("steampublic3", () -> {
ui.showCustomConfirm("@setting.publichost.name", "@public.confirm", "@yes", "@no", () -> {
Core.settings.put("publichost", true);
platform.updateLobby();
ui.showCustomConfirm("@setting.publichost.name", "@public.confirm.really", "@no", "@yes", () -> {
Core.settings.put("publichost", true);
platform.updateLobby();
}, () -> {
Core.settings.put("publichost", false);
platform.updateLobby();
});
}, () -> {
Core.settings.put("publichost", false);
platform.updateLobby();
});
}));
if(Version.modifier.contains("beta") || Version.modifier.contains("alpha")){
Core.settings.put("publichost", false);
platform.updateLobby();
Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta"));
}
}
if(Version.modifier.contains("beta")){
Core.settings.put("publichost", false);
platform.updateLobby();
Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("@public.beta"));
}
}catch(IOException e){
ui.showException("@server.error", e);
}

View File

@@ -1,6 +1,7 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.Net.*;
import arc.graphics.*;
import arc.input.*;
import arc.math.*;
@@ -8,6 +9,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.Timer.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.core.*;
@@ -31,6 +33,11 @@ public class JoinDialog extends BaseDialog{
Table hosts = new Table();
int totalHosts;
int refreshes;
boolean showHidden;
String lastIp;
int lastPort;
Task ping;
public JoinDialog(){
super("@joingame");
@@ -193,7 +200,7 @@ public class JoinDialog extends BaseDialog{
void refreshServer(Server server){
server.content.clear();
server.content.label(() -> Core.bundle.get("server.refreshing") + Strings.animated(Time.time(), 4, 11, "."));
server.content.label(() -> Core.bundle.get("server.refreshing") + Strings.animated(Time.time, 4, 11, "."));
net.pingHost(server.ip, server.port, host -> setupServer(server, host), e -> {
server.content.clear();
@@ -252,9 +259,9 @@ public class JoinDialog extends BaseDialog{
hosts.clear();
section("@servers.local", local);
section("@servers.remote", remote);
section("@servers.global", global);
section("@servers.local", local, false);
section("@servers.remote", remote, false);
section("@servers.global", global, true);
ScrollPane pane = new ScrollPane(hosts);
pane.setFadeScrollBars(false);
@@ -292,7 +299,7 @@ public class JoinDialog extends BaseDialog{
pad = 6;
}
Cell cell = ((Table)pane.parent).getCell(button);
var cell = ((Table)pane.parent).getCell(button);
if(!Mathf.equal(cell.minWidth(), pw)){
cell.width(pw);
@@ -302,12 +309,21 @@ public class JoinDialog extends BaseDialog{
});
}
void section(String label, Table servers){
void section(String label, Table servers, boolean eye){
Collapser coll = new Collapser(servers, Core.settings.getBool("collapsed-" + label, false));
coll.setDuration(0.1f);
hosts.table(name -> {
name.add(label).pad(10).growX().left().color(Pal.accent);
if(eye){
name.button(Icon.eyeSmall, Styles.emptyi, () -> {
showHidden = !showHidden;
refreshGlobal();
}).update(i -> i.getStyle().imageUp = (showHidden ? Icon.eyeSmall : Icon.eyeOffSmall))
.size(40f).right().padRight(3).tooltip("@servers.showhidden");
}
name.button(Icon.downOpen, Styles.emptyi, () -> {
coll.toggle(false);
Core.settings.put("collapsed-" + label, coll.isCollapsed());
@@ -325,7 +341,7 @@ public class JoinDialog extends BaseDialog{
local.clear();
local.background(null);
local.table(Tex.button, t -> t.label(() -> "[accent]" + Core.bundle.get("hosts.discovering.any") + Strings.animated(Time.time(), 4, 10f, ".")).pad(10f)).growX();
local.table(Tex.button, t -> t.label(() -> "[accent]" + Core.bundle.get("hosts.discovering.any") + Strings.animated(Time.time, 4, 10f, ".")).pad(10f)).growX();
net.discoverServers(this::addLocalHost, this::finishLocalHosts);
}
@@ -334,17 +350,73 @@ public class JoinDialog extends BaseDialog{
global.clear();
global.background(null);
for(String host : defaultServers){
String resaddress = host.contains(":") ? host.split(":")[0] : host;
int resport = host.contains(":") ? Strings.parseInt(host.split(":")[1]) : port;
net.pingHost(resaddress, resport, res -> {
if(refreshes != cur) return;
res.port = resport;
addGlobalHost(res);
}, e -> {});
for(ServerGroup group : defaultServers){
boolean hidden = group.hidden();
if(hidden && !showHidden){
continue;
}
Table[] groupTable = {null};
//table containing all groups
for(String address : group.addresses){
String resaddress = address.contains(":") ? address.split(":")[0] : address;
int resport = address.contains(":") ? Strings.parseInt(address.split(":")[1]) : port;
net.pingHost(resaddress, resport, res -> {
if(refreshes != cur) return;
res.port = resport;
//add header
if(groupTable[0] == null){
global.table(t -> groupTable[0] = t).row();
groupTable[0].table(head -> {
if(!group.name.isEmpty()){
head.add(group.name).color(Color.lightGray).padRight(4);
}
head.image().height(3f).growX().color(Color.lightGray);
//button for showing/hiding servers
ImageButton[] image = {null};
image[0] = head.button(hidden ? Icon.eyeOffSmall : Icon.eyeSmall, Styles.accenti, () -> {
group.setHidden(!group.hidden());
image[0].getStyle().imageUp = group.hidden() ? Icon.eyeOffSmall : Icon.eyeSmall;
if(group.hidden() && !showHidden){
groupTable[0].remove();
}
}).size(40f).get();
image[0].addListener(new Tooltip(t -> t.background(Styles.black6).margin(4).label(() -> !group.hidden() ? "@server.shown" : "@server.hidden")));
}).width(targetWidth()).padBottom(-2).row();
}
addGlobalHost(res, groupTable[0]);
groupTable[0].margin(5f);
groupTable[0].pack();
}, e -> {});
}
}
}
void addGlobalHost(Host host, Table container){
global.background(null);
float w = targetWidth();
container.button(b -> buildServer(host, b), Styles.cleart, () -> {
Events.fire(new ClientPreConnectEvent(host));
if(!Core.settings.getBool("server-disclaimer", false)){
ui.showCustomConfirm("@warning", "@servers.disclaimer", "@ok", "@back", () -> {
Core.settings.put("server-disclaimer", true);
safeConnect(host.address, host.port, host.version);
}, () -> {
Core.settings.put("server-disclaimer", false);
});
}else{
safeConnect(host.address, host.port, host.version);
}
}).width(w).row();
}
void finishLocalHosts(){
if(totalHosts == 0){
local.clear();
@@ -367,22 +439,10 @@ public class JoinDialog extends BaseDialog{
local.row();
TextButton button = local.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
.width(w).pad(5f).get();
button.clearChildren();
buildServer(host, button);
}
void addGlobalHost(Host host){
global.background(null);
float w = targetWidth();
global.row();
TextButton button = global.button("", Styles.cleart, () -> safeConnect(host.address, host.port, host.version))
.width(w).pad(5f).get();
button.clearChildren();
buildServer(host, button);
local.button(b -> buildServer(host, b), Styles.cleart, () -> {
Events.fire(new ClientPreConnectEvent(host));
safeConnect(host.address, host.port, host.version);
}).width(w);
}
public void connect(String ip, int port){
@@ -402,13 +462,34 @@ public class JoinDialog extends BaseDialog{
logic.reset();
net.reset();
Vars.netClient.beginConnecting();
net.connect(ip, port, () -> {
net.connect(lastIp = ip, lastPort = port, () -> {
hide();
add.hide();
});
});
}
public void reconnect(){
if(lastIp == null || lastIp.isEmpty()) return;
ui.loadfrag.show("@reconnecting");
ping = Timer.schedule(() -> {
net.pingHost(lastIp, lastPort, host -> {
if(ping == null) return;
ping.cancel();
ping = null;
connect(lastIp, lastPort);
}, exception -> {});
}, 1, 1);
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
if(ping == null) return;
ping.cancel();
ping = null;
});
}
void safeConnect(String ip, int port, int version){
if(version != Version.build && Version.build != -1 && version != -1){
ui.showInfo("[scarlet]" + (version > Version.build ? KickReason.clientOutdated : KickReason.serverOutdated).toString() + "\n[]" +
@@ -432,23 +513,42 @@ public class JoinDialog extends BaseDialog{
Core.settings.remove("server-list");
}
var url = becontrol.active() ? serverJsonBeURL : serverJsonV6URL;
Log.info("Fetching community servers at @", url);
//get servers
Core.net.httpGet(becontrol.active() ? serverJsonBeURL : serverJsonV6URL, result -> {
Core.net.httpGet(url, result -> {
try{
if(result.getStatus() != HttpStatus.OK){
Log.warn("Failed to fetch community servers: @", result.getStatus());
return;
}
Jval val = Jval.read(result.getResultAsString());
Core.app.post(() -> {
try{
defaultServers.clear();
val.asArray().each(child -> defaultServers.add(child.getString("address", "<invalid>")));
Log.info("Fetched @ global servers.", defaultServers.size);
}catch(Throwable ignored){
val.asArray().each(child -> {
String name = child.getString("name", "");
String[] addresses;
if(child.has("addresses") || (child.has("address") && child.get("address").isArray())){
addresses = (child.has("addresses") ? child.get("addresses") : child.get("address")).asArray().map(Jval::asString).toArray(String.class);
}else{
addresses = new String[]{child.getString("address", "<invalid>")};
}
defaultServers.add(new ServerGroup(name, addresses));
});
Log.info("Fetched @ community servers.", defaultServers.size);
}catch(Throwable e){
Log.err("Failed to parse community servers.");
Log.err(e);
}
});
}catch(Throwable e){
Log.err("Failed to fetch community servers.");
Log.err(e);
}
}, t -> {});
}, Log::err);
}
private void saveServers(){

View File

@@ -1,17 +1,15 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.struct.*;
import arc.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.Table;
import arc.util.Log;
import arc.util.Strings;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.ui.*;
import java.util.Locale;
import java.util.*;
import static mindustry.Vars.locales;
import static mindustry.Vars.ui;
import static mindustry.Vars.*;
public class LanguageDialog extends BaseDialog{
private Locale lastLocale;

View File

@@ -29,18 +29,30 @@ public class LaunchLoadoutDialog extends BaseDialog{
super("@configure");
}
public void show(CoreBlock core, Building build, Runnable confirm){
public void show(CoreBlock core, Sector sector, Runnable confirm){
cont.clear();
buttons.clear();
addCloseButton();
buttons.defaults().size(160f, 64f);
buttons.button("@back", Icon.left, this::hide);
addCloseListener();
ItemSeq sitems = sector.items();
//updates sum requirements
Runnable update = () -> {
int cap = selected.findCore().itemCapacity;
//cap resources based on core type
ItemSeq resources = universe.getLaunchResources();
resources.min(cap);
universe.updateLaunchResources(resources);
total.clear();
selected.requirements().each(total::add);
universe.getLaunchResources().each(total::add);
valid = build.items.has(total);
valid = sitems.has(total);
};
Cons<Table> rebuild = table -> {
@@ -51,14 +63,14 @@ public class LaunchLoadoutDialog extends BaseDialog{
ItemSeq launches = universe.getLaunchResources();
for(ItemStack s : total){
table.image(s.item.icon(Cicon.small)).left();
table.image(s.item.icon(Cicon.small)).left().size(Cicon.small.size);
int as = schems.get(s.item), al = launches.get(s.item);
String amountStr = "[lightgray]" + (al + " + [accent]" + as + "[lightgray]");
String amountStr = (al + as) + "[gray] (" + (al + " + " + as + ")");
table.add(
build.items.has(s.item, s.amount) ? amountStr :
"[scarlet]" + (Math.min(build.items.get(s.item), s.amount) + "[lightgray]/" + amountStr)).padLeft(2).left().padRight(4);
sitems.has(s.item, s.amount) ? amountStr :
"[scarlet]" + (Math.min(sitems.get(s.item), s.amount) + "[lightgray]/" + amountStr)).padLeft(2).left().padRight(4);
if(++i % 4 == 0){
table.row();
@@ -74,12 +86,12 @@ public class LaunchLoadoutDialog extends BaseDialog{
ItemSeq stacks = universe.getLaunchResources();
Seq<ItemStack> out = stacks.toSeq();
loadout.show(core.itemCapacity, out, UnlockableContent::unlocked, out::clear, () -> {}, () -> {
loadout.show(selected.findCore().itemCapacity, out, UnlockableContent::unlocked, out::clear, () -> {}, () -> {
universe.updateLaunchResources(new ItemSeq(out));
update.run();
rebuildItems.run();
});
});
}).width(204);
buttons.button("@launch.text", Icon.ok, () -> {
universe.updateLoadout(core, selected);
@@ -91,25 +103,35 @@ public class LaunchLoadoutDialog extends BaseDialog{
ButtonGroup<Button> group = new ButtonGroup<>();
selected = universe.getLoadout(core);
cont.add(Core.bundle.format("launch.from", sector.name())).row();
cont.pane(t -> {
int i = 0;
for(Schematic s : schematics.getLoadouts(core)){
for(var entry : schematics.getLoadouts()){
if(entry.key.size <= core.size){
for(Schematic s : entry.value){
t.button(b -> b.add(new SchematicImage(s)), Styles.togglet, () -> {
selected = s;
update.run();
rebuildItems.run();
}).group(group).pad(4).disabled(!build.items.has(s.requirements())).checked(s == selected).size(200f);
t.button(b -> b.add(new SchematicImage(s)), Styles.togglet, () -> {
selected = s;
update.run();
rebuildItems.run();
}).group(group).pad(4).checked(s == selected).size(200f);
if(++i % cols == 0){
t.row();
if(++i % cols == 0){
t.row();
}
}
}
}
}).growX().get().setScrollingDisabled(true, false);
cont.row();
cont.add(items);
cont.pane(items);
cont.row();
cont.add("@sector.missingresources").visible(() -> !valid);
update.run();
rebuildItems.run();

View File

@@ -4,7 +4,6 @@ import arc.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.maps.*;
@@ -15,7 +14,7 @@ import static mindustry.Vars.*;
public class MapPlayDialog extends BaseDialog{
CustomRulesDialog dialog = new CustomRulesDialog();
Rules rules;
@NonNull Gamemode selectedGamemode = Gamemode.survival;
Gamemode selectedGamemode = Gamemode.survival;
Map lastMap;
public MapPlayDialog(){

View File

@@ -2,7 +2,6 @@ package mindustry.ui.dialogs;
import arc.*;
import arc.graphics.*;
import arc.input.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
@@ -24,11 +23,7 @@ public class MapsDialog extends BaseDialog{
buttons.remove();
keyDown(key -> {
if(key == KeyCode.escape || key == KeyCode.back){
Core.app.post(this::hide);
}
});
addCloseListener();
shown(this::setup);
onResize(() -> {
@@ -182,11 +177,14 @@ public class MapsDialog extends BaseDialog{
t.row();
t.add("@editor.author").padRight(10).color(Color.gray);
t.row();
t.add(!map.custom && map.author().isEmpty() ? "Anuke" : map.author()).growX().wrap().padTop(2);
t.add(!map.custom && map.tags.get("author", "").isEmpty() ? "Anuke" : map.author()).growX().wrap().padTop(2);
t.row();
t.add("@editor.description").padRight(10).color(Color.gray).top();
t.row();
t.add(map.description()).growX().wrap().padTop(2);
if(!map.tags.get("description", "").isEmpty()){
t.add("@editor.description").padRight(10).color(Color.gray).top();
t.row();
t.add(map.description()).growX().wrap().padTop(2);
}
}).height(mapsize).width(mapsize);
table.row();

View File

@@ -8,7 +8,7 @@ import arc.scene.event.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import static mindustry.Vars.renderer;
import static mindustry.Vars.*;
public class MinimapDialog extends BaseDialog{

View File

@@ -6,11 +6,19 @@ import arc.files.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.scene.style.*;
import arc.scene.ui.TextButton.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.mod.*;
import mindustry.mod.Mods.*;
import mindustry.ui.*;
@@ -19,6 +27,8 @@ import java.io.*;
import static mindustry.Vars.*;
public class ModsDialog extends BaseDialog{
private String searchtxt = "";
private @Nullable Seq<ModListing> modList;
public ModsDialog(){
super("@mods");
@@ -27,6 +37,9 @@ public class ModsDialog extends BaseDialog{
buttons.button("@mods.guide", Icon.link, () -> Core.app.openURI(modGuideURL)).size(210, 64f);
shown(this::setup);
if(mobile){
onResize(this::setup);
}
hidden(() -> {
if(mods.requiresReload()){
@@ -51,6 +64,30 @@ public class ModsDialog extends BaseDialog{
}
}
void getModList(Cons<Seq<ModListing>> listener){
if(modList == null){
Core.net.httpGet("https://raw.githubusercontent.com/Anuken/MindustryMods/master/mods.json", response -> {
String strResult = response.getResultAsString();
var status = response.getStatus();
Core.app.post(() -> {
if(status != HttpStatus.OK){
ui.showErrorMessage(Core.bundle.format("connectfail", status));
}else{
modList = new Json().fromJson(Seq.class, ModListing.class, strResult);
//potentially sort mods by game version compatibility, or other criteria
//modList.sort(Structs.comparingBool(m -> !Version.isAtLeast(m.minGameVersion)));
listener.get(modList);
}
});
}, error -> Core.app.post(() -> ui.showException(error)));
}else{
listener.get(modList);
}
}
void setup(){
float h = 110f;
float w = mobile ? 440f : 524f;
@@ -103,24 +140,87 @@ public class ModsDialog extends BaseDialog{
t.button("@mod.import.github", Icon.github, bstyle, () -> {
dialog.hide();
ui.showTextInput("@mod.import.github", "", 64, Core.settings.getString("lastmod", "Anuken/ExampleMod"), text -> {
ui.showTextInput("@mod.import.github", "", 64, Core.settings.getString("lastmod", ""), text -> {
Core.settings.put("lastmod", text);
ui.loadfrag.show();
// Try to download the 6.0 branch first, but if it doesnt exist try master.
githubImport("6.0", text, e1 -> {
githubImport("master", text, e2 -> {
ui.showErrorMessage(Core.bundle.format("connectfail", e2));
ui.loadfrag.hide();
});
});
githubImportMod(text);
});
}).margin(12f);
});
t.row();
t.button("@mod.featured.dialog.title", Icon.star, bstyle, () -> {
Runnable[] rebuildBrowser = {null};
dialog.hide();
BaseDialog browser = new BaseDialog("$mod.featured.dialog.title");
browser.cont.table(table -> {
table.left();
table.image(Icon.zoom);
table.field(searchtxt, res -> {
searchtxt = res;
rebuildBrowser[0].run();
}).growX().get();
}).fillX().padBottom(4);
browser.cont.row();
browser.cont.pane(tablebrow -> {
tablebrow.margin(10f).top();
rebuildBrowser[0] = () -> {
tablebrow.clear();
tablebrow.add("@loading");
getModList(listings -> {
tablebrow.clear();
for(ModListing mod : listings){
if(mod.hasJava || !searchtxt.isEmpty() && !mod.repo.contains(searchtxt) || (Vars.ios && mod.hasScripts)) continue;
tablebrow.button(btn -> {
btn.top().left();
btn.margin(12f);
btn.table(con -> {
con.left();
con.add("[accent]" + mod.name + "[white]\n[lightgray]Author:[] " + mod.author + "\n[lightgray]\uE809 " + mod.stars +
(Version.isAtLeast(mod.minGameVersion) ? "" : "\n" + Core.bundle.format("mod.requiresversion", mod.minGameVersion)))
.width(388f).wrap().growX().pad(0f, 6f, 0f, 6f).left().labelAlign(Align.left);
con.add().growX().pad(0f, 6f, 0f, 6f);
}).fillY().growX().pad(0f, 6f, 0f, 6f);
}, Styles.modsb, () -> {
var sel = new BaseDialog(mod.name);
sel.cont.add(mod.description).width(mobile ? 400f : 500f).wrap().pad(4f).labelAlign(Align.center, Align.left);
sel.buttons.defaults().size(150f, 54f).pad(2f);
sel.setFillParent(false);
sel.buttons.button("@back", Icon.left, () -> {
sel.clear();
sel.hide();
});
sel.buttons.button("@mods.browser.add", Icon.download, () -> {
sel.hide();
githubImportMod(mod.repo);
});
sel.buttons.button("@mods.github.open", Icon.link, () -> {
Core.app.openURI("https://github.com/" + mod.repo);
});
sel.keyDown(KeyCode.escape, sel::hide);
sel.keyDown(KeyCode.back, sel::hide);
sel.show();
}).width(480f).growX().left().fillY();
tablebrow.row();
}
});
};
rebuildBrowser[0].run();
});
browser.addCloseButton();
browser.show();
}).margin(12f);
});
dialog.addCloseButton();
dialog.show();
}).margin(margin);
if(!mobile){
@@ -131,14 +231,12 @@ public class ModsDialog extends BaseDialog{
cont.row();
if(!mods.list().isEmpty()){
cont.pane(table -> {
table.margin(10f).top();
boolean anyDisabled = false;
for(LoadedMod mod : mods.list()){
if(!mod.enabled() && !anyDisabled && mods.list().size > 0){
anyDisabled = true;
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();
@@ -212,20 +310,14 @@ public class ModsDialog extends BaseDialog{
}).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();
}else{
cont.table(Styles.black6, t -> t.add("@mods.none")).height(80f);
}
cont.row();
}
private void reload(){
@@ -260,54 +352,86 @@ public class ModsDialog extends BaseDialog{
desc.add("@editor.description").padRight(10).color(Color.gray).top();
desc.row();
desc.add(mod.meta.description).growX().wrap().padTop(2);
desc.row();
}
//TODO add this when mods work properly
/*
Array<UnlockableContent> all = Array.with(content.getContentMap()).<Content>flatten().select(c -> c.minfo.mod == mod && c instanceof UnlockableContent).as(UnlockableContent.class);
}).width(400f);
//TODO maybe enable later
if(false){
Seq<UnlockableContent> all = Seq.with(content.getContentMap()).<Content>flatten().select(c -> c.minfo.mod == mod && c instanceof UnlockableContent).as();
if(all.any()){
desc.add("@mod.content").padRight(10).color(Color.gray).top();
desc.row();
desc.pane(cs -> {
dialog.cont.row();
dialog.cont.pane(cs -> {
int i = 0;
for(UnlockableContent c : all){
cs.addImageButton(new TextureRegionDrawable(c.icon(Cicon.medium)), () -> {
cs.button(new TextureRegionDrawable(c.icon(Cicon.medium)), Styles.cleari, Cicon.medium.size, () -> {
ui.content.show(c);
}).size(50f).with(im -> {
var click = im.getClickListener();
im.update(() -> im.getImage().color.lerp(!click.isOver() ? Color.lightGray : Color.white, 0.4f * Time.delta));
});
if(++i % 8 == 0) cs.row();
}
}).growX().minHeight(60f);
}*/
}).width(400f);
}
}
dialog.show();
}
private void githubImport(String branch, String repo, Cons<HttpStatus> err){
Core.net.httpGet("http://api.github.com/repos/" + repo + "/zipball/" + branch, loc -> {
Core.net.httpGet(loc.getHeader("Location"), result -> {
if(result.getStatus() != HttpStatus.OK){
err.get(result.getStatus());
}else{
try{
Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip");
Streams.copy(result.getResultAsStream(), file.write(false));
mods.importMod(file);
file.delete();
Core.app.post(() -> {
try{
setup();
ui.loadfrag.hide();
}catch(Throwable e){
ui.showException(e);
}
});
}catch(Throwable e){
modError(e);
}
private void handleMod(String repo, HttpResponse result){
try{
Fi file = tmpDirectory.child(repo.replace("/", "") + ".zip");
Streams.copy(result.getResultAsStream(), file.write(false));
mods.importMod(file);
file.delete();
Core.app.post(() -> {
try{
setup();
ui.loadfrag.hide();
}catch(Throwable e){
ui.showException(e);
}
}, t2 -> Core.app.post(() -> modError(t2)));
});
}catch(Throwable e){
modError(e);
}
}
private void githubImportMod(String name){
//try several branches
//TODO use only the main branch as specified in meta
githubImportBranch("6.0", name, e1 -> {
githubImportBranch("master", name, e2 -> {
githubImportBranch("main", name, e3 -> {
ui.showErrorMessage(Core.bundle.format("connectfail", e2));
ui.loadfrag.hide();
});
});
});
}
private void githubImportBranch(String branch, String repo, Cons<HttpStatus> err){
Core.net.httpGet("https://api.github.com/repos/" + repo + "/zipball/" + branch, loc -> {
if(loc.getStatus() == HttpStatus.OK){
if(loc.getHeader("Location") != null){
Core.net.httpGet(loc.getHeader("Location"), result -> {
if(result.getStatus() != HttpStatus.OK){
err.get(result.getStatus());
}else{
handleMod(repo, result);
}
}, t2 -> Core.app.post(() -> modError(t2)));
}else{
handleMod(repo, loc);
}
}else{
err.get(loc.getStatus());
}
}, t2 -> Core.app.post(() -> modError(t2)));
}
}

View File

@@ -2,9 +2,7 @@ package mindustry.ui.dialogs;
import arc.func.*;
import arc.graphics.*;
import arc.input.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.ui.*;
@@ -19,29 +17,24 @@ public class PaletteDialog extends Dialog{
}
private void build(){
Table table = new Table();
cont.add(table);
cont.table(table -> {
for(int i = 0; i < playerColors.length; i++){
Color color = playerColors[i];
for(int i = 0; i < playerColors.length; i++){
Color color = playerColors[i];
ImageButton button = table.button(Tex.whiteui, Styles.clearTogglei, 34, () -> {
cons.get(color);
hide();
}).size(48).get();
button.setChecked(player.color().equals(color));
button.getStyle().imageUpColor = color;
ImageButton button = table.button(Tex.whiteui, Styles.clearTogglei, 34, () -> {
cons.get(color);
hide();
}).size(48).get();
button.setChecked(player.color().equals(color));
button.getStyle().imageUpColor = color;
if(i % 4 == 3){
table.row();
if(i % 4 == 3){
table.row();
}
}
}
keyDown(key -> {
if(key == KeyCode.escape || key == KeyCode.back)
hide();
});
closeOnBack();
}
public void show(Cons<Color> cons){

View File

@@ -1,7 +1,6 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.input.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -17,11 +16,7 @@ public class PausedDialog extends BaseDialog{
shown(this::rebuild);
keyDown(key -> {
if(key == KeyCode.escape || key == KeyCode.back){
hide();
}
});
addCloseListener();
}
void rebuild(){
@@ -34,43 +29,34 @@ public class PausedDialog extends BaseDialog{
});
if(!mobile){
//TODO localize
cont.label(() -> state.getSector() == null ? "" :
("[lightgray]Next turn in [accent]" + state.getSector().displayTimeRemaining() +
(state.rules.winWave > 0 && !state.getSector().isCaptured() ? "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture" : "")))
.visible(() -> state.getSector() != null).colspan(2);
cont.row();
float dw = 220f;
cont.defaults().width(dw).height(55).pad(5f);
cont.button("@back", Icon.left, this::hide);
cont.button("@settings", Icon.settings, ui.settings::show);
if(!state.rules.tutorial){
if(!state.isCampaign() && !state.isEditor()){
cont.row();
cont.button("@savegame", Icon.save, save::show);
cont.button("@loadgame", Icon.upload, load::show).disabled(b -> net.active());
}
cont.button("@back", Icon.left, this::hide).name("back");
cont.button("@settings", Icon.settings, ui.settings::show).name("settings");
if(!state.isCampaign() && !state.isEditor()){
cont.row();
cont.button("@hostserver", Icon.host, () -> {
if(net.server() && steam){
platform.inviteFriends();
}else{
if(steam){
ui.host.runHost();
}else{
ui.host.show();
}
}
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(2).width(dw * 2 + 20f).update(e -> e.setText(net.server() && steam ? "@invitefriends" : "@hostserver"));
cont.button("@savegame", Icon.save, save::show);
cont.button("@loadgame", Icon.upload, load::show).disabled(b -> net.active());
}
cont.row();
cont.button("@hostserver", Icon.host, () -> {
if(net.server() && steam){
platform.inviteFriends();
}else{
if(steam){
ui.host.runHost();
}else{
ui.host.show();
}
}
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(2).width(dw * 2 + 20f).update(e -> e.setText(net.server() && steam ? "@invitefriends" : "@hostserver"));
cont.row();
cont.button("@quit", Icon.exit, this::showQuitConfirm).colspan(2).width(dw + 20f).update(s -> s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "@save.quit" : "@quit"));
}else{
@@ -85,10 +71,7 @@ public class PausedDialog extends BaseDialog{
cont.buttonRow("@load", Icon.download, load::show).disabled(b -> net.active());
}else if(state.isCampaign()){
cont.buttonRow("@launchcore", Icon.up, () -> {
hide();
ui.planet.showLaunch(state.getSector(), player.team().core());
}).disabled(b -> player.team().core() == null);
cont.buttonRow("@research", Icon.tree, ui.research::show);
cont.row();
@@ -110,10 +93,7 @@ public class PausedDialog extends BaseDialog{
}
void showQuitConfirm(){
ui.showConfirm("@confirm", state.rules.tutorial ? "@quit.confirm.tutorial" : "@quit.confirm", () -> {
if(state.rules.tutorial){
Core.settings.put("playedtutorial", true);
}
ui.showConfirm("@confirm", "@quit.confirm", () -> {
wasClient = net.client();
if(net.client()) netClient.disconnectQuietly();
runExitSave();
@@ -127,7 +107,7 @@ public class PausedDialog extends BaseDialog{
return;
}
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || state.rules.tutorial || wasClient){
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || wasClient){
logic.reset();
return;
}

View File

@@ -12,52 +12,85 @@ import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.Objectives.*;
import mindustry.game.SectorInfo.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.g3d.*;
import mindustry.input.*;
import mindustry.io.legacy.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
import static mindustry.graphics.g3d.PlanetRenderer.*;
import static mindustry.ui.dialogs.PlanetDialog.Mode.*;
public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
final FrameBuffer buffer = new FrameBuffer(2, 2, true);
final PlanetRenderer planets = renderer.planets;
final LaunchLoadoutDialog loadouts = new LaunchLoadoutDialog();
final Table stable = new Table().background(Styles.black3);
//if true, enables launching anywhere for testing
public static boolean debugSelect = false;
public static float sectorShowDuration = 60f * 2.4f;
int launchRange;
float zoom = 1f, selectAlpha = 1f;
@Nullable Sector selected, hovered, launchSector;
CoreBuild launcher;
Mode mode = look;
boolean launching;
Cons<Sector> listener = s -> {};
public final FrameBuffer buffer = new FrameBuffer(2, 2, true);
public final PlanetRenderer planets = renderer.planets;
public final LaunchLoadoutDialog loadouts = new LaunchLoadoutDialog();
public float zoom = 1f, selectAlpha = 1f;
public @Nullable Sector selected, hovered, launchSector;
public Mode mode = look;
public boolean launching;
public Cons<Sector> listener = s -> {};
public Seq<Sector> newPresets = new Seq<>();
public float presetShow = 0f;
public boolean showed = false;
public Table sectorTop = new Table();
public Label hoverLabel = new Label("");
public PlanetDialog(){
super("", Styles.fullDialog);
shouldPause = true;
getCell(buttons).padBottom(-4);
buttons.background(Styles.black).defaults().growX().height(64f).pad(0);
keyDown(key -> {
if(key == KeyCode.escape || key == KeyCode.back){
Core.app.post(this::hide);
if(key == KeyCode.escape || key == KeyCode.back || key == Core.keybinds.get(Binding.planet_map).key){
if(showing() && newPresets.size > 1){
//clear all except first, which is the last sector.
newPresets.truncate(1);
}else if(selected != null){
selected = null;
updateSelected();
}else{
Core.app.post(this::hide);
}
}
});
hoverLabel.setStyle(Styles.outlineLabel);
hoverLabel.setAlignment(Align.center);
rebuildButtons();
onResize(this::rebuildButtons);
dragged((cx, cy) -> {
//no multitouch drag
if(Core.input.getTouches() > 1) return;
if(showing()){
newPresets.clear();
}
Vec3 pos = planets.camPos;
float upV = pos.angle(Vec3.Y);
@@ -80,21 +113,122 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
zoom = Mathf.clamp(zoom + value / 10f, 0.5f, 2f);
});
addCaptureListener(new ElementGestureListener(){
float lastZoom = -1f;
@Override
public void zoom(InputEvent event, float initialDistance, float distance){
if(lastZoom < 0){
lastZoom = zoom;
}
zoom = (Mathf.clamp(initialDistance / distance * lastZoom, 0.5f, 2f));
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
lastZoom = zoom;
}
});
shown(this::setup);
}
/** show with no limitations, just as a map. */
@Override
public Dialog show(){
if(net.client()){
ui.showInfo("@map.multiplayer");
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;
launching = false;
zoom = 1f;
planets.zoom = 1f;
selectAlpha = 0f;
launchSector = state.getSector();
presetShow = 0f;
showed = false;
newPresets.clear();
//announce new presets
for(SectorPreset preset : content.sectors()){
if(preset.unlocked() && !preset.alwaysUnlocked && !preset.sector.info.shown && !preset.sector.hasBase()){
newPresets.add(preset.sector);
preset.sector.info.shown = true;
preset.sector.saveInfo();
}
}
if(newPresets.any()){
newPresets.add(planets.planet.getLastSector());
}
newPresets.reverse();
updateSelected();
if(planets.planet.getLastSector() != null){
lookAt(planets.planet.getLastSector());
}
return super.show();
}
void rebuildButtons(){
buttons.clearChildren();
buttons.bottom();
if(Core.graphics.isPortrait()){
buttons.add(sectorTop).colspan(2).fillX().row();
addBack();
addTech();
}else{
addBack();
buttons.add().growX();
buttons.add(sectorTop).minWidth(230f);
buttons.add().growX();
addTech();
}
}
void addBack(){
buttons.button("@back", Icon.left, this::hide).size(200, 54).pad(2).bottom();
}
void addTech(){
buttons.button("@techtree", Icon.tree, () -> ui.research.show()).size(200, 54).pad(2).bottom();
}
public void showOverview(){
//TODO implement later if necessary
/*
sectors.captured = Captured Sectors
sectors.explored = Explored Sectors
sectors.production.total = Total Production
sectors.resources.total = Total Resources
*/
var dialog = new BaseDialog("@overview");
dialog.addCloseButton();
dialog.add("@sectors.captured");
}
public void showSelect(Sector sector, Cons<Sector> listener){
selected = null;
hovered = null;
@@ -104,7 +238,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
//update view to sector
lookAt(sector);
zoom = 1f;
planets.zoom = 2f;
planets.zoom = 1f;
selectAlpha = 0f;
launchSector = sector;
@@ -113,54 +247,58 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
super.show();
}
public void showLaunch(Sector sector, CoreBuild launcher){
if(launcher == null) return;
this.launcher = launcher;
selected = null;
hovered = null;
launching = false;
//update view to sector
lookAt(sector);
zoom = 1f;
planets.zoom = 2f;
selectAlpha = 0f;
launchRange = ((CoreBlock)launcher.block).launchRange;
launchSector = sector;
mode = launch;
super.show();
}
private void lookAt(Sector sector){
void lookAt(Sector sector){
planets.camPos.set(Tmp.v33.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation()));
}
boolean canSelect(Sector sector){
if(mode == select) return sector.hasBase();
if(sector.hasBase()) return true;
//preset sectors can only be selected once unlocked
if(sector.preset != null){
TechNode node = sector.preset.node();
return node == null || node.parent == null || node.parent.content.unlocked();
}
return mode == launch &&
(sector.tile.v.within(launchSector.tile.v, (launchRange + 0.5f) * planets.planet.sectorApproxRadius*2) //within range
|| (sector.preset != null && sector.preset.unlocked())); //is an unlocked preset
return sector.hasBase() || sector.near().contains(Sector::hasBase); //near an occupied sector
}
Sector findLauncher(Sector to){
Sector launchSector = this.launchSector != null && this.launchSector.hasBase() ? this.launchSector : null;
//directly nearby.
if(to.near().contains(launchSector)) return launchSector;
Sector launchFrom = launchSector;
if(launchFrom == null || (to.preset == null && !to.near().contains(launchSector))){
//TODO pick one with the most resources
launchFrom = to.near().find(Sector::hasBase);
if(launchFrom == null && to.preset != null){
if(launchSector != null) return launchSector;
launchFrom = planets.planet.sectors.min(s -> !s.hasBase() ? Float.MAX_VALUE : s.tile.v.dst2(to.tile.v));
if(!launchFrom.hasBase()) launchFrom = null;
}
}
return launchFrom;
}
boolean showing(){
return newPresets.any();
}
@Override
public void renderSectors(Planet planet){
//draw all sector stuff
for(Sector sec : planet.sectors){
if(selectAlpha > 0.01f){
if(canSelect(sec) || sec.unlocked()){
if(sec.baseCoverage > 0){
planets.fill(sec, Tmp.c1.set(Team.crux.color).a(0.5f * sec.baseCoverage * selectAlpha), -0.002f);
}
if(selectAlpha > 0.01f){
for(Sector sec : planet.sectors){
if(canSelect(sec) || sec.unlocked() || debugSelect){
Color color =
sec.hasBase() ? Team.sharded.color :
sec.preset != null ? Team.derelict.color :
sec.hasBase() ? Tmp.c2.set(Team.sharded.color).lerp(Team.crux.color, sec.hasEnemyBase() ? 0.5f : 0f) :
sec.preset != null ?
sec.preset.unlocked() ? Tmp.c2.set(Team.derelict.color).lerp(Color.white, Mathf.absin(Time.time, 10f, 1f)) :
Color.gray :
sec.hasEnemyBase() ? Team.crux.color :
null;
@@ -173,8 +311,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
}
if(launchSector != null){
planets.fill(launchSector, hoverColor, -0.001f);
Sector current = state.getSector() != null && state.getSector().isBeingPlayed() && state.getSector().planet == planets.planet ? state.getSector() : null;
if(current != null){
planets.fill(current, hoverColor, -0.001f);
}
//draw hover border
@@ -191,57 +331,93 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
planets.batch.flush(Gl.triangles);
if(mode == launch || mode == select){
if(hovered != launchSector && hovered != null && canSelect(hovered)){
planets.drawArc(planet, launchSector.tile.v, hovered.tile.v);
if(hovered != null && !hovered.hasBase()){
Sector launchFrom = findLauncher(hovered);
if(launchFrom != null && hovered != launchFrom && canSelect(hovered)){
planets.drawArc(planet, launchFrom.tile.v, hovered.tile.v);
}
}
/*
//TODO render arcs
if(selected != null && selected.preset != null){
for(Objective o : selected.preset.requirements){
if(o instanceof SectorObjective){
SectorPreset preset = ((SectorObjective)o).preset;
planets.drawArc(planet, selected.tile.v, preset.sector.tile.v);
if(selectAlpha > 0.001f){
for(Sector sec : planet.sectors){
if(sec.hasBase()){
for(Sector enemy : sec.near()){
if(enemy.hasEnemyBase()){
planets.drawArc(planet, enemy.tile.v, sec.tile.v, Team.crux.color.write(Tmp.c2).a(selectAlpha), Color.clear, 0.24f, 110f, 25);
}
}
}
}
}*/
}
}
@Override
public void renderProjections(){
if(hovered != null){
planets.drawPlane(hovered, () -> {
Draw.color(Color.white, Pal.accent, Mathf.absin(5f, 1f));
public void renderProjections(Planet planet){
float iw = 48f/4f;
TextureRegion icon = hovered.locked() && !canSelect(hovered) ? Icon.lock.getRegion() : null;
for(Sector sec : planet.sectors){
if(sec != hovered){
var preficon = sec.icon();
var icon =
sec.isAttacked() ? Fonts.getLargeIcon("warning") :
!sec.hasBase() && sec.preset != null && sec.preset.unlocked() && preficon == null ?
Fonts.getLargeIcon("terrain") :
sec.preset != null && sec.preset.locked() && sec.preset.node() != null && !sec.preset.node().parent.content.locked() ? Fonts.getLargeIcon("lock") :
preficon;
var color = sec.preset != null && !sec.hasBase() ? Team.derelict.color : Team.sharded.color;
if(icon != null){
Draw.rect(icon, 0, 0);
planets.drawPlane(sec, () -> {
Draw.color(color, selectAlpha);
Draw.rect(icon, 0, 0, iw, iw * icon.height / icon.width);
});
}
}
}
Draw.reset();
if(hovered != null){
planets.drawPlane(hovered, () -> {
Draw.color(hovered.isAttacked() ? Pal.remove : Color.white, Pal.accent, Mathf.absin(5f, 1f));
var icon = hovered.locked() && !canSelect(hovered) ? Fonts.getLargeIcon("lock") : hovered.isAttacked() ? Fonts.getLargeIcon("warning") : hovered.icon();
if(icon != null){
Draw.rect(icon, 0, 0, iw, iw * icon.height / icon.width);
}
Draw.reset();
});
}
Draw.reset();
}
void setup(){
zoom = planets.zoom = 1f;
selectAlpha = 1f;
ui.minimapfrag.hide();
cont.clear();
titleTable.remove();
clearChildren();
margin(0f);
cont.stack(
stack(
new Element(){
{
//add listener to the background rect, so it doesn't get unnecessary touch input
addListener(new ElementGestureListener(){
@Override
public void tap(InputEvent event, float x, float y, int count, KeyCode button){
if(hovered != null && (mode == launch ? canSelect(hovered) && hovered != launchSector : hovered.unlocked())){
if(showing()) return;
if(hovered != null && selected == hovered && count == 2){
playSelected();
}
if(hovered != null && (canSelect(hovered) || debugSelect)){
selected = hovered;
}
@@ -254,14 +430,47 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
@Override
public void draw(){
planets.orbitAlpha = selectAlpha;
planets.render(PlanetDialog.this);
Core.scene.setScrollFocus(PlanetDialog.this);
if(Core.scene.getDialog() == PlanetDialog.this){
Core.scene.setScrollFocus(PlanetDialog.this);
}
}
},
//info text
new Table(t -> {
//TODO localize
t.touchable = Touchable.disabled;
t.top();
t.label(() -> mode == select ? "@sectors.select" : mode == launch ? "Select Launch Sector" : "Turn " + universe.turn()).style(Styles.outlineLabel).color(Pal.accent);
t.label(() -> mode == select ? "@sectors.select" : "").style(Styles.outlineLabel).color(Pal.accent);
}),
buttons,
//planet selection
new Table(t -> {
t.right();
if(content.planets().count(p -> p.accessible) > 1){
t.table(Styles.black6, pt -> {
pt.add("@planets").color(Pal.accent);
pt.row();
pt.image().growX().height(4f).pad(6f).color(Pal.accent);
pt.row();
for(int i = 0; i < content.planets().size; i++){
Planet planet = content.planets().get(i);
if(planet.accessible){
pt.button(planet.localizedName, Styles.clearTogglet, () -> {
selected = null;
launchSector = null;
renderer.planets.planet = planet;
}).width(200).height(40).growX().update(bb -> bb.setChecked(renderer.planets.planet == planet));
pt.row();
}
}
});
}
}),
new Table(t -> {
t.top();
//t.add(sectorTop);
})).grow();
}
@@ -286,23 +495,59 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
}
}
public void lookAt(Sector sector, float alpha){
float len = planets.camPos.len();
planets.camPos.slerp(Tmp.v31.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation()).setLength(len), alpha);
}
@Override
public void act(float delta){
super.act(delta);
if(selected != null){
addChild(stable);
Vec3 pos = planets.cam.project(Tmp.v31.set(selected.tile.v).setLength(PlanetRenderer.outlineRad).rotate(Vec3.Y, -planets.planet.getRotation()).add(planets.planet.position));
stable.setPosition(pos.x, pos.y, Align.center);
stable.toFront();
if(hovered != null && !mobile){
addChild(hoverLabel);
hoverLabel.toFront();
hoverLabel.touchable = Touchable.disabled;
//smooth camera toward the sector
if(mode == launch && launching){
float len = planets.camPos.len();
planets.camPos.slerp(Tmp.v31.set(selected.tile.v).rotate(Vec3.Y,-selected.planet.getRotation()).setLength(len), 0.1f);
Vec3 pos = planets.cam.project(Tmp.v31.set(hovered.tile.v).setLength(PlanetRenderer.outlineRad).rotate(Vec3.Y, -planets.planet.getRotation()).add(planets.planet.position));
hoverLabel.setPosition(pos.x - Core.scene.marginLeft, pos.y - Core.scene.marginBottom, Align.center);
hoverLabel.getText().setLength(0);
if(hovered != null){
StringBuilder tx = hoverLabel.getText();
if(!canSelect(hovered)){
tx.append("[gray]").append(Iconc.lock).append(" ").append(Core.bundle.get("locked"));
}else{
tx.append("[accent][[ [white]").append(hovered.name()).append("[accent] ]");
}
}
hoverLabel.invalidateHierarchy();
}else{
stable.remove();
hoverLabel.remove();
}
if(launching && selected != null){
lookAt(selected, 0.1f);
}
if(showing()){
Sector to = newPresets.peek();
presetShow += Time.delta;
lookAt(to, 0.11f);
zoom = 0.75f;
if(presetShow >= 20f && !showed && newPresets.size > 1){
showed = true;
ui.announce(Iconc.lockOpen + " [accent]" + to.name(), 2f);
}
if(presetShow > sectorShowDuration){
newPresets.pop();
showed = false;
presetShow = 0f;
}
}
if(planets.planet.isLandable()){
@@ -315,11 +560,95 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
selectAlpha = Mathf.lerpDelta(selectAlpha, Mathf.num(planets.zoom < 1.9f), 0.1f);
}
void showStats(Sector sector){
BaseDialog dialog = new BaseDialog(sector.name());
dialog.cont.pane(c -> {
Cons2<ObjectMap<Item, ExportStat>, String> display = (stats, name) -> {
Table t = new Table().left();
float scl = sector.getProductionScale();
int[] i = {0};
stats.each((item, stat) -> {
int total = (int)(stat.mean * 60 * scl);
if(total > 1){
t.image(item.icon(Cicon.small)).padRight(3);
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray).padRight(3);
if(++i[0] % 3 == 0){
t.row();
}
}
});
if(t.getChildren().any()){
c.add(name).left().row();
c.add(t).padLeft(10f).left().row();
}
};
c.defaults().padBottom(5);
c.add(Core.bundle.get("sectors.time") + " [accent]" + sector.save.getPlayTime()).left().row();
if(sector.info.waves && sector.hasBase()){
c.add(Core.bundle.get("sectors.wave") + " [accent]" + (sector.info.wave + sector.info.wavesPassed)).left().row();
}
if(sector.isAttacked() || !sector.hasBase()){
c.add(Core.bundle.get("sectors.threat") + " [accent]" + sector.displayThreat()).left().row();
}
if(sector.save != null && sector.info.resources.any()){
c.add("@sectors.resources").left().row();
c.table(t -> {
for(UnlockableContent uc : sector.info.resources){
t.image(uc.icon(Cicon.small)).padRight(3).size(Cicon.small.size);
}
}).padLeft(10f).left().row();
}
//production
display.get(sector.info.production, "@sectors.production");
//export
display.get(sector.info.export, "@sectors.export");
ItemSeq items = sector.items();
//stored resources
if(sector.hasBase() && items.total > 0){
c.add("@sectors.stored").left().row();
c.table(t -> {
t.left();
t.table(res -> {
int i = 0;
for(ItemStack stack : items){
res.image(stack.item.icon(Cicon.small)).padRight(3);
res.add(UI.formatAmount(Math.max(stack.amount, 0))).color(Color.lightGray);
if(++i % 4 == 0){
res.row();
}
}
}).padLeft(10f);
}).left().row();
}
});
dialog.addCloseButton();
dialog.show();
}
void updateSelected(){
Sector sector = selected;
Table stable = sectorTop;
if(sector == null){
stable.remove();
stable.clear();
return;
}
@@ -327,117 +656,117 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
stable.clear();
stable.background(Styles.black6);
stable.add("[accent]" + (sector.preset == null ? sector.id : sector.preset.localizedName)).row();
stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
stable.add(sector.save != null ? sector.save.getPlayTime() : "@sectors.unexplored").row();
if(sector.hasWaves() || sector.hasEnemyBase()){
stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row();
}
stable.table(title -> {
title.add("[accent]" + sector.name()).padLeft(3);
if(sector.preset == null){
title.add().growX();
if(sector.hasBase() && sector.hasWaves()){
//TODO localize when finalized
//these mechanics are likely to change and as such are not added to the bundle
stable.add("[scarlet]Under attack!");
stable.row();
stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s)\nuntil destruction");
stable.row();
}
title.button(Icon.pencilSmall, Styles.clearPartiali, () -> {
ui.showTextInput("@sectors.rename", "@name", 20, sector.name(), v -> {
sector.setName(v);
updateSelected();
});
}).size(40f).padLeft(4);
}
if(sector.save != null){
stable.add("@sectors.resources").row();
stable.table(t -> {
var icon = Icon.icons.get(sector.info.icon + "Small");
if(sector.save != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.resources.any()){
t.left();
int idx = 0;
int max = 5;
for(UnlockableContent c : sector.save.meta.secinfo.resources){
t.image(c.icon(Cicon.small)).padRight(3);
if(++idx % max == 0) t.row();
}
}else{
t.add("@unknown").color(Color.lightGray);
}
title.button(icon == null ? Icon.noneSmall : icon, Styles.clearPartiali, () -> {
new Dialog(""){{
closeOnBack();
setFillParent(true);
cont.pane(t -> {
t.marginRight(19f);
t.defaults().size(48);
t.button(Icon.none, Styles.clearTogglei, () -> {
sector.info.icon = null;
sector.saveInfo();
hide();
updateSelected();
}).checked(sector.info.icon == null);
}).fillX().row();
}
int i = 1;
for(var entry : Icon.icons.entries()){
if(entry.key.endsWith("Small") || entry.key.contains("none")) continue;
String key = entry.key;
//production
if(sector.hasBase() && sector.save.meta.hasProduction){
stable.add("@sectors.production").row();
stable.table(t -> {
t.left();
t.button(entry.value, Styles.cleari, () -> {
sector.info.icon = key;
sector.saveInfo();
hide();
updateSelected();
}).checked(entry.key.equals(sector.info.icon));
sector.save.meta.secinfo.production.each((item, stat) -> {
int total = (int)(stat.mean * 60);
if(total > 1){
t.image(item.icon(Cicon.small)).padRight(3);
t.add(UI.formatAmount(total) + " " + Core.bundle.get("unit.perminute")).color(Color.lightGray);
t.row();
}
});
}).row();
}
//stored resources
if(sector.hasBase() && sector.save.meta.secinfo.coreItems.size > 0){
stable.add("@sectors.stored").row();
stable.table(t -> {
t.left();
t.table(res -> {
ItemSeq items = sector.calculateItems();
int i = 0;
for(ItemStack stack : items){
res.image(stack.item.icon(Cicon.small)).padRight(3);
res.add(UI.formatAmount(stack.amount)).color(Color.lightGray);
if(++i % 2 == 0){
res.row();
if(++i % 8 == 0) t.row();
}
}
});
});
}}.show();
}).size(40f);
}).row();
stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
boolean locked = sector.preset != null && sector.preset.locked() && !sector.hasBase() && sector.preset.node() != null;
if(locked){
stable.table(r -> {
r.add("@complete").colspan(2).left();
r.row();
for(Objective o : sector.preset.node().objectives){
if(o.complete()) continue;
r.add("> " + o.display()).color(Color.lightGray).left();
r.image(o.complete() ? Icon.ok : Icon.cancel, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3);
r.row();
}
}).row();
}else if(!sector.hasBase()){
stable.add(Core.bundle.get("sectors.threat") + " [accent]" + sector.displayThreat()).row();
}
if(sector.isAttacked()){
stable.add(Core.bundle.format("sectors.underattack", (int)(sector.info.damage * 100)));
stable.row();
if(sector.info.wavesSurvived >= 0 && sector.info.wavesSurvived - sector.info.wavesPassed >= 0 && !sector.isBeingPlayed()){
int toCapture = sector.info.attack || sector.info.winWave <= 1 ? -1 : sector.info.winWave - (sector.info.wave + sector.info.wavesPassed);
boolean plus = (sector.info.wavesSurvived - sector.info.wavesPassed) >= SectorDamage.maxRetWave - 1;
stable.add(Core.bundle.format("sectors.survives", Math.min(sector.info.wavesSurvived - sector.info.wavesPassed, toCapture <= 0 ? 200 : toCapture) +
(plus ? "+" : "") + (toCapture < 0 ? "" : "/" + toCapture)));
stable.row();
}
}else if(sector.hasBase() && sector.near().contains(Sector::hasEnemyBase)){
stable.add("@sectors.vulnerable");
stable.row();
}else if(!sector.hasBase() && sector.hasEnemyBase()){
stable.add("@sectors.enemybase");
stable.row();
}
if(sector.save != null && sector.info.resources.any()){
stable.table(t -> {
t.add("@sectors.resources").padRight(4);
for(UnlockableContent c : sector.info.resources){
if(c == null) continue; //apparently this is possible.
t.image(c.icon(Cicon.small)).padRight(3).size(Cicon.small.size);
}
}).padLeft(10f).fillX().row();
}
stable.row();
if((sector.hasBase() && mode == look) || canSelect(sector) || (sector.preset != null && sector.preset.alwaysUnlocked)){
stable.button(mode == select ? "@sectors.select" : sector.hasBase() ? "@sectors.resume" : "@sectors.launch", Styles.transt, () -> {
if(sector.hasBase()){
stable.button("@stats", Icon.info, Styles.transt, () -> showStats(sector)).height(40f).fillX().row();
}
boolean shouldHide = true;
//save before launch.
if(control.saves.getCurrent() != null && state.isGame() && mode != select){
try{
control.saves.getCurrent().save();
}catch(Throwable e){
e.printStackTrace();
ui.showException("[accent]" + Core.bundle.get("savefail"), e);
}
}
if(mode == launch && !sector.hasBase()){
Sector current = state.rules.sector;
shouldHide = false;
loadouts.show((CoreBlock)launcher.block, launcher, () -> {
control.handleLaunch(launcher);
launching = true;
zoom = 0.5f;
ui.hudfrag.showLaunchDirect();
Time.runTask(launchDuration, () -> control.playSector(current, sector));
});
}else if(mode == select){
listener.get(sector);
}else{
control.playSector(sector);
}
if(shouldHide) hide();
}).growX().padTop(2f).height(50f).minWidth(170f).disabled(b -> state.rules.sector == sector && !state.isMenu());
if((sector.hasBase() && mode == look) || canSelect(sector) || (sector.preset != null && sector.preset.alwaysUnlocked) || debugSelect){
stable.button(
mode == select ? "@sectors.select" :
sector.isBeingPlayed() ? "@sectors.resume" :
sector.hasBase() ? "@sectors.go" :
locked ? "@locked" : "@sectors.launch",
locked ? Icon.lock : Icon.play, this::playSelected).growX().height(54f).minWidth(170f).padTop(4).disabled(locked);
}
stable.pack();
@@ -453,22 +782,77 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
float dot = planets.cam.direction.dot(Tmp.v31);
stable.color.a = Math.max(dot, 0f)*2f;
if(dot*2f <= -0.1f){
stable.remove();
selected = null;
updateSelected();
}
}
}
});
stable.act(0f);
}
enum Mode{
void playSelected(){
if(selected == null) return;
Sector sector = selected;
if(sector.isBeingPlayed()){
//already at this sector
hide();
return;
}
if(sector.preset != null && sector.preset.locked() && !sector.hasBase()){
return;
}
boolean shouldHide = true;
//save before launch.
if(control.saves.getCurrent() != null && state.isGame() && mode != select){
try{
control.saves.getCurrent().save();
}catch(Throwable e){
e.printStackTrace();
ui.showException("[accent]" + Core.bundle.get("savefail"), e);
}
}
if(mode == look && !sector.hasBase()){
shouldHide = false;
Sector from = findLauncher(sector);
if(from == null){
//clear loadout information, so only the basic loadout gets used
universe.clearLoadoutInfo();
//free launch.
control.playSector(sector);
}else{
CoreBlock block = from.info.bestCoreType instanceof CoreBlock b ? b : (CoreBlock)Blocks.coreShard;
loadouts.show(block, from, () -> {
from.removeItems(universe.getLastLoadout().requirements());
from.removeItems(universe.getLaunchResources());
launching = true;
zoom = 0.5f;
ui.hudfrag.showLaunchDirect();
Time.runTask(launchDuration, () -> control.playSector(from, sector));
});
}
}else if(mode == select){
listener.get(sector);
}else{
control.playSector(sector);
}
if(shouldHide) hide();
}
public enum Mode{
/** Look around for existing sectors. Can only deploy. */
look,
/** Launch to a new location. */
launch,
/** Select a sector for some purpose. */
select
}

View File

@@ -14,7 +14,6 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.ArcAnnotate.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.core.*;
@@ -22,6 +21,7 @@ import mindustry.game.EventType.*;
import mindustry.game.Objectives.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.layout.*;
@@ -32,14 +32,14 @@ import java.util.*;
import static mindustry.Vars.*;
public class ResearchDialog extends BaseDialog{
final float nodeSize = Scl.scl(60f);
ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
TechTreeNode root = new TechTreeNode(TechTree.root, null);
Rect bounds = new Rect();
ItemsDisplay itemDisplay;
View view;
public final float nodeSize = Scl.scl(60f);
public ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
public TechTreeNode root = new TechTreeNode(TechTree.root, null);
public Rect bounds = new Rect();
public ItemsDisplay itemDisplay;
public View view;
ItemSeq items;
public ItemSeq items;
public ResearchDialog(){
super("");
@@ -60,10 +60,13 @@ public class ResearchDialog extends BaseDialog{
//add global counts of each sector
for(Planet planet : content.planets()){
for(Sector sector : planet.sectors){
if(sector.hasSave()){
ItemSeq cached = sector.calculateItems();
add(cached);
if(sector.hasSave() && sector.hasBase()){
ItemSeq cached = sector.items();
cache.put(sector, cached);
cached.each((item, amount) -> {
values[item.id] += Math.max(amount, 0);
total += Math.max(amount, 0);
});
}
}
}
@@ -112,10 +115,16 @@ public class ResearchDialog extends BaseDialog{
addCloseButton();
keyDown(key -> {
if(key == Core.keybinds.get(Binding.research).key){
Core.app.post(this::hide);
}
});
buttons.button("@database", Icon.book, () -> {
hide();
ui.database.show();
}).size(210f, 64f);
}).size(210f, 64f).name("database");
//scaling/drag input
addListener(new InputListener(){
@@ -238,18 +247,6 @@ public class ResearchDialog extends BaseDialog{
return node.content.unlocked() || !node.objectives.contains(i -> !i.complete());
}
void showToast(String info){
Table table = new Table();
table.actions(Actions.fadeOut(0.5f, Interp.fade), Actions.remove());
table.top().add(info);
table.name = "toast";
table.update(() -> {
table.toFront();
table.setPosition(Core.graphics.getWidth() / 2f, Core.graphics.getHeight() - 21, Align.top);
});
Core.scene.add(table);
}
boolean locked(TechNode node){
return node.content.locked();
}
@@ -267,11 +264,11 @@ public class ResearchDialog extends BaseDialog{
}
}
class TechTreeNode extends TreeNode<TechTreeNode>{
final TechNode node;
boolean visible = true, selectable = true;
public class TechTreeNode extends TreeNode<TechTreeNode>{
public final TechNode node;
public boolean visible = true, selectable = true;
TechTreeNode(TechNode node, TechTreeNode parent){
public TechTreeNode(TechNode node, TechTreeNode parent){
this.node = node;
this.parent = parent;
this.width = this.height = nodeSize;
@@ -285,11 +282,11 @@ public class ResearchDialog extends BaseDialog{
}
}
class View extends Group{
float panX = 0, panY = -200, lastZoom = -1;
boolean moved = false;
ImageButton hoverNode;
Table infoTable = new Table();
public class View extends Group{
public float panX = 0, panY = -200, lastZoom = -1;
public boolean moved = false;
public ImageButton hoverNode;
public Table infoTable = new Table();
{
infoTable.touchable = Touchable.enabled;
@@ -388,7 +385,8 @@ public class ResearchDialog extends BaseDialog{
}
}
return false;
//can always spend when locked
return node.content.locked();
}
void spend(TechNode node){
@@ -402,7 +400,7 @@ public class ResearchDialog extends BaseDialog{
ItemStack completed = node.finishedRequirements[i];
//amount actually taken from inventory
int used = Math.min(req.amount - completed.amount, items.get(req.item));
int used = Math.max(Math.min(req.amount - completed.amount, items.get(req.item)), 0);
items.remove(req.item, used);
completed.amount += used;
@@ -431,7 +429,14 @@ public class ResearchDialog extends BaseDialog{
void unlock(TechNode node){
node.content.unlock();
showToast(Core.bundle.format("researched", node.content.localizedName));
//unlock parent nodes in multiplayer.
TechNode parent = node.parent;
while(parent != null){
parent.content.unlock();
parent = parent.parent;
}
checkNodes(root);
hoverNode = null;
treeLayout();
@@ -474,7 +479,7 @@ public class ResearchDialog extends BaseDialog{
infoTable.table(b -> {
b.margin(0).left().defaults().left();
if(selectable){
if(selectable && (node.content.description != null || node.content.stats.toMap().size > 0)){
b.button(Icon.info, Styles.cleari, () -> ui.content.show(node.content)).growY().width(50f);
}
b.add().grow();
@@ -499,8 +504,7 @@ public class ResearchDialog extends BaseDialog{
if(shine != null) shiny |= shine[i];
}
int percent = (int)(used / sum * 100);
Label label = t.add(Core.bundle.format("research.progress", percent)).left().get();
Label label = t.add(Core.bundle.format("research.progress", Math.min((int)(used / sum * 100), 99))).left().get();
if(shiny){
label.setColor(Pal.accent);
@@ -570,7 +574,7 @@ public class ResearchDialog extends BaseDialog{
});
infoTable.row();
if(node.content.description != null && selectable){
if(node.content.description != null && node.content.inlineDescription && selectable){
infoTable.table(t -> t.margin(3f).left().labelWrap(node.content.displayDescription()).color(Color.lightGray).growX()).fillX();
}

View File

@@ -1,9 +1,9 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.scene.ui.TextButton;
import arc.util.Time;
import mindustry.game.Saves.SaveSlot;
import arc.*;
import arc.scene.ui.*;
import arc.util.*;
import mindustry.game.Saves.*;
import mindustry.gen.*;
import static mindustry.Vars.*;

View File

@@ -4,6 +4,7 @@ import arc.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.scene.style.*;
import arc.scene.ui.*;
@@ -11,7 +12,7 @@ import arc.scene.ui.ImageButton.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.Vars;
import mindustry.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
@@ -29,9 +30,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 -> ((Texture)t).setWrap(TextureWrap.repeat);
shouldPause = true;
addCloseButton();
@@ -106,18 +105,36 @@ public class SchematicsDialog extends BaseDialog{
});
buttons.button(Icon.pencil, style, () -> {
ui.showTextInput("@schematic.rename", "@name", s.name(), res -> {
Schematic replacement = schematics.all().find(other -> other.name().equals(res) && other != s);
if(replacement != null){
//renaming to an existing schematic is not allowed, as it is not clear how the tags would be merged, and which one should be removed
ui.showErrorMessage("@schematic.exists");
return;
}
new Dialog("@schematic.rename"){{
cont.margin(30).add("@name").padRight(6f);
TextField nameField = cont.field(s.name(), null).size(400f, 55f).addInputDialog().get();
s.tags.put("name", res);
s.save();
rebuildPane[0].run();
});
cont.row();
cont.margin(30).add("@editor.description").padRight(6f);
TextField descripionField = cont.area(s.description(), Styles.areaField, t -> {}).size(400f, 140f).addInputDialog().get();
Runnable accept = () -> {
s.tags.put("name", nameField.getText());
s.tags.put("description", descripionField.getText());
s.save();
hide();
rebuildPane[0].run();
};
buttons.defaults().size(120, 54).pad(4);
buttons.button("@ok", accept).disabled(b -> nameField.getText().isEmpty());
buttons.button("@cancel", this::hide);
keyDown(KeyCode.enter, () -> {
if(!nameField.getText().isEmpty() && Core.scene.getKeyboardFocus() != descripionField){
accept.run();
}
});
keyDown(KeyCode.escape, this::hide);
keyDown(KeyCode.back, this::hide);
show();
}};
});
if(s.hasSteamID()){
@@ -264,6 +281,17 @@ public class SchematicsDialog extends BaseDialog{
Core.scene.setKeyboardFocus(searchField);
}
@Override
public Dialog show(){
super.show();
if(Core.app.isDesktop()){
focusSearchField();
}
return this;
}
public static class SchematicImage extends Image{
public float scaling = 16f;
public float thickness = 4f;

View File

@@ -8,14 +8,16 @@ import arc.input.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.SettingsDialog.SettingsTable.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.core.GameState.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
@@ -30,9 +32,9 @@ import static mindustry.Vars.net;
import static mindustry.Vars.*;
public class SettingsMenuDialog extends SettingsDialog{
private SettingsTable graphics;
private SettingsTable game;
private SettingsTable sound;
public SettingsTable graphics;
public SettingsTable game;
public SettingsTable sound;
private Table prefs;
private Table menu;
@@ -116,6 +118,45 @@ public class SettingsMenuDialog extends SettingsDialog{
t.row();
t.button("@settings.clearresearch", Icon.trash, style, () -> {
ui.showConfirm("@confirm", "@settings.clearresearch.confirm", () -> {
universe.clearLoadoutInfo();
for(TechNode node : TechTree.all){
node.reset();
}
content.each(c -> {
if(c instanceof UnlockableContent u){
u.clearUnlock();
}
});
settings.remove("unlocks");
});
}).marginLeft(4);
t.row();
t.button("@settings.clearcampaignsaves", Icon.trash, style, () -> {
ui.showConfirm("@confirm", "@settings.clearcampaignsaves.confirm", () -> {
for(var planet : content.planets()){
for(var sec : planet.sectors){
sec.clearInfo();
if(sec.save != null){
sec.save.delete();
sec.save = null;
}
}
}
for(var slot : control.saves.getSaveSlots().copy()){
if(slot.isSector()){
slot.delete();
}
}
});
}).marginLeft(4);
t.row();
t.button("@data.export", Icon.upload, style, () -> {
if(ios){
Fi file = Core.files.local("mindustry-data-export.zip");
@@ -160,6 +201,25 @@ public class SettingsMenuDialog extends SettingsDialog{
t.row();
t.button("@data.openfolder", Icon.folder, style, () -> Core.app.openFolder(Core.settings.getDataDirectory().absolutePath())).marginLeft(4);
}
t.row();
t.button("@crash.export", Icon.upload, style, () -> {
if(settings.getDataDirectory().child("crashes").list().length == 0 && !settings.getDataDirectory().child("last_log.txt").exists()){
ui.showInfo("@crash.none");
}else{
if(ios){
Fi logs = tmpDirectory.child("logs.txt");
logs.writeString(getLogs());
platform.shareFile(logs);
}else{
platform.showFileChooser(false, "txt", file -> {
file.writeString(getLogs());
app.post(() -> ui.showInfo("@crash.exported"));
});
}
}
}).marginLeft(4);
});
ScrollPane pane = new ScrollPane(prefs);
@@ -191,6 +251,21 @@ public class SettingsMenuDialog extends SettingsDialog{
addSettings();
}
String getLogs(){
Fi log = settings.getDataDirectory().child("last_log.txt");
StringBuilder out = new StringBuilder();
for(Fi fi : settings.getDataDirectory().child("crashes").list()){
out.append(fi.name()).append("\n\n").append(fi.readString()).append("\n");
}
if(log.exists()){
out.append("\nlast log:\n").append(log.readString());
}
return out.toString();
}
void rebuildMenu(){
menu.clearChildren();
@@ -238,8 +313,6 @@ public class SettingsMenuDialog extends SettingsDialog{
game.sliderPref("saveinterval", 60, 10, 5 * 120, 10, i -> Core.bundle.format("setting.seconds", i));
if(!mobile){
game.sliderPref("blockselecttimeout", 750, 0, 2000, 50, i -> Core.bundle.format("setting.milliseconds", i));
game.checkPref("crashreport", true);
}
@@ -247,10 +320,11 @@ public class SettingsMenuDialog extends SettingsDialog{
game.checkPref("blockreplace", true);
game.checkPref("conveyorpathfinding", true);
game.checkPref("hints", true);
if(!mobile){
game.checkPref("backgroundpause", true);
game.checkPref("buildautopause", false);
}
game.checkPref("mapcenter", true);
if(steam){
game.sliderPref("playerlimit", 16, 2, 32, i -> {
@@ -265,19 +339,6 @@ public class SettingsMenuDialog extends SettingsDialog{
}
}
game.pref(new Setting(){
@Override
public void add(SettingsTable table){
table.button("@tutorial.retake", () -> {
hide();
control.playTutorial();
}).size(220f, 60f).pad(6).left();
table.add();
table.row();
hide();
}
});
graphics.sliderPref("uiscale", 100, 25, 300, 25, s -> {
if(ui.settings != null){
Core.settings.put("uiscalechanged", true);
@@ -292,7 +353,7 @@ public class SettingsMenuDialog extends SettingsDialog{
}
return s + "%";
});
graphics.sliderPref("bridgeopacity", 75, 0, 100, 5, s -> s + "%");
graphics.sliderPref("bridgeopacity", 100, 0, 100, 5, s -> s + "%");
if(!mobile){
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
@@ -334,29 +395,26 @@ public class SettingsMenuDialog extends SettingsDialog{
graphics.checkPref("blockstatus", false);
graphics.checkPref("playerchat", true);
if(!mobile){
graphics.checkPref("coreitems", false);
graphics.checkPref("coreitems", true);
}
graphics.checkPref("minimap", !mobile);
graphics.checkPref("smoothcamera", true);
graphics.checkPref("position", false);
graphics.checkPref("fps", false);
if(!mobile){
graphics.checkPref("blockselectkeys", true);
}
graphics.checkPref("playerindicators", true);
graphics.checkPref("indicators", true);
graphics.checkPref("showweather", true);
graphics.checkPref("animatedwater", true);
if(Shaders.shield != null){
graphics.checkPref("animatedshields", !mobile);
}
if(!ios){
graphics.checkPref("bloom", true, val -> renderer.toggleBloom(val));
}else{
Core.settings.put("bloom", false);
}
graphics.checkPref("pixelate", false, val -> {
if(val){
Events.fire(Trigger.enablePixelation);
@@ -414,12 +472,17 @@ public class SettingsMenuDialog extends SettingsDialog{
throw new IllegalArgumentException("Not valid save data.");
}
//delete old saves so they don't interfere
saveDirectory.deleteDirectory();
//purge existing tmp data, keep everything else
tmpDirectory.deleteDirectory();
zipped.walk(f -> f.copyTo(base.child(f.path())));
dest.delete();
//clear old data
settings.clear();
//load data so it's saved on exit
settings.load();
}

View File

@@ -1,9 +1,9 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.scene.ui.layout.Table;
import arc.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.net.Administration.TraceInfo;
import mindustry.net.Administration.*;
public class TraceDialog extends BaseDialog{

View File

@@ -7,6 +7,7 @@ import arc.scene.actions.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -32,6 +33,11 @@ public class BlockConfigFragment extends Fragment{
}
}
});
Events.on(ResetEvent.class, e -> {
table.visible = false;
configTile = null;
});
}
public boolean isShown(){

View File

@@ -10,16 +10,12 @@ import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.scene.ui.layout.Stack;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.*;
@@ -42,29 +38,6 @@ public class BlockInventoryFragment extends Fragment{
Events.on(WorldLoadEvent.class, e -> hide());
}
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Building tile, Item item, int amount){
if(player == null || tile == null || !tile.interactable(player.team()) || !player.within(tile, buildingRange)) return;
amount = Math.min(player.unit().maxAccepted(item), amount);
int fa = amount;
if(amount == 0) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.withdrawItem, tile.tile(), action -> {
action.item = item;
action.itemAmount = fa;
}))) throw new ValidateException(player, "Player cannot request items.");
int removed = tile.removeStack(item, amount);
player.unit().addItem(item, removed);
Events.fire(new WithdrawEvent(tile, player, item, amount));
for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){
Time.run(j * 3f, () -> Call.transferItemEffect(item, tile.x, tile.y, player.unit()));
}
}
@Override
public void build(Group parent){
table.name = "inventory";
@@ -148,6 +121,10 @@ public class BlockInventoryFragment extends Fragment{
}
if(dirty) rebuild(false);
}
if(table.getChildren().isEmpty()){
hide();
}
}
});
@@ -168,7 +145,7 @@ public class BlockInventoryFragment extends Fragment{
Boolp canPick = () -> player.unit().acceptsItem(item) && !state.isPaused() && player.within(tile, itemTransferRange);
HandCursorListener l = new HandCursorListener();
l.setEnabled(canPick);
l.enabled = canPick;
Element image = itemImage(item.icon(Cicon.xlarge), () -> {
if(tile == null || !tile.isValid()){

View File

@@ -2,7 +2,7 @@ package mindustry.ui.fragments;
import arc.*;
import arc.Input.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -10,10 +10,12 @@ import arc.scene.*;
import arc.scene.ui.*;
import arc.scene.ui.Label.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.ui.*;
import static arc.Core.*;
@@ -27,6 +29,7 @@ public class ChatFragment extends Table{
private boolean shown = false;
private TextField chatfield;
private Label fieldlabel = new Label(">");
private ChatMode mode = ChatMode.normal;
private Font font;
private GlyphLayout layout = new GlyphLayout();
private float offsetx = Scl.scl(4), offsety = Scl.scl(4), fontoffsetx = Scl.scl(2), chatspace = Scl.scl(50);
@@ -57,7 +60,7 @@ public class ChatFragment extends Table{
}
}
return net.active() && ui.hudfrag.shown();
return net.active() && ui.hudfrag.shown;
});
update(() -> {
@@ -76,6 +79,9 @@ public class ChatFragment extends Table{
historyPos--;
updateChat();
}
if(input.keyTap(Binding.chat_mode)){
nextMode();
}
scrollPos = (int)Mathf.clamp(scrollPos + input.axis(Binding.chat_scroll), 0, Math.max(0, messages.size - messagesShown));
}
});
@@ -124,7 +130,7 @@ public class ChatFragment extends Table{
Draw.color(shadowColor);
if(shown){
Fill.crect(offsetx, chatfield.y, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
Fill.crect(offsetx, chatfield.y + scene.marginBottom, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
}
super.draw();
@@ -137,7 +143,7 @@ public class ChatFragment extends Table{
Draw.color(shadowColor);
Draw.alpha(shadowColor.a * opacity);
float theight = offsety + spacing + getMarginBottom();
float theight = offsety + spacing + getMarginBottom() + scene.marginBottom;
for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos && (i < fadetime || shown); i++){
layout.setText(font, messages.get(i).formattedMessage, Color.white, textWidth, Align.bottomLeft, true);
@@ -163,15 +169,16 @@ public class ChatFragment extends Table{
Draw.color();
if(fadetime > 0 && !shown)
if(fadetime > 0 && !shown){
fadetime -= Time.delta / 180f;
}
}
private void sendMessage(){
String message = chatfield.getText();
String message = chatfield.getText().trim();
clearChatInput();
if(message.replace(" ", "").isEmpty()) return;
if(message.isEmpty()) return;
history.insert(1, message);
@@ -182,7 +189,7 @@ public class ChatFragment extends Table{
if(!shown){
scene.setKeyboardFocus(chatfield);
shown = !shown;
shown = true;
if(mobile){
TextInput input = new TextInput();
input.maxLength = maxTextLength;
@@ -198,10 +205,13 @@ public class ChatFragment extends Table{
chatfield.fireClick();
}
}else{
scene.setKeyboardFocus(null);
shown = !shown;
scrollPos = 0;
sendMessage();
//sending chat has a delay; workaround for issue #1943
Time.run(2f, () ->{
scene.setKeyboardFocus(null);
shown = false;
scrollPos = 0;
sendMessage();
});
}
}
@@ -212,14 +222,35 @@ public class ChatFragment extends Table{
}
public void updateChat(){
chatfield.setText(history.get(historyPos));
chatfield.setCursorPosition(chatfield.getText().length());
chatfield.setText(mode.normalizedPrefix() + history.get(historyPos));
updateCursor();
}
public void nextMode(){
ChatMode prev = mode;
do{
mode = mode.next();
}while(!mode.isValid());
if(chatfield.getText().startsWith(prev.normalizedPrefix())){
chatfield.setText(mode.normalizedPrefix() + chatfield.getText().substring(prev.normalizedPrefix().length()));
}else{
chatfield.setText(mode.normalizedPrefix());
}
updateCursor();
}
public void clearChatInput(){
historyPos = 0;
history.set(0, "");
chatfield.setText("");
chatfield.setText(mode.normalizedPrefix());
updateCursor();
}
public void updateCursor(){
chatfield.setCursorPosition(chatfield.getText().length());
}
public boolean shown(){
@@ -252,4 +283,36 @@ public class ChatFragment extends Table{
}
}
private enum ChatMode{
normal(""),
team("/t"),
admin("/a", player::admin)
;
public String prefix;
public Boolp valid;
public static final ChatMode[] all = values();
ChatMode(String prefix){
this.prefix = prefix;
this.valid = () -> true;
}
ChatMode(String prefix, Boolp valid){
this.prefix = prefix;
this.valid = valid;
}
public ChatMode next(){
return all[(ordinal() + 1) % all.length];
}
public String normalizedPrefix(){
return prefix.isEmpty() ? "" : prefix + " ";
}
public boolean isValid(){
return valid.get();
}
}
}

View File

@@ -1,6 +1,6 @@
package mindustry.ui.fragments;
import arc.scene.Group;
import arc.scene.*;
public abstract class Fragment{
public abstract void build(Group parent);

View File

@@ -0,0 +1,277 @@
package mindustry.ui.fragments;
import arc.*;
import arc.func.*;
import arc.input.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class HintsFragment extends Fragment{
private static final Boolp isTutorial = () -> Vars.state.rules.sector == SectorPresets.groundZero.sector;
private static final float foutTime = 0.6f;
/** All hints to be displayed in the game. */
public Seq<Hint> hints = new Seq<>().and(DefaultHint.values()).as();
@Nullable Hint current;
Group group = new WidgetGroup();
ObjectSet<String> events = new ObjectSet<>();
ObjectSet<Block> placedBlocks = new ObjectSet<>();
Table last;
@Override
public void build(Group parent){
group.setFillParent(true);
group.touchable = Touchable.childrenOnly;
group.visibility = () -> Core.settings.getBool("hints", true) && ui.hudfrag.shown;
group.update(() -> {
if(current != null){
//current got completed
if(current.complete()){
complete();
}else if(!current.show()){ //current became hidden
hide();
}
}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){
display(hint);
}
}
});
parent.addChild(group);
checkNext();
Events.on(BlockBuildEndEvent.class, event -> {
if(!event.breaking && event.unit == player.unit()){
placedBlocks.add(event.tile.block());
}
if(event.breaking){
events.add("break");
}
});
Events.on(ResetEvent.class, e -> {
placedBlocks.clear();
events.clear();
});
}
void checkNext(){
if(current != null) return;
hints.removeAll(h -> !h.valid() || h.finished() || (h.show() && h.complete()));
hints.sort(Hint::order);
Hint first = hints.find(Hint::show);
if(first != null){
hints.remove(first);
display(first);
}
}
void display(Hint hint){
if(current != null) return;
group.fill(t -> {
last = t;
t.left();
t.table(Styles.black5, cont -> {
cont.actions(Actions.alpha(0f), Actions.alpha(1f, 1f, Interp.smooth));
cont.margin(6f).add(hint.text()).width(Vars.mobile ? 270f : 400f).left().labelAlign(Align.left).wrap();
});
t.row();
t.button("@hint.skip", Styles.nonet, () -> {
if(current != null){
complete();
}
}).size(112f, 40f).left();
});
this.current = hint;
}
/** Completes and hides the current hint. */
void complete(){
if(current == null) return;
current.finish();
hints.remove(current);
hide();
}
/** Hides the current hint, but does not complete it. */
void hide(){
//hide previous child if found
if(last != null){
last.actions(Actions.parallel(Actions.alpha(0f, foutTime, Interp.smooth), Actions.translateBy(0f, Scl.scl(-200f), foutTime, Interp.smooth)), Actions.remove());
}
//check for next hint to display immediately
current = null;
last = null;
checkNext();
}
public boolean shown(){
return current != null;
}
public enum DefaultHint implements Hint{
desktopMove(visibleDesktop, () -> Core.input.axis(Binding.move_x) != 0 || Core.input.axis(Binding.move_y) != 0),
zoom(visibleDesktop, () -> Core.input.axis(KeyCode.scroll) != 0),
mine(() -> player.unit().canMine() && isTutorial.get(), () -> player.unit().mining()),
placeDrill(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.mechanicalDrill)),
placeConveyor(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.conveyor)),
placeTurret(isTutorial, () -> ui.hints.placedBlocks.contains(Blocks.duo)),
breaking(isTutorial, () -> ui.hints.events.contains("break")),
desktopShoot(visibleDesktop, () -> Vars.state.enemies > 0, () -> player.shooting),
depositItems(() -> player.unit().hasItem(), () -> !player.unit().hasItem()),
desktopPause(visibleDesktop, () -> isTutorial.get() && !Vars.net.active(), () -> Core.input.keyTap(Binding.pause)),
research(isTutorial, () -> ui.research.isShown()),
unitControl(() -> state.rules.defaultTeam.data().units.size > 2 && !net.active() && !player.dead(), () -> !player.dead() && !player.unit().spawnedByCore),
respawn(visibleMobile, () -> !player.dead() && !player.unit().spawnedByCore, () -> !player.dead() && player.unit().spawnedByCore),
launch(() -> isTutorial.get() && state.rules.sector.isCaptured(), () -> ui.planet.isShown()),
schematicSelect(visibleDesktop, () -> ui.hints.placedBlocks.contains(Blocks.router), () -> Core.input.keyRelease(Binding.schematic_select) || Core.input.keyTap(Binding.pick)),
conveyorPathfind(() -> control.input.block == Blocks.titaniumConveyor, () -> Core.input.keyRelease(Binding.diagonal_placement) || (mobile && Core.settings.getBool("swapdiagonal"))),
boost(visibleDesktop, () -> !player.dead() && player.unit().type.canBoost, () -> Core.input.keyDown(Binding.boost)),
command(() -> state.rules.defaultTeam.data().units.size > 3 && !net.active(), () -> player.unit().isCommanding()),
payloadPickup(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().isEmpty(), () -> player.unit() instanceof Payloadc p && p.payloads().any()),
payloadDrop(() -> !player.unit().dead && player.unit() instanceof Payloadc p && p.payloads().any(), () -> player.unit() instanceof Payloadc p && p.payloads().isEmpty()),
waveFire(() -> Groups.fire.size() > 0 && Blocks.wave.unlockedNow(), () -> indexer.getAllied(state.rules.defaultTeam, BlockFlag.extinguisher).size() > 0),
generator(() -> control.input.block == Blocks.combustionGenerator, () -> ui.hints.placedBlocks.contains(Blocks.combustionGenerator)),
guardian(() -> state.boss() != null && state.boss().armor >= 4, () -> state.boss() == null),
coreUpgrade(() -> state.isCampaign() && Blocks.coreFoundation.unlocked()
&& state.rules.defaultTeam.core() != null
&& state.rules.defaultTeam.core().block == Blocks.coreShard
&& state.rules.defaultTeam.core().items.has(Blocks.coreFoundation.requirements),
() -> ui.hints.placedBlocks.contains(Blocks.coreFoundation)),
presetLaunch(() -> state.isCampaign()
&& state.getSector().preset == null
&& SectorPresets.frozenForest.unlocked()
&& SectorPresets.frozenForest.sector.save == null,
() -> state.isCampaign() && state.getSector().preset == SectorPresets.frozenForest),
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),
;
@Nullable
String text;
int visibility = visibleAll;
Hint[] dependencies = {};
boolean finished, cached;
Boolp complete, shown = () -> true;
DefaultHint(Boolp complete){
this.complete = complete;
}
DefaultHint(int visiblity, Boolp complete){
this(complete);
this.visibility = visiblity;
}
DefaultHint(Boolp shown, Boolp complete){
this(complete);
this.shown = shown;
}
DefaultHint(int visiblity, Boolp shown, Boolp complete){
this(complete);
this.shown = shown;
this.visibility = visiblity;
}
@Override
public boolean finished(){
if(!cached){
cached = true;
finished = Core.settings.getBool(name() + "-hint-done", false);
}
return finished;
}
@Override
public void finish(){
Core.settings.put(name() + "-hint-done", finished = true);
}
@Override
public String text(){
if(text == null){
text = Vars.mobile && Core.bundle.has("hint." + name() + ".mobile") ? Core.bundle.get("hint." + name() + ".mobile") : Core.bundle.get("hint." + name());
if(!Vars.mobile) text = text.replace("tap", "click").replace("Tap", "Click");
}
return text;
}
@Override
public boolean complete(){
return complete.get();
}
@Override
public boolean show(){
return shown.get() && (dependencies.length == 0 || !Structs.contains(dependencies, d -> !d.finished()));
}
@Override
public int order(){
return ordinal();
}
@Override
public boolean valid(){
return (Vars.mobile && (visibility & visibleMobile) != 0) || (!Vars.mobile && (visibility & visibleDesktop) != 0);
}
}
/** Hint interface for defining any sort of message appearing at the left. */
public interface Hint{
int visibleDesktop = 1, visibleMobile = 2, visibleAll = visibleDesktop | visibleMobile;
/** Hint name for preference storage. */
String name();
/** Displayed text. */
String text();
/** @return true if hint objective is complete */
boolean complete();
/** @return whether the hint is ready to be shown */
boolean show();
/** @return order integer, determines priority */
int order();
/** @return whether this hint should be processed, used for platform splits */
boolean valid();
/** finishes the hint - it should not be shown again */
default void finish(){
Core.settings.put(name() + "-hint-done", true);
}
/** @return whether the hint is finished - if true, it should not be shown again */
default boolean finished(){
return Core.settings.getBool(name() + "-hint-done", false);
}
}
}

View File

@@ -4,7 +4,6 @@ import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
@@ -16,6 +15,7 @@ import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
@@ -26,7 +26,6 @@ import mindustry.input.*;
import mindustry.net.Packets.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
@@ -34,37 +33,54 @@ public class HudFragment extends Fragment{
private static final float dsize = 65f;
public final PlacementFragment blockfrag = new PlacementFragment();
//TODO localize
public String sectorText = "Out of sector time.";
public Seq<Sector> attackedSectors = new Seq<>();
public boolean shown = true;
private ImageButton flip;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private boolean shown = true;
private CoreItemsDisplay coreItems = new CoreItemsDisplay();
private String hudText = "";
private boolean showHudText;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private long lastToast;
@Override
public void build(Group parent){
//TODO details and stuff
Events.on(SectorCaptureEvent.class, e ->{
//TODO localize
showToast("Sector[accent] captured[]!");
//warn about guardian/boss waves
Events.on(WaveEvent.class, e -> {
int max = 10;
int winWave = state.isCampaign() && state.rules.winWave > 0 ? state.rules.winWave : Integer.MAX_VALUE;
outer:
for(int i = state.wave - 1; i <= Math.min(state.wave + max, winWave - 2); i++){
for(SpawnGroup group : state.rules.spawns){
if(group.effect == StatusEffects.boss && group.getSpawned(i) > 0){
int diff = (i + 2) - state.wave;
//increments at which to warn about incoming guardian
if(diff == 1 || diff == 2 || diff == 5 || diff == 10){
showToast(Icon.warning, Core.bundle.format("wave.guardianwarn" + (diff == 1 ? ".one" : ""), diff));
}
break outer;
}
}
}
});
Events.on(SectorCaptureEvent.class, e -> {
showToast(Core.bundle.format("sector.captured", e.sector.isBeingPlayed() ? "" : e.sector.name() + " "));
});
//TODO localize
Events.on(SectorLoseEvent.class, e -> {
showToast(Icon.warning, "Sector " + e.sector.id + " [scarlet]lost!");
showToast(Icon.warning, Core.bundle.format("sector.lost", e.sector.name()));
});
Events.on(SectorInvasionEvent.class, e -> {
showToast(Icon.warning, Core.bundle.format("sector.attacked", e.sector.name()));
});
//TODO full implementation
Events.on(ResetEvent.class, e -> {
coreItems.resetUsed();
coreItems.clear();
@@ -72,24 +88,28 @@ public class HudFragment extends Fragment{
//paused table
parent.fill(t -> {
t.top().visible(() -> state.isPaused() && !state.isOutOfTime()).touchable = Touchable.disabled;
t.table(Styles.black5, top -> top.add("@paused").style(Styles.outlineLabel).pad(8f)).growX();
t.name = "paused";
t.top().visible(() -> state.isPaused() && shown).touchable = Touchable.disabled;
t.table(Styles.black5, top -> top.label(() -> state.gameOver && state.isCampaign() ? "@sector.curlost" : "@paused").style(Styles.outlineLabel).pad(8f)).growX();
});
//minimap + position
parent.fill(t -> {
t.visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial && shown);
t.name = "minimap/position";
t.visible(() -> Core.settings.getBool("minimap") && shown);
//minimap
t.add(new Minimap());
t.add(new Minimap()).name("minimap");
t.row();
//position
t.label(() -> player.tileX() + "," + player.tileY())
.visible(() -> Core.settings.getBool("position") && !state.rules.tutorial)
.touchable(Touchable.disabled);
.visible(() -> Core.settings.getBool("position"))
.touchable(Touchable.disabled)
.name("position");
t.top().right();
});
//TODO tear this all down
ui.hints.build(parent);
//menu at top left
parent.fill(cont -> {
cont.name = "overlaymarker";
@@ -97,15 +117,18 @@ public class HudFragment extends Fragment{
if(mobile){
cont.table(select -> {
select.name = "mobile buttons";
select.left();
select.defaults().size(dsize).left();
ImageButtonStyle style = Styles.clearTransi;
select.button(Icon.menu, style, ui.paused::show);
select.button(Icon.menu, style, ui.paused::show).name("menu");
flip = select.button(Icon.upOpen, style, this::toggleMenus).get();
flip.name = "flip";
select.button(Icon.paste, style, ui.schematics::show);
select.button(Icon.paste, style, ui.schematics::show)
.name("schematics");
select.button(Icon.pause, style, () -> {
if(net.active()){
@@ -134,7 +157,7 @@ public class HudFragment extends Fragment{
}else{
ui.database.show();
}
}).update(i -> {
}).name("chat").update(i -> {
if(net.active() && mobile){
i.getStyle().imageUp = Icon.chat;
}else if(state.isCampaign()){
@@ -154,42 +177,46 @@ public class HudFragment extends Fragment{
cont.update(() -> {
if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.shown() && !Core.scene.hasDialog() && !(Core.scene.getKeyboardFocus() instanceof TextField)){
Core.settings.getBoolOnce("ui-hidden", () -> {
ui.announce(Core.bundle.format("showui", Core.keybinds.get(Binding.toggle_menus).key.toString(), 11));
});
toggleMenus();
}
});
Table wavesMain, editorMain;
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight());
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight())
.name("waves/editor");
wavesMain.visible(() -> shown && !state.isEditor());
wavesMain.top().left();
wavesMain.top().left().name = "waves";
wavesMain.table(s -> {
//wave info button with text
s.add(makeStatusTable()).grow();
s.add(makeStatusTable()).grow().name("status");
//table with button to skip wave
s.button(Icon.play, Styles.righti, 30f, () -> {
if(net.client() && player.admin){
Call.adminRequest(player, AdminAction.wave);
}else if(inLaunchWave()){
ui.showConfirm("@confirm", "@launch.skip.confirm", () -> !canSkipWave(), () -> logic.skipWave());
}else{
logic.skipWave();
}
}).growY().fillX().right().width(40f).disabled(b -> !canSkipWave()).visible(() -> state.rules.waves);
}).growY().fillX().right().width(40f).disabled(b -> !canSkipWave()).name("skip");
}).width(dsize * 5 + 4f);
wavesMain.row();
wavesMain.table(Tex.button, t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.white))
.grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).get();
.grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).name("boss");
wavesMain.row();
editorMain.name = "editor";
editorMain.table(Tex.buttonEdge4, t -> {
//t.margin(0f);
t.name = "teams";
t.add("@editor.teams").growX().left();
t.row();
t.table(teams -> {
@@ -210,43 +237,57 @@ public class HudFragment extends Fragment{
}).width(dsize * 5 + 4f);
editorMain.visible(() -> shown && state.isEditor());
//fps display
cont.table(info -> {
info.name = "fps/ping";
info.touchable = Touchable.disabled;
info.top().left().margin(4).visible(() -> Core.settings.getBool("fps") && shown);
info.update(() -> info.setTranslation(state.rules.waves || state.isEditor() ? 0f : -Scl.scl(dsize * 4 + 3), 0));
IntFormat fps = new IntFormat("fps");
IntFormat ping = new IntFormat("ping");
IntFormat mem = new IntFormat("memory");
IntFormat memnative = new IntFormat("memory2");
info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style(Styles.outlineLabel);
info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style(Styles.outlineLabel).name("fps");
info.row();
info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel);
if(android){
info.label(() -> memnative.get((int)(Core.app.getJavaHeap() / 1024 / 1024), (int)(Core.app.getNativeHeap() / 1024 / 1024))).left().style(Styles.outlineLabel).name("memory2");
}else{
info.label(() -> mem.get((int)(Core.app.getJavaHeap() / 1024 / 1024))).left().style(Styles.outlineLabel).name("memory");
}
info.row();
info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel).name("ping");
}).top().left();
});
//core items
parent.fill(t -> {
t.name = "coreitems";
t.top().add(coreItems);
t.visible(() -> Core.settings.getBool("coreitems") && !mobile && !state.isPaused() && shown);
});
//spawner warning
parent.fill(t -> {
t.name = "nearpoint";
t.touchable = Touchable.disabled;
t.table(Styles.black, c -> c.add("@nearpoint")
.update(l -> l.setColor(Tmp.c1.set(Color.white).lerp(Color.scarlet, Mathf.absin(Time.time(), 10f, 1f))))
.update(l -> l.setColor(Tmp.c1.set(Color.white).lerp(Color.scarlet, Mathf.absin(Time.time, 10f, 1f))))
.get().setAlignment(Align.center, Align.center))
.margin(6).update(u -> u.color.a = Mathf.lerpDelta(u.color.a, Mathf.num(spawner.playerNear()), 0.1f)).get().color.a = 0f;
});
parent.fill(t -> {
t.name = "waiting";
t.visible(() -> netServer.isWaitingForPlayers());
t.table(Tex.button, c -> c.add("@waiting.players"));
});
//'core is under attack' table
parent.fill(t -> {
t.name = "coreattack";
t.touchable = Touchable.disabled;
float notifDuration = 240f;
float[] coreAttackTime = {0};
@@ -257,6 +298,7 @@ public class HudFragment extends Fragment{
});
t.top().visible(() -> {
if(!shown) return false;
if(state.isMenu() || !state.teams.get(player.team()).hasCore()){
coreAttackTime[0] = 0f;
return false;
@@ -274,69 +316,18 @@ public class HudFragment extends Fragment{
return coreAttackOpacity[0] > 0;
});
t.table(Tex.button, top -> top.add("@coreattack").pad(2)
.update(label -> label.color.set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)))).touchable(Touchable.disabled);
});
//paused table for when the player is out of time
parent.fill(t -> {
t.top().visible(() -> state.isOutOfTime());
t.table(Styles.black5, top -> {
//TODO localize
top.add(sectorText).style(Styles.outlineLabel).color(Pal.accent).update(l -> {
l.color.a = Mathf.absin(Time.globalTime(), 7f, 1f);
l.setText(sectorText);
}).colspan(2);
top.row();
top.defaults().pad(2).size(150f, 54f);
//TODO localize
top.button("Skip", () -> {
universe.runTurn();
state.set(State.playing);
//announce turn info only when something is skipped.
ui.announce("[accent][[ Turn " + universe.turn() + " ]\n[scarlet]" + attackedSectors.size + "[lightgray] sector(s) attacked.");
});
//TODO localize
top.button("Switch Sectors", () -> {
ui.paused.runExitSave();
//switch to first attacked sector
control.playSector(attackedSectors.first());
}).disabled(b -> attackedSectors.isEmpty());
}).margin(8).growX();
});
//tutorial text
parent.fill(t -> {
Runnable resize = () -> {
t.clearChildren();
t.top().right().visible(() -> state.rules.tutorial);
t.stack(new Button(){{
marginLeft(48f);
labelWrap(() -> control.tutorial.stage.text() + (control.tutorial.canNext() ? "\n\n" + Core.bundle.get("tutorial.next") : "")).width(!Core.graphics.isPortrait() ? 400f : 160f).pad(2f);
clicked(() -> control.tutorial.nextSentence());
setDisabled(() -> !control.tutorial.canNext());
}},
new Table(f -> {
f.left().button(Icon.left, Styles.emptyi, () -> {
control.tutorial.prevSentence();
}).width(44f).growY().visible(() -> control.tutorial.canPrev());
}));
};
resize.run();
Events.on(ResizeEvent.class, e -> resize.run());
.update(label -> label.color.set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time, 2f, 1f)))).touchable(Touchable.disabled);
});
//'saving' indicator
parent.fill(t -> {
t.name = "saving";
t.bottom().visible(() -> control.saves.isSaving());
t.add("@saving").style(Styles.outlineLabel);
});
parent.fill(p -> {
p.name = "hudtext";
p.top().table(Styles.black3, t -> t.margin(4).label(() -> hudText)
.style(Styles.outlineLabel)).padTop(10).visible(p.color.a >= 0.001f);
p.update(() -> {
@@ -352,6 +343,7 @@ public class HudFragment extends Fragment{
//TODO DEBUG: rate table
if(false)
parent.fill(t -> {
t.name = "rates";
t.bottom().left();
t.table(Styles.black6, c -> {
Bits used = new Bits(content.items().size);
@@ -360,9 +352,9 @@ public class HudFragment extends Fragment{
c.clearChildren();
for(Item item : content.items()){
if(state.secinfo.getExport(item) >= 1){
if(state.rules.sector != null && state.rules.sector.info.getExport(item) >= 1){
c.image(item.icon(Cicon.small));
c.label(() -> (int)state.secinfo.getExport(item) + " /s").color(Color.lightGray);
c.label(() -> (int)state.rules.sector.info.getExport(item) + " /s").color(Color.lightGray);
c.row();
}
}
@@ -371,7 +363,7 @@ public class HudFragment extends Fragment{
c.update(() -> {
boolean wrong = false;
for(Item item : content.items()){
boolean has = state.secinfo.getExport(item) >= 1;
boolean has = state.rules.sector != null && state.rules.sector.info.getExport(item) >= 1;
if(used.get(item.id) != has){
used.set(item.id, has);
wrong = true;
@@ -381,7 +373,7 @@ public class HudFragment extends Fragment{
rebuild.run();
}
});
}).visible(() -> state.isCampaign() && content.items().contains(i -> state.secinfo.getExport(i) > 0));
}).visible(() -> state.isCampaign() && content.items().contains(i -> state.rules.sector != null && state.rules.sector.info.getExport(i) > 0));
});
blockfrag.build(parent);
@@ -427,7 +419,7 @@ public class HudFragment extends Fragment{
Table table = new Table(Tex.button);
table.update(() -> {
if(state.isMenu()){
if(state.isMenu() || !ui.hudfrag.shown){
table.remove();
}
});
@@ -446,15 +438,11 @@ public class HudFragment extends Fragment{
});
}
public boolean shown(){
return shown;
}
/** Show unlock notification for a new recipe. */
public void showUnlock(UnlockableContent content){
//some content may not have icons... yet
//also don't play in the tutorial to prevent confusion
if(state.isMenu() || state.rules.tutorial) return;
if(state.isMenu()) return;
Sounds.message.play();
@@ -576,40 +564,6 @@ public class HudFragment extends Fragment{
Core.scene.add(image);
}
private void showLaunchConfirm(){
BaseDialog dialog = new BaseDialog("@launch");
dialog.update(() -> {
if(!inLaunchWave()){
dialog.hide();
}
});
dialog.cont.add("@launch.confirm").width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
dialog.buttons.defaults().size(200f, 54f).pad(2f);
dialog.setFillParent(false);
dialog.buttons.button("@cancel", dialog::hide);
dialog.buttons.button("@ok", () -> {
dialog.hide();
Call.launchZone();
});
dialog.keyDown(KeyCode.escape, dialog::hide);
dialog.keyDown(KeyCode.back, dialog::hide);
dialog.show();
}
//TODO launching is disabled, possibly forever
private boolean inLaunchWave(){
return false;
/*
return state.hasSector() &&
state.getSector().metCondition() &&
!net.client() &&
state.wave % state.getSector().launchPeriod == 0 && !spawner.isSpawning();*/
}
private boolean canLaunch(){
return inLaunchWave() && state.enemies <= 0;
}
private void toggleMenus(){
if(flip != null){
flip.getStyle().imageUp = shown ? Icon.downOpen : Icon.upOpen;
@@ -619,13 +573,16 @@ public class HudFragment extends Fragment{
}
private Table makeStatusTable(){
Button table = new Button(Styles.waveb);
Table table = new Table(Tex.wavepane);
StringBuilder ibuild = new StringBuilder();
IntFormat wavef = new IntFormat("wave");
IntFormat wavefc = new IntFormat("wave.cap");
IntFormat enemyf = new IntFormat("wave.enemy");
IntFormat enemiesf = new IntFormat("wave.enemies");
IntFormat enemycf = new IntFormat("wave.enemycore");
IntFormat enemycsf = new IntFormat("wave.enemycores");
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
ibuild.setLength(0);
int m = i/60;
@@ -641,7 +598,6 @@ public class HudFragment extends Fragment{
return ibuild.toString();
});
table.clearChildren();
table.touchable = Touchable.enabled;
StringBuilder builder = new StringBuilder();
@@ -669,6 +625,8 @@ public class HudFragment extends Fragment{
public void draw(){
float next = amount.get();
if(Float.isNaN(next) || Float.isInfinite(next)) next = 1f;
if(next < last && flash.get()){
blink = 1f;
}
@@ -677,6 +635,8 @@ public class HudFragment extends Fragment{
value = Mathf.lerpDelta(value, next, 0.15f);
last = next;
if(Float.isNaN(value) || Float.isInfinite(value)) value = 1f;
drawInner(Pal.darkishGray);
Draw.beginStencil();
@@ -737,11 +697,17 @@ public class HudFragment extends Fragment{
float bw = 40f;
float pad = -20;
t.margin(0);
t.clicked(() -> {
if(!player.dead() && mobile){
Call.unitClear(player);
control.input.controlledType = null;
}
});
t.add(new SideBar(() -> player.unit().healthf(), () -> true, true)).width(bw).growY().padRight(pad);
t.image(() -> player.icon()).scaling(Scaling.bounded).grow().maxWidth(54f);
t.add(new SideBar(() -> player.dead() ? 0f : player.displayAmmo() ? player.unit().ammof() : player.unit().healthf(), () -> !player.displayAmmo(), false)).width(bw).growY().padLeft(pad).update(b -> {
b.color.set(player.displayAmmo() ? player.dead() ? Pal.ammo : player.unit().type().ammoType.color : Pal.health);
b.color.set(player.displayAmmo() ? player.dead() || player.unit() instanceof BlockUnitc ? Pal.ammo : player.unit().type.ammoType.color : Pal.health);
});
t.getChildren().get(1).toFront();
@@ -749,25 +715,28 @@ public class HudFragment extends Fragment{
table.labelWrap(() -> {
builder.setLength(0);
builder.append(wavef.get(state.wave));
builder.append("\n");
if(inLaunchWave()){
builder.append("[#");
Tmp.c1.set(Color.white).lerp(state.enemies > 0 ? Color.white : Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)).toString(builder);
builder.append("]");
if(!canLaunch()){
builder.append(Core.bundle.get("launch.unable2"));
}else{
builder.append(Core.bundle.get("launch"));
builder.append("\n");
builder.append(Core.bundle.format("launch.next", state.wave + state.getSector().launchPeriod));
builder.append("\n");
}
builder.append("[]\n");
if(!state.rules.waves && state.rules.attackMode){
int sum = Math.max(state.teams.present.sum(t -> t.team != player.team() ? t.cores.size : 0), 1);
builder.append(sum > 1 ? enemycsf.get(sum) : enemycf.get(sum));
return builder;
}
if(!state.rules.waves && state.isCampaign()){
builder.append("[lightgray]").append(Core.bundle.get("sector.curcapture"));
}
if(!state.rules.waves){
return builder;
}
if(state.rules.winWave > 1 && state.rules.winWave >= state.wave && state.isCampaign()){
builder.append(wavefc.get(state.wave, state.rules.winWave));
}else{
builder.append(wavef.get(state.wave));
}
builder.append("\n");
if(state.enemies > 0){
if(state.enemies == 1){
builder.append(enemyf.get(state.enemies));
@@ -778,7 +747,7 @@ public class HudFragment extends Fragment{
}
if(state.rules.waveTimer){
builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60)))));
builder.append((logic.isWaitingWave() ? Core.bundle.get("wave.waveInProgress") : (waitingf.get((int)(state.wavetime/60)))));
}else if(state.enemies == 0){
builder.append(Core.bundle.get("waiting"));
}
@@ -786,19 +755,25 @@ public class HudFragment extends Fragment{
return builder;
}).growX().pad(8f);
table.setDisabled(() -> !canLaunch());
table.visible(() -> state.rules.waves);
table.clicked(() -> {
if(canLaunch()){
showLaunchConfirm();
table.row();
var count = new float[]{-1};
table.table().update(t -> {
if(player.unit() instanceof Payloadc payload){
if(count[0] != payload.payloadUsed()){
payload.contentInfo(t, 8 * 2, 275f);
count[0] = payload.payloadUsed();
}
}else{
t.clear();
}
});
}).growX().visible(() -> player.unit() instanceof Payloadc p && p.payloadUsed() > 0).colspan(2);
return table;
}
private boolean canSkipWave(){
return state.rules.waves && ((net.server() || player.admin) || !net.active()) && state.enemies == 0 && !spawner.isSpawning() && !state.rules.tutorial;
return state.rules.waves && ((net.server() || player.admin) || !net.active()) && state.enemies == 0 && !spawner.isSpawning();
}
}

View File

@@ -1,7 +1,9 @@
package mindustry.ui.fragments;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
@@ -18,7 +20,12 @@ public class LoadingFragment extends Fragment{
@Override
public void build(Group parent){
parent.fill(Styles.black8, t -> {
parent.fill(t -> {
//rect must fill screen completely.
t.rect((x, y, w, h) -> {
Draw.alpha(t.color.a);
Styles.black8.draw(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight());
});
t.visible = false;
t.touchable = Touchable.enabled;
t.add().height(133f).row();

View File

@@ -45,6 +45,7 @@ public class MenuFragment extends Fragment{
parent.fill(c -> {
container = c;
c.name = "menu container";
if(!mobile){
buildDesktop();
@@ -57,8 +58,8 @@ public class MenuFragment extends Fragment{
//info icon
if(mobile){
parent.fill(c -> c.bottom().left().button("", Styles.infot, ui.about::show).size(84, 45));
parent.fill(c -> c.bottom().right().button("", Styles.discordt, ui.discord::show).size(84, 45));
parent.fill(c -> c.bottom().left().button("", Styles.infot, ui.about::show).size(84, 45).name("info"));
parent.fill(c -> c.bottom().right().button("", Styles.discordt, ui.discord::show).size(84, 45).name("discord"));
}else if(becontrol.active()){
parent.fill(c -> c.bottom().right().button("@be.check", Icon.refresh, () -> {
ui.loadfrag.show();
@@ -68,7 +69,7 @@ public class MenuFragment extends Fragment{
ui.showInfo("@be.noupdates");
}
});
}).size(200, 60).update(t -> {
}).size(200, 60).name("becheck").update(t -> {
t.getLabel().setColor(becontrol.isUpdateAvailable() ? Tmp.c1.set(Color.white).lerp(Pal.accent, Mathf.absin(5f, 1f)) : Color.white);
}));
}
@@ -76,12 +77,13 @@ public class MenuFragment extends Fragment{
String versionText = ((Version.build == -1) ? "[#fc8140aa]" : "[#ffffffba]") + Version.combined();
parent.fill((x, y, w, h) -> {
TextureRegion logo = Core.atlas.find("logo");
float width = Core.graphics.getWidth(), height = Core.graphics.getHeight() - Core.scene.marginTop;
float logoscl = Scl.scl(1);
float logow = Math.min(logo.width * logoscl, Core.graphics.getWidth() - Scl.scl(20));
float logoh = logow * (float)logo.height / logo.width;
float fx = (int)(Core.graphics.getWidth() / 2f);
float fy = (int)(Core.graphics.getHeight() - 6 - logoh) + logoh / 2 - (Core.graphics.isPortrait() ? Scl.scl(30f) : 0f);
float fx = (int)(width / 2f);
float fy = (int)(height - 6 - logoh) + logoh / 2 - (Core.graphics.isPortrait() ? Scl.scl(30f) : 0f);
Draw.color();
Draw.rect(logo, fx, fy, logow, logoh);
@@ -93,6 +95,7 @@ public class MenuFragment extends Fragment{
private void buildMobile(){
container.clear();
container.name = "buttons";
container.setSize(Core.graphics.getWidth(), Core.graphics.getHeight());
float size = 120f;
@@ -106,7 +109,6 @@ public class MenuFragment extends Fragment{
editor = new MobileButton(Icon.terrain, "@editor", () -> checkPlay(ui.maps::show)),
tools = new MobileButton(Icon.settings, "@settings", ui.settings::show),
mods = new MobileButton(Icon.book, "@mods", ui.mods::show),
donate = new MobileButton(Icon.link, "@website", () -> Core.app.openURI("https://anuke.itch.io/mindustry")),
exit = new MobileButton(Icon.exit, "@quit", () -> Core.app.exit());
if(!Core.graphics.isPortrait()){
@@ -153,7 +155,6 @@ public class MenuFragment extends Fragment{
container.clear();
container.setSize(Core.graphics.getWidth(), Core.graphics.getHeight());
float width = 230f;
Drawable background = Styles.black6;
@@ -161,17 +162,17 @@ public class MenuFragment extends Fragment{
container.add().width(Core.graphics.getWidth()/10f);
container.table(background, t -> {
t.defaults().width(width).height(70f);
t.name = "buttons";
buttons(t,
new Buttoni("@play", Icon.play,
new Buttoni("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
new Buttoni("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
new Buttoni("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
new Buttoni("@loadgame", Icon.download, () -> checkPlay(ui.load::show)),
new Buttoni("@tutorial", Icon.info, () -> checkPlay(control::playTutorial))
new Buttoni("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
),
new Buttoni("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("@workshop", Icon.book, platform::openWorkshop) : null,
new Buttoni(Core.bundle.get("mods"), Icon.bookOpen, ui.mods::show),
new Buttoni("@mods", Icon.book, ui.mods::show),
//not enough space for this button
//new Buttoni("@schematics", Icon.paste, ui.schematics::show),
new Buttoni("@settings", Icon.settings, ui.settings::show),
@@ -183,6 +184,7 @@ public class MenuFragment extends Fragment{
container.table(background, t -> {
submenu = t;
t.name = "submenu";
t.color.a = 0f;
t.top();
t.defaults().width(width).height(70f);
@@ -192,6 +194,7 @@ public class MenuFragment extends Fragment{
}
private void checkPlay(Runnable run){
if(!mods.hasContentErrors()){
run.run();
}else{
@@ -228,7 +231,7 @@ public class MenuFragment extends Fragment{
submenu.clearChildren();
fadeInMenu();
//correctly offset the button
submenu.add().height((Core.graphics.getHeight() - out[0].getY(Align.topLeft)) / Scl.scl(1f));
submenu.add().height((Core.graphics.getHeight() - Core.scene.marginTop - Core.scene.marginBottom - out[0].getY(Align.topLeft)) / Scl.scl(1f));
submenu.row();
buttons(submenu, b.submenu);
}else{

View File

@@ -18,7 +18,7 @@ public class MinimapFragment extends Fragment{
private boolean shown;
float panx, pany, zoom = 1f, lastZoom = -1;
private float baseSize = Scl.scl(5f);
private Element elem;
public Element elem;
@Override
public void build(Group parent){
@@ -28,11 +28,11 @@ public class MinimapFragment extends Fragment{
float size = baseSize * zoom * world.width();
Draw.color(Color.black);
Fill.crect(x, y, w, h);
Fill.crect(0, 0, w, h);
if(renderer.minimap.getTexture() != null){
Draw.color();
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().getWidth();
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
TextureRegion reg = Draw.wrap(renderer.minimap.getTexture());
Draw.rect(reg, w/2f + panx*zoom, h/2f + pany*zoom, size, size * ratio);
renderer.minimap.drawEntities(w/2f + panx*zoom - size/2f, h/2f + pany*zoom - size/2f * ratio, size, size * ratio, zoom, true);
@@ -110,13 +110,16 @@ public class MinimapFragment extends Fragment{
return shown;
}
public void hide(){
shown = false;
}
public void toggle(){
if(Core.settings.getBool("mapcenter")){
float size = baseSize * zoom * world.width();
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().getWidth();
panx = (size/2f - player.x() / (world.width() * tilesize) * size) / zoom;
pany = (size*ratio/2f - player.y() / (world.height() * tilesize) * size*ratio) / zoom;
}
float size = baseSize * zoom * world.width();
float ratio = (float)renderer.minimap.getTexture().height / renderer.minimap.getTexture().width;
float px = player.dead() ? Core.camera.position.x : player.x, py = player.dead() ? Core.camera.position.y : player.y;
panx = (size/2f - px / (world.width() * tilesize) * size) / zoom;
pany = (size*ratio/2f - py / (world.height() * tilesize) * size*ratio) / zoom;
shown = !shown;
}
}

View File

@@ -10,7 +10,6 @@ import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.core.*;
import mindustry.entities.*;
@@ -31,6 +30,7 @@ public class PlacementFragment extends Fragment{
final int rowWidth = 4;
public Category currentCategory = Category.distribution;
Seq<Block> returnArray = new Seq<>(), returnArray2 = new Seq<>();
Seq<Category> returnCatArray = new Seq<>();
boolean[] categoryEmpty = new boolean[Category.all.length];
@@ -81,6 +81,10 @@ public class PlacementFragment extends Fragment{
});
}
public Displayable hover(){
return hover;
}
void rebuild(){
currentCategory = Category.turret;
Group group = toggler.parent;
@@ -93,12 +97,12 @@ public class PlacementFragment extends Fragment{
boolean gridUpdate(InputHandler input){
scrollPositions.put(currentCategory, blockPane.getScrollY());
if(Core.input.keyDown(Binding.pick) && player.isBuilder()){ //mouse eyedropper select
if(Core.input.keyTap(Binding.pick) && player.isBuilder()){ //mouse eyedropper select
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
Block tryRecipe = tile == null ? null : tile.block instanceof ConstructBlock ? ((ConstructBuild)tile).cblock : tile.block;
Object tryConfig = tile == null ? null : tile.config();
for(BuildPlan req : player.builder().plans()){
for(BuildPlan req : player.unit().plans()){
if(!req.breaking && req.block.bounds(req.x, req.y, Tmp.r1).contains(Core.input.mouseWorld())){
tryRecipe = req.block;
tryConfig = req.config;
@@ -118,31 +122,30 @@ public class PlacementFragment extends Fragment{
for(int i = 0; i < blockSelect.length; i++){
if(Core.input.keyTap(blockSelect[i])){
if(i > 9) { //select block directionally
if(i > 9){ //select block directionally
Seq<Block> blocks = getUnlockedByCategory(currentCategory);
Block currentBlock = getSelectedBlock(currentCategory);
for(int j = 0; j < blocks.size; j++){
if(blocks.get(j) == currentBlock){
switch(i){
case 10: //left
j = (j - 1 + blocks.size) % blocks.size;
break;
case 11: //right
j = (j + 1) % blocks.size;
break;
case 12: //up
//left
case 10 -> j = (j - 1 + blocks.size) % blocks.size;
//right
case 11 -> j = (j + 1) % blocks.size;
//up
case 12 -> {
j = (j > 3 ? j - 4 : blocks.size - blocks.size % 4 + j);
j -= (j < blocks.size ? 0 : 4);
break;
case 13: //down
j = (j < blocks.size - 4 ? j + 4 : j % 4);
}
//down
case 13 -> j = (j < blocks.size - 4 ? j + 4 : j % 4);
}
input.block = blocks.get(j);
selectedBlocks.put(currentCategory, input.block);
break;
}
}
}else if(blockSelectEnd || Time.timeSinceMillis(blockSelectSeqMillis) > Core.settings.getInt("blockselecttimeout")){ //1st number of combo, select category
}else if(blockSelectEnd || Time.timeSinceMillis(blockSelectSeqMillis) > 400){ //1st number of combo, select category
//select only visible categories
if(!getUnlockedByCategory(Category.all[i]).isEmpty()){
currentCategory = Category.all[i];
@@ -194,7 +197,7 @@ public class PlacementFragment extends Fragment{
public void build(Group parent){
parent.fill(full -> {
toggler = full;
full.bottom().right().visible(() -> ui.hudfrag.shown());
full.bottom().right().visible(() -> ui.hudfrag.shown);
full.table(frame -> {
@@ -286,7 +289,7 @@ public class PlacementFragment extends Fragment{
topTable.table(header -> {
String keyCombo = "";
if(!mobile && Core.settings.getBool("blockselectkeys")){
if(!mobile){
Seq<Block> blocks = getByCategory(currentCategory);
for(int i = 0; i < blocks.size; i++){
if(blocks.get(i) == displayBlock && (i + 1) / 10 - 1 < blockSelect.length){
@@ -339,7 +342,7 @@ public class PlacementFragment extends Fragment{
topTable.row();
topTable.table(b -> {
b.image(Icon.cancel).padRight(2).color(Color.scarlet);
b.add(!player.isBuilder() ? "@unit.nobuild" : displayBlock.unplaceableMessage()).width(190f).wrap();
b.add(!player.isBuilder() ? "@unit.nobuild" : "@banned").width(190f).wrap();
b.left();
}).padTop(2).left();
}

View File

@@ -6,6 +6,7 @@ import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
@@ -20,11 +21,13 @@ public class PlayerListFragment extends Fragment{
private boolean visible = false;
private Interval timer = new Interval();
private TextField sField;
private boolean found = false;
private Seq<Player> players = new Seq<>();
@Override
public void build(Group parent){
content.name = "players";
parent.fill(cont -> {
cont.name = "playerlist";
cont.visible(() -> visible);
cont.update(() -> {
if(!(net.active() && state.isGame())){
@@ -36,7 +39,7 @@ public class PlayerListFragment extends Fragment{
rebuild();
content.pack();
content.act(Core.graphics.getDeltaTime());
//TODO hack
//hacky
Core.scene.act(0f);
}
});
@@ -47,6 +50,7 @@ public class PlayerListFragment extends Fragment{
sField = pane.field(null, text -> {
rebuild();
}).grow().pad(8).get();
sField.name = "search";
sField.setMaxLength(maxNameLength);
sField.setMessageText(Core.bundle.format("players.search"));
@@ -56,6 +60,7 @@ public class PlayerListFragment extends Fragment{
pane.table(menu -> {
menu.defaults().growX().height(50f).fillY();
menu.name = "menu";
menu.button("@server.bans", ui.bans::show).disabled(b -> net.client());
menu.button("@server.admins", ui.admins::show).disabled(b -> net.client());
@@ -72,10 +77,14 @@ public class PlayerListFragment extends Fragment{
content.clear();
float h = 74f;
found = false;
boolean found = false;
Groups.player.sort(Structs.comparing(Player::team));
Groups.player.each(user -> {
players.clear();
Groups.player.copy(players);
players.sort(Structs.comps(Structs.comparing(Player::team), Structs.comparingBool(p -> !p.admin)));
for(var user : players){
found = true;
NetConnection connection = user.con;
@@ -99,6 +108,7 @@ public class PlayerListFragment extends Fragment{
};
table.margin(8);
table.add(new Image(user.icon()).setScaling(Scaling.bounded)).grow();
table.name = user.name();
button.add(table).size(h);
button.labelWrap("[#" + user.color().toString().toUpperCase() + "]" + user.name()).width(170f).pad(10);
@@ -115,13 +125,9 @@ public class PlayerListFragment extends Fragment{
t.defaults().size(bs);
t.button(Icon.hammer, Styles.clearPartiali,
() -> {
ui.showConfirm("@confirm", Core.bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban));
});
() -> ui.showConfirm("@confirm", Core.bundle.format("confirmban", user.name()), () -> Call.adminRequest(user, AdminAction.ban)));
t.button(Icon.cancel, Styles.clearPartiali,
() -> {
ui.showConfirm("@confirm", Core.bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick));
});
() -> ui.showConfirm("@confirm", Core.bundle.format("confirmkick", user.name()), () -> Call.adminRequest(user, AdminAction.kick)));
t.row();
@@ -158,7 +164,7 @@ public class PlayerListFragment extends Fragment{
content.row();
content.image().height(4f).color(state.rules.pvp ? user.team().color : Pal.gray).growX();
content.row();
});
}
if(!found){
content.add(Core.bundle.format("players.notfound")).padBottom(6).width(350f).maxHeight(h + 14);

View File

@@ -39,7 +39,6 @@ public class ScriptConsoleFragment extends Table{
};
public ScriptConsoleFragment(){
setFillParent(true);
font = Fonts.def;
@@ -113,7 +112,7 @@ public class ScriptConsoleFragment extends Table{
Draw.color(shadowColor);
if(open){
Fill.crect(offsetx, chatfield.y, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
Fill.crect(offsetx, chatfield.y + scene.marginBottom, chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
}
super.draw();
@@ -126,7 +125,7 @@ public class ScriptConsoleFragment extends Table{
Draw.color(shadowColor);
Draw.alpha(shadowColor.a * opacity);
float theight = offsety + spacing + getMarginBottom();
float theight = offsety + spacing + getMarginBottom() + scene.marginBottom;
for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos; i++){
layout.setText(font, messages.get(i), Color.white, textWidth, Align.bottomLeft, true);

View File

@@ -1,7 +1,7 @@
package mindustry.ui.layout;
import arc.struct.*;
import arc.math.geom.*;
import arc.struct.*;
/**
* Algorithm taken from <a href="https://github.com/abego/treelayout">TreeLayout</a>.

View File

@@ -1,7 +1,7 @@
package mindustry.ui.layout;
import arc.struct.*;
import arc.math.*;
import arc.struct.*;
public class RadialTreeLayout implements TreeLayout{
private static ObjectSet<TreeNode> visited = new ObjectSet<>();