it is done

This commit is contained in:
Anuken
2019-12-25 01:39:38 -05:00
parent 5b21873f3c
commit 514d4817c8
488 changed files with 4572 additions and 4574 deletions

View File

@@ -0,0 +1,103 @@
package mindustry.ui;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.style.*;
import arc.util.pooling.*;
import mindustry.gen.*;
public class Bar extends Element{
private static Rectangle scissor = new Rectangle();
private Floatp fraction;
private String name = "";
private float value, lastValue, blink;
private Color blinkColor = new Color();
public Bar(String name, Color color, Floatp fraction){
this.fraction = fraction;
this.name = Core.bundle.get(name);
this.blinkColor.set(color);
lastValue = value = fraction.get();
setColor(color);
}
public Bar(Prov<String> name, Prov<Color> color, Floatp fraction){
this.fraction = fraction;
lastValue = value = Mathf.clamp(fraction.get());
update(() -> {
this.name = name.get();
this.blinkColor.set(color.get());
setColor(color.get());
});
}
public Bar(){
}
public void reset(float value){
this.value = lastValue = blink = value;
}
public void set(Prov<String> name, Floatp fraction, Color color){
this.fraction = fraction;
this.lastValue = fraction.get();
this.blinkColor.set(color);
setColor(color);
update(() -> this.name = name.get());
}
public Bar blink(Color color){
blinkColor.set(color);
return this;
}
@Override
public void draw(){
if(fraction == null) return;
float computed = Mathf.clamp(fraction.get());
if(!Mathf.equal(lastValue, computed)){
blink = 1f;
lastValue = computed;
}
blink = Mathf.lerpDelta(blink, 0f, 0.2f);
value = Mathf.lerpDelta(value, computed, 0.15f);
Drawable bar = Tex.bar;
Draw.colorl(0.1f);
bar.draw(x, y, width, height);
Draw.color(color, blinkColor, blink);
Drawable top = Tex.barTop;
float topWidth = width * value;
if(topWidth > Core.atlas.find("bar-top").getWidth()){
top.draw(x, y, topWidth, height);
}else{
if(ScissorStack.pushScissors(scissor.set(x, y, topWidth, height))){
top.draw(x, y, Core.atlas.find("bar-top").getWidth(), height);
ScissorStack.popScissors();
}
}
Draw.color();
BitmapFont font = Fonts.outline;
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
lay.setText(font, name);
font.setColor(Color.white);
font.draw(name, x + width / 2f - lay.width / 2f, y + height / 2f + lay.height / 2f + 1);
Pools.free(lay);
}
}

View File

@@ -0,0 +1,49 @@
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;
public class BorderImage extends Image{
public float thickness = 4f;
public Color borderColor = Pal.gray;
public BorderImage(){
}
public BorderImage(Texture texture){
super(texture);
}
public BorderImage(Texture texture, float thick){
super(texture);
thickness = thick;
}
public BorderImage(TextureRegion region, float thick){
super(region);
thickness = thick;
}
public BorderImage border(Color color){
this.borderColor = color;
return this;
}
@Override
public void draw(){
super.draw();
float scaleX = getScaleX();
float scaleY = getScaleY();
Draw.color(borderColor);
Draw.alpha(parentAlpha);
Lines.stroke(Scl.scl(thickness));
Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY);
Draw.reset();
}
}

View File

@@ -0,0 +1,23 @@
package mindustry.ui;
import java.util.*;
/** Defines sizes of a content's preview icon. */
public enum Cicon{
/** Full size. */
full(0),
tiny(8 * 2),
small(8 * 3),
medium(8 * 4),
large(8 * 5),
xlarge(8 * 6);
public static final Cicon[] all = values();
public static final Cicon[] scaled = Arrays.copyOfRange(all, 1, all.length);
public final int size;
Cicon(int size){
this.size = size;
}
}

View File

@@ -0,0 +1,210 @@
package mindustry.ui;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.scene.ui.layout.*;
import arc.util.*;
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.addImage(block.icon(Cicon.xlarge)).size(size);
title.add("[accent]" + block.localizedName).padLeft(5);
});
table.row();
table.addImage().height(3).color(Color.lightGray).pad(8).padLeft(0).padRight(0).fillX();
table.row();
if(block.description != null){
table.add(block.description).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.addImage().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, Array<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("[LIGHT_GRAY]" + stat.localized() + ":[] ").left();
Array<StatValue> arr = map.get(stat);
for(StatValue value : arr){
value.display(inset);
inset.add().size(10f);
}
//map.get(stat).display(inset);
}).fillX().padLeft(10);
table.row();
}
}
}
public static void displayItem(Table table, Item item){
table.table(title -> {
title.addImage(item.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + item.localizedName).padLeft(5);
});
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(item.description != null){
table.add(item.description).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
}
table.left().defaults().fillX();
table.add(Core.bundle.format("item.corestorable", item.type == ItemType.material ? Core.bundle.format("yes") : Core.bundle.format("no")));
table.row();
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.addImage(liquid.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + liquid.localizedName).padLeft(5);
});
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(liquid.description != null){
table.add(liquid.description).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.addImage().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 displayMech(Table table, Mech mech){
table.table(title -> {
title.addImage(mech.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + mech.localizedName).padLeft(5);
});
table.left().defaults().left();
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(mech.description != null){
table.add(mech.description).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
}
table.left().defaults().fillX();
if(Core.bundle.has("mech." + mech.name + ".weapon")){
table.add(Core.bundle.format("mech.weapon", Core.bundle.get("mech." + mech.name + ".weapon")));
table.row();
}
if(Core.bundle.has("mech." + mech.name + ".ability")){
table.add(Core.bundle.format("mech.ability", Core.bundle.get("mech." + mech.name + ".ability")));
table.row();
}
table.add(Core.bundle.format("mech.buildspeed", (int)(mech.buildPower * 100f)));
table.row();
table.add(Core.bundle.format("mech.health", (int)mech.health));
table.row();
table.add(Core.bundle.format("mech.itemcapacity", mech.itemCapacity));
table.row();
if(mech.drillPower > 0){
table.add(Core.bundle.format("mech.minespeed", (int)(mech.mineSpeed * 100f)));
table.row();
table.add(Core.bundle.format("mech.minepower", mech.drillPower));
table.row();
}
}
public static void displayUnit(Table table, UnitType unit){
table.table(title -> {
title.addImage(unit.icon(Cicon.xlarge)).size(8 * 6);
title.add("[accent]" + unit.localizedName).padLeft(5);
});
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
if(unit.description != null){
table.add(unit.description).padLeft(5).padRight(5).width(400f).wrap().fillX();
table.row();
table.addImage().height(3).color(Color.lightGray).pad(15).padLeft(0).padRight(0).fillX();
table.row();
}
table.left().defaults().fillX();
table.add(Core.bundle.format("unit.health", unit.health));
table.row();
table.add(Core.bundle.format("unit.speed", Strings.fixed(unit.speed, 1)));
table.row();
table.row();
}
}

View File

@@ -0,0 +1,9 @@
package mindustry.ui;
import arc.graphics.g2d.*;
public class Fonts{
public static BitmapFont def;
public static BitmapFont outline;
public static BitmapFont chat;
}

View File

@@ -0,0 +1,38 @@
package mindustry.ui;
import arc.graphics.g2d.Fill;
import arc.scene.Element;
public class GridImage extends Element{
private int imageWidth, imageHeight;
public GridImage(int w, int h){
this.imageWidth = w;
this.imageHeight = h;
}
@Override
public void draw(){
float xspace = (getWidth() / imageWidth);
float yspace = (getHeight() / imageHeight);
float s = 1f;
int minspace = 10;
int jumpx = (int)(Math.max(minspace, xspace) / xspace);
int jumpy = (int)(Math.max(minspace, yspace) / yspace);
for(int x = 0; x <= imageWidth; x += jumpx){
Fill.crect((int)(getX() + xspace * x - s), getY() - s, 2, getHeight() + (x == imageWidth ? 1 : 0));
}
for(int y = 0; y <= imageHeight; y += jumpy){
Fill.crect(getX() - s, (int)(getY() + y * yspace - s), getWidth(), 2);
}
}
public void setImageSize(int w, int h){
this.imageWidth = w;
this.imageHeight = h;
}
}

View File

@@ -0,0 +1,14 @@
package mindustry.ui;
public enum IconSize{
def(48),
small(32),
smaller(30),
tiny(16);
public final int size;
IconSize(int size){
this.size = size;
}
}

View File

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

View File

@@ -0,0 +1,27 @@
package mindustry.ui;
import arc.scene.ui.layout.Table;
import mindustry.type.Item;
import mindustry.type.ItemStack;
/** An item image with text. */
public class ItemDisplay extends Table{
public final Item item;
public final int amount;
public ItemDisplay(Item item){
this(item, 0);
}
public ItemDisplay(Item item, int amount, boolean showName){
add(new ItemImage(new ItemStack(item, amount))).size(8 * 4).padRight(amount > 99 ? 12 : 0);
if(showName) add(item.localizedName).padLeft(4 + amount > 99 ? 4 : 0);
this.item = item;
this.amount = amount;
}
public ItemDisplay(Item item, int amount){
this(item, amount, true);
}
}

View File

@@ -0,0 +1,34 @@
package mindustry.ui;
import arc.graphics.g2d.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.type.*;
public class ItemImage extends Stack{
public ItemImage(TextureRegion region, int amount){
Table t = new Table().left().bottom();
t.add(amount + "").name("item-label");
add(new Image(region));
add(t);
}
public ItemImage(TextureRegion region){
Table t = new Table().left().bottom();
add(new Image(region));
add(t);
}
public ItemImage(ItemStack stack){
add(new Image(stack.item.icon(Cicon.medium)));
if(stack.amount != 0){
Table t = new Table().left().bottom();
t.add(stack.amount + "").name("item-label");
add(t);
}
}
}

View File

@@ -0,0 +1,48 @@
package mindustry.ui;
import arc.graphics.*;
import arc.scene.ui.layout.*;
import mindustry.core.GameState.*;
import mindustry.gen.*;
import mindustry.type.*;
import static mindustry.Vars.*;
/** Displays a list of items, e.g. launched items.*/
public class ItemsDisplay extends Table{
private StringBuilder builder = new StringBuilder();
public ItemsDisplay(){
rebuild();
}
public void rebuild(){
clear();
top().left();
margin(0);
table(Tex.button,t -> {
t.margin(10).marginLeft(15).marginTop(15f);
t.label(() -> state.is(State.menu) ? "$launcheditems" : "$launchinfo").colspan(3).padBottom(4).left().colspan(3).width(210f).wrap();
t.row();
for(Item item : content.items()){
if(item.type == ItemType.material && data.isUnlocked(item)){
t.label(() -> format(item)).left();
t.addImage(item.icon(Cicon.small)).size(8 * 3).padLeft(4).padRight(4);
t.add(item.localizedName).color(Color.lightGray).left();
t.row();
}
}
});
}
private String format(Item item){
builder.setLength(0);
builder.append(ui.formatAmount(data.items().get(item, 0)));
if(!state.is(State.menu) && !state.teams.get(player.getTeam()).cores.isEmpty() && state.teams.get(player.getTeam()).cores.first().entity != null && state.teams.get(player.getTeam()).cores.first().entity.items.get(item) > 0){
builder.append(" [unlaunched]+ ");
builder.append(ui.formatAmount(state.teams.get(player.getTeam()).cores.first().entity.items.get(item)));
}
return builder.toString();
}
}

View File

@@ -0,0 +1,49 @@
package mindustry.ui;
import arc.Core;
import arc.util.Strings;
import arc.graphics.Color;
import mindustry.graphics.Pal;
public class Links{
private static LinkEntry[] links;
private static void createLinks(){
links = new LinkEntry[]{
new LinkEntry("discord", "https://discord.gg/mindustry", Color.valueOf("7289da")),
new LinkEntry("changelog", "https://github.com/Anuken/Mindustry/releases", Pal.accent.cpy()),
new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Color.valueOf("026aa7")),
new LinkEntry("wiki", "https://mindustrygame.github.io/wiki/", Color.valueOf("0f142f")),
new LinkEntry("feathub", "https://feathub.com/Anuken/Mindustry/", Color.valueOf("ebebeb")),
new LinkEntry("reddit", "https://www.reddit.com/r/Mindustry/", Color.valueOf("ee593b")),
new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Color.valueOf("fa5c5c")),
new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Color.valueOf("689f38")),
new LinkEntry("f-droid", "https://f-droid.org/packages/io.anuke.mindustry/", Color.valueOf("026aa7")),
new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Color.valueOf("24292e")),
new LinkEntry("dev-builds", "https://github.com/Anuken/MindustryBuilds", Color.valueOf("fafbfc"))
};
}
public static LinkEntry[] getLinks(){
if(links == null){
createLinks();
}
return links;
}
public static class LinkEntry{
public final String name, title, description, link;
public final Color color;
public LinkEntry(String name, String link, Color color){
this.name = name;
this.color = color;
this.description = Core.bundle.getNotNull("link." + name + ".description");
this.link = link;
String title = Core.bundle.getOrNull("link." + name + ".title");
this.title = title != null ? title : Strings.capitalize(name.replace("-", " "));
}
}
}

View File

@@ -0,0 +1,38 @@
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;
/** An ItemDisplay, but for liquids. */
public class LiquidDisplay extends Table{
public final Liquid liquid;
public final float amount;
public final boolean perSecond;
public LiquidDisplay(Liquid liquid, float amount, boolean perSecond){
this.liquid = liquid;
this.amount = amount;
this.perSecond = perSecond;
add(new Stack(){{
add(new Image(liquid.icon(Cicon.medium)));
if(amount != 0){
Table t = new Table().left().bottom();
t.add(Strings.autoFixed(amount, 1));
add(t);
}
}}).size(8 * 4).padRight(3 + (amount != 0 && Strings.autoFixed(amount, 1).length() > 2 ? 8 : 0));
if(perSecond){
add(StatUnit.perSecond.localized()).padLeft(2).padRight(5).color(Color.lightGray);
}
add(liquid.localizedName);
}
}

View File

@@ -0,0 +1,100 @@
package mindustry.ui;
import arc.Core;
import arc.graphics.g2d.Draw;
import arc.input.KeyCode;
import arc.scene.Element;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
public class Minimap extends Table{
public Minimap(){
background(Tex.pane);
float margin = 5f;
touchable(Touchable.enabled);
add(new Element(){
{
setSize(Scl.scl(140f));
}
@Override
public void act(float delta){
setPosition(Scl.scl(margin), Scl.scl(margin));
super.act(delta);
}
@Override
public void draw(){
if(renderer.minimap.getRegion() == null) return;
Draw.rect(renderer.minimap.getRegion(), x + width / 2f, y + height / 2f, width, height);
if(renderer.minimap.getTexture() != null){
renderer.minimap.drawEntities(x, y, width, height, false);
}
}
}).size(140f);
margin(margin);
addListener(new InputListener(){
@Override
public boolean scrolled(InputEvent event, float x, float y, float amountx, float amounty){
renderer.minimap.zoomBy(amounty);
return true;
}
});
addListener(new ClickListener(){
{
tapSquareSize = Scl.scl(11f);
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
if(inTapSquare()){
super.touchUp(event, x, y, pointer, button);
}else{
pressed = false;
pressedPointer = -1;
pressedButton = null;
cancelled = false;
}
}
@Override
public void touchDragged(InputEvent event, float x, float y, int pointer){
if(!inTapSquare(x, y)){
invalidateTapSquare();
}
super.touchDragged(event, x, y, pointer);
if(mobile){
float max = Math.min(world.width(), world.height()) / 16f / 2f;
renderer.minimap.setZoom(1f + y / height * (max - 1f));
}
}
@Override
public void clicked(InputEvent event, float x, float y){
ui.minimap.show();
}
});
update(() -> {
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e != null && e.isDescendantOf(this)){
requestScroll();
}else if(hasScroll()){
Core.scene.setScrollFocus(null);
}
});
}
}

View File

@@ -0,0 +1,15 @@
package mindustry.ui;
import arc.scene.style.*;
import arc.scene.ui.ImageButton;
import arc.util.Align;
public class MobileButton extends ImageButton{
public MobileButton(Drawable icon, String text, Runnable listener){
super(icon);
clicked(listener);
row();
add(text).growX().wrap().center().get().setAlignment(Align.center, Align.center);
}
}

View File

@@ -0,0 +1,33 @@
package mindustry.ui;
import arc.struct.Array;
import arc.scene.ui.layout.Stack;
import arc.util.Time;
public class MultiReqImage extends Stack{
private Array<ReqImage> displays = new Array<>();
private float time;
public void add(ReqImage display){
displays.add(display);
super.add(display);
}
@Override
public void act(float delta){
super.act(delta);
time += Time.delta() / 60f;
displays.each(req -> req.visible(false));
ReqImage valid = displays.find(ReqImage::valid);
if(valid != null){
valid.visible(true);
}else{
if(displays.size > 0){
displays.get((int)(time) % displays.size).visible(true);
}
}
}
}

View File

@@ -0,0 +1,40 @@
package mindustry.ui;
import arc.func.Boolp;
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;
public class ReqImage extends Stack{
private final Boolp valid;
public ReqImage(Element image, Boolp valid){
this.valid = valid;
add(image);
add(new Element(){
{
visible(() -> !valid.get());
}
@Override
public void draw(){
Lines.stroke(Scl.scl(2f), Pal.removeBack);
Lines.line(x, y - 2f + height, x + width, y - 2f);
Draw.color(Pal.remove);
Lines.line(x, y + height, x + width, y);
Draw.reset();
}
});
}
public ReqImage(TextureRegion region, Boolp valid){
this(new Image(region), valid);
}
public boolean valid(){
return valid.get();
}
}

View File

@@ -0,0 +1,339 @@
package mindustry.ui;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.g2d.TextureAtlas.*;
import arc.scene.style.*;
import arc.scene.ui.Button.*;
import arc.scene.ui.CheckBox.*;
import arc.scene.ui.Dialog.*;
import arc.scene.ui.ImageButton.*;
import arc.scene.ui.KeybindDialog.*;
import arc.scene.ui.Label.*;
import arc.scene.ui.ScrollPane.*;
import arc.scene.ui.Slider.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.TextField.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import static mindustry.gen.Tex.*;
@StyleDefaults
public class Styles{
public static Drawable black, black9, black8, black6, black3, none, flatDown, flatOver;
public static ButtonStyle defaultb, waveb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt;
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane;
public static KeybindDialogStyle defaultKeybindDialog;
public static SliderStyle defaultSlider, vSlider;
public static LabelStyle defaultLabel, outlineLabel;
public static TextFieldStyle defaultField, areaField;
public static CheckBoxStyle defaultCheck;
public static DialogStyle defaultDialog, fullDialog;
public static void load(){
black = whiteui.tint(0f, 0f, 0f, 1f);
black9 = whiteui.tint(0f, 0f, 0f, 0.9f);
black8 = whiteui.tint(0f, 0f, 0f, 0.8f);
black6 = whiteui.tint(0f, 0f, 0f, 0.6f);
black3 = whiteui.tint(0f, 0f, 0f, 0.3f);
none = whiteui.tint(0f, 0f, 0f, 0f);
flatDown = createFlatDown();
flatOver = whiteui.tint(Color.valueOf("454545"));
defaultb = new ButtonStyle(){{
down = buttonDown;
up = button;
over = buttonOver;
disabled = buttonDisabled;
}};
waveb = new ButtonStyle(){{
up = buttonEdge4;
over = buttonEdgeOver4;
disabled = buttonEdge4;
}};
defaultt = new TextButtonStyle(){{
over = buttonOver;
disabled = buttonDisabled;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
down = buttonDown;
up = button;
}};
squaret = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
over = buttonSquareOver;
disabled = buttonDisabled;
down = buttonSquareDown;
up = buttonSquare;
}};
nodet = new TextButtonStyle(){{
disabled = button;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
up = buttonOver;
over = buttonDown;
}};
cleart = new TextButtonStyle(){{
over = flatOver;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
down = flatOver;
up = black;
}};
discordt = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;
up = discordBanner;
}};
infot = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;
up = infoBanner;
}};
clearPartialt = new TextButtonStyle(){{
down = whiteui;
up = pane;
over = flatDown;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
transt = new TextButtonStyle(){{
down = flatDown;
up = none;
over = flatOver;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
clearTogglet = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;
checked = flatDown;
down = flatDown;
up = black;
over = flatOver;
disabled = black;
disabledFontColor = Color.gray;
}};
clearToggleMenut = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;
checked = flatDown;
down = flatDown;
up = clear;
over = flatOver;
disabled = black;
disabledFontColor = Color.gray;
}};
togglet = new TextButtonStyle(){{
font = Fonts.def;
fontColor = Color.white;
checked = buttonDown;
down = buttonDown;
up = button;
over = buttonOver;
disabled = buttonDisabled;
disabledFontColor = Color.gray;
}};
defaulti = new ImageButtonStyle(){{
down = buttonDown;
up = button;
over = buttonOver;
imageDisabledColor = Color.gray;
imageUpColor = Color.white;
disabled = buttonDisabled;
}};
nodei = new ImageButtonStyle(){{
up = buttonOver;
over = buttonDown;
}};
righti = new ImageButtonStyle(){{
over = buttonRightOver;
down = buttonRightDown;
up = buttonRight;
}};
emptyi = new ImageButtonStyle(){{
imageDownColor = Pal.accent;
imageUpColor = Color.white;
}};
emptytogglei = new ImageButtonStyle(){{
imageCheckedColor = Color.white;
imageDownColor = Color.white;
imageUpColor = Color.gray;
}};
selecti = new ImageButtonStyle(){{
checked = buttonSelect;
up = none;
}};
cleari = new ImageButtonStyle(){{
down = flatOver;
up = black;
over = flatOver;
}};
clearFulli = new ImageButtonStyle(){{
down = whiteui;
up = pane;
over = flatDown;
}};
clearPartiali = new ImageButtonStyle(){{
down = flatDown;
up = none;
over = flatOver;
}};
clearPartial2i = new ImageButtonStyle(){{
down = whiteui;
up = pane;
over = flatDown;
}};
clearTogglei = new ImageButtonStyle(){{
down = flatDown;
checked = flatDown;
up = black;
over = flatOver;
}};
clearTransi = new ImageButtonStyle(){{
down = flatDown;
up = black6;
over = flatOver;
disabled = black8;
imageDisabledColor = Color.lightGray;
imageUpColor = Color.white;
}};
clearToggleTransi = new ImageButtonStyle(){{
down = flatDown;
checked = flatDown;
up = black6;
over = flatOver;
}};
clearTogglePartiali = new ImageButtonStyle(){{
down = flatDown;
checked = flatDown;
up = none;
over = flatOver;
}};
defaultPane = new ScrollPaneStyle(){{
vScroll = scroll;
vScrollKnob = scrollKnobVerticalBlack;
}};
horizontalPane = new ScrollPaneStyle(){{
vScroll = scroll;
vScrollKnob = scrollKnobVerticalBlack;
hScroll = scrollHorizontal;
hScrollKnob = scrollKnobHorizontalBlack;
}};
smallPane = new ScrollPaneStyle(){{
vScroll = clear;
vScrollKnob = scrollKnobVerticalThin;
}};
defaultKeybindDialog = new KeybindDialogStyle(){{
keyColor = Pal.accent;
keyNameColor = Color.white;
controllerColor = Color.lightGray;
}};
defaultSlider = new SliderStyle(){{
background = slider;
knob = sliderKnob;
knobOver = sliderKnobOver;
knobDown = sliderKnobDown;
}};
vSlider = new SliderStyle(){{
background = sliderVertical;
knob = sliderKnob;
knobOver = sliderKnobOver;
knobDown = sliderKnobDown;
}};
defaultLabel = new LabelStyle(){{
font = Fonts.def;
fontColor = Color.white;
}};
outlineLabel = new LabelStyle(){{
font = Fonts.outline;
fontColor = Color.white;
}};
defaultField = new TextFieldStyle(){{
font = Fonts.chat;
fontColor = Color.white;
disabledFontColor = Color.gray;
disabledBackground = underlineDisabled;
selection = Tex.selection;
background = underline;
invalidBackground = underlineRed;
cursor = Tex.cursor;
messageFont = Fonts.def;
messageFontColor = Color.gray;
}};
areaField = new TextFieldStyle(){{
font = Fonts.chat;
fontColor = Color.white;
disabledFontColor = Color.gray;
selection = Tex.selection;
background = underline;
cursor = Tex.cursor;
messageFont = Fonts.def;
messageFontColor = Color.gray;
}};
defaultCheck = new CheckBoxStyle(){{
checkboxOn = checkOn;
checkboxOff = checkOff;
checkboxOnOver = checkOnOver;
checkboxOver = checkOver;
checkboxOnDisabled = checkOnDisabled;
checkboxOffDisabled = checkDisabled;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
defaultDialog = new DialogStyle(){{
stageBackground = black9;
titleFont = Fonts.def;
background = windowEmpty;
titleFontColor = Pal.accent;
}};
fullDialog = new DialogStyle(){{
stageBackground = black;
titleFont = Fonts.def;
background = windowEmpty;
titleFontColor = Pal.accent;
}};
}
private static Drawable createFlatDown(){
AtlasRegion region = Core.atlas.find("flat-down-base");
int[] splits = region.splits;
ScaledNinePatchDrawable copy = new ScaledNinePatchDrawable(new NinePatch(region, splits[0], splits[1], splits[2], splits[3])){
public float getLeftWidth(){ return 0; }
public float getRightWidth(){ return 0; }
public float getTopHeight(){ return 0; }
public float getBottomHeight(){ return 0; }
};
copy.setMinWidth(0);
copy.setMinHeight(0);
copy.setTopHeight(0);
copy.setRightWidth(0);
copy.setBottomHeight(0);
copy.setLeftWidth(0);
return copy;
}
}

View File

@@ -0,0 +1,118 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.ui.Links.*;
import static mindustry.Vars.*;
public class AboutDialog extends FloatingDialog{
private Array<String> contributors = new Array<>();
private static ObjectSet<String> bannedItems = ObjectSet.with("google-play", "itch.io", "dev-builds", "f-droid");
public AboutDialog(){
super("$about.button");
shown(() -> {
contributors = Array.with(Core.files.internal("contributors").readString("UTF-8").split("\n"));
Core.app.post(this::setup);
});
shown(this::setup);
onResize(this::setup);
}
void setup(){
cont.clear();
buttons.clear();
float h = Core.graphics.isPortrait() ? 90f : 80f;
float w = Core.graphics.isPortrait() ? 330f : 600f;
Table in = new Table();
ScrollPane pane = new ScrollPane(in);
for(LinkEntry link : Links.getLinks()){
if((ios || OS.isMac || steam) && bannedItems.contains(link.name)){
continue;
}
Table table = new Table(Tex.underline);
table.margin(0);
table.table(img -> {
img.addImage().height(h - 5).width(40f).color(link.color);
img.row();
img.addImage().height(5).width(40f).color(link.color.cpy().mul(0.8f, 0.8f, 0.8f, 1f));
}).expandY();
table.table(i -> {
i.background(Tex.buttonEdge3);
i.addImage(Core.atlas.drawable("icon-" + link.name));
}).size(h - 5, h);
table.table(inset -> {
inset.add("[accent]" + link.title).growX().left();
inset.row();
inset.labelWrap(link.description).width(w - 100f).color(Color.lightGray).growX();
}).padLeft(8);
table.addImageButton(Icon.link, () -> {
if(link.name.equals("wiki")) Events.fire(Trigger.openWiki);
if(!Core.net.openURI(link.link)){
ui.showErrorMessage("$linkfail");
Core.app.setClipboardText(link.link);
}
}).size(h - 5, h);
in.add(table).size(w, h).padTop(5).row();
}
shown(() -> Time.run(1f, () -> Core.scene.setScrollFocus(pane)));
cont.add(pane).growX();
addCloseButton();
buttons.addButton("$credits", this::showCredits).size(200f, 64f);
if(Core.graphics.isPortrait()){
for(Cell<?> cell : buttons.getCells()){
cell.width(140f);
}
}
}
public void showCredits(){
FloatingDialog dialog = new FloatingDialog("$credits");
dialog.addCloseButton();
dialog.cont.add("$credits.text");
dialog.cont.row();
if(!contributors.isEmpty()){
dialog.cont.addImage().color(Pal.accent).fillX().height(3f).pad(3f);
dialog.cont.row();
dialog.cont.add("$contributors");
dialog.cont.row();
dialog.cont.pane(new Table(){{
int i = 0;
left();
for(String c : contributors){
add("[lightgray]" + c).left().pad(3).padLeft(6).padRight(6);
if(++i % 3 == 0){
row();
}
}
}});
}
dialog.show();
}
}

View File

@@ -0,0 +1,59 @@
package mindustry.ui.dialogs;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import static mindustry.Vars.*;
public class AdminsDialog extends FloatingDialog{
public AdminsDialog(){
super("$server.admins");
addCloseButton();
setup();
shown(this::setup);
}
private void setup(){
cont.clear();
float w = 400f, h = 80f;
Table table = new Table();
ScrollPane pane = new ScrollPane(table);
pane.setFadeScrollBars(false);
if(netServer.admins.getAdmins().size == 0){
table.add("$server.admins.none");
}
for(PlayerInfo info : netServer.admins.getAdmins()){
Table res = new Table(Tex.button);
res.margin(14f);
res.labelWrap("[LIGHT_GRAY]" + info.lastName).width(w - h - 24f);
res.add().growX();
res.addImageButton(Icon.cancel, () -> {
ui.showConfirm("$confirm", "$confirmunadmin", () -> {
netServer.admins.unAdminPlayer(info.id);
playerGroup.all().each(player -> {
if(player != null && player.uuid != null && player.uuid.equals(info.id)){
player.isAdmin = false;
}
});
setup();
});
}).size(h).pad(-14f);
table.add(res).width(w).height(h);
table.row();
}
cont.add(pane);
}
}

View File

@@ -0,0 +1,55 @@
package mindustry.ui.dialogs;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import mindustry.net.Administration.*;
import static mindustry.Vars.*;
public class BansDialog extends FloatingDialog{
public BansDialog(){
super("$server.bans");
addCloseButton();
setup();
shown(this::setup);
}
private void setup(){
cont.clear();
float w = 400f, h = 80f;
Table table = new Table();
ScrollPane pane = new ScrollPane(table);
pane.setFadeScrollBars(false);
if(netServer.admins.getBanned().size == 0){
table.add("$server.bans.none");
}
for(PlayerInfo info : netServer.admins.getBanned()){
Table res = new Table(Tex.button);
res.margin(14f);
res.labelWrap("IP: [LIGHT_GRAY]" + info.lastIP + "\n[]Name: [LIGHT_GRAY]" + info.lastName).width(w - h - 24f);
res.add().growX();
res.addImageButton(Icon.cancel, () -> {
ui.showConfirm("$confirm", "$confirmunban", () -> {
netServer.admins.unbanPlayerID(info.id);
setup();
});
}).size(h).pad(-14f);
table.add(res).width(w).height(h);
table.row();
}
cont.add(pane);
}
}

View File

@@ -0,0 +1,63 @@
package mindustry.ui.dialogs;
import arc.func.*;
import arc.graphics.*;
import arc.scene.ui.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class ColorPicker extends FloatingDialog{
private Cons<Color> cons = c -> {};
private Color current = new Color();
public ColorPicker(){
super("$pickcolor");
}
public void show(Color color, Cons<Color> consumer){
show(color, true, consumer);
}
public void show(Color color, boolean alpha, Cons<Color> consumer){
this.current.set(color);
this.cons = consumer;
show();
cont.clear();
cont.pane(t -> {
t.table(Tex.pane, i -> {
i.stack(new Image(Tex.alphaBg), new Image(){{
setColor(current);
update(() -> setColor(current));
}}).size(200f);
}).colspan(2).padBottom(5);
float w = 150f;
t.row();
t.defaults().padBottom(4);
t.add("R").color(Pal.remove);
t.addSlider(0f, 1f, 0.01f, current.r, current::r).width(w);
t.row();
t.add("G").color(Color.lime);
t.addSlider(0f, 1f, 0.01f, current.g, current::g).width(w);
t.row();
t.add("B").color(Color.royal);
t.addSlider(0f, 1f, 0.01f, current.b, current::b).width(w);
t.row();
if(alpha){
t.add("A");
t.addSlider(0f, 1f, 0.01f, current.a, current::a).width(w);
t.row();
}
});
buttons.clear();
addCloseButton();
buttons.addImageTextButton("$ok", Icon.checkSmall, () -> {
cons.get(current);
hide();
});
}
}

View File

@@ -0,0 +1,28 @@
package mindustry.ui.dialogs;
import arc.scene.ui.ScrollPane;
import arc.scene.ui.layout.Table;
import mindustry.ctype.UnlockableContent;
public class ContentInfoDialog extends FloatingDialog{
public ContentInfoDialog(){
super("$info.title");
addCloseButton();
}
public void show(UnlockableContent content){
cont.clear();
Table table = new Table();
table.margin(10);
content.displayInfo(table);
ScrollPane pane = new ScrollPane(table);
cont.add(pane);
show();
}
}

View File

@@ -0,0 +1,27 @@
package mindustry.ui.dialogs;
import arc.input.*;
import arc.scene.ui.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class ControlsDialog extends KeybindDialog{
public ControlsDialog(){
setFillParent(true);
title.setAlignment(Align.center);
titleTable.row();
titleTable.add(new Image()).growX().height(3f).pad(4f).get().setColor(Pal.accent);
}
@Override
public void addCloseButton(){
buttons.addImageTextButton("$back", Icon.arrowLeftSmall, this::hide).size(230f, 64f);
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK)
hide();
});
}
}

View File

@@ -0,0 +1,92 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.maps.*;
import mindustry.ui.*;
public class CustomGameDialog extends FloatingDialog{
private MapPlayDialog dialog = new MapPlayDialog();
public CustomGameDialog(){
super("$customgame");
addCloseButton();
shown(this::setup);
onResize(this::setup);
}
void setup(){
clearChildren();
add(titleTable);
row();
stack(cont, buttons).grow();
buttons.bottom();
cont.clear();
Table maps = new Table();
maps.marginRight(14);
maps.marginBottom(55f);
ScrollPane pane = new ScrollPane(maps);
pane.setFadeScrollBars(false);
int maxwidth = Mathf.clamp((int)(Core.graphics.getWidth() / Scl.scl(200)), 1, 8);
float images = 146f;
int i = 0;
maps.defaults().width(170).fillY().top().pad(4f);
for(Map map : Vars.maps.all()){
if(i % maxwidth == 0){
maps.row();
}
ImageButton image = new ImageButton(new TextureRegion(map.safeTexture()), Styles.cleari);
image.margin(5);
image.top();
Image img = image.getImage();
img.remove();
image.row();
image.table(t -> {
t.left();
for(Gamemode mode : Gamemode.all){
if(mode.valid(map) && Core.atlas.has("icon-mode-" + mode.name())){
t.addImage(Core.atlas.drawable("icon-mode-" + mode.name())).size(16f).pad(4f);
}
}
}).left();
image.row();
image.add(map.name()).pad(1f).growX().wrap().left().get().setEllipsis(true);
image.row();
image.addImage(Tex.whiteui, Pal.gray).growX().pad(3).height(4f);
image.row();
image.add(img).size(images);
BorderImage border = new BorderImage(map.safeTexture(), 3f);
border.setScaling(Scaling.fit);
image.replaceImage(border);
image.clicked(() -> dialog.show(map));
maps.add(image);
i++;
}
if(Vars.maps.all().size == 0){
maps.add("$maps.none").pad(50);
}
cont.add(pane).uniformX();
}
}

View File

@@ -0,0 +1,220 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.world.*;
import static mindustry.Vars.*;
public class CustomRulesDialog extends FloatingDialog{
private Table main;
private Rules rules;
private Prov<Rules> resetter;
private LoadoutDialog loadoutDialog;
private FloatingDialog banDialog;
public CustomRulesDialog(){
super("$mode.custom");
loadoutDialog = new LoadoutDialog();
banDialog = new FloatingDialog("$bannedblocks");
banDialog.addCloseButton();
banDialog.shown(this::rebuildBanned);
banDialog.buttons.addImageTextButton("$addall", Icon.arrow16Small, () -> {
rules.bannedBlocks.addAll(content.blocks().select(Block::isBuildable));
rebuildBanned();
}).size(180, 64f);
banDialog.buttons.addImageTextButton("$clear", Icon.trash16Small, () -> {
rules.bannedBlocks.clear();
rebuildBanned();
}).size(180, 64f);
setFillParent(true);
shown(this::setup);
addCloseButton();
}
private void rebuildBanned(){
float previousScroll = banDialog.cont.getChildren().isEmpty() ? 0f : ((ScrollPane)banDialog.cont.getChildren().first()).getScrollY();
banDialog.cont.clear();
banDialog.cont.pane(t -> {
t.margin(10f);
if(rules.bannedBlocks.isEmpty()){
t.add("$empty");
}
Array<Block> array = Array.with(rules.bannedBlocks);
array.sort();
int cols = mobile && Core.graphics.isPortrait() ? 1 : mobile ? 2 : 3;
int i = 0;
for(Block block : array){
t.table(Tex.underline, b -> {
b.left().margin(4f);
b.addImage(block.icon(Cicon.medium)).size(Cicon.medium.size).padRight(3);
b.add(block.localizedName).color(Color.lightGray).padLeft(3).growX().left().wrap();
b.addImageButton(Icon.cancelSmall, Styles.clearPartiali, () -> {
rules.bannedBlocks.remove(block);
rebuildBanned();
}).size(70f).pad(-4f).padLeft(0f);
}).size(300f, 70f).padRight(5);
if(++i % cols == 0){
t.row();
}
}
}).get().setScrollYForce(previousScroll);
banDialog.cont.row();
banDialog.cont.addImageTextButton("$add", Icon.addSmall, () -> {
FloatingDialog dialog = new FloatingDialog("$add");
dialog.cont.pane(t -> {
t.left().margin(14f);
int[] i = {0};
content.blocks().each(b -> !rules.bannedBlocks.contains(b) && b.isBuildable(), b -> {
int cols = mobile && Core.graphics.isPortrait() ? 4 : 12;
t.addImageButton(new TextureRegionDrawable(b.icon(Cicon.medium)), Styles.cleari, () -> {
rules.bannedBlocks.add(b);
rebuildBanned();
dialog.hide();
}).size(60f).get().resizeImage(Cicon.medium.size);
if(++i[0] % cols == 0){
t.row();
}
});
});
dialog.addCloseButton();
dialog.show();
}).size(300f, 64f);
}
public void show(Rules rules, Prov<Rules> resetter){
this.rules = rules;
this.resetter = resetter;
show();
}
void setup(){
cont.clear();
cont.pane(m -> main = m).get().setScrollingDisabled(true, false);
main.margin(10f);
main.addButton("$settings.reset", () -> {
rules = resetter.get();
setup();
requestKeyboard();
requestScroll();
}).size(300f, 50f);
main.left().defaults().fillX().left().pad(5);
main.row();
title("$rules.title.waves");
check("$rules.waves", b -> rules.waves = b, () -> rules.waves);
check("$rules.wavetimer", b -> rules.waveTimer = b, () -> rules.waveTimer);
check("$rules.waitForWaveToEnd", b -> rules.waitForWaveToEnd = b, () -> rules.waitForWaveToEnd);
number("$rules.wavespacing", false, f -> rules.waveSpacing = f * 60f, () -> rules.waveSpacing / 60f, () -> true);
number("$rules.dropzoneradius", false, f -> rules.dropZoneRadius = f * tilesize, () -> rules.dropZoneRadius / tilesize, () -> true);
title("$rules.title.respawns");
number("$rules.respawntime", f -> rules.respawnTime = f * 60f, () -> rules.respawnTime / 60f);
title("$rules.title.resourcesbuilding");
check("$rules.infiniteresources", b -> rules.infiniteResources = b, () -> rules.infiniteResources);
check("$rules.reactorexplosions", b -> rules.reactorExplosions = b, () -> rules.reactorExplosions);
number("$rules.buildcostmultiplier", false, f -> rules.buildCostMultiplier = f, () -> rules.buildCostMultiplier, () -> !rules.infiniteResources);
number("$rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier);
main.addButton("$configure",
() -> loadoutDialog.show(Blocks.coreShard.itemCapacity, rules.loadout,
() -> {
rules.loadout.clear();
rules.loadout.add(new ItemStack(Items.copper, 100));
}, () -> {}, () -> {}
)).left().width(300f);
main.row();
main.addButton("$bannedblocks", banDialog::show).left().width(300f);
main.row();
title("$rules.title.player");
number("$rules.playerhealthmultiplier", f -> rules.playerHealthMultiplier = f, () -> rules.playerHealthMultiplier);
number("$rules.playerdamagemultiplier", f -> rules.playerDamageMultiplier = f, () -> rules.playerDamageMultiplier);
title("$rules.title.unit");
check("$rules.unitdrops", b -> rules.unitDrops = b, () -> rules.unitDrops, () -> true);
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);
title("$rules.title.enemy");
check("$rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
check("$rules.enemyCheat", b -> rules.enemyCheat = b, () -> rules.enemyCheat);
number("$rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200));
title("$rules.title.experimental");
check("$rules.lighting", b -> rules.lighting = b, () -> rules.lighting);
main.addButton(b -> {
b.left();
b.table(Tex.pane, in -> {
in.stack(new Image(Tex.alphaBg), new Image(Tex.whiteui){{
update(() -> setColor(rules.ambientLight));
}}).grow();
}).margin(4).size(50f).padRight(10);
b.add("$rules.ambientlight");
}, () -> ui.picker.show(rules.ambientLight, rules.ambientLight::set)).left().width(250f);
main.row();
}
void number(String text, Floatc cons, Floatp prov){
number(text, false, cons, prov, () -> true);
}
void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition){
main.table(t -> {
t.left();
t.add(text).left().padRight(5)
.update(a -> a.setColor(condition.get() ? Color.white : Color.gray));
Vars.platform.addDialog(t.addField((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().get());
}).padTop(0);
main.row();
}
void check(String text, Boolc cons, Boolp prov){
check(text, cons, prov, () -> true);
}
void check(String text, Boolc cons, Boolp prov, Boolp condition){
main.addCheck(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get())).padRight(100f).get().left();
main.row();
}
void title(String text){
main.add(text).color(Pal.accent).padTop(20).padRight(100f).padBottom(-3);
main.row();
main.addImage().color(Pal.accent).height(3f).padRight(100f).padBottom(20);
main.row();
}
}

View File

@@ -0,0 +1,86 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.core.GameState.*;
import mindustry.ctype.*;
import mindustry.ctype.ContentType;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
public class DatabaseDialog extends FloatingDialog{
public DatabaseDialog(){
super("$database");
shouldPause = true;
addCloseButton();
shown(this::rebuild);
onResize(this::rebuild);
}
void rebuild(){
cont.clear();
Table table = new Table();
table.margin(20);
ScrollPane pane = new ScrollPane(table);
Array<Content>[] allContent = Vars.content.getContentMap();
for(int j = 0; j < allContent.length; j++){
ContentType type = ContentType.values()[j];
Array<Content> array = allContent[j].select(c -> c instanceof UnlockableContent && !((UnlockableContent)c).isHidden());
if(array.size == 0) continue;
table.add("$content." + type.name() + ".name").growX().left().color(Pal.accent);
table.row();
table.addImage().growX().pad(5).padLeft(0).padRight(0).height(3).color(Pal.accent);
table.row();
table.table(list -> {
list.left();
int maxWidth = Core.graphics.isPortrait() ? 7 : 13;
int count = 0;
for(int i = 0; i < array.size; i++){
UnlockableContent unlock = (UnlockableContent)array.get(i);
Image image = unlocked(unlock) ? new Image(unlock.icon(Cicon.medium)) : new Image(Icon.lockedSmall, Pal.gray);
list.add(image).size(8*4).pad(3);
ClickListener listener = new ClickListener();
image.addListener(listener);
if(!Vars.mobile && unlocked(unlock)){
image.addListener(new HandCursorListener());
image.update(() -> image.getColor().lerp(!listener.isOver() ? Color.lightGray : Color.white, 0.4f * Time.delta()));
}
if(unlocked(unlock)){
image.clicked(() -> Vars.ui.content.show(unlock));
image.addListener(new Tooltip(t -> t.background(Tex.button).add(unlock.localizedName)));
}
if((++count) % maxWidth == 0){
list.row();
}
}
}).growX().left().padBottom(10);
table.row();
}
cont.add(pane);
}
boolean unlocked(UnlockableContent content){
return (!Vars.world.isZone() && !Vars.state.is(State.menu)) || content.unlocked();
}
}

View File

@@ -0,0 +1,319 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.scene.utils.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.game.Saves.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.SaveIO.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.layout.*;
import mindustry.ui.layout.TreeLayout.*;
import static mindustry.Vars.*;
public class DeployDialog extends FloatingDialog{
private final float nodeSize = Scl.scl(230f);
private ObjectSet<ZoneNode> nodes = new ObjectSet<>();
private ZoneInfoDialog info = new ZoneInfoDialog();
private Rectangle bounds = new Rectangle();
private View view = new View();
public DeployDialog(){
super("", Styles.fullDialog);
treeLayout();
Events.on(ContentReloadEvent.class, e -> treeLayout());
addCloseButton();
buttons.addImageTextButton("$techtree", Icon.tree, () -> ui.tech.show()).size(230f, 64f);
shown(this::setup);
//view input.
addListener(new InputListener(){
@Override
public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY){
view.setScale(Mathf.clamp(view.getScaleX() - amountY / 40f, 0.25f, 1f));
view.setOrigin(Align.center);
view.setTransform(true);
return true;
}
@Override
public boolean mouseMoved(InputEvent event, float x, float y){
view.requestScroll();
return super.mouseMoved(event, x, y);
}
});
addListener(new ElementGestureListener(){
@Override
public void zoom(InputEvent event, float initialDistance, float distance){
if(view.lastZoom < 0){
view.lastZoom = view.getScaleX();
}
view.setScale(Mathf.clamp(distance / initialDistance * view.lastZoom, 0.25f, 1f));
view.setOrigin(Align.center);
view.setTransform(true);
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
view.lastZoom = view.getScaleX();
}
@Override
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY){
view.panX += deltaX / view.getScaleX();
view.panY += deltaY / view.getScaleY();
view.moved = true;
view.clamp();
}
});
}
void treeLayout(){
nodes.clear();
ZoneNode root = new ZoneNode(Zones.groundZero, null);
BranchTreeLayout layout = new BranchTreeLayout();
layout.gapBetweenLevels = layout.gapBetweenNodes = Scl.scl(60f);
layout.gapBetweenNodes = Scl.scl(120f);
layout.layout(root);
bounds.set(layout.getBounds());
bounds.y += nodeSize*0.4f;
}
public void setup(){
platform.updateRPC();
cont.clear();
titleTable.remove();
margin(0f).marginBottom(8);
Stack stack = new Stack();
stack.add(new Image(new Texture("sprites/backgrounds/stars.png"){{
setFilter(TextureFilter.Linear);
}}).setScaling(Scaling.fill));
stack.add(new Image(new Texture("sprites/backgrounds/planet-zero.png"){{
setFilter(TextureFilter.Linear);
}}){{
float[] time = {0};
setColor(Color.fromGray(0.3f));
setScale(1.5f);
update(() -> {
setOrigin(Align.center);
time[0] += Core.graphics.getDeltaTime() * 10f;
setTranslation(Mathf.sin(time[0], 60f, 70f) + view.panX / 30f, Mathf.cos(time[0], 140f, 80f) + (view.panY + 200) / 30f);
});
}}.setScaling(Scaling.fit));
if(control.saves.getZoneSlot() != null){
float size = 250f;
stack.add(new Table(t -> {
SaveSlot slot = control.saves.getZoneSlot();
Stack sub = new Stack();
if(slot.getZone() != null){
sub.add(new Table(f -> f.margin(4f).add(new Image()).color(Color.fromGray(0.1f)).grow()));
sub.add(new Table(f -> f.margin(4f).add(new Image(slot.getZone().preview).setScaling(Scaling.fit)).update(img -> {
TextureRegionDrawable draw = (TextureRegionDrawable)img.getDrawable();
if(draw.getRegion().getTexture().isDisposed()){
draw.setRegion(slot.getZone().preview);
}
Texture text = slot.previewTexture();
if(draw.getRegion() == slot.getZone().preview && text != null){
draw.setRegion(new TextureRegion(text));
}
}).color(Color.darkGray).grow()));
}
TextButton button = Elements.newButton(Core.bundle.format("resume", slot.getZone().localizedName), Styles.squaret, () -> {
control.saves.getZoneSlot().cautiousLoad(() -> {
hide();
ui.loadAnd(() -> {
logic.reset();
net.reset();
try{
slot.load();
state.set(State.playing);
}catch(SaveException e){ //make sure to handle any save load errors!
e.printStackTrace();
if(control.saves.getZoneSlot() != null) control.saves.getZoneSlot().delete();
Core.app.post(() -> ui.showInfo("$save.corrupted"));
show();
}
});
});
});
sub.add(button);
t.add(sub).size(size);
String color = "[lightgray]";
button.defaults().colspan(2);
button.row();
button.add(Core.bundle.format("save", color + slot.getWave()));
button.row();
button.label(() -> Core.bundle.format("save.playtime", color + slot.getPlayTime()));
button.row();
t.row();
t.addButton("$abandon", () -> {
ui.showConfirm("$warning", "$abandon.text", () -> {
slot.delete();
setup();
});
}).width(size).height(50f).padTop(3);
}));
}else{
stack.add(view = new View());
}
stack.add(new ItemsDisplay());
cont.add(stack).grow();
//set up direct and indirect children
for(ZoneNode node : nodes){
node.allChildren.clear();
node.allChildren.addAll(node.children);
for(ZoneNode other : nodes){
if(other.zone.requirements.contains(req -> req.zone() == node.zone)){
node.allChildren.add(other);
}
}
}
view.setOrigin(Align.center);
view.setTransform(true);
}
boolean hidden(Zone zone){
return zone.requirements.contains(o -> o.zone() != null && o.zone().locked());
}
void buildButton(Zone zone, Button button){
button.setDisabled(() -> hidden(zone));
button.clicked(() -> {
if(!view.moved){
info.show(zone);
}
});
if(zone.unlocked() && !hidden(zone)){
button.labelWrap(zone.localizedName).style(Styles.outlineLabel).width(140).growX().get().setAlignment(Align.center);
}else{
Cons<Element> flasher = zone.canUnlock() && !hidden(zone) ? e -> e.update(() -> e.getColor().set(Color.white).lerp(Pal.accent, Mathf.absin(3f, 1f))) : e -> {};
flasher.get(button.addImage(Icon.locked).get());
button.row();
flasher.get(button.add("$locked").get());
}
}
class View extends Group{
float panX = 0, panY = -200, lastZoom = -1;
boolean moved = false;
{
for(ZoneNode node : nodes){
Stack stack = new Stack();
Tmp.v1.set(node.width, node.height);
if(node.zone.preview != null){
Tmp.v1.set(Scaling.fit.apply(node.zone.preview.getWidth(), node.zone.preview.getHeight(), node.width, node.height));
}
stack.setSize(Tmp.v1.x, Tmp.v1.y);
stack.add(new Table(t -> t.margin(4f).add(new Image(node.zone.preview).setScaling(Scaling.stretch)).color(node.zone.unlocked() ? Color.darkGray : Color.fromGray(0.2f)).grow()));
stack.update(() -> stack.setPosition(node.x + panX + width / 2f, node.y + panY + height / 2f, Align.center));
Button button = new Button(Styles.squaret);
buildButton(node.zone, button);
stack.add(button);
addChild(stack);
}
released(() -> moved = false);
}
void clamp(){
float pad = nodeSize;
float ox = width/2f, oy = height/2f;
float rx = bounds.x + panX + ox, ry = panY + oy + bounds.y;
float rw = bounds.width, rh = bounds.height;
rx = Mathf.clamp(rx, -rw + pad, Core.graphics.getWidth() - pad);
ry = Mathf.clamp(ry, pad, Core.graphics.getHeight() - rh - pad);
panX = rx - bounds.x - ox;
panY = ry - bounds.y - oy;
}
@Override
public void drawChildren(){
clamp();
float offsetX = panX + width / 2f, offsetY = panY + height / 2f;
for(ZoneNode node : nodes){
for(ZoneNode child : node.allChildren){
Lines.stroke(Scl.scl(4f), node.zone.locked() || child.zone.locked() ? Pal.gray : Pal.gray);
Draw.alpha(parentAlpha);
Lines.line(node.x + offsetX, node.y + offsetY, child.x + offsetX, child.y + offsetY);
}
}
Draw.reset();
super.drawChildren();
}
}
class ZoneNode extends TreeNode<ZoneNode>{
final Array<Zone> arr = new Array<>();
final Array<ZoneNode> allChildren = new Array<>();
final Zone zone;
ZoneNode(Zone zone, ZoneNode parent){
this.zone = zone;
this.parent = parent;
this.width = this.height = nodeSize;
//this.height /= 2f;
nodes.add(this);
arr.selectFrom(content.zones(), other -> other.requirements.size > 0 && other.requirements.first().zone() == zone);
children = new ZoneNode[arr.size];
for(int i = 0; i < children.length; i++){
children[i] = new ZoneNode(arr.get(i), this);
}
}
}
}

View File

@@ -0,0 +1,52 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.graphics.Color;
import arc.scene.ui.Dialog;
import mindustry.gen.*;
import mindustry.graphics.Pal;
import static mindustry.Vars.*;
public class DiscordDialog extends Dialog{
public DiscordDialog(){
super("");
float h = 70f;
cont.margin(12f);
Color color = Color.valueOf("7289da");
cont.table(t -> {
t.background(Tex.button).margin(0);
t.table(img -> {
img.addImage().height(h - 5).width(40f).color(color);
img.row();
img.addImage().height(5).width(40f).color(color.cpy().mul(0.8f, 0.8f, 0.8f, 1f));
}).expandY();
t.table(i -> {
i.background(Tex.button);
i.addImage(Icon.discord);
}).size(h).left();
t.add("$discord").color(Pal.accent).growX().padLeft(10f);
}).size(440f, h).pad(10f);
buttons.defaults().size(150f, 50);
buttons.addButton("$back", this::hide);
buttons.addButton("$copylink", () -> {
Core.app.setClipboardText(discordURL);
});
buttons.addButton("$openlink", () -> {
if(!Core.net.openURI(discordURL)){
ui.showErrorMessage("$linkfail");
Core.app.setClipboardText(discordURL);
}
});
}
}

View File

@@ -0,0 +1,309 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.files.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.pooling.*;
import mindustry.gen.*;
import mindustry.ui.*;
import java.util.*;
import static mindustry.Vars.platform;
public class FileChooser extends FloatingDialog{
private static final Fi homeDirectory = Core.files.absolute(Core.files.getExternalStoragePath());
private static Fi lastDirectory = homeDirectory;
private Table files;
private Fi directory = lastDirectory;
private ScrollPane pane;
private TextField navigation, filefield;
private TextButton ok;
private FileHistory stack = new FileHistory();
private Boolf<Fi> filter;
private Cons<Fi> selectListener;
private boolean open;
public FileChooser(String title, Boolf<Fi> filter, boolean open, Cons<Fi> result){
super(title);
setFillParent(true);
this.open = open;
this.filter = filter;
this.selectListener = result;
onResize(() -> {
cont.clear();
setupWidgets();
});
shown(() -> {
cont.clear();
setupWidgets();
});
}
private void setupWidgets(){
cont.margin(-10);
Table content = new Table();
filefield = new TextField();
filefield.setOnlyFontChars(false);
if(!open) platform.addDialog(filefield);
filefield.setDisabled(open);
ok = new TextButton(open ? "$load" : "$save");
ok.clicked(() -> {
if(ok.isDisabled()) return;
if(selectListener != null)
selectListener.get(directory.child(filefield.getText()));
hide();
});
filefield.changed(() -> {
ok.setDisabled(filefield.getText().replace(" ", "").isEmpty());
});
filefield.change();
TextButton cancel = new TextButton("$cancel");
cancel.clicked(this::hide);
navigation = new TextField("");
navigation.touchable(Touchable.disabled);
files = new Table();
files.marginRight(10);
files.marginLeft(3);
pane = new ScrollPane(files);
pane.setOverscroll(false, false);
pane.setFadeScrollBars(false);
updateFiles(true);
Table icontable = new Table();
ImageButton up = new ImageButton(Icon.folderParent);
up.clicked(() -> {
directory = directory.parent();
updateFiles(true);
});
ImageButton back = new ImageButton(Icon.arrowLeft);
ImageButton forward = new ImageButton(Icon.arrowRight);
forward.clicked(() -> stack.forward());
back.clicked(() -> stack.back());
forward.setDisabled(() -> !stack.canForward());
back.setDisabled(() -> !stack.canBack());
ImageButton home = new ImageButton(Icon.home);
home.clicked(() -> {
directory = homeDirectory;
lastDirectory = directory;
updateFiles(true);
});
icontable.defaults().height(60).growX().padTop(5).uniform();
icontable.add(home);
icontable.add(back);
icontable.add(forward);
icontable.add(up);
Table fieldcontent = new Table();
fieldcontent.bottom().left().add(new Label("$filename"));
fieldcontent.add(filefield).height(40f).fillX().expandX().padLeft(10f);
Table buttons = new Table();
buttons.defaults().growX().height(60);
buttons.add(cancel);
buttons.add(ok);
content.top().left();
content.add(icontable).expandX().fillX();
content.row();
content.center().add(pane).colspan(3).grow();
content.row();
if(!open){
content.bottom().left().add(fieldcontent).colspan(3).grow().padTop(-2).padBottom(2);
content.row();
}
content.add(buttons).growX();
cont.add(content).grow();
}
private void updateFileFieldStatus(){
if(!open){
ok.setDisabled(filefield.getText().replace(" ", "").isEmpty());
}else{
ok.setDisabled(!directory.child(filefield.getText()).exists() || directory.child(filefield.getText()).isDirectory());
}
}
private Fi[] getFileNames(){
Fi[] handles = directory.list(file -> !file.getName().startsWith("."));
Arrays.sort(handles, (a, b) -> {
if(a.isDirectory() && !b.isDirectory()) return -1;
if(!a.isDirectory() && b.isDirectory()) return 1;
return String.CASE_INSENSITIVE_ORDER.compare(a.name(), b.name());
});
return handles;
}
private void updateFiles(boolean push){
if(push) stack.push(directory);
navigation.setText(directory.toString());
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
layout.setText(Fonts.def, navigation.getText());
if(layout.width < navigation.getWidth()){
navigation.setCursorPosition(0);
}else{
navigation.setCursorPosition(navigation.getText().length());
}
Pools.free(layout);
files.clearChildren();
files.top().left();
Fi[] names = getFileNames();
Image upimage = new Image(Icon.folderParentSmall);
TextButton upbutton = new TextButton(".." + directory.toString(), Styles.clearTogglet);
upbutton.clicked(() -> {
directory = directory.parent();
lastDirectory = directory;
updateFiles(true);
});
upbutton.left().add(upimage).padRight(4f).padLeft(4);
upbutton.getLabel().setAlignment(Align.left);
upbutton.getCells().reverse();
files.add(upbutton).align(Align.topLeft).fillX().expandX().height(50).pad(2).colspan(2);
files.row();
ButtonGroup<TextButton> group = new ButtonGroup<>();
group.setMinCheckCount(0);
for(Fi file : names){
if(!file.isDirectory() && !filter.get(file)) continue; //skip non-filtered files
String filename = file.name();
TextButton button = new TextButton(filename, Styles.clearTogglet);
button.getLabel().setWrap(false);
button.getLabel().setEllipsis(true);
group.add(button);
button.clicked(() -> {
if(!file.isDirectory()){
filefield.setText(filename);
updateFileFieldStatus();
}else{
directory = directory.child(filename);
lastDirectory = directory;
updateFiles(true);
}
});
filefield.changed(() -> {
button.setChecked(filename.equals(filefield.getText()));
});
Image image = new Image(file.isDirectory() ? Icon.folderSmall : Icon.fileTextSmall);
button.add(image).padRight(4f).padLeft(4);
button.getCells().reverse();
files.top().left().add(button).align(Align.topLeft).fillX().expandX()
.height(50).pad(2).padTop(0).padBottom(0).colspan(2);
button.getLabel().setAlignment(Align.left);
files.row();
}
pane.setScrollY(0f);
updateFileFieldStatus();
if(open) filefield.clearText();
}
private String shorten(String string){
int max = 30;
if(string.length() <= max){
return string;
}else{
return string.substring(0, max - 3).concat("...");
}
}
public class FileHistory{
private Array<Fi> history = new Array<>();
private int index;
public FileHistory(){
}
public void push(Fi file){
if(index != history.size) history.truncate(index);
history.add(file);
index++;
}
public void back(){
if(!canBack()) return;
index--;
directory = history.get(index - 1);
lastDirectory = directory;
updateFiles(false);
}
public void forward(){
if(!canForward()) return;
directory = history.get(index);
lastDirectory = directory;
index++;
updateFiles(false);
}
public boolean canForward(){
return !(index >= history.size);
}
public boolean canBack(){
return !(index == 1) && index > 0;
}
void print(){
System.out.println("\n\n\n\n\n\n");
int i = 0;
for(Fi file : history){
i++;
if(index == i){
System.out.println("[[" + file.toString() + "]]");
}else{
System.out.println("--" + file.toString() + "--");
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.input.*;
import arc.scene.ui.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import static mindustry.Vars.*;
public class FloatingDialog extends Dialog{
private boolean wasPaused;
protected boolean shouldPause;
public FloatingDialog(String title, DialogStyle style){
super(title, style);
setFillParent(true);
this.title.setAlignment(Align.center);
titleTable.row();
titleTable.addImage(Tex.whiteui, Pal.accent)
.growX().height(3f).pad(4f);
hidden(() -> {
if(shouldPause && !state.is(State.menu)){
if(!wasPaused || net.active()){
state.set(State.playing);
}
}
Sounds.back.play();
});
shown(() -> {
if(shouldPause && !state.is(State.menu)){
wasPaused = state.is(State.paused);
state.set(State.paused);
}
});
}
public FloatingDialog(String title){
this(title, Core.scene.getStyle(DialogStyle.class));
}
protected void onResize(Runnable run){
Events.on(ResizeEvent.class, event -> {
if(isShown() && Core.scene.getDialog() == this){
run.run();
updateScrollFocus();
}
});
}
@Override
public void addCloseButton(){
buttons.defaults().size(210f, 64f);
buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f);
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK){
Core.app.post(this::hide);
}
});
}
}

View File

@@ -0,0 +1,102 @@
package mindustry.ui.dialogs;
import arc.*;
import mindustry.core.GameState.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.game.Stats.*;
import mindustry.type.*;
import mindustry.ui.Cicon;
import static mindustry.Vars.*;
public class GameOverDialog extends FloatingDialog{
private Team winner;
public GameOverDialog(){
super("$gameover");
setFillParent(true);
shown(this::rebuild);
}
public void show(Team winner){
this.winner = winner;
show();
if(winner == player.getTeam()){
Events.fire(new WinEvent());
}else{
Events.fire(new LoseEvent());
}
}
void rebuild(){
title.setText(state.launched ? "$launch.title" : "$gameover");
buttons.clear();
cont.clear();
buttons.margin(10);
if(state.rules.pvp){
cont.add(Core.bundle.format("gameover.pvp", winner.localized())).pad(6);
buttons.addButton("$menu", () -> {
hide();
state.set(State.menu);
logic.reset();
}).size(130f, 60f);
}else{
if(control.isHighScore()){
cont.add("$highscore").pad(6);
cont.row();
}
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();
if(world.isZone() && !state.stats.itemsDelivered.isEmpty()){
t.add("$stat.delivered");
t.row();
for(Item item : content.items()){
if(state.stats.itemsDelivered.get(item, 0) > 0){
t.table(items -> {
items.add(" [LIGHT_GRAY]" + state.stats.itemsDelivered.get(item, 0));
items.addImage(item.icon(Cicon.small)).size(8 * 3).pad(4);
}).left();
t.row();
}
}
}
if(world.isZone()){
RankResult result = state.stats.calculateRank(world.getZone(), state.launched);
t.add(Core.bundle.format("stat.rank", result.rank + result.modifier));
t.row();
}
}).pad(12);
if(world.isZone()){
buttons.addButton("$continue", () -> {
hide();
state.set(State.menu);
logic.reset();
ui.deploy.show();
}).size(130f, 60f);
}else{
buttons.addButton("$menu", () -> {
hide();
state.set(State.menu);
logic.reset();
}).size(130f, 60f);
}
}
}
}

View File

@@ -0,0 +1,96 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.graphics.*;
import arc.scene.ui.*;
import arc.util.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.ui.*;
import java.io.*;
import static mindustry.Vars.*;
public class HostDialog extends FloatingDialog{
float w = 300;
public HostDialog(){
super("$hostserver");
addCloseButton();
cont.table(t -> {
t.add("$name").padRight(10);
t.addField(Core.settings.getString("name"), text -> {
player.name = text;
Core.settings.put("name", text);
Core.settings.save();
ui.listfrag.rebuild();
}).grow().pad(8).get().setMaxLength(40);
ImageButton button = t.addImageButton(Tex.whiteui, Styles.clearFulli, 40, () -> {
new PaletteDialog().show(color -> {
player.color.set(color);
Core.settings.put("color-0", Color.rgba8888(color));
Core.settings.save();
});
}).size(54f).get();
button.update(() -> button.getStyle().imageUpColor = player.color);
}).width(w).height(70f).pad(4).colspan(3);
cont.row();
cont.add().width(65f);
cont.addButton("$host", () -> {
if(Core.settings.getString("name").trim().isEmpty()){
ui.showInfo("$noname");
return;
}
runHost();
}).width(w).height(70f);
cont.addButton("?", () -> ui.showInfo("$host.info")).size(65f, 70f).padLeft(6f);
shown(() -> {
if(!steam){
Core.app.post(() -> Core.settings.getBoolOnce("hostinfo", () -> ui.showInfo("$host.info")));
}
});
}
public void runHost(){
ui.loadfrag.show("$hosting");
Time.runTask(5f, () -> {
try{
net.host(Vars.port);
player.isAdmin = true;
if(steam){
Core.app.post(() -> Core.settings.getBoolOnce("steampublic2", () -> {
ui.showCustomConfirm("$setting.publichost.name", "$public.confirm", "$yes", "$no", () -> {
Core.settings.putSave("publichost", true);
platform.updateLobby();
}, () -> {
Core.settings.putSave("publichost", false);
platform.updateLobby();
});
}));
}
if(Version.modifier.contains("beta")){
Core.settings.putSave("publichost", false);
platform.updateLobby();
Core.settings.getBoolOnce("betapublic", () -> ui.showInfo("$public.beta"));
}
}catch(IOException e){
ui.showException("$server.error", e);
}
ui.loadfrag.hide();
hide();
});
}
}

View File

@@ -0,0 +1,417 @@
package mindustry.ui.dialogs;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.input.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.serialization.*;
import mindustry.*;
import mindustry.core.*;
import mindustry.gen.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class JoinDialog extends FloatingDialog{
Array<Server> servers = new Array<>();
Dialog add;
Server renaming;
Table local = new Table();
Table remote = new Table();
Table hosts = new Table();
int totalHosts;
public JoinDialog(){
super("$joingame");
loadServers();
if(!steam) buttons.add().width(60f);
buttons.add().growX().width(-1);
addCloseButton();
buttons.add().growX().width(-1);
if(!steam){
buttons.addButton("?", () -> ui.showInfo("$join.info")).size(60f, 64f).width(-1);
}
add = new FloatingDialog("$joingame.title");
add.cont.add("$joingame.ip").padRight(5f).left();
TextField field = add.cont.addField(Core.settings.getString("ip"), text -> {
Core.settings.put("ip", text);
Core.settings.save();
}).size(320f, 54f).get();
platform.addDialog(field, 100);
add.cont.row();
add.buttons.defaults().size(140f, 60f).pad(4f);
add.buttons.addButton("$cancel", add::hide);
add.buttons.addButton("$ok", () -> {
if(renaming == null){
Server server = new Server();
server.setIP(Core.settings.getString("ip"));
servers.add(server);
saveServers();
setupRemote();
refreshRemote();
}else{
renaming.setIP(Core.settings.getString("ip"));
saveServers();
setupRemote();
refreshRemote();
}
add.hide();
}).disabled(b -> Core.settings.getString("ip").isEmpty() || net.active());
add.shown(() -> {
add.title.setText(renaming != null ? "$server.edit" : "$server.add");
if(renaming != null){
field.setText(renaming.displayIP());
}
});
keyDown(KeyCode.F5, () -> {
refreshLocal();
refreshRemote();
});
shown(() -> {
setup();
refreshLocal();
refreshRemote();
if(!steam){
Core.app.post(() -> Core.settings.getBoolOnce("joininfo", () -> ui.showInfo("$join.info")));
}
});
onResize(this::setup);
}
void setupRemote(){
remote.clear();
for(Server server : servers){
//why are java lambdas this bad
TextButton[] buttons = {null};
TextButton button = buttons[0] = remote.addButton("[accent]" + server.displayIP(), Styles.cleart, () -> {
if(!buttons[0].childrenPressed()){
if(server.lastHost != null){
safeConnect(server.ip, server.port, server.lastHost.version);
}else{
connect(server.ip, server.port);
}
}
}).width(targetWidth()).pad(4f).get();
button.getLabel().setWrap(true);
Table inner = new Table();
button.clearChildren();
button.add(inner).growX();
inner.add(button.getLabel()).growX();
inner.addImageButton(Icon.arrowUpSmall, Styles.emptyi, () -> {
int index = servers.indexOf(server);
if(index > 0){
servers.remove(index);
servers.insert(0, server);
saveServers();
setupRemote();
for(Server other : servers){
if(other.lastHost != null){
setupServer(other, other.lastHost);
}else{
refreshServer(other);
}
}
}
}).margin(3f).padTop(6f).top().right();
inner.addImageButton(Icon.loadingSmall, Styles.emptyi, () -> {
refreshServer(server);
}).margin(3f).padTop(6f).top().right();
inner.addImageButton(Icon.pencilSmall, Styles.emptyi, () -> {
renaming = server;
add.show();
}).margin(3f).padTop(6f).top().right();
inner.addImageButton(Icon.trash16Small, Styles.emptyi, () -> {
ui.showConfirm("$confirm", "$server.delete", () -> {
servers.removeValue(server, true);
saveServers();
setupRemote();
refreshRemote();
});
}).margin(3f).pad(6).top().right();
button.row();
server.content = button.table(t -> {}).grow().get();
remote.row();
}
}
void refreshRemote(){
for(Server server : servers){
refreshServer(server);
}
}
void refreshServer(Server server){
server.content.clear();
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();
server.content.add("$host.invalid");
});
}
void setupServer(Server server, Host host){
server.lastHost = host;
server.content.clear();
buildServer(host, server.content);
}
void buildServer(Host host, Table content){
String versionString;
if(host.version == -1){
versionString = Core.bundle.format("server.version", Core.bundle.get("server.custombuild"), "");
}else if(host.version == 0){
versionString = Core.bundle.get("server.outdated");
}else if(host.version < Version.build && Version.build != -1){
versionString = Core.bundle.get("server.outdated") + "\n" +
Core.bundle.format("server.version", host.version, "");
}else if(host.version > Version.build && Version.build != -1){
versionString = Core.bundle.get("server.outdated.client") + "\n" +
Core.bundle.format("server.version", host.version, "");
}else if(host.version == Version.build && Version.type.equals(host.versionType)){
//not important
versionString = "";
}else{
versionString = Core.bundle.format("server.version", host.version, host.versionType);
}
content.table(t -> {
t.add("[lightgray]" + host.name + " " + versionString).width(targetWidth() - 10f).left().get().setEllipsis(true);
t.row();
t.add("[lightgray]" + (Core.bundle.format("players" + (host.players == 1 && host.playerLimit <= 0 ? ".single" : ""), (host.players == 0 ? "[lightgray]" : "[accent]") + host.players + (host.playerLimit > 0 ? "[lightgray]/[accent]" + host.playerLimit : "")+ "[lightgray]"))).left();
t.row();
t.add("[lightgray]" + Core.bundle.format("save.map", host.mapname) + "[lightgray] / " + host.mode.toString()).width(targetWidth() - 10f).left().get().setEllipsis(true);
}).expand().left().bottom().padLeft(12f).padBottom(8);
}
void setup(){
float w = targetWidth();
hosts.clear();
hosts.add(remote).growX();
hosts.row();
hosts.add(local).width(w);
ScrollPane pane = new ScrollPane(hosts);
pane.setFadeScrollBars(false);
pane.setScrollingDisabled(true, false);
setupRemote();
refreshRemote();
cont.clear();
cont.table(t -> {
t.add("$name").padRight(10);
if(!steam){
t.addField(Core.settings.getString("name"), text -> {
player.name = text;
Core.settings.put("name", text);
Core.settings.save();
}).grow().pad(8).get().setMaxLength(maxNameLength);
}else{
t.add(player.name).update(l -> l.setColor(player.color)).grow().pad(8);
}
ImageButton button = t.addImageButton(Tex.whiteui, Styles.clearFulli, 40, () -> {
new PaletteDialog().show(color -> {
player.color.set(color);
Core.settings.put("color-0", Color.rgba8888(color));
Core.settings.save();
});
}).size(54f).get();
button.update(() -> button.getStyle().imageUpColor = player.color);
}).width(w).height(70f).pad(4);
cont.row();
cont.add(pane).width(w + 38).pad(0);
cont.row();
cont.addCenteredImageTextButton("$server.add", Icon.add, () -> {
renaming = null;
add.show();
}).marginLeft(6).width(w).height(80f).update(button -> {
float pw = w;
float pad = 0f;
if(pane.getChildren().first().getPrefHeight() > pane.getHeight()){
pw = w + 30;
pad = 6;
}
Cell cell = ((Table)pane.getParent()).getCell(button);
if(!Mathf.equal(cell.minWidth(), pw)){
cell.width(pw);
cell.padLeft(pad);
pane.getParent().invalidateHierarchy();
}
});
}
void refreshLocal(){
totalHosts = 0;
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();
net.discoverServers(this::addLocalHost, this::finishLocalHosts);
for(String host : defaultServers){
net.pingHost(host, port, this::addLocalHost, e -> {});
}
}
void finishLocalHosts(){
if(totalHosts == 0){
local.clear();
local.background(Tex.button);
local.add("$hosts.none").pad(10f);
local.add().growX();
local.addImageButton(Icon.loading, this::refreshLocal).pad(-12f).padLeft(0).size(70f);
}else{
local.background(null);
}
}
void addLocalHost(Host host){
if(totalHosts == 0){
local.clear();
}
local.background(null);
totalHosts++;
float w = targetWidth();
local.row();
TextButton button = local.addButton("", Styles.cleart, () -> safeConnect(host.address, port, host.version))
.width(w).pad(5f).get();
button.clearChildren();
buildServer(host, button);
}
public void connect(String ip, int port){
if(player.name.trim().isEmpty()){
ui.showInfo("$noname");
return;
}
ui.loadfrag.show("$connecting");
ui.loadfrag.setButton(() -> {
ui.loadfrag.hide();
netClient.disconnectQuietly();
});
Time.runTask(2f, () -> {
logic.reset();
net.reset();
Vars.netClient.beginConnecting();
net.connect(ip, port, () -> {
hide();
add.hide();
});
});
}
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[]" +
Core.bundle.format("server.versions", Version.build, version));
}else{
connect(ip, port);
}
}
float targetWidth(){
return Core.graphics.isPortrait() ? 350f : 500f;
}
@SuppressWarnings("unchecked")
private void loadServers(){
servers = Core.settings.getObject("server-list", Array.class, Array::new);
//get servers
Core.net.httpGet(serverJsonURL, result -> {
try{
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 {0} global servers.", defaultServers.size);
}catch(Throwable ignored){}
});
}catch(Throwable ignored){}
}, t -> {});
}
private void saveServers(){
Core.settings.putObject("server-list", servers);
Core.settings.save();
}
@Serialize
public static class Server{
public String ip;
public int port;
transient Table content;
transient Host lastHost;
void setIP(String ip){
//parse ip:port, if unsuccessful, use default values
if(ip.lastIndexOf(':') != -1 && ip.lastIndexOf(':') != ip.length() - 1){
try{
int idx = ip.lastIndexOf(':');
this.ip = ip.substring(0, idx);
this.port = Integer.parseInt(ip.substring(idx + 1));
}catch(Exception e){
this.ip = ip;
this.port = Vars.port;
}
}else{
this.ip = ip;
this.port = Vars.port;
}
}
String displayIP(){
return ip + (port != Vars.port ? ":" + port : "");
}
public Server(){
}
}
}

View File

@@ -0,0 +1,90 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.struct.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.Table;
import arc.util.Log;
import arc.util.Strings;
import mindustry.ui.*;
import java.util.Locale;
import static mindustry.Vars.locales;
import static mindustry.Vars.ui;
public class LanguageDialog extends FloatingDialog{
private Locale lastLocale;
private ObjectMap<Locale, String> displayNames = ObjectMap.of(
Locale.TRADITIONAL_CHINESE, "正體中文",
Locale.SIMPLIFIED_CHINESE, "简体中文"
);
public LanguageDialog(){
super("$settings.language");
addCloseButton();
setup();
}
private void setup(){
Table langs = new Table();
langs.marginRight(24f).marginLeft(24f);
ScrollPane pane = new ScrollPane(langs);
pane.setFadeScrollBars(false);
ButtonGroup<TextButton> group = new ButtonGroup<>();
for(Locale loc : locales){
TextButton button = new TextButton(Strings.capitalize(displayNames.get(loc, loc.getDisplayName(loc))), Styles.clearTogglet);
button.clicked(() -> {
if(getLocale().equals(loc)) return;
Core.settings.put("locale", loc.toString());
Core.settings.save();
Log.info("Setting locale: {0}", loc.toString());
ui.showInfo("$language.restart");
});
langs.add(button).group(group).update(t -> t.setChecked(loc.equals(getLocale()))).size(400f, 50f).row();
}
cont.add(pane);
}
public Locale getLocale(){
String loc = Core.settings.getString("locale");
if(loc.equals("default")){
findClosestLocale();
}
if(lastLocale == null || !lastLocale.toString().equals(loc)){
if(loc.contains("_")){
String[] split = loc.split("_");
lastLocale = new Locale(split[0], split[1]);
}else{
lastLocale = new Locale(loc);
}
}
return lastLocale;
}
void findClosestLocale(){
//check exact locale
for(Locale l : locales){
if(l.equals(Locale.getDefault())){
Core.settings.put("locale", l.toString());
return;
}
}
//find by language
for(Locale l : locales){
if(l.getLanguage().equals(Locale.getDefault().getLanguage())){
Core.settings.put("locale", l.toString());
return;
}
}
Core.settings.put("locale", new Locale("en").toString());
}
}

View File

@@ -0,0 +1,216 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.files.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.game.Saves.*;
import mindustry.gen.*;
import mindustry.io.*;
import mindustry.io.SaveIO.*;
import mindustry.ui.*;
import mindustry.ui.Styles;
import java.io.*;
import static mindustry.Vars.*;
public class LoadDialog extends FloatingDialog{
ScrollPane pane;
Table slots;
public LoadDialog(){
this("$loadgame");
}
public LoadDialog(String title){
super(title);
setup();
shown(() -> {
setup();
Time.runTask(2f, () -> Core.scene.setScrollFocus(pane));
});
addCloseButton();
}
protected void setup(){
cont.clear();
slots = new Table();
pane = new ScrollPane(slots);
pane.setFadeScrollBars(false);
pane.setScrollingDisabled(true, false);
slots.marginRight(24);
Time.runTask(2f, () -> Core.scene.setScrollFocus(pane));
Array<SaveSlot> array = control.saves.getSaveSlots();
array.sort((slot, other) -> -Long.compare(slot.getTimestamp(), other.getTimestamp()));
for(SaveSlot slot : array){
if(slot.isHidden()) continue;
TextButton button = new TextButton("", Styles.cleart);
button.getLabel().remove();
button.clearChildren();
button.defaults().left();
button.table(title -> {
title.add("[accent]" + slot.getName()).left().growX().width(230f).wrap();
title.table(t -> {
t.right();
t.addImageButton(Icon.floppy, Styles.emptytogglei, () -> {
slot.setAutosave(!slot.isAutosave());
}).checked(slot.isAutosave()).right();
t.addImageButton(Icon.trash, Styles.emptyi, () -> {
ui.showConfirm("$confirm", "$save.delete.confirm", () -> {
slot.delete();
setup();
});
}).right();
t.addImageButton(Icon.pencil, Styles.emptyi, () -> {
ui.showTextInput("$save.rename", "$save.rename.text", slot.getName(), text -> {
slot.setName(text);
setup();
});
}).right();
t.addImageButton(Icon.save, Styles.emptyi, () -> {
if(!ios){
platform.showFileChooser(false, saveExtension, file -> {
try{
slot.exportFile(file);
setup();
}catch(IOException e){
ui.showException("save.export.fail", e);
}
});
}else{
try{
Fi file = Core.files.local("save-" + slot.getName() + "." + saveExtension);
slot.exportFile(file);
platform.shareFile(file);
}catch(Exception e){
ui.showException("save.export.fail", e);
}
}
}).right();
}).padRight(-10).growX();
}).growX().colspan(2);
button.row();
String color = "[lightgray]";
TextureRegion def = Core.atlas.find("nomap");
button.left().add(new BorderImage(def, 4f)).update(i -> {
TextureRegionDrawable draw = (TextureRegionDrawable)i.getDrawable();
if(draw.getRegion().getTexture().isDisposed()){
draw.setRegion(def);
}
Texture text = slot.previewTexture();
if(draw.getRegion() == def && text != null){
draw.setRegion(new TextureRegion(text));
}
i.setScaling(Scaling.fit);
}).left().size(160f).padRight(6);
button.table(meta -> {
meta.left().top();
meta.defaults().padBottom(-2).left().width(290f);
meta.row();
meta.labelWrap(Core.bundle.format("save.map", color + (slot.getMap() == null ? Core.bundle.get("unknown") : slot.getMap().name())));
meta.row();
meta.labelWrap(slot.mode().toString() + " /" + color + " " + Core.bundle.format("save.wave", color + slot.getWave()));
meta.row();
meta.labelWrap(() -> Core.bundle.format("save.autosave", color + Core.bundle.get(slot.isAutosave() ? "on" : "off")));
meta.row();
meta.labelWrap(() -> Core.bundle.format("save.playtime", color + slot.getPlayTime()));
meta.row();
meta.labelWrap(color + slot.getDate());
meta.row();
}).left().growX().width(250f);
modifyButton(button, slot);
slots.add(button).uniformX().fillX().pad(4).padRight(-4).margin(10f).row();
}
cont.add(pane);
addSetup();
}
public void addSetup(){
boolean valids = false;
for(SaveSlot slot : control.saves.getSaveSlots()) if(!slot.isHidden()) valids = true;
if(!valids){
slots.row();
slots.addButton("$save.none", () -> {
}).disabled(true).fillX().margin(20f).minWidth(340f).height(80f).pad(4f);
}
slots.row();
slots.addImageTextButton("$save.import", Icon.add, () -> {
platform.showFileChooser(true, saveExtension, file -> {
if(SaveIO.isSaveValid(file)){
try{
control.saves.importSave(file);
setup();
}catch(IOException e){
e.printStackTrace();
ui.showException("$save.import.fail", e);
}
}else{
ui.showErrorMessage("$save.import.invalid");
}
});
}).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4);
}
public void runLoadSave(SaveSlot slot){
slot.cautiousLoad(() -> {
ui.loadAnd(() -> {
hide();
ui.paused.hide();
try{
net.reset();
slot.load();
state.rules.editor = false;
state.rules.zone = null;
state.set(State.playing);
}catch(SaveException e){
Log.err(e);
state.set(State.menu);
logic.reset();
ui.showErrorMessage("$save.corrupted");
}
});
});
}
public void modifyButton(TextButton button, SaveSlot slot){
button.clicked(() -> {
if(!button.childrenPressed()){
runLoadSave(slot);
}
});
}
}

View File

@@ -0,0 +1,127 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.input.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import static mindustry.Vars.*;
public class LoadoutDialog extends FloatingDialog{
private Runnable hider;
private Runnable resetter;
private Runnable updater;
private Array<ItemStack> stacks = new Array<>();
private Array<ItemStack> originalStacks = new Array<>();
private Table items;
private int capacity;
public LoadoutDialog(){
super("$configure");
setFillParent(true);
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK){
Core.app.post(this::hide);
}
});
cont.pane(t -> items = t.margin(10f)).left();
shown(this::setup);
hidden(() -> {
originalStacks.selectFrom(stacks, s -> s.amount > 0);
updater.run();
if(hider != null){
hider.run();
}
});
buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f);
buttons.addImageTextButton("$settings.reset", Icon.refreshSmall, () -> {
resetter.run();
reseed();
updater.run();
setup();
}).size(210f, 64f);
}
public void show(int capacity, Array<ItemStack> stacks, Runnable reseter, Runnable updater, Runnable hider){
this.originalStacks = stacks;
reseed();
this.resetter = reseter;
this.updater = updater;
this.capacity = capacity;
this.hider = hider;
//this.filter = filter;
show();
}
void setup(){
items.clearChildren();
items.left();
float bsize = 40f;
int i = 0;
for(ItemStack stack : stacks){
items.table(Tex.pane, t -> {
t.margin(4).marginRight(8).left();
t.addButton("-", Styles.cleart, () -> {
stack.amount = Math.max(stack.amount - step(stack.amount), 0);
updater.run();
}).size(bsize);
t.addButton("+", Styles.cleart, () -> {
stack.amount = Math.min(stack.amount + step(stack.amount), capacity);
updater.run();
}).size(bsize);
t.addImageButton(Icon.pencilSmaller, Styles.cleari, () -> ui.showTextInput("$configure", stack.item.localizedName, 10, stack.amount + "", true, str -> {
if(Strings.canParsePostiveInt(str)){
int amount = Strings.parseInt(str);
if(amount >= 0 && amount <= capacity){
stack.amount = amount;
updater.run();
return;
}
}
ui.showInfo(Core.bundle.format("configure.invalid", capacity));
})).size(bsize);
t.addImage(stack.item.icon(Cicon.small)).size(8 * 3).padRight(4).padLeft(4);
t.label(() -> stack.amount + "").left().width(90f);
}).pad(2).left().fillX();
if(++i % 2 == 0 || (mobile && Core.graphics.isPortrait())){
items.row();
}
}
}
private void reseed(){
this.stacks = originalStacks.map(ItemStack::copy);
this.stacks.addAll(content.items().select(i -> i.type == ItemType.material &&
!stacks.contains(stack -> stack.item == i)).map(i -> new ItemStack(i, 0)));
this.stacks.sort(Structs.comparingInt(s -> s.item.id));
}
private int step(int amount){
if(amount < 1000){
return 100;
}else if(amount < 2000){
return 200;
}else if(amount < 5000){
return 500;
}else{
return 1000;
}
}
}

View File

@@ -0,0 +1,110 @@
package mindustry.ui.dialogs;
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.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class MapPlayDialog extends FloatingDialog{
CustomRulesDialog dialog = new CustomRulesDialog();
Rules rules;
@NonNull
Gamemode selectedGamemode = Gamemode.survival;
Map lastMap;
public MapPlayDialog(){
super("");
setFillParent(false);
onResize(() -> {
if(lastMap != null){
Rules rules = this.rules;
show(lastMap);
this.rules = rules;
}
});
}
public void show(Map map){
this.lastMap = map;
title.setText(map.name());
cont.clearChildren();
//reset to any valid mode after switching to attack (one must exist)
if(!selectedGamemode.valid(map)){
selectedGamemode = Structs.find(Gamemode.all, m -> m.valid(map));
if(selectedGamemode == null){
selectedGamemode = Gamemode.survival;
}
}
rules = map.applyRules(selectedGamemode);
Table selmode = new Table();
selmode.add("$level.mode").colspan(4);
selmode.row();
int i = 0;
Table modes = new Table();
for(Gamemode mode : Gamemode.values()){
if(mode.hidden) continue;
modes.addButton(mode.toString(), Styles.togglet, () -> {
selectedGamemode = mode;
rules = map.applyRules(mode);
}).update(b -> b.setChecked(selectedGamemode == mode)).size(140f, 54f).disabled(!mode.valid(map));
if(i++ % 2 == 1) modes.row();
}
selmode.add(modes);
selmode.addButton("?", this::displayGameModeHelp).width(50f).fillY().padLeft(18f);
cont.add(selmode);
cont.row();
cont.addImageTextButton("$customize", Icon.toolsSmall, () -> dialog.show(rules, () -> rules = map.applyRules(selectedGamemode))).width(230);
cont.row();
cont.add(new BorderImage(map.safeTexture(), 3f)).size(mobile && !Core.graphics.isPortrait() ? 150f : 250f).get().setScaling(Scaling.fit);
//only maps with survival are valid for high scores
if(Gamemode.survival.valid(map)){
cont.row();
cont.label((() -> Core.bundle.format("level.highscore", map.getHightScore()))).pad(3f);
}
buttons.clearChildren();
addCloseButton();
buttons.addImageTextButton("$play", Icon.play, () -> {
control.playMap(map, rules);
hide();
ui.custom.hide();
}).size(210f, 64f);
show();
}
private void displayGameModeHelp(){
FloatingDialog d = new FloatingDialog(Core.bundle.get("mode.help.title"));
d.setFillParent(false);
Table table = new Table();
table.defaults().pad(1f);
ScrollPane pane = new ScrollPane(table);
pane.setFadeScrollBars(false);
table.row();
for(Gamemode mode : Gamemode.values()){
if(mode.hidden) continue;
table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f);
table.row();
}
d.cont.add(pane);
d.buttons.addButton("$ok", d::hide).size(110, 50).pad(10f);
d.show();
}
}

View File

@@ -0,0 +1,220 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.graphics.*;
import arc.input.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class MapsDialog extends FloatingDialog{
private FloatingDialog dialog;
public MapsDialog(){
super("$maps");
buttons.remove();
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK){
Core.app.post(this::hide);
}
});
shown(this::setup);
onResize(() -> {
if(dialog != null){
dialog.hide();
}
setup();
});
}
void setup(){
buttons.clearChildren();
if(Core.graphics.isPortrait()){
buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f*2f, 64f).colspan(2);
buttons.row();
}else{
buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f);
}
buttons.addImageTextButton("$editor.newmap", Icon.add, () -> {
ui.showTextInput("$editor.newmap", "$name", "", text -> {
Runnable show = () -> ui.loadAnd(() -> {
hide();
ui.editor.show();
ui.editor.editor.getTags().put("name", text);
Events.fire(new MapMakeEvent());
});
if(maps.byName(text) != null){
ui.showErrorMessage("$editor.exists");
}else{
show.run();
}
});
}).size(210f, 64f);
buttons.addImageTextButton("$editor.importmap", Icon.load, () -> {
platform.showFileChooser(true, mapExtension, file -> {
ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showErrorMessage("$editor.errorimage");
return;
}
Map map = MapIO.createMap(file, true);
//when you attempt to import a save, it will have no name, so generate one
String name = map.tags.getOr("name", () -> {
String result = "unknown";
int number = 0;
while(maps.byName(result + number++) != null);
return result + number;
});
//this will never actually get called, but it remains just in case
if(name == null){
ui.showErrorMessage("$editor.errorname");
return;
}
Map conflict = maps.all().find(m -> m.name().equals(name));
if(conflict != null && !conflict.custom){
ui.showInfo(Core.bundle.format("editor.import.exists", name));
}else if(conflict != null){
ui.showConfirm("$confirm", "$editor.overwrite.confirm", () -> {
maps.tryCatchMapError(() -> {
maps.removeMap(conflict);
maps.importMap(map.file);
setup();
});
});
}else{
maps.importMap(map.file);
setup();
}
});
});
});
}).size(210f, 64f);
cont.clear();
Table maps = new Table();
maps.marginRight(24);
ScrollPane pane = new ScrollPane(maps);
pane.setFadeScrollBars(false);
int maxwidth = Mathf.clamp((int)(Core.graphics.getWidth() / Scl.scl(230)), 1, 8);
float mapsize = 200f;
int i = 0;
for(Map map : Vars.maps.all()){
if(i % maxwidth == 0){
maps.row();
}
TextButton button = maps.addButton("", Styles.cleart, () -> showMapInfo(map)).width(mapsize).pad(8).get();
button.clearChildren();
button.margin(9);
button.add(map.name()).width(mapsize - 18f).center().get().setEllipsis(true);
button.row();
button.addImage().growX().pad(4).color(Pal.gray);
button.row();
button.stack(new Image(map.safeTexture()).setScaling(Scaling.fit), new BorderImage(map.safeTexture()).setScaling(Scaling.fit)).size(mapsize - 20f);
button.row();
button.add(map.custom ? "$custom" : map.workshop ? "$workshop" : map.mod != null ? "[lightgray]" + map.mod.meta.displayName() : "$builtin").color(Color.gray).padTop(3);
i++;
}
if(Vars.maps.all().size == 0){
maps.add("$maps.none");
}
cont.add(buttons).growX();
cont.row();
cont.add(pane).uniformX();
}
void showMapInfo(Map map){
dialog = new FloatingDialog("$editor.mapinfo");
dialog.addCloseButton();
float mapsize = Core.graphics.isPortrait() ? 160f : 300f;
Table table = dialog.cont;
table.stack(new Image(map.safeTexture()).setScaling(Scaling.fit), new BorderImage(map.safeTexture()).setScaling(Scaling.fit)).size(mapsize);
table.table(Styles.black, desc -> {
desc.top();
Table t = new Table();
t.margin(6);
ScrollPane pane = new ScrollPane(t);
desc.add(pane).grow();
t.top();
t.defaults().padTop(10).left();
t.add("$editor.name").padRight(10).color(Color.gray).padTop(0);
t.row();
t.add(map.name()).growX().wrap().padTop(2);
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.row();
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();
table.addImageTextButton("$editor.openin", Icon.loadMapSmall, () -> {
try{
Vars.ui.editor.beginEditMap(map.file);
dialog.hide();
hide();
}catch(Exception e){
e.printStackTrace();
ui.showErrorMessage("$error.mapnotfound");
}
}).fillX().height(54f).marginLeft(10);
table.addImageTextButton(map.workshop && steam ? "$view.workshop" : "$delete", map.workshop && steam ? Icon.linkSmall : Icon.trash16Small, () -> {
if(map.workshop && steam){
platform.viewListing(map);
}else{
ui.showConfirm("$confirm", Core.bundle.format("map.delete", map.name()), () -> {
maps.removeMap(map);
dialog.hide();
setup();
});
}
}).fillX().height(54f).marginLeft(10).disabled(!map.workshop && !map.custom);
dialog.show();
}
}

View File

@@ -0,0 +1,70 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
import static mindustry.Vars.renderer;
public class MinimapDialog extends FloatingDialog{
public MinimapDialog(){
super("$minimap");
setFillParent(true);
shown(this::setup);
addCloseButton();
shouldPause = true;
titleTable.remove();
onResize(this::setup);
}
void setup(){
cont.clear();
cont.table(Tex.pane,t -> {
t.addRect((x, y, width, height) -> {
if(renderer.minimap.getRegion() == null) return;
Draw.color(Color.white);
Draw.alpha(parentAlpha);
Draw.rect(renderer.minimap.getRegion(), x + width / 2f, y + height / 2f, width, height);
if(renderer.minimap.getTexture() != null){
renderer.minimap.drawEntities(x, y, width, height);
}
}).grow();
}).size(Math.min(Core.graphics.getWidth() / 1.1f, Core.graphics.getHeight() / 1.3f) / Scl.scl(1f)).padTop(-20f);
cont.addListener(new InputListener(){
@Override
public boolean scrolled(InputEvent event, float x, float y, float amountx, float amounty){
renderer.minimap.zoomBy(amounty);
return true;
}
});
cont.addListener(new ElementGestureListener(){
float lzoom = -1f;
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
lzoom = renderer.minimap.getZoom();
}
@Override
public void zoom(InputEvent event, float initialDistance, float distance){
if(lzoom < 0){
lzoom = renderer.minimap.getZoom();
}
renderer.minimap.setZoom(initialDistance / distance * lzoom);
}
});
Core.app.post(() -> Core.scene.setScrollFocus(cont));
}
}

View File

@@ -0,0 +1,195 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.Net.*;
import arc.files.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.mod.Mods.*;
import mindustry.ui.*;
import java.io.*;
import static mindustry.Vars.*;
public class ModsDialog extends FloatingDialog{
public ModsDialog(){
super("$mods");
addCloseButton();
buttons.addImageTextButton(mobile ? "$mods.report" : "$mods.openfolder", Icon.link,
() -> {
if(mobile){
Core.net.openURI(reportIssueURL);
}else{
Core.net.openFolder(modDirectory.absolutePath());
}
})
.size(250f, 64f);
buttons.row();
buttons.addImageTextButton("$mods.guide", Icon.wiki,
() -> Core.net.openURI(modGuideURL))
.size(210, 64f);
buttons.addImageTextButton("$mod.import.github", Icon.github, () -> {
ui.showTextInput("$mod.import.github", "", 64, "Anuken/ExampleMod", text -> {
ui.loadfrag.show();
Core.net.httpGet("http://api.github.com/repos/" + text + "/zipball/master", loc -> {
Core.net.httpGet(loc.getHeader("Location"), result -> {
if(result.getStatus() != HttpStatus.OK){
ui.showErrorMessage(Core.bundle.format("connectfail", result.getStatus()));
ui.loadfrag.hide();
}else{
try{
Fi file = tmpDirectory.child(text.replace("/", "") + ".zip");
Streams.copyStream(result.getResultAsStream(), file.write(false));
mods.importMod(file);
file.delete();
Core.app.post(() -> {
try{
mods.reloadContent();
setup();
ui.loadfrag.hide();
}catch(Throwable e){
ui.showException(e);
}
});
}catch(Throwable e){
modError(e);
}
}
}, t -> Core.app.post(() -> modError(t)));
}, t -> Core.app.post(() -> modError(t)));
});
}).size(250f, 64f);
shown(this::setup);
hidden(() -> {
if(mods.requiresReload()){
ui.loadAnd("$reloading", () -> {
mods.eachEnabled(mod -> {
if(mod.hasUnmetDependencies()){
ui.showErrorMessage(Core.bundle.format("mod.nowdisabled", mod.name, mod.missingDependencies.toString(", ")));
}
});
mods.reloadContent();
});
}
});
shown(() -> Core.app.post(() -> {
Core.settings.getBoolOnce("modsalpha", () -> {
ui.showText("$mods", "$mods.alphainfo");
});
}));
}
void modError(Throwable error){
ui.loadfrag.hide();
if(Strings.getCauses(error).contains(t -> t.getMessage() != null && (t.getMessage().contains("SSL") || t.getMessage().contains("protocol")))){
ui.showErrorMessage("$feature.unsupported");
}else{
ui.showException(error);
}
}
void setup(){
cont.clear();
cont.defaults().width(mobile ? 500 : 560f).pad(4);
cont.add("$mod.reloadrequired").visible(mods::requiresReload).center().get().setAlignment(Align.center);
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;
table.row();
table.addImage().growX().height(4f).pad(6f).color(Pal.gray);
table.row();
}
table.table(Styles.black6, t -> {
t.defaults().pad(2).left().top();
t.margin(14f).left();
t.table(title -> {
title.left();
title.add("[accent]" + mod.meta.displayName() + "[lightgray] v" + mod.meta.version + (mod.enabled() ? "" : "\n" + Core.bundle.get("mod.disabled") + "")).width(200f).wrap();
title.add().growX();
title.addImageTextButton(mod.enabled() ? "$mod.disable" : "$mod.enable", mod.enabled() ? Icon.arrowDownSmall : Icon.arrowUpSmall, Styles.cleart, () -> {
mods.setEnabled(mod, !mod.enabled());
setup();
}).height(50f).margin(8f).width(130f).disabled(!mod.isSupported());
if(steam && !mod.hasSteamID()){
title.addImageButton(Icon.loadMapSmall, Styles.cleari, () -> {
platform.publish(mod);
}).size(50f);
}
title.addImageButton(mod.hasSteamID() ? Icon.linkSmall : Icon.trash16Small, Styles.cleari, () -> {
if(!mod.hasSteamID()){
ui.showConfirm("$confirm", "$mod.remove.confirm", () -> {
mods.removeMod(mod);
setup();
});
}else{
platform.viewListing(mod);
}
}).size(50f);
}).growX().left().padTop(-14f).padRight(-14f);
t.row();
if(mod.meta.author != null){
t.add(Core.bundle.format("mod.author", mod.meta.author));
t.row();
}
if(mod.meta.description != null){
t.labelWrap("[lightgray]" + mod.meta.description).growX();
t.row();
}
if(!mod.isSupported()){
t.labelWrap(Core.bundle.format("mod.requiresversion", mod.meta.minGameVersion)).growX();
t.row();
}else if(mod.hasUnmetDependencies()){
t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row();
}else if(mod.hasContentErrors()){
t.labelWrap("$mod.erroredcontent").growX();
t.row();
}
}).width(mobile ? 430f : 500f);
table.row();
}
});
}else{
cont.table(Styles.black6, t -> t.add("$mods.none")).height(80f);
}
cont.row();
cont.addImageTextButton("$mod.import", Icon.add, () -> {
platform.showFileChooser(true, "zip", file -> {
try{
mods.importMod(file);
setup();
}catch(IOException e){
ui.showException(e);
e.printStackTrace();
}
});
}).margin(12f).width(400f);
}
}

View File

@@ -0,0 +1,51 @@
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.*;
import static mindustry.Vars.*;
public class PaletteDialog extends Dialog{
private Cons<Color> cons;
public PaletteDialog(){
super("");
build();
}
private void build(){
Table table = new Table();
cont.add(table);
for(int i = 0; i < playerColors.length; i++){
Color color = playerColors[i];
ImageButton button = table.addImageButton(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();
}
}
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK)
hide();
});
}
public void show(Cons<Color> cons){
this.cons = cons;
show();
}
}

View File

@@ -0,0 +1,134 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.input.*;
import mindustry.core.GameState.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
public class PausedDialog extends FloatingDialog{
private SaveDialog save = new SaveDialog();
private LoadDialog load = new LoadDialog();
private boolean wasClient = false;
public PausedDialog(){
super("$menu");
shouldPause = true;
shown(this::rebuild);
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK){
hide();
}
});
}
void rebuild(){
cont.clear();
update(() -> {
if(state.is(State.menu) && isShown()){
hide();
}
});
if(!mobile){
float dw = 210f;
cont.defaults().width(dw).height(50).pad(5f);
cont.addButton("$back", this::hide).colspan(2).width(dw * 2 + 20f);
cont.row();
if(world.isZone()){
cont.addButton("$techtree", ui.tech::show);
}else{
cont.addButton("$database", ui.database::show);
}
cont.addButton("$settings", ui.settings::show);
if(!state.rules.tutorial){
if(!world.isZone() && !state.isEditor()){
cont.row();
cont.addButton("$savegame", save::show);
cont.addButton("$loadgame", load::show).disabled(b -> net.active());
}
cont.row();
cont.addButton("$hostserver", () -> {
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.addButton("$quit", this::showQuitConfirm).colspan(2).width(dw + 10f).update(s -> s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "$save.quit" : "$quit"));
}else{
cont.defaults().size(130f).pad(5);
cont.addRowImageTextButton("$back", Icon.play2, this::hide);
cont.addRowImageTextButton("$settings", Icon.tools, ui.settings::show);
if(!world.isZone() && !state.isEditor()){
cont.addRowImageTextButton("$save", Icon.save, save::show);
cont.row();
cont.addRowImageTextButton("$load", Icon.load, load::show).disabled(b -> net.active());
}else{
cont.row();
}
cont.addRowImageTextButton("$hostserver.mobile", Icon.host, ui.host::show).disabled(b -> net.active());
cont.addRowImageTextButton("$quit", Icon.quit, this::showQuitConfirm).update(s -> s.setText(control.saves.getCurrent() != null && control.saves.getCurrent().isAutosave() ? "$save.quit" : "$quit"));
}
}
void showQuitConfirm(){
ui.showConfirm("$confirm", state.rules.tutorial ? "$quit.confirm.tutorial" : "$quit.confirm", () -> {
if(state.rules.tutorial){
Core.settings.put("playedtutorial", true);
Core.settings.save();
}
wasClient = net.client();
if(net.client()) netClient.disconnectQuietly();
runExitSave();
hide();
});
}
public void runExitSave(){
if(state.isEditor() && !wasClient){
ui.editor.resumeEditing();
return;
}
if(control.saves.getCurrent() == null || !control.saves.getCurrent().isAutosave() || state.rules.tutorial || wasClient){
state.set(State.menu);
logic.reset();
return;
}
ui.loadAnd("$saveload", () -> {
try{
control.saves.getCurrent().save();
}catch(Throwable e){
e.printStackTrace();
ui.showException("[accent]" + Core.bundle.get("savefail"), e);
}
state.set(State.menu);
logic.reset();
});
}
}

View File

@@ -0,0 +1,62 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.scene.ui.TextButton;
import arc.util.Time;
import mindustry.core.GameState.State;
import mindustry.game.Saves.SaveSlot;
import mindustry.gen.*;
import static mindustry.Vars.*;
public class SaveDialog extends LoadDialog{
public SaveDialog(){
super("$savegame");
update(() -> {
if(state.is(State.menu) && isShown()){
hide();
}
});
}
public void addSetup(){
slots.row();
slots.addImageTextButton("$save.new", Icon.add, () ->
ui.showTextInput("$save", "$save.newslot", 30, "", text -> {
ui.loadAnd("$saving", () -> {
control.saves.addSave(text);
Core.app.post(() -> Core.app.post(this::setup));
});
})
).fillX().margin(10f).minWidth(300f).height(70f).pad(4f).padRight(-4);
}
@Override
public void modifyButton(TextButton button, SaveSlot slot){
button.clicked(() -> {
if(button.childrenPressed()) return;
ui.showConfirm("$overwrite", "$save.overwrite", () -> save(slot));
});
}
void save(SaveSlot slot){
ui.loadfrag.show("$saveload");
Time.runTask(5f, () -> {
hide();
ui.loadfrag.hide();
try{
slot.save();
}catch(Throwable e){
e.printStackTrace();
ui.showException("[accent]" + Core.bundle.get("savefail"), e);
}
});
}
}

View File

@@ -0,0 +1,325 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
import arc.graphics.g2d.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.ImageButton.*;
import arc.scene.ui.TextButton.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class SchematicsDialog extends FloatingDialog{
private SchematicInfoDialog info = new SchematicInfoDialog();
private String search = "";
public SchematicsDialog(){
super("$schematics");
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> {
((Texture)t).setWrap(TextureWrap.Repeat);
};
shouldPause = true;
addCloseButton();
buttons.addImageTextButton("$schematic.import", Icon.loadMapSmall, this::showImport);
shown(this::setup);
onResize(this::setup);
}
void setup(){
search = "";
Runnable[] rebuildPane = {null};
cont.top();
cont.clear();
cont.table(s -> {
s.left();
s.addImage(Icon.zoom);
s.addField(search, res -> {
search = res;
rebuildPane[0].run();
}).growX();
}).fillX().padBottom(4);
cont.row();
cont.pane(t -> {
t.top();
t.margin(20f);
rebuildPane[0] = () -> {
t.clear();
int i = 0;
if(!schematics.all().contains(s -> search.isEmpty() || s.name().toLowerCase().contains(search.toLowerCase()))){
t.add("$none");
}
for(Schematic s : schematics.all()){
if(!search.isEmpty() && !s.name().toLowerCase().contains(search.toLowerCase())) continue;
Button[] sel = {null};
sel[0] = t.addButton(b -> {
b.top();
b.margin(0f);
b.table(buttons -> {
buttons.left();
buttons.defaults().size(50f);
ImageButtonStyle style = Styles.clearPartiali;
buttons.addImageButton(Icon.infoSmall, style, () -> {
showInfo(s);
});
buttons.addImageButton(Icon.loadMapSmall, style, () -> {
showExport(s);
});
buttons.addImageButton(Icon.pencilSmall, style, () -> {
ui.showTextInput("$schematic.rename", "$name", s.name(), res -> {
s.tags.put("name", res);
s.save();
rebuildPane[0].run();
});
});
if(s.hasSteamID()){
buttons.addImageButton(Icon.linkSmall, style, () -> platform.viewListing(s));
}else{
buttons.addImageButton(Icon.trash16Small, style, () -> {
if(s.mod != null){
ui.showInfo(Core.bundle.format("mod.item.remove", s.mod.meta.displayName()));
}else{
ui.showConfirm("$confirm", "$schematic.delete.confirm", () -> {
schematics.remove(s);
rebuildPane[0].run();
});
}
});
}
}).growX().height(50f);
b.row();
b.stack(new SchematicImage(s).setScaling(Scaling.fit), new Table(n -> {
n.top();
n.table(Styles.black3, c -> {
Label label = c.add(s.name()).style(Styles.outlineLabel).color(Color.white).top().growX().maxWidth(200f - 8f).get();
label.setEllipsis(true);
label.setAlignment(Align.center);
}).growX().margin(1).pad(4).maxWidth(Scl.scl(200f - 8f)).padBottom(0);
})).size(200f);
}, () -> {
if(sel[0].childrenPressed()) return;
if(state.is(State.menu)){
showInfo(s);
}else{
control.input.useSchematic(s);
hide();
}
}).pad(4).style(Styles.cleari).get();
sel[0].getStyle().up = Tex.pane;
if(++i % (mobile ? Core.graphics.isPortrait() ? 2 : 3 : 4) == 0){
t.row();
}
}
};
rebuildPane[0].run();
}).get().setScrollingDisabled(true, false);
}
public void showInfo(Schematic schematic){
info.show(schematic);
}
public void showImport(){
FloatingDialog dialog = new FloatingDialog("$editor.export");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
t.row();
t.addImageTextButton("$schematic.copy.import", Icon.copySmall, style, () -> {
dialog.hide();
try{
Schematic s = Schematics.readBase64(Core.app.getClipboardText());
s.removeSteamID();
schematics.add(s);
setup();
ui.showInfoFade("$schematic.saved");
showInfo(s);
}catch(Exception e){
ui.showException(e);
}
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null || !Core.app.getClipboardText().startsWith(schematicBaseStart));
t.row();
t.addImageTextButton("$schematic.importfile", Icon.saveMapSmall, style, () -> platform.showFileChooser(true, schematicExtension, file -> {
dialog.hide();
try{
Schematic s = Schematics.read(file);
s.removeSteamID();
schematics.add(s);
setup();
showInfo(s);
}catch(Exception e){
ui.showException(e);
}
})).marginLeft(12f);
t.row();
if(steam){
t.addImageTextButton("$schematic.browseworkshop", Icon.wikiSmall, style, () -> {
dialog.hide();
platform.openWorkshop();
}).marginLeft(12f);
}
});
});
dialog.addCloseButton();
dialog.show();
}
public void showExport(Schematic s){
FloatingDialog dialog = new FloatingDialog("$editor.export");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
if(steam && !s.hasSteamID()){
t.addImageTextButton("$schematic.shareworkshop", Icon.wikiSmall, style,
() -> platform.publish(s)).marginLeft(12f);
t.row();
dialog.hide();
}
t.addImageTextButton("$schematic.copy", Icon.copySmall, style, () -> {
dialog.hide();
ui.showInfoFade("$copied");
Core.app.setClipboardText(schematics.writeBase64(s));
}).marginLeft(12f);
t.row();
t.addImageTextButton("$schematic.exportfile", Icon.saveMapSmall, style, () -> platform.showFileChooser(false, schematicExtension, file -> {
dialog.hide();
try{
Schematics.write(s, file);
}catch(Exception e){
ui.showException(e);
}
})).marginLeft(12f);
});
});
dialog.addCloseButton();
dialog.show();
}
public static class SchematicImage extends Image{
public float scaling = 16f;
public float thickness = 4f;
public Color borderColor = Pal.gray;
private Schematic schematic;
boolean set;
public SchematicImage(Schematic s){
super(Tex.clear);
setScaling(Scaling.fit);
schematic = s;
if(schematics.hasPreview(s)){
setPreview();
set = true;
}
}
@Override
public void draw(){
boolean checked = getParent().getParent() instanceof Button
&& ((Button)getParent().getParent()).isOver();
boolean wasSet = set;
if(!set){
Core.app.post(this::setPreview);
set = true;
}
Texture background = Core.assets.get("sprites/schematic-background.png", Texture.class);
TextureRegion region = Draw.wrap(background);
float xr = width / scaling;
float yr = height / scaling;
region.setU2(xr);
region.setV2(yr);
Draw.color();
Draw.alpha(parentAlpha);
Draw.rect(region, x + width/2f, y + height/2f, width, height);
if(wasSet){
super.draw();
}else{
Draw.rect(Icon.loading.getRegion(), x + width/2f, y + height/2f, width/4f, height/4f);
}
Draw.color(checked ? Pal.accent : borderColor);
Draw.alpha(parentAlpha);
Lines.stroke(Scl.scl(thickness));
Lines.rect(x, y, width, height);
Draw.reset();
}
private void setPreview(){
TextureRegionDrawable draw = new TextureRegionDrawable(new TextureRegion(schematics.getPreview(schematic)));
setDrawable(draw);
setScaling(Scaling.fit);
}
}
public static class SchematicInfoDialog extends FloatingDialog{
SchematicInfoDialog(){
super("");
setFillParent(true);
addCloseButton();
}
public void show(Schematic schem){
cont.clear();
title.setText("[[" + Core.bundle.get("schematic") + "] " +schem.name());
cont.add(Core.bundle.format("schematic.info", schem.width, schem.height, schem.tiles.size)).color(Color.lightGray);
cont.row();
cont.add(new SchematicImage(schem)).maxSize(800f);
cont.row();
Array<ItemStack> arr = schem.requirements();
cont.table(r -> {
int i = 0;
for(ItemStack s : arr){
r.addImage(s.item.icon(Cicon.small)).left();
r.add(s.amount + "").padLeft(2).left().color(Color.lightGray).padRight(4);
if(++i % 4 == 0){
r.row();
}
}
});
show();
}
}
}

View File

@@ -0,0 +1,376 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.files.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
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.util.*;
import mindustry.core.GameState.*;
import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.ui.*;
import static arc.Core.bundle;
import static mindustry.Vars.*;
public class SettingsMenuDialog extends SettingsDialog{
private SettingsTable graphics;
private SettingsTable game;
private SettingsTable sound;
private Table prefs;
private Table menu;
private FloatingDialog dataDialog;
private boolean wasPaused;
public SettingsMenuDialog(){
hidden(() -> {
Sounds.back.play();
if(!state.is(State.menu)){
if(!wasPaused || net.active())
state.set(State.playing);
}
});
shown(() -> {
back();
if(!state.is(State.menu)){
wasPaused = state.is(State.paused);
state.set(State.paused);
}
rebuildMenu();
});
setFillParent(true);
title.setAlignment(Align.center);
titleTable.row();
titleTable.add(new Image()).growX().height(3f).pad(4f).get().setColor(Pal.accent);
cont.clearChildren();
cont.remove();
buttons.remove();
menu = new Table(Tex.button);
game = new SettingsTable();
graphics = new SettingsTable();
sound = new SettingsTable();
prefs = new Table();
prefs.top();
prefs.margin(14f);
rebuildMenu();
prefs.clearChildren();
prefs.add(menu);
dataDialog = new FloatingDialog("$settings.data");
dataDialog.addCloseButton();
dataDialog.cont.table(Tex.button, t -> {
t.defaults().size(240f, 60f).left();
TextButtonStyle style = Styles.cleart;
t.addButton("$settings.cleardata", style, () -> ui.showConfirm("$confirm", "$settings.clearall.confirm", () -> {
ObjectMap<String, Object> map = new ObjectMap<>();
for(String value : Core.settings.keys()){
if(value.contains("usid") || value.contains("uuid")){
map.put(value, Core.settings.getString(value));
}
}
Core.settings.clear();
Core.settings.putAll(map);
Core.settings.save();
for(Fi file : dataDirectory.list()){
file.deleteDirectory();
}
Core.app.exit();
}));
t.row();
t.addButton("$data.export", style, () -> {
if(ios){
Fi file = Core.files.local("mindustry-data-export.zip");
try{
data.exportData(file);
}catch(Exception e){
ui.showException(e);
}
platform.shareFile(file);
}else{
platform.showFileChooser(false, "zip", file -> {
try{
data.exportData(file);
ui.showInfo("$data.exported");
}catch(Exception e){
e.printStackTrace();
ui.showException(e);
}
});
}
});
t.row();
t.addButton("$data.import", style, () -> ui.showConfirm("$confirm", "$data.import.confirm", () -> platform.showFileChooser(true, "zip", file -> {
try{
data.importData(file);
Core.app.exit();
}catch(IllegalArgumentException e){
ui.showErrorMessage("$data.invalid");
}catch(Exception e){
e.printStackTrace();
if(e.getMessage() == null || !e.getMessage().contains("too short")){
ui.showException(e);
}else{
ui.showErrorMessage("$data.invalid");
}
}
})));
});
ScrollPane pane = new ScrollPane(prefs);
pane.addCaptureListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
Element actor = pane.hit(x, y, true);
if(actor instanceof Slider){
pane.setFlickScroll(false);
return true;
}
return super.touchDown(event, x, y, pointer, button);
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
pane.setFlickScroll(true);
super.touchUp(event, x, y, pointer, button);
}
});
pane.setFadeScrollBars(false);
row();
add(pane).grow().top();
row();
add(buttons).fillX();
addSettings();
}
void rebuildMenu(){
menu.clearChildren();
TextButtonStyle style = Styles.cleart;
menu.defaults().size(300f, 60f);
menu.addButton("$settings.game", style, () -> visible(0));
menu.row();
menu.addButton("$settings.graphics", style, () -> visible(1));
menu.row();
menu.addButton("$settings.sound", style, () -> visible(2));
menu.row();
menu.addButton("$settings.language", style, ui.language::show);
if(!mobile || Core.settings.getBool("keyboard")){
menu.row();
menu.addButton("$settings.controls", style, ui.controls::show);
}
menu.row();
menu.addButton("$settings.data", style, () -> dataDialog.show());
}
void addSettings(){
sound.sliderPref("musicvol", bundle.get("setting.musicvol.name", "Music Volume"), 100, 0, 100, 1, i -> i + "%");
sound.sliderPref("sfxvol", bundle.get("setting.sfxvol.name", "SFX Volume"), 100, 0, 100, 1, i -> i + "%");
sound.sliderPref("ambientvol", bundle.get("setting.ambientvol.name", "Ambient Volume"), 100, 0, 100, 1, i -> i + "%");
game.screenshakePref();
if(mobile){
game.checkPref("autotarget", true);
game.checkPref("keyboard", false, val -> control.setInput(val ? new DesktopInput() : new MobileInput()));
if(Core.settings.getBool("keyboard")){
control.setInput(new DesktopInput());
}
}
//the issue with touchscreen support on desktop is that:
//1) I can't test it
//2) the SDL backend doesn't support multitouch
/*else{
game.checkPref("touchscreen", false, val -> control.setInput(!val ? new DesktopInput() : new MobileInput()));
if(Core.settings.getBool("touchscreen")){
control.setInput(new MobileInput());
}
}*/
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);
}
game.checkPref("savecreate", true);
game.checkPref("blockreplace", true);
game.checkPref("conveyorpathfinding", true);
game.checkPref("hints", true);
if(!mobile){
game.checkPref("buildautopause", false);
}
if(steam && !Version.modifier.contains("beta")){
game.checkPref("publichost", false, i -> {
platform.updateLobby();
});
}
game.pref(new Setting(){
@Override
public void add(SettingsTable table){
table.addButton("$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);
}
return s + "%";
});
graphics.sliderPref("fpscap", 240, 15, 245, 5, s -> (s > 240 ? Core.bundle.get("setting.fpscap.none") : Core.bundle.format("setting.fpscap.text", s)));
graphics.sliderPref("chatopacity", 100, 0, 100, 5, s -> s + "%");
graphics.sliderPref("lasersopacity", 100, 0, 100, 5, s -> {
if(ui.settings != null){
Core.settings.put("preferredlaseropacity", s);
}
return s + "%";
});
if(!mobile){
graphics.checkPref("vsync", true, b -> Core.graphics.setVSync(b));
graphics.checkPref("fullscreen", false, b -> {
if(b){
Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode());
}else{
Core.graphics.setWindowedMode(Core.graphics.getWidth(), Core.graphics.getHeight());
}
});
graphics.checkPref("borderlesswindow", false, b -> Core.graphics.setUndecorated(b));
Core.graphics.setVSync(Core.settings.getBool("vsync"));
if(Core.settings.getBool("fullscreen")){
Core.app.post(() -> Core.graphics.setFullscreenMode(Core.graphics.getDisplayMode()));
}
if(Core.settings.getBool("borderlesswindow")){
Core.app.post(() -> Core.graphics.setUndecorated(true));
}
}else{
graphics.checkPref("landscape", false, b -> {
if(b){
platform.beginForceLandscape();
}else{
platform.endForceLandscape();
}
});
if(Core.settings.getBool("landscape")){
platform.beginForceLandscape();
}
}
graphics.checkPref("effects", true);
graphics.checkPref("destroyedblocks", true);
graphics.checkPref("playerchat", true);
graphics.checkPref("minimap", !mobile);
graphics.checkPref("position", false);
graphics.checkPref("fps", false);
if(!mobile){
graphics.checkPref("blockselectkeys", true);
}
graphics.checkPref("indicators", true);
graphics.checkPref("animatedwater", !mobile);
if(Shaders.shield != null){
graphics.checkPref("animatedshields", !mobile);
}
graphics.checkPref("bloom", !mobile, val -> renderer.toggleBloom(val));
graphics.checkPref("pixelate", false, val -> {
if(val){
Events.fire(Trigger.enablePixelation);
}
});
graphics.checkPref("linear", !mobile, b -> {
for(Texture tex : Core.atlas.getTextures()){
TextureFilter filter = b ? TextureFilter.Linear : TextureFilter.Nearest;
tex.setFilter(filter, filter);
}
});
if(Core.settings.getBool("linear")){
for(Texture tex : Core.atlas.getTextures()){
TextureFilter filter = TextureFilter.Linear;
tex.setFilter(filter, filter);
}
}
if(!mobile){
Core.settings.put("swapdiagonal", false);
}
}
private void back(){
rebuildMenu();
prefs.clearChildren();
prefs.add(menu);
}
private void visible(int index){
prefs.clearChildren();
prefs.add(new Table[]{game, graphics, sound}[index]);
}
@Override
public void addCloseButton(){
buttons.addImageTextButton("$back", Icon.arrowLeftSmaller, () -> {
if(prefs.getChildren().first() != menu){
back();
}else{
hide();
}
}).size(230f, 64f);
keyDown(key -> {
if(key == KeyCode.ESCAPE || key == KeyCode.BACK){
if(prefs.getChildren().first() != menu){
back();
}else{
hide();
}
}
});
}
}

View File

@@ -0,0 +1,389 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.ui.layout.*;
import mindustry.ui.layout.TreeLayout.*;
import static mindustry.Vars.*;
public class TechTreeDialog extends FloatingDialog{
private final float nodeSize = Scl.scl(60f);
private ObjectSet<TechTreeNode> nodes = new ObjectSet<>();
private TechTreeNode root = new TechTreeNode(TechTree.root, null);
private Rectangle bounds = new Rectangle();
private ItemsDisplay items;
private View view;
public TechTreeDialog(){
super("");
titleTable.remove();
margin(0f).marginBottom(8);
Stack stack = cont.stack(view = new View(), items = new ItemsDisplay()).grow().get();
Events.on(ContentReloadEvent.class, e -> {
nodes.clear();
root = new TechTreeNode(TechTree.root, null);
checkNodes(root);
treeLayout();
stack.getChildren().get(0).remove();
stack.addChildAt(0, view = new View());
});
shown(() -> {
checkNodes(root);
treeLayout();
});
hidden(ui.deploy::setup);
addCloseButton();
buttons.addImageTextButton("$database", Icon.database, () -> {
hide();
ui.database.show();
}).size(210f, 64f);
//scaling/drag input
addListener(new InputListener(){
@Override
public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY){
view.setScale(Mathf.clamp(view.getScaleX() - amountY / 40f, 0.25f, 1f));
view.setOrigin(Align.center);
view.setTransform(true);
return true;
}
@Override
public boolean mouseMoved(InputEvent event, float x, float y){
view.requestScroll();
return super.mouseMoved(event, x, y);
}
});
addListener(new ElementGestureListener(){
@Override
public void zoom(InputEvent event, float initialDistance, float distance){
if(view.lastZoom < 0){
view.lastZoom = view.getScaleX();
}
view.setScale(Mathf.clamp(distance / initialDistance * view.lastZoom, 0.25f, 1f));
view.setOrigin(Align.center);
view.setTransform(true);
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
view.lastZoom = view.getScaleX();
}
@Override
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY){
view.panX += deltaX / view.getScaleX();
view.panY += deltaY / view.getScaleY();
view.moved = true;
view.clamp();
}
});
}
void treeLayout(){
RadialTreeLayout layout = new RadialTreeLayout();
LayoutNode node = new LayoutNode(root, null);
layout.layout(node);
float minx = 0f, miny = 0f, maxx = 0f, maxy = 0f;
copyInfo(node);
for(TechTreeNode n : nodes){
if(!n.visible) continue;
minx = Math.min(n.x - n.width/2f, minx);
maxx = Math.max(n.x + n.width/2f, maxx);
miny = Math.min(n.y - n.height/2f, miny);
maxy = Math.max(n.y + n.height/2f, maxy);
}
bounds = new Rectangle(minx, miny, maxx - minx, maxy - miny);
bounds.y += nodeSize*1.5f;
}
void copyInfo(LayoutNode node){
node.node.x = node.x;
node.node.y = node.y;
if(node.children != null){
for(LayoutNode child : node.children){
copyInfo(child);
}
}
}
void checkNodes(TechTreeNode node){
boolean locked = locked(node.node);
if(!locked) node.visible = true;
for(TechTreeNode l : node.children){
l.visible = !locked;
checkNodes(l);
}
items.rebuild();
}
void showToast(String info){
Table table = new Table();
table.actions(Actions.fadeOut(0.5f, Interpolation.fade), Actions.remove());
table.top().add(info);
table.setName("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.block.locked();
}
class LayoutNode extends TreeNode<LayoutNode>{
final TechTreeNode node;
LayoutNode(TechTreeNode node, LayoutNode parent){
this.node = node;
this.parent = parent;
this.width = this.height = nodeSize;
if(node.children != null){
children = Array.with(node.children).map(t -> new LayoutNode(t, this)).toArray(LayoutNode.class);
}
}
}
class TechTreeNode extends TreeNode<TechTreeNode>{
final TechNode node;
boolean visible = true;
TechTreeNode(TechNode node, TechTreeNode parent){
this.node = node;
this.parent = parent;
this.width = this.height = nodeSize;
nodes.add(this);
if(node.children != null){
children = new TechTreeNode[node.children.size];
for(int i = 0; i < children.length; i++){
children[i] = new TechTreeNode(node.children.get(i), this);
}
}
}
}
class View extends Group{
float panX = 0, panY = -200, lastZoom = -1;
boolean moved = false;
ImageButton hoverNode;
Table infoTable = new Table();
{
infoTable.touchable(Touchable.enabled);
for(TechTreeNode node : nodes){
ImageButton button = new ImageButton(node.node.block.icon(Cicon.medium), Styles.nodei);
button.visible(() -> node.visible);
button.clicked(() -> {
if(moved) return;
if(mobile){
hoverNode = button;
rebuild();
float right = infoTable.getRight();
if(right > Core.graphics.getWidth()){
float moveBy = right - Core.graphics.getWidth();
addAction(new RelativeTemporalAction(){
{
setDuration(0.1f);
setInterpolation(Interpolation.fade);
}
@Override
protected void updateRelative(float percentDelta){
panX -= moveBy * percentDelta;
}
});
}
}else if(data.hasItems(node.node.requirements) && locked(node.node)){
unlock(node.node);
}
});
button.hovered(() -> {
if(!mobile && hoverNode != button && node.visible){
hoverNode = button;
rebuild();
}
});
button.exited(() -> {
if(!mobile && hoverNode == button && !infoTable.hasMouse() && !hoverNode.hasMouse()){
hoverNode = null;
rebuild();
}
});
button.touchable(() -> !node.visible ? Touchable.disabled : Touchable.enabled);
button.setUserObject(node.node);
button.setSize(nodeSize);
button.update(() -> {
float offset = (Core.graphics.getHeight() % 2) / 2f;
button.setPosition(node.x + panX + width / 2f, node.y + panY + height / 2f + offset, Align.center);
button.getStyle().up = !locked(node.node) ? Tex.buttonOver : !data.hasItems(node.node.requirements) ? Tex.buttonRed : Tex.button;
((TextureRegionDrawable)button.getStyle().imageUp)
.setRegion(node.visible ? node.node.block.icon(Cicon.medium) : Core.atlas.find("icon-locked"));
button.getImage().setColor(!locked(node.node) ? Color.white : Color.gray);
});
addChild(button);
}
if(mobile){
tapped(() -> {
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e == this){
hoverNode = null;
rebuild();
}
});
}
setOrigin(Align.center);
setTransform(true);
released(() -> moved = false);
}
void clamp(){
float pad = nodeSize;
float ox = width/2f, oy = height/2f;
float rx = bounds.x + panX + ox, ry = panY + oy + bounds.y;
float rw = bounds.width, rh = bounds.height;
rx = Mathf.clamp(rx, -rw + pad, Core.graphics.getWidth() - pad);
ry = Mathf.clamp(ry, -rh + pad, Core.graphics.getHeight() - pad);
panX = rx - bounds.x - ox;
panY = ry - bounds.y - oy;
}
void unlock(TechNode node){
data.unlockContent(node.block);
data.removeItems(node.requirements);
showToast(Core.bundle.format("researched", node.block.localizedName));
checkNodes(root);
hoverNode = null;
treeLayout();
rebuild();
Core.scene.act();
Sounds.unlock.play();
Events.fire(new ResearchEvent(node.block));
}
void rebuild(){
ImageButton button = hoverNode;
infoTable.remove();
infoTable.clear();
infoTable.update(null);
if(button == null) return;
TechNode node = (TechNode)button.getUserObject();
infoTable.exited(() -> {
if(hoverNode == button && !infoTable.hasMouse() && !hoverNode.hasMouse()){
hoverNode = null;
rebuild();
}
});
infoTable.update(() -> infoTable.setPosition(button.getX() + button.getWidth(), button.getY() + button.getHeight(), Align.topLeft));
infoTable.left();
infoTable.background(Tex.button).margin(8f);
infoTable.table(b -> {
b.margin(0).left().defaults().left();
b.addImageButton(Icon.infoSmall, Styles.cleari, () -> ui.content.show(node.block)).growY().width(50f);
b.add().grow();
b.table(desc -> {
desc.left().defaults().left();
desc.add(node.block.localizedName);
desc.row();
if(locked(node)){
desc.table(t -> {
t.left();
for(ItemStack req : node.requirements){
t.table(list -> {
list.left();
list.addImage(req.item.icon(Cicon.small)).size(8 * 3).padRight(3);
list.add(req.item.localizedName).color(Color.lightGray);
list.label(() -> " " + Math.min(data.getItem(req.item), req.amount) + " / " + req.amount)
.update(l -> l.setColor(data.has(req.item, req.amount) ? Color.lightGray : Color.scarlet));
}).fillX().left();
t.row();
}
});
}else{
desc.add("$completed");
}
}).pad(9);
if(mobile && locked(node)){
b.row();
b.addImageTextButton("$research", Icon.checkSmall, Styles.nodet, () -> unlock(node))
.disabled(i -> !data.hasItems(node.requirements)).growX().height(44f).colspan(3);
}
});
infoTable.row();
if(node.block.description != null){
infoTable.table(t -> t.margin(3f).left().labelWrap(node.block.description).color(Color.lightGray).growX()).fillX();
}
addChild(infoTable);
infoTable.pack();
infoTable.act(Core.graphics.getDeltaTime());
}
@Override
public void drawChildren(){
clamp();
float offsetX = panX + width / 2f, offsetY = panY + height / 2f;
for(TechTreeNode node : nodes){
if(!node.visible) continue;
for(TechTreeNode child : node.children){
if(!child.visible) continue;
Lines.stroke(Scl.scl(4f), locked(node.node) || locked(child.node) ? Pal.gray : Pal.accent);
Draw.alpha(parentAlpha);
Lines.line(node.x + offsetX, node.y + offsetY, child.x + offsetX, child.y + offsetY);
}
}
Draw.reset();
super.drawChildren();
}
}
}

View File

@@ -0,0 +1,44 @@
package mindustry.ui.dialogs;
import arc.Core;
import arc.scene.ui.layout.Table;
import mindustry.entities.type.Player;
import mindustry.gen.*;
import mindustry.net.Administration.TraceInfo;
public class TraceDialog extends FloatingDialog{
public TraceDialog(){
super("$trace");
addCloseButton();
setFillParent(false);
}
public void show(Player player, TraceInfo info){
cont.clear();
Table table = new Table(Tex.clear);
table.margin(14);
table.defaults().pad(1);
table.defaults().left();
table.add(Core.bundle.format("trace.playername", player.name));
table.row();
table.add(Core.bundle.format("trace.ip", info.ip));
table.row();
table.add(Core.bundle.format("trace.id", info.uuid));
table.row();
table.add(Core.bundle.format("trace.modclient", info.modded));
table.row();
table.add(Core.bundle.format("trace.mobile", info.mobile));
table.row();
table.add().pad(5);
table.row();
cont.add(table);
show();
}
}

View File

@@ -0,0 +1,168 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.game.*;
import mindustry.game.Objectives.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.Cicon;
import static mindustry.Vars.*;
public class ZoneInfoDialog extends FloatingDialog{
private LoadoutDialog loadout = new LoadoutDialog();
public ZoneInfoDialog(){
super("");
titleTable.remove();
addCloseButton();
}
public void show(Zone zone){
setup(zone);
show();
}
private void setup(Zone zone){
cont.clear();
Table iteminfo = new Table();
Runnable rebuildItems = () -> {
int i = 0;
iteminfo.clear();
if(!zone.unlocked()) return;
for(ItemStack stack : zone.getLaunchCost()){
if(stack.amount == 0) continue;
if(i++ % 2 == 0){
iteminfo.row();
}
iteminfo.addImage(stack.item.icon(mindustry.ui.Cicon.small)).size(8 * 3).padRight(1);
iteminfo.add(stack.amount + "").color(Color.lightGray).padRight(5);
}
};
rebuildItems.run();
cont.pane(cont -> {
if(zone.locked()){
cont.addImage(Icon.locked);
cont.row();
cont.add("$locked").padBottom(6);
cont.row();
cont.table(req -> {
req.defaults().left();
Array<Objective> zones = zone.requirements.select(o -> !(o instanceof Unlock));
if(!zones.isEmpty()){
req.table(r -> {
r.add("$complete").colspan(2).left();
r.row();
for(Objective o : zones){
r.addImage(Icon.terrain).padRight(4);
r.add(o.display()).color(Color.lightGray);
r.addImage(o.complete() ? Icon.checkSmall : Icon.cancelSmall, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3);
r.row();
}
});
}
req.row();
Array<Unlock> blocks = zone.requirements.select(o -> o instanceof Unlock).as(Unlock.class);
if(!blocks.isEmpty()){
req.table(r -> {
r.add("$research.list").colspan(2).left();
r.row();
for(Unlock blocko : blocks){
r.addImage(blocko.block.icon(mindustry.ui.Cicon.small)).size(8 * 3).padRight(5);
r.add(blocko.block.localizedName).color(Color.lightGray).left();
r.addImage(blocko.block.unlocked() ? Icon.checkSmall : Icon.cancelSmall, blocko.block.unlocked() ? Color.lightGray : Color.scarlet).padLeft(3);
r.row();
}
}).padTop(10);
}
}).growX();
}else{
cont.add(zone.localizedName).color(Pal.accent).growX().center();
cont.row();
cont.addImage().color(Pal.accent).height(3).pad(6).growX();
cont.row();
cont.table(desc -> {
desc.left().defaults().left().width(Core.graphics.isPortrait() ? 350f : 500f);
desc.pane(t -> t.marginRight(12f).add(zone.description).wrap().growX()).fillX().maxHeight(mobile ? 300f : 450f).pad(2).padBottom(8f).get().setScrollingDisabled(true, false);
desc.row();
desc.table(t -> {
t.left();
t.add("$zone.resources").padRight(6);
if(zone.resources.size > 0){
t.table(r -> {
t.left();
int i = 0;
for(Item item : zone.resources){
r.addImage(item.icon(Cicon.small)).size(8 * 3);
if(++i % 4 == 0){
r.row();
}
}
});
}else{
t.add("$none");
}
});
Rules rules = zone.getRules();
desc.row();
desc.add(Core.bundle.format("zone.objective", Core.bundle.get(!rules.attackMode ? "zone.objective.survival" : "zone.objective.attack")));
if(zone.bestWave() > 0){
desc.row();
desc.add(Core.bundle.format("bestwave", zone.bestWave()));
}
});
cont.row();
}
cont.marginRight(12f);
});
cont.row();
cont.addButton(zone.canConfigure() ? "$configure" : Core.bundle.format("configure.locked", zone.configureObjective.display()),
() -> loadout.show(zone.loadout.findCore().itemCapacity, zone.getStartingItems(), zone::resetStartingItems, zone::updateLaunchCost, rebuildItems)
).fillX().pad(3).disabled(b -> !zone.canConfigure());
cont.row();
Button button = cont.addButton(zone.locked() ? "$uncover" : "$launch", () -> {
if(!data.isUnlocked(zone)){
Sounds.unlock.play();
data.unlockContent(zone);
ui.deploy.setup();
setup(zone);
}else{
ui.deploy.hide();
data.removeItems(zone.getLaunchCost());
hide();
control.playZone(zone);
}
}).minWidth(200f).margin(13f).padTop(5).disabled(b -> zone.locked() ? !zone.canUnlock() : !data.hasItems(zone.getLaunchCost())).uniformY().get();
button.row();
button.add(iteminfo);
}
}

View File

@@ -0,0 +1,83 @@
package mindustry.ui.fragments;
import arc.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.core.GameState.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class BlockConfigFragment extends Fragment{
private Table table = new Table();
private Tile configTile;
private Block configBlock;
@Override
public void build(Group parent){
table.visible(false);
parent.addChild(table);
//hacky way to hide block config when in menu
//TODO remove?
Core.scene.add(new Element(){
@Override
public void act(float delta){
super.act(delta);
if(state.is(State.menu)){
table.visible(false);
configTile = null;
}
}
});
}
public boolean isShown(){
return table.isVisible() && configTile != null;
}
public Tile getSelectedTile(){
return configTile;
}
public void showConfig(Tile tile){
configTile = tile;
configBlock = tile.block();
table.visible(true);
table.clear();
tile.block().buildConfiguration(tile, table);
table.pack();
table.setTransform(true);
table.actions(Actions.scaleTo(0f, 1f), Actions.visible(true),
Actions.scaleTo(1f, 1f, 0.07f, Interpolation.pow3Out));
table.update(() -> {
if(configTile != null && configTile.block().shouldHideConfigure(configTile, player)){
hideConfig();
return;
}
table.setOrigin(Align.center);
if(configTile == null || configTile.block() == Blocks.air || configTile.block() != configBlock){
hideConfig();
}else{
configTile.block().updateTableAlign(tile, table);
}
});
}
public boolean hasConfigMouse(){
Element e = Core.scene.hit(Core.input.mouseX(), Core.graphics.getHeight() - Core.input.mouseY(), true);
return e != null && (e == table || e.isDescendantOf(table));
}
public void hideConfig(){
configTile = null;
table.actions(Actions.scaleTo(0f, 1f, 0.06f, Interpolation.pow3Out), Actions.visible(false));
}
}

View File

@@ -0,0 +1,217 @@
package mindustry.ui.fragments;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.Cicon;
import mindustry.world.*;
import static mindustry.Vars.*;
public class BlockInventoryFragment extends Fragment{
private final static float holdWithdraw = 20f;
private Table table = new Table();
private Tile tile;
private float holdTime = 0f;
private boolean holding;
private Item lastItem;
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Tile tile, Item item, int amount){
if(player == null || tile == null || !player.timer.get(Player.timerTransfer, 20) || !tile.interactable(player.getTeam())) return;
if(!Units.canInteract(player, tile)) return;
int removed = tile.block().removeStack(tile, item, amount);
player.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.drawx(), tile.drawy(), player));
}
}
@Override
public void build(Group parent){
table.setName("inventory");
table.setTransform(true);
parent.setTransform(true);
parent.addChild(table);
}
public void showFor(Tile t){
if(this.tile == t){
hide();
return;
}
this.tile = t;
if(tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0)
return;
rebuild(true);
}
public void hide(){
if(table == null) return;
table.actions(Actions.scaleTo(0f, 1f, 0.06f, Interpolation.pow3Out), Actions.run(() -> {
table.clearChildren();
table.clearListeners();
table.update(null);
}), Actions.visible(false));
table.touchable(Touchable.disabled);
tile = null;
}
private void rebuild(boolean actions){
IntSet container = new IntSet();
table.clearChildren();
table.clearActions();
table.background(Tex.inventory);
table.touchable(Touchable.enabled);
table.update(() -> {
if(state.is(State.menu) || tile == null || tile.entity == null || !tile.block().isAccessible() || tile.entity.items.total() == 0){
hide();
}else{
if(holding && lastItem != null){
holdTime += Time.delta();
if(holdTime >= holdWithdraw){
int amount = Math.min(tile.entity.items.get(lastItem), player.maxAccepted(lastItem));
Call.requestItem(player, tile, lastItem, amount);
holding = false;
holdTime = 0f;
if(net.client()) Events.fire(new WithdrawEvent(tile, player, lastItem, amount));
}
}
updateTablePosition();
if(tile.block().hasItems){
for(int i = 0; i < content.items().size; i++){
boolean has = tile.entity.items.has(content.item(i));
if(has != container.contains(i)){
rebuild(false);
}
}
}
}
});
int cols = 3;
int row = 0;
table.margin(4f);
table.defaults().size(8 * 5).pad(4f);
if(tile.block().hasItems){
for(int i = 0; i < content.items().size; i++){
Item item = content.item(i);
if(!tile.entity.items.has(item)) continue;
container.add(i);
Boolp canPick = () -> player.acceptsItem(item) && !state.isPaused();
HandCursorListener l = new HandCursorListener();
l.setEnabled(canPick);
Element image = itemImage(item.icon(Cicon.xlarge), () -> {
if(tile == null || tile.entity == null){
return "";
}
return round(tile.entity.items.get(item));
});
image.addListener(l);
image.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
if(!canPick.get() || tile == null || tile.entity == null || tile.entity.items == null || !tile.entity.items.has(item)) return false;
int amount = Math.min(1, player.maxAccepted(item));
if(amount > 0){
Call.requestItem(player, tile, item, amount);
lastItem = item;
holding = true;
holdTime = 0f;
if(net.client()) Events.fire(new WithdrawEvent(tile, player, item, amount));
}
return true;
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
holding = false;
lastItem = null;
}
});
table.add(image);
if(row++ % cols == cols - 1) table.row();
}
}
if(row == 0){
table.setSize(0f, 0f);
}
updateTablePosition();
table.visible(true);
if(actions){
table.setScale(0f, 1f);
table.actions(Actions.scaleTo(1f, 1f, 0.07f, Interpolation.pow3Out));
}else{
table.setScale(1f, 1f);
}
}
private String round(float f){
f = (int)f;
if(f >= 1000000){
return (int)(f / 1000000f) + "[gray]" + Core.bundle.getOrNull("unit.millions") + "[]";
}else if(f >= 1000){
return (int)(f / 1000) + Core.bundle.getOrNull("unit.thousands");
}else{
return (int)f + "";
}
}
private void updateTablePosition(){
Vector2 v = Core.input.mouseScreen(tile.drawx() + tile.block().size * tilesize / 2f, tile.drawy() + tile.block().size * tilesize / 2f);
table.pack();
table.setPosition(v.x, v.y, Align.topLeft);
}
private Element itemImage(TextureRegion region, Prov<CharSequence> text){
Stack stack = new Stack();
Table t = new Table().left().bottom();
t.label(text);
stack.add(new Image(region));
stack.add(t);
return stack;
}
}

View File

@@ -0,0 +1,252 @@
package mindustry.ui.fragments;
import arc.*;
import arc.Input.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.ui.*;
import arc.scene.ui.Label.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.input.*;
import mindustry.ui.*;
import static arc.Core.*;
import static mindustry.Vars.net;
import static mindustry.Vars.*;
public class ChatFragment extends Table{
private final static int messagesShown = 10;
private Array<ChatMessage> messages = new Array<>();
private float fadetime;
private boolean shown = false;
private TextField chatfield;
private Label fieldlabel = new Label(">");
private BitmapFont font;
private GlyphLayout layout = new GlyphLayout();
private float offsetx = Scl.scl(4), offsety = Scl.scl(4), fontoffsetx = Scl.scl(2), chatspace = Scl.scl(50);
private Color shadowColor = new Color(0, 0, 0, 0.4f);
private float textspacing = Scl.scl(10);
private Array<String> history = new Array<>();
private int historyPos = 0;
private int scrollPos = 0;
private Fragment container = new Fragment(){
@Override
public void build(Group parent){
scene.add(ChatFragment.this);
}
};
public ChatFragment(){
super();
setFillParent(true);
font = Fonts.def;
visible(() -> {
if(!net.active() && messages.size > 0){
clearMessages();
if(shown){
hide();
}
}
return net.active();
});
update(() -> {
if(net.active() && input.keyTap(Binding.chat) && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
toggle();
}
if(shown){
if(input.keyTap(Binding.chat_history_prev) && historyPos < history.size - 1){
if(historyPos == 0) history.set(0, chatfield.getText());
historyPos++;
updateChat();
}
if(input.keyTap(Binding.chat_history_next) && historyPos > 0){
historyPos--;
updateChat();
}
scrollPos = (int)Mathf.clamp(scrollPos + input.axis(Binding.chat_scroll), 0, Math.max(0, messages.size - messagesShown));
}
});
history.insert(0, "");
setup();
}
public Fragment container(){
return container;
}
public void clearMessages(){
messages.clear();
history.clear();
history.insert(0, "");
}
private void setup(){
fieldlabel.setStyle(new LabelStyle(fieldlabel.getStyle()));
fieldlabel.getStyle().font = font;
fieldlabel.setStyle(fieldlabel.getStyle());
chatfield = new TextField("", new TextField.TextFieldStyle(scene.getStyle(TextField.TextFieldStyle.class)));
chatfield.setMaxLength(Vars.maxTextLength);
chatfield.getStyle().background = null;
chatfield.getStyle().font = Fonts.chat;
chatfield.getStyle().fontColor = Color.white;
chatfield.setStyle(chatfield.getStyle());
bottom().left().marginBottom(offsety).marginLeft(offsetx * 2).add(fieldlabel).padBottom(6f);
add(chatfield).padBottom(offsety).padLeft(offsetx).growX().padRight(offsetx).height(28);
if(Vars.mobile){
marginBottom(105f);
marginRight(240f);
}
}
@Override
public void draw(){
float opacity = Core.settings.getInt("chatopacity") / 100f;
float textWidth = Math.min(Core.graphics.getWidth()/1.5f, Scl.scl(700f));
Draw.color(shadowColor);
if(shown){
Fill.crect(offsetx, chatfield.getY(), chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
}
super.draw();
float spacing = chatspace;
chatfield.visible(shown);
fieldlabel.visible(shown);
Draw.color(shadowColor);
Draw.alpha(shadowColor.a * opacity);
float theight = offsety + spacing + getMarginBottom();
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);
theight += layout.height + textspacing;
if(i - scrollPos == 0) theight -= textspacing + 1;
font.getCache().clear();
font.getCache().addText(messages.get(i).formattedMessage, fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true);
if(!shown && fadetime - i < 1f && fadetime - i >= 0f){
font.getCache().setAlphas((fadetime - i) * opacity);
Draw.color(0, 0, 0, shadowColor.a * (fadetime - i) * opacity);
}else{
font.getCache().setAlphas(opacity);
}
Fill.crect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing);
Draw.color(shadowColor);
Draw.alpha(opacity * shadowColor.a);
font.getCache().draw();
}
Draw.color();
if(fadetime > 0 && !shown)
fadetime -= Time.delta() / 180f;
}
private void sendMessage(){
String message = chatfield.getText();
clearChatInput();
if(message.replaceAll(" ", "").isEmpty()) return;
history.insert(1, message);
Call.sendChatMessage(message);
}
public void toggle(){
if(!shown){
scene.setKeyboardFocus(chatfield);
shown = !shown;
if(mobile){
TextInput input = new TextInput();
input.maxLength = maxTextLength;
input.accepted = text -> {
chatfield.setText(text);
sendMessage();
hide();
Core.input.setOnscreenKeyboardVisible(false);
};
input.canceled = this::hide;
Core.input.getTextInput(input);
}else{
chatfield.fireClick();
}
}else{
scene.setKeyboardFocus(null);
shown = !shown;
scrollPos = 0;
sendMessage();
}
}
public void hide(){
scene.setKeyboardFocus(null);
shown = false;
clearChatInput();
}
public void updateChat(){
chatfield.setText(history.get(historyPos));
chatfield.setCursorPosition(chatfield.getText().length());
}
public void clearChatInput(){
historyPos = 0;
history.set(0, "");
chatfield.setText("");
}
public boolean shown(){
return shown;
}
public void addMessage(String message, String sender){
messages.insert(0, new ChatMessage(message, sender));
fadetime += 1f;
fadetime = Math.min(fadetime, messagesShown) + 1f;
}
private static class ChatMessage{
public final String sender;
public final String message;
public final String formattedMessage;
public ChatMessage(String message, String sender){
this.message = message;
this.sender = sender;
if(sender == null){ //no sender, this is a server message?
formattedMessage = message;
}else{
formattedMessage = "[CORAL][[" + sender + "[CORAL]]:[WHITE] " + message;
}
}
}
}

View File

@@ -0,0 +1,39 @@
package mindustry.ui.fragments;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.event.*;
/** Fades in a black overlay.*/
public class FadeInFragment extends Fragment{
private static final float duration = 40f;
float time = 0f;
@Override
public void build(Group parent){
parent.addChild(new Element(){
{
setFillParent(true);
touchable(Touchable.disabled);
}
@Override
public void draw(){
Draw.color(0f, 0f, 0f, Mathf.clamp(1f - time));
Fill.crect(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight());
Draw.color();
}
@Override
public void act(float delta){
super.act(delta);
time += 1f / duration;
if(time > 1){
remove();
}
}
});
}
}

View File

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

View File

@@ -0,0 +1,664 @@
package mindustry.ui.fragments;
import arc.*;
import mindustry.annotations.Annotations.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.ImageButton.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.ctype.ContentType;
import mindustry.ctype.UnlockableContent;
import mindustry.entities.*;
import mindustry.entities.type.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.net.Packets.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class HudFragment extends Fragment{
public final PlacementFragment blockfrag = new PlacementFragment();
private ImageButton flip;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private boolean shown = true;
private float dsize = 47.2f;
private long lastToast;
public void build(Group parent){
//menu at top left
parent.fill(cont -> {
cont.setName("overlaymarker");
cont.top().left();
if(mobile){
{
Table select = new Table();
select.left();
select.defaults().size(dsize).left();
ImageButtonStyle style = Styles.clearTransi;
select.addImageButton(Icon.menuLargeSmall, style, ui.paused::show);
flip = select.addImageButton(Icon.arrowUpSmall, style, this::toggleMenus).get();
select.addImageButton(Icon.pasteSmall, style, ui.schematics::show);
select.addImageButton(Icon.pauseSmall, style, () -> {
if(net.active()){
ui.listfrag.toggle();
}else{
state.set(state.is(State.paused) ? State.playing : State.paused);
}
}).name("pause").update(i -> {
if(net.active()){
i.getStyle().imageUp = Icon.playersSmall;
}else{
i.setDisabled(false);
i.getStyle().imageUp = state.is(State.paused) ? Icon.playSmall : Icon.pauseSmall;
}
});
select.addImageButton(Icon.chatSmall, style,() -> {
if(net.active() && mobile){
if(ui.chatfrag.shown()){
ui.chatfrag.hide();
}else{
ui.chatfrag.toggle();
}
}else if(world.isZone()){
ui.tech.show();
}else{
ui.database.show();
}
}).update(i -> {
if(net.active() && mobile){
i.getStyle().imageUp = Icon.chatSmall;
}else{
i.getStyle().imageUp = Icon.databaseSmall;
}
});
select.addImage().color(Pal.gray).width(4f).fillY();
float size = Scl.scl(dsize);
Array<Element> children = new Array<>(select.getChildren());
//now, you may be wondering, why is this necessary? the answer is, I don't know, but it fixes layout issues somehow
int index = 0;
for(Element elem : children){
int fi = index++;
parent.addChild(elem);
elem.visible(() -> {
if(fi < 5){
elem.setSize(size);
}else{
elem.setSize(Scl.scl(4f), size);
}
elem.setPosition(fi * size, Core.graphics.getHeight(), Align.topLeft);
return true;
});
}
cont.add().size(dsize * 5 + 3, dsize).left();
}
cont.row();
cont.addImage().height(4f).color(Pal.gray).fillX();
cont.row();
}
cont.update(() -> {
if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.shown() && !Core.scene.hasDialog() && !(Core.scene.getKeyboardFocus() instanceof TextField)){
toggleMenus();
}
});
Table wavesMain, editorMain;
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight());
{
wavesMain.visible(() -> shown && !state.isEditor());
wavesMain.top().left();
Stack stack = new Stack();
Button waves = new Button(Styles.waveb);
Table btable = new Table().margin(0);
stack.add(waves);
stack.add(btable);
addWaveTable(waves);
addPlayButton(btable);
wavesMain.add(stack).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();
wavesMain.row();
}
{
editorMain.table(Tex.buttonEdge4, t -> {
//t.margin(0f);
t.add("$editor.teams").growX().left();
t.row();
t.table(teams -> {
teams.left();
int i = 0;
for(Team team : Team.all){
ImageButton button = teams.addImageButton(Tex.whiteui, Styles.clearTogglePartiali, 40f, () -> Call.setPlayerTeamEditor(player, team))
.size(50f).margin(6f).get();
button.getImageCell().grow();
button.getStyle().imageUpColor = team.color;
button.update(() -> button.setChecked(player.getTeam() == team));
if(++i % 3 == 0){
teams.row();
}
}
}).left();
if(enableUnitEditing){
t.row();
t.addImageTextButton("$editor.spawn", Icon.add, () -> {
FloatingDialog dialog = new FloatingDialog("$editor.spawn");
int i = 0;
for(UnitType type : content.<UnitType>getBy(ContentType.unit)){
dialog.cont.addImageButton(Tex.whiteui, 8 * 6f, () -> {
Call.spawnUnitEditor(player, type);
dialog.hide();
}).get().getStyle().imageUp = new TextureRegionDrawable(type.icon(Cicon.xlarge));
if(++i % 4 == 0) dialog.cont.row();
}
dialog.addCloseButton();
dialog.setFillParent(false);
dialog.show();
}).fillX();
float[] size = {0};
float[] position = {0, 0};
t.row();
t.addImageTextButton("$editor.removeunit", Icon.quit, Styles.togglet, () -> {}).fillX().update(b -> {
boolean[] found = {false};
if(b.isChecked()){
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e == null){
Vector2 world = Core.input.mouseWorld();
Units.nearby(world.x, world.y, 1f, 1f, unit -> {
if(!found[0] && unit instanceof BaseUnit){
if(Core.input.keyTap(KeyCode.MOUSE_LEFT)){
Call.removeUnitEditor(player, (BaseUnit)unit);
}
found[0] = true;
unit.hitbox(Tmp.r1);
size[0] = Mathf.lerpDelta(size[0], Tmp.r1.width * 2f + Mathf.absin(Time.time(), 10f, 5f), 0.1f);
position[0] = unit.x;
position[1] = unit.y;
}
});
}
}
Draw.color(Pal.accent, Color.white, Mathf.absin(Time.time(), 8f, 1f));
Lines.poly(position[0], position[1], 4, size[0] / 2f);
Draw.reset();
if(!found[0]){
size[0] = Mathf.lerpDelta(size[0], 0f, 0.2f);
}
});
}
}).width(dsize * 5 + 4f);
editorMain.visible(() -> shown && state.isEditor());
}
//fps display
cont.table(info -> {
info.top().left().margin(4).visible(() -> Core.settings.getBool("fps"));
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");
info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style(Styles.outlineLabel);
info.row();
info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel);
}).top().left();
});
parent.fill(t -> {
t.visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial);
//minimap
t.add(new Minimap());
t.row();
//position
t.label(() -> world.toTile(player.x) + "," + world.toTile(player.y))
.visible(() -> Core.settings.getBool("position") && !state.rules.tutorial);
t.top().right();
});
//spawner warning
parent.fill(t -> {
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))))
.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.visible(() -> netServer.isWaitingForPlayers());
t.table(Tex.button, c -> c.add("$waiting.players"));
});
//'core is under attack' table
parent.fill(t -> {
t.touchable(Touchable.disabled);
float notifDuration = 240f;
float[] coreAttackTime = {0};
float[] coreAttackOpacity = {0};
Events.on(Trigger.teamCoreDamage, () -> {
coreAttackTime[0] = notifDuration;
});
t.top().visible(() -> {
if(state.is(State.menu) || state.teams.get(player.getTeam()).cores.size == 0 || state.teams.get(player.getTeam()).cores.first().entity == null){
coreAttackTime[0] = 0f;
return false;
}
t.getColor().a = coreAttackOpacity[0];
if(coreAttackTime[0] > 0){
coreAttackOpacity[0] = Mathf.lerpDelta(coreAttackOpacity[0], 1f, 0.1f);
}else{
coreAttackOpacity[0] = Mathf.lerpDelta(coreAttackOpacity[0], 0f, 0.1f);
}
coreAttackTime[0] -= Time.delta();
return coreAttackOpacity[0] > 0;
});
t.table(Tex.button, top -> top.add("$coreattack").pad(2)
.update(label -> label.getColor().set(Color.orange).lerp(Color.scarlet, Mathf.absin(Time.time(), 2f, 1f)))).touchable(Touchable.disabled);
});
//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().addImageButton(Icon.arrowLeftSmall, Styles.emptyi, () -> {
control.tutorial.prevSentence();
}).width(44f).growY().visible(() -> control.tutorial.canPrev());
}));
};
resize.run();
Events.on(ResizeEvent.class, e -> resize.run());
});
//paused table
parent.fill(t -> {
t.top().visible(() -> state.isPaused()).touchable(Touchable.disabled);
t.table(Tex.buttonTrans, top -> top.add("$paused").pad(5f));
});
//'saving' indicator
parent.fill(t -> {
t.bottom().visible(() -> control.saves.isSaving());
t.add("$saveload").style(Styles.outlineLabel);
});
blockfrag.build(parent);
}
@Remote(targets = Loc.both, forward = true, called = Loc.both)
public static void setPlayerTeamEditor(Player player, Team team){
if(state.isEditor() && player != null){
player.setTeam(team);
}
}
@Remote(targets = Loc.both, called = Loc.server)
public static void spawnUnitEditor(Player player, UnitType type){
if(state.isEditor()){
BaseUnit unit = type.create(player.getTeam());
unit.set(player.x, player.y);
unit.rotation = player.rotation;
unit.add();
}
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void removeUnitEditor(Player player, BaseUnit unit){
if(state.isEditor() && unit != null){
unit.remove();
}
}
private void scheduleToast(Runnable run){
long duration = (int)(3.5 * 1000);
long since = Time.timeSinceMillis(lastToast);
if(since > duration){
lastToast = Time.millis();
run.run();
}else{
Time.runTask((duration - since) / 1000f * 60f, run);
lastToast += duration;
}
}
public void showToast(String text){
if(state.is(State.menu)) return;
scheduleToast(() -> {
Sounds.message.play();
Table table = new Table(Tex.button);
table.update(() -> {
if(state.is(State.menu)){
table.remove();
}
});
table.margin(12);
table.addImage(Icon.check).pad(3);
table.add(text).wrap().width(280f).get().setAlignment(Align.center, Align.center);
table.pack();
//create container table which will align and move
Table container = Core.scene.table();
container.top().add(table);
container.setTranslation(0, table.getPrefHeight());
container.actions(Actions.translateBy(0, -table.getPrefHeight(), 1f, Interpolation.fade), Actions.delay(2.5f),
//nesting actions() calls is necessary so the right prefHeight() is used
Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interpolation.fade), Actions.remove())));
});
}
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.is(State.menu) || state.rules.tutorial) return;
Sounds.message.play();
//if there's currently no unlock notification...
if(lastUnlockTable == null){
scheduleToast(() -> {
Table table = new Table(Tex.button);
table.update(() -> {
if(state.is(State.menu)){
table.remove();
lastUnlockLayout = null;
lastUnlockTable = null;
}
});
table.margin(12);
Table in = new Table();
//create texture stack for displaying
Image image = new Image(content.icon(Cicon.xlarge));
image.setScaling(Scaling.fit);
in.add(image).size(8 * 6).pad(2);
//add to table
table.add(in).padRight(8);
table.add("$unlocked");
table.pack();
//create container table which will align and move
Table container = Core.scene.table();
container.top().add(table);
container.setTranslation(0, table.getPrefHeight());
container.actions(Actions.translateBy(0, -table.getPrefHeight(), 1f, Interpolation.fade), Actions.delay(2.5f),
//nesting actions() calls is necessary so the right prefHeight() is used
Actions.run(() -> container.actions(Actions.translateBy(0, table.getPrefHeight(), 1f, Interpolation.fade), Actions.run(() -> {
lastUnlockTable = null;
lastUnlockLayout = null;
}), Actions.remove())));
lastUnlockTable = container;
lastUnlockLayout = in;
});
}else{
//max column size
int col = 3;
//max amount of elements minus extra 'plus'
int cap = col * col - 1;
//get old elements
Array<Element> elements = new Array<>(lastUnlockLayout.getChildren());
int esize = elements.size;
//...if it's already reached the cap, ignore everything
if(esize > cap) return;
//get size of each element
float size = 48f / Math.min(elements.size + 1, col);
lastUnlockLayout.clearChildren();
lastUnlockLayout.defaults().size(size).pad(2);
for(int i = 0; i < esize; i++){
lastUnlockLayout.add(elements.get(i));
if(i % col == col - 1){
lastUnlockLayout.row();
}
}
//if there's space, add it
if(esize < cap){
Image image = new Image(content.icon(Cicon.medium));
image.setScaling(Scaling.fit);
lastUnlockLayout.add(image);
}else{ //else, add a specific icon to denote no more space
lastUnlockLayout.addImage(Icon.add);
}
lastUnlockLayout.pack();
}
}
public void showLaunch(){
Image image = new Image();
image.getColor().a = 0f;
image.setFillParent(true);
image.actions(Actions.fadeIn(40f / 60f));
image.update(() -> {
if(state.is(State.menu)){
image.remove();
}
});
Core.scene.add(image);
}
public void showLand(){
Image image = new Image();
image.getColor().a = 1f;
image.touchable(Touchable.disabled);
image.setFillParent(true);
image.actions(Actions.fadeOut(0.8f), Actions.remove());
image.update(() -> {
image.toFront();
if(state.is(State.menu)){
image.remove();
}
});
Core.scene.add(image);
}
private void showLaunchConfirm(){
FloatingDialog dialog = new FloatingDialog("$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.addButton("$cancel", dialog::hide);
dialog.buttons.addButton("$ok", () -> {
dialog.hide();
Call.launchZone();
});
dialog.keyDown(KeyCode.ESCAPE, dialog::hide);
dialog.keyDown(KeyCode.BACK, dialog::hide);
dialog.show();
}
private boolean inLaunchWave(){
return world.isZone() &&
world.getZone().metCondition() &&
!net.client() &&
state.wave % world.getZone().launchPeriod == 0 && !spawner.isSpawning();
}
private boolean canLaunch(){
return inLaunchWave() && state.enemies() <= 0;
}
private void toggleMenus(){
if(flip != null){
flip.getStyle().imageUp = shown ? Icon.arrowDownSmall : Icon.arrowUpSmall;
}
shown = !shown;
}
private void addWaveTable(Button table){
StringBuilder ibuild = new StringBuilder();
IntFormat wavef = new IntFormat("wave");
IntFormat enemyf = new IntFormat("wave.enemy");
IntFormat enemiesf = new IntFormat("wave.enemies");
IntFormat waitingf = new IntFormat("wave.waiting", i -> {
ibuild.setLength(0);
int m = i/60;
int s = i % 60;
if(m <= 0){
ibuild.append(s);
}else{
ibuild.append(m);
ibuild.append(":");
if(s < 10){
ibuild.append("0");
}
ibuild.append(s);
}
return ibuild.toString();
});
table.clearChildren();
table.touchable(Touchable.enabled);
StringBuilder builder = new StringBuilder();
table.setName("waves");
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 + world.getZone().launchPeriod));
builder.append("\n");
}
builder.append("[]\n");
}
if(state.enemies() > 0){
if(state.enemies() == 1){
builder.append(enemyf.get(state.enemies()));
}else{
builder.append(enemiesf.get(state.enemies()));
}
builder.append("\n");
}
if(state.rules.waveTimer){
builder.append((state.rules.waitForWaveToEnd && unitGroups[waveTeam.ordinal()].size() > 0) ? Core.bundle.get("wave.waveInProgress") : ( waitingf.get((int)(state.wavetime/60))));
}else if(state.enemies() == 0){
builder.append(Core.bundle.get("waiting"));
}
return builder;
}).growX().pad(8f);
table.setDisabled(() -> !canLaunch());
table.visible(() -> state.rules.waves);
table.clicked(() -> {
if(canLaunch()){
showLaunchConfirm();
}
});
}
private boolean canSkipWave(){
return state.rules.waves && ((net.server() || player.isAdmin) || !net.active()) && state.enemies() == 0 && !spawner.isSpawning() && !state.rules.tutorial;
}
private void addPlayButton(Table table){
table.right().addImageButton(Icon.playSmaller, Styles.righti, 30f, () -> {
if(net.client() && player.isAdmin){
Call.onAdminRequest(player, AdminAction.wave);
}else if(inLaunchWave()){
ui.showConfirm("$confirm", "$launch.skip.confirm", () -> !canSkipWave(), () -> state.wavetime = 0f);
}else{
state.wavetime = 0f;
}
}).growY().fillX().right().width(40f)
.visible(this::canSkipWave);
}
}

View File

@@ -0,0 +1,77 @@
package mindustry.ui.fragments;
import arc.func.*;
import arc.graphics.*;
import arc.scene.Group;
import arc.scene.actions.*;
import arc.scene.event.Touchable;
import arc.scene.ui.Label;
import arc.scene.ui.TextButton;
import arc.scene.ui.layout.Table;
import mindustry.graphics.Pal;
import mindustry.ui.*;
public class LoadingFragment extends Fragment{
private Table table;
private TextButton button;
private Bar bar;
@Override
public void build(Group parent){
parent.fill(Styles.black8, t -> {
t.visible(false);
t.touchable(Touchable.enabled);
t.add().height(133f).row();
t.addImage().growX().height(3f).pad(4f).growX().get().setColor(Pal.accent);
t.row();
t.add("$loading").name("namelabel").pad(10f);
t.row();
t.addImage().growX().height(3f).pad(4f).growX().get().setColor(Pal.accent);
t.row();
bar = t.add(new Bar()).pad(3).size(500f, 40f).visible(false).get();
t.row();
button = t.addButton("$cancel", () -> {}).pad(20).size(250f, 70f).visible(false).get();
table = t;
});
}
public void setProgress(Floatp progress){
bar.reset(0f);
bar.visible(true);
bar.set(() -> ((int)(progress.get() * 100) + "%"), progress, Pal.accent);
}
public void setButton(Runnable listener){
button.visible(true);
button.getListeners().remove(button.getListeners().size - 1);
button.clicked(listener);
}
public void setText(String text){
table.<Label>find("namelabel").setText(text);
table.<Label>find("namelabel").setColor(Pal.accent);
}
public void show(){
show("$loading");
}
public void show(String text){
table.<Label>find("namelabel").setColor(Color.white);
bar.visible(false);
table.clearActions();
table.touchable(Touchable.enabled);
table.<Label>find("namelabel").setText(text);
table.visible(true);
table.getColor().a = 1f;
table.toFront();
}
public void hide(){
table.clearActions();
table.toFront();
table.touchable(Touchable.disabled);
table.actions(Actions.fadeOut(0.5f), Actions.visible(false));
}
}

View File

@@ -0,0 +1,255 @@
package mindustry.ui.fragments;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class MenuFragment extends Fragment{
private Table container, submenu;
private Button currentMenu;
private MenuRenderer renderer;
public MenuFragment(){
Events.on(DisposeEvent.class, event -> {
renderer.dispose();
});
}
@Override
public void build(Group parent){
renderer = new MenuRenderer();
Group group = new WidgetGroup();
group.setFillParent(true);
group.visible(() -> !ui.editor.isShown());
parent.addChild(group);
parent = group;
parent.fill((x, y, w, h) -> renderer.render());
parent.fill(c -> {
container = c;
if(!mobile){
buildDesktop();
Events.on(ResizeEvent.class, event -> buildDesktop());
}else{
buildMobile();
Events.on(ResizeEvent.class, event -> buildMobile());
}
});
//info icon
if(mobile){
parent.fill(c -> c.bottom().left().addButton("", Styles.infot, ui.about::show).size(84, 45));
parent.fill(c -> c.bottom().right().addButton("", Styles.discordt, ui.discord::show).size(84, 45));
}
String versionText = "[#ffffffba]" + ((Version.build == -1) ? "[#fc8140aa]custom build" : (Version.type.equals("official") ? Version.modifier : Version.type) + " build " + Version.build + (Version.revision == 0 ? "" : "." + Version.revision));
parent.fill((x, y, w, h) -> {
TextureRegion logo = Core.atlas.find("logo");
float logoscl = Scl.scl(1);
float logow = Math.min(logo.getWidth() * logoscl, Core.graphics.getWidth() - Scl.scl(20));
float logoh = logow * (float)logo.getHeight() / logo.getWidth();
float fx = (int)(Core.graphics.getWidth() / 2f);
float fy = (int)(Core.graphics.getHeight() - 6 - logoh) + logoh / 2 - (Core.graphics.isPortrait() ? Scl.scl(30f) : 0f);
Draw.color();
Draw.rect(logo, fx, fy, logow, logoh);
Fonts.def.setColor(Color.white);
Fonts.def.draw(versionText, fx, fy - logoh/2f, Align.center);
}).touchable(Touchable.disabled);
}
private void buildMobile(){
container.clear();
container.setSize(Core.graphics.getWidth(), Core.graphics.getHeight());
float size = 120f;
container.defaults().size(size).pad(5).padTop(4f);
MobileButton
play = new MobileButton(Icon.play2, "$campaign", () -> checkPlay(ui.deploy::show)),
custom = new MobileButton(Icon.playCustom, "$customgame", () -> checkPlay(ui.custom::show)),
maps = new MobileButton(Icon.load, "$loadgame", () -> checkPlay(ui.load::show)),
join = new MobileButton(Icon.add, "$joingame", () -> checkPlay(ui.join::show)),
editor = new MobileButton(Icon.editor, "$editor", () -> checkPlay(ui.maps::show)),
tools = new MobileButton(Icon.tools, "$settings", ui.settings::show),
mods = new MobileButton(Icon.wiki, "$mods", ui.mods::show),
donate = new MobileButton(Icon.link, "$website", () -> Core.net.openURI("https://anuke.itch.io/mindustry")),
exit = new MobileButton(Icon.exit, "$quit", () -> Core.app.exit());
if(!Core.graphics.isPortrait()){
container.marginTop(60f);
container.add(play);
container.add(join);
container.add(custom);
container.add(maps);
container.row();
container.table(table -> {
table.defaults().set(container.defaults());
table.add(editor);
table.add(tools);
table.add(mods);
//if(platform.canDonate()) table.add(donate);
if(!ios) table.add(exit);
}).colspan(4);
}else{
container.marginTop(0f);
container.add(play);
container.add(maps);
container.row();
container.add(custom);
container.add(join);
container.row();
container.add(editor);
container.add(tools);
container.row();
container.table(table -> {
table.defaults().set(container.defaults());
table.add(mods);
//if(platform.canDonate()) table.add(donate);
if(!ios) table.add(exit);
}).colspan(2);
}
}
private void buildDesktop(){
container.clear();
container.setSize(Core.graphics.getWidth(), Core.graphics.getHeight());
float width = 230f;
Drawable background = Styles.black6;
container.left();
container.add().width(Core.graphics.getWidth()/10f);
container.table(background, t -> {
t.defaults().width(width).height(70f);
buttons(t,
new Buttoni("$play", Icon.play2Small,
new Buttoni("$campaign", Icon.play2Small, () -> checkPlay(ui.deploy::show)),
new Buttoni("$joingame", Icon.addSmall, () -> checkPlay(ui.join::show)),
new Buttoni("$customgame", Icon.editorSmall, () -> checkPlay(ui.custom::show)),
new Buttoni("$loadgame", Icon.loadSmall, () -> checkPlay(ui.load::show)),
new Buttoni("$tutorial", Icon.infoSmall, () -> checkPlay(control::playTutorial))
),
new Buttoni("$editor", Icon.editorSmall, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show),
//not enough space for this button
//new Buttoni("$schematics", Icon.pasteSmall, ui.schematics::show),
new Buttoni("$settings", Icon.toolsSmall, ui.settings::show),
new Buttoni("$about.button", Icon.infoSmall, ui.about::show),
new Buttoni("$quit", Icon.exitSmall, Core.app::exit)
);
}).width(width).growY();
container.table(background, t -> {
submenu = t;
t.getColor().a = 0f;
t.top();
t.defaults().width(width).height(70f);
t.visible(() -> !t.getChildren().isEmpty());
}).width(width).growY();
}
private void checkPlay(Runnable run){
if(!mods.hasContentErrors()){
run.run();
}else{
ui.showInfo("$mod.noerrorplay");
}
}
private void fadeInMenu(){
submenu.clearActions();
submenu.actions(Actions.alpha(1f, 0.15f, Interpolation.fade));
}
private void fadeOutMenu(){
//nothing to fade out
if(submenu.getChildren().isEmpty()){
return;
}
submenu.clearActions();
submenu.actions(Actions.alpha(1f), Actions.alpha(0f, 0.2f, Interpolation.fade), Actions.run(() -> submenu.clearChildren()));
}
private void buttons(Table t, Buttoni... buttons){
for(Buttoni b : buttons){
if(b == null) continue;
Button[] out = {null};
out[0] = t.addImageTextButton(b.text, b.icon, Styles.clearToggleMenut, () -> {
if(currentMenu == out[0]){
currentMenu = null;
fadeOutMenu();
}else{
if(b.submenu != null){
currentMenu = out[0];
submenu.clearChildren();
fadeInMenu();
//correctly offset the button
submenu.add().height((Core.graphics.getHeight() - out[0].getY(Align.topLeft)) / Scl.scl(1f));
submenu.row();
buttons(submenu, b.submenu);
}else{
currentMenu = null;
fadeOutMenu();
b.runnable.run();
}
}
}).marginLeft(11f).get();
out[0].update(() -> out[0].setChecked(currentMenu == out[0]));
t.row();
}
}
private class Buttoni{
final Drawable icon;
final String text;
final Runnable runnable;
final Buttoni[] submenu;
public Buttoni(String text, Drawable icon, Runnable runnable){
this.icon = icon;
this.text = text;
this.runnable = runnable;
this.submenu = null;
}
public Buttoni(String text, Drawable icon, Buttoni... buttons){
this.icon = icon;
this.text = text;
this.runnable = () -> {};
this.submenu = buttons;
}
}
}

View File

@@ -0,0 +1,32 @@
package mindustry.ui.fragments;
import arc.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import mindustry.*;
/** Fragment for displaying overlays such as block inventories. */
public class OverlayFragment{
public final BlockInventoryFragment inv;
public final BlockConfigFragment config;
private WidgetGroup group = new WidgetGroup();
public OverlayFragment(){
group.touchable(Touchable.childrenOnly);
inv = new BlockInventoryFragment();
config = new BlockConfigFragment();
}
public void add(){
group.setFillParent(true);
Vars.ui.hudGroup.addChildBefore(Core.scene.find("overlaymarker"), group);
inv.build(group);
config.build(group);
}
public void remove(){
group.remove();
}
}

View File

@@ -0,0 +1,467 @@
package mindustry.ui.fragments;
import arc.*;
import arc.struct.*;
import arc.graphics.*;
import arc.math.geom.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.entities.traits.BuilderTrait.*;
import mindustry.entities.type.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.Cicon;
import mindustry.world.*;
import static mindustry.Vars.*;
public class PlacementFragment extends Fragment{
final int rowWidth = 4;
public Category currentCategory = Category.distribution;
Array<Block> returnArray = new Array<>();
Array<Category> returnCatArray = new Array<>();
boolean[] categoryEmpty = new boolean[Category.all.length];
ObjectMap<Category,Block> selectedBlocks = new ObjectMap<Category,Block>();
Block hovered, lastDisplay;
Tile lastHover;
Tile hoverTile;
Table blockTable, toggler, topTable;
boolean lastGround;
boolean blockSelectEnd;
int blockSelectSeq;
long blockSelectSeqMillis;
Binding[] blockSelect = {
Binding.block_select_01,
Binding.block_select_02,
Binding.block_select_03,
Binding.block_select_04,
Binding.block_select_05,
Binding.block_select_06,
Binding.block_select_07,
Binding.block_select_08,
Binding.block_select_09,
Binding.block_select_10,
Binding.block_select_left,
Binding.block_select_right,
Binding.block_select_up,
Binding.block_select_down
};
public PlacementFragment(){
Events.on(WorldLoadEvent.class, event -> {
Core.app.post(() -> {
control.input.block = null;
rebuild();
});
});
Events.on(UnlockEvent.class, event -> {
if(event.content instanceof Block){
rebuild();
}
});
Events.on(ResetEvent.class, event -> {
selectedBlocks.clear();
});
}
void rebuild(){
currentCategory = Category.turret;
Group group = toggler.getParent();
int index = toggler.getZIndex();
toggler.remove();
build(group);
toggler.setZIndex(index);
}
boolean gridUpdate(InputHandler input){
if(Core.input.keyDown(Binding.pick)){ //mouse eyedropper select
Tile tile = world.ltileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
Block tryRecipe = tile == null ? null : tile.block();
for(BuildRequest req : player.buildQueue()){
if(!req.breaking && req.block.bounds(req.x, req.y, Tmp.r1).contains(Core.input.mouseWorld())){
tryRecipe = req.block;
break;
}
}
if(tryRecipe != null && tryRecipe.isVisible() && unlocked(tryRecipe)){
input.block = tryRecipe;
currentCategory = input.block.category;
return true;
}
}
if(ui.chatfrag.shown() || Core.scene.hasKeyboard()) return false;
for(int i = 0; i < blockSelect.length; i++){
if(Core.input.keyTap(blockSelect[i])){
if(i > 9) { //select block directionally
Array<Block> blocks = getByCategory(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
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);
}
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
//select only visible categories
if(!getByCategory(Category.all[i]).isEmpty()){
currentCategory = Category.all[i];
if(input.block != null){
input.block = getSelectedBlock(currentCategory);
}
blockSelectEnd = false;
blockSelectSeq = 0;
blockSelectSeqMillis = Time.millis();
}
}else{ //select block
if(blockSelectSeq == 0){ //2nd number of combo
blockSelectSeq = i + 1;
}else{ //3rd number of combo
//entering "X,1,0" selects the same block as "X,0"
i += (blockSelectSeq - (i != 9 ? 0 : 1)) * 10;
blockSelectEnd = true;
}
Array<Block> blocks = getByCategory(currentCategory);
input.block = (i < blocks.size) ? blocks.get(i) : null;
selectedBlocks.put(currentCategory, input.block);
blockSelectSeqMillis = Time.millis();
}
return true;
}
}
if(Core.input.keyTap(Binding.category_prev)){
do{
currentCategory = currentCategory.prev();
}while(categoryEmpty[currentCategory.ordinal()]);
input.block = getSelectedBlock(currentCategory);
return true;
}
if(Core.input.keyTap(Binding.category_next)){
do{
currentCategory = currentCategory.next();
}while(categoryEmpty[currentCategory.ordinal()]);
input.block = getSelectedBlock(currentCategory);
return true;
}
return false;
}
@Override
public void build(Group parent){
parent.fill(full -> {
toggler = full;
full.bottom().right().visible(() -> ui.hudfrag.shown());
full.table(frame -> {
//rebuilds the category table with the correct recipes
Runnable rebuildCategory = () -> {
blockTable.clear();
blockTable.top().margin(5);
int index = 0;
ButtonGroup<ImageButton> group = new ButtonGroup<>();
group.setMinCheckCount(0);
for(Block block : getByCategory(currentCategory)){
if(index++ % rowWidth == 0){
blockTable.row();
}
ImageButton button = blockTable.addImageButton(Icon.lockedSmall, Styles.selecti, () -> {
if(unlocked(block)){
control.input.block = control.input.block == block ? null : block;
selectedBlocks.put(currentCategory, control.input.block);
}
}).size(46f).group(group).name("block-" + block.name).get();
button.getStyle().imageUp = new TextureRegionDrawable(block.icon(Cicon.medium));
button.update(() -> { //color unplacable things gray
TileEntity core = player.getClosestCore();
Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.requirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.white : Color.gray;
button.forEach(elem -> elem.setColor(color));
button.setChecked(control.input.block == block);
if(state.rules.bannedBlocks.contains(block)){
button.forEach(elem -> elem.setColor(Color.darkGray));
}
});
button.hovered(() -> hovered = block);
button.exited(() -> {
if(hovered == block){
hovered = null;
}
});
}
//add missing elements to even out table size
if(index < 4){
for(int i = 0; i < 4-index; i++){
blockTable.add().size(46f);
}
}
blockTable.act(0f);
};
//top table with hover info
frame.table(Tex.buttonEdge2,top -> {
topTable = top;
top.add(new Table()).growX().update(topTable -> {
//don't refresh unnecessarily
if((tileDisplayBlock() == null && lastDisplay == getSelected() && !lastGround)
|| (tileDisplayBlock() != null && lastHover == hoverTile && lastDisplay == tileDisplayBlock() && lastGround))
return;
topTable.clear();
topTable.top().left().margin(5);
lastHover = hoverTile;
lastDisplay = getSelected();
lastGround = tileDisplayBlock() != null;
if(lastDisplay != null){ //show selected recipe
lastGround = false;
topTable.table(header -> {
String keyCombo = "";
if(!mobile && Core.settings.getBool("blockselectkeys")){
Array<Block> blocks = getByCategory(currentCategory);
for(int i = 0; i < blocks.size; i++){
if(blocks.get(i) == lastDisplay && (i + 1) / 10 - 1 < blockSelect.length){
keyCombo = Core.bundle.format("placement.blockselectkeys", Core.keybinds.get(blockSelect[currentCategory.ordinal()]).key.toString())
+ (i < 10 ? "" : Core.keybinds.get(blockSelect[(i + 1) / 10 - 1]).key.toString() + ",")
+ Core.keybinds.get(blockSelect[i % 10]).key.toString() + "]";
break;
}
}
}
final String keyComboFinal = keyCombo;
header.left();
header.add(new Image(lastDisplay.icon(Cicon.medium))).size(8 * 4);
header.labelWrap(() -> !unlocked(lastDisplay) ? Core.bundle.get("block.unknown") : lastDisplay.localizedName + keyComboFinal)
.left().width(190f).padLeft(5);
header.add().growX();
if(unlocked(lastDisplay)){
header.addButton("?", Styles.clearPartialt, () -> {
ui.content.show(lastDisplay);
Events.fire(new BlockInfoEvent());
}).size(8 * 5).padTop(-5).padRight(-5).right().grow().name("blockinfo");
}
}).growX().left();
topTable.row();
//add requirement table
topTable.table(req -> {
req.top().left();
for(ItemStack stack : lastDisplay.requirements){
req.table(line -> {
line.left();
line.addImage(stack.item.icon(Cicon.small)).size(8 * 2);
line.add(stack.item.localizedName).maxWidth(140f).fillX().color(Color.lightGray).padLeft(2).left().get().setEllipsis(true);
line.labelWrap(() -> {
TileEntity core = player.getClosestCore();
if(core == null || state.rules.infiniteResources) return "*/*";
int amount = core.items.get(stack.item);
int stackamount = Math.round(stack.amount * state.rules.buildCostMultiplier);
String color = (amount < stackamount / 2f ? "[red]" : amount < stackamount ? "[accent]" : "[white]");
return color + ui.formatAmount(amount) + "[white]/" + stackamount;
}).padLeft(5);
}).left();
req.row();
}
}).growX().left().margin(3);
if(state.rules.bannedBlocks.contains(lastDisplay)){
topTable.row();
topTable.table(b -> {
b.addImage(Icon.cancelSmall).padRight(2).color(Color.scarlet);
b.add("$banned");
b.left();
}).padTop(2).left();
}
}else if(tileDisplayBlock() != null){ //show selected tile
lastDisplay = tileDisplayBlock();
topTable.table(t -> {
t.left();
t.add(new Image(lastDisplay.getDisplayIcon(hoverTile))).size(8 * 4);
t.labelWrap(lastDisplay.getDisplayName(hoverTile)).left().width(190f).padLeft(5);
}).growX().left();
if(hoverTile.getTeam() == player.getTeam()){
topTable.row();
topTable.table(t -> {
t.left().defaults().left();
lastDisplay.display(hoverTile, t);
}).left().growX();
}
}
});
}).colspan(3).fillX().visible(() -> getSelected() != null || tileDisplayBlock() != null).touchable(Touchable.enabled);
frame.row();
frame.addImage().color(Pal.gray).colspan(3).height(4).growX();
frame.row();
frame.table(Tex.pane2, blocksSelect -> {
blocksSelect.margin(4).marginTop(0);
blocksSelect.pane(blocks -> blockTable = blocks).height(194f).update(pane -> {
if(pane.hasScroll()){
Element result = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(result == null || !result.isDescendantOf(pane)){
Core.scene.setScrollFocus(null);
}
}
}).grow().get().setStyle(Styles.smallPane);
blocksSelect.row();
blocksSelect.table(control.input::buildPlacementUI).name("inputTable").growX();
}).fillY().bottom().touchable(Touchable.enabled);
frame.table(categories -> {
categories.bottom();
categories.add(new Image(Styles.black6){
@Override
public void draw(){
if(height <= Scl.scl(3f)) return;
getDrawable().draw(x, y, width, height - Scl.scl(3f));
}
}).colspan(2).growX().growY().padTop(-3f).row();
categories.defaults().size(50f);
ButtonGroup<ImageButton> group = new ButtonGroup<>();
//update category empty values
for(Category cat : Category.all){
Array<Block> blocks = getByCategory(cat);
categoryEmpty[cat.ordinal()] = blocks.isEmpty();
}
int f = 0;
for(Category cat : getCategories()){
if(f++ % 2 == 0) categories.row();
if(categoryEmpty[cat.ordinal()]){
categories.addImage(Styles.black6);
continue;
}
categories.addImageButton(Core.atlas.drawable("icon-" + cat.name() + "-smaller"), Styles.clearToggleTransi, () -> {
currentCategory = cat;
if(control.input.block != null){
control.input.block = getSelectedBlock(currentCategory);
}
rebuildCategory.run();
}).group(group).update(i -> i.setChecked(currentCategory == cat)).name("category-" + cat.name());
}
}).fillY().bottom().touchable(Touchable.enabled);
rebuildCategory.run();
frame.update(() -> {
if(gridUpdate(control.input)) rebuildCategory.run();
});
});
});
}
Array<Category> getCategories(){
returnCatArray.clear();
returnCatArray.addAll(Category.all);
returnCatArray.sort((c1, c2) -> Boolean.compare(categoryEmpty[c1.ordinal()], categoryEmpty[c2.ordinal()]));
return returnCatArray;
}
Array<Block> getByCategory(Category cat){
returnArray.clear();
for(Block block : content.blocks()){
if(block.category == cat && block.isVisible() && unlocked(block)){
returnArray.add(block);
}
}
returnArray.sort((b1, b2) -> {
int locked = -Boolean.compare(unlocked(b1), unlocked(b2));
if(locked != 0) return locked;
return Boolean.compare(state.rules.bannedBlocks.contains(b1), state.rules.bannedBlocks.contains(b2));
});
return returnArray;
}
Block getSelectedBlock(Category cat){
if(selectedBlocks.get(cat) == null){
selectedBlocks.put(cat, getByCategory(cat).find(this::unlocked));
}
return selectedBlocks.get(cat);
}
boolean unlocked(Block block){
return !world.isZone() || data.isUnlocked(block);
}
/** Returns the currently displayed block in the top box. */
Block getSelected(){
Block toDisplay = null;
Vector2 v = topTable.stageToLocalCoordinates(Core.input.mouse());
//setup hovering tile
if(!Core.scene.hasMouse() && topTable.hit(v.x, v.y, false) == null){
Tile tile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
if(tile != null){
hoverTile = tile.link();
}else{
hoverTile = null;
}
}else{
hoverTile = null;
}
//block currently selected
if(control.input.block != null){
toDisplay = control.input.block;
}
//block hovered on in build menu
if(hovered != null){
toDisplay = hovered;
}
return toDisplay;
}
/** Returns the block currently being hovered over in the world. */
Block tileDisplayBlock(){
return hoverTile == null ? null : hoverTile.block().synthetic() ? hoverTile.block() : hoverTile.drop() != null && hoverTile.block() == Blocks.air ? hoverTile.overlay().itemDrop != null ? hoverTile.overlay() : hoverTile.floor() : null;
}
}

View File

@@ -0,0 +1,155 @@
package mindustry.ui.fragments;
import arc.*;
import arc.graphics.g2d.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.core.GameState.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.net.*;
import mindustry.net.Packets.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class PlayerListFragment extends Fragment{
private boolean visible = false;
private Table content = new Table().marginRight(13f).marginLeft(13f);
private Interval timer = new Interval();
@Override
public void build(Group parent){
parent.fill(cont -> {
cont.visible(() -> visible);
cont.update(() -> {
if(!(net.active() && !state.is(State.menu))){
visible = false;
return;
}
if(visible && timer.get(20)){
rebuild();
content.pack();
content.act(Core.graphics.getDeltaTime());
//TODO hack
Core.scene.act(0f);
}
});
cont.table(Tex.buttonTrans, pane -> {
pane.label(() -> Core.bundle.format(playerGroup.size() == 1 ? "players.single" : "players", playerGroup.size()));
pane.row();
pane.pane(content).grow().get().setScrollingDisabled(true, false);
pane.row();
pane.table(menu -> {
menu.defaults().growX().height(50f).fillY();
menu.addButton("$server.bans", ui.bans::show).disabled(b -> net.client());
menu.addButton("$server.admins", ui.admins::show).disabled(b -> net.client());
menu.addButton("$close", this::toggle);
}).margin(0f).pad(10f).growX();
}).touchable(Touchable.enabled).margin(14f);
});
rebuild();
}
public void rebuild(){
content.clear();
float h = 74f;
playerGroup.all().sort((p1, p2) -> p1.getTeam().compareTo(p2.getTeam()));
playerGroup.all().each(user -> {
NetConnection connection = user.con;
if(connection == null && net.server() && !user.isLocal) return;
Table button = new Table();
button.left();
button.margin(5).marginBottom(10);
Table table = new Table(){
@Override
public void draw(){
super.draw();
Draw.color(Pal.gray);
Draw.alpha(parentAlpha);
Lines.stroke(Scl.scl(4f));
Lines.rect(x, y, width, height);
Draw.reset();
}
};
table.margin(8);
table.add(new Image(user.getIconRegion()).setScaling(Scaling.none)).grow();
button.add(table).size(h);
button.labelWrap("[#" + user.color.toString().toUpperCase() + "]" + user.name).width(170f).pad(10);
button.add().grow();
button.addImage(Icon.admin).visible(() -> user.isAdmin && !(!user.isLocal && net.server())).padRight(5).get().updateVisibility();
if((net.server() || player.isAdmin) && !user.isLocal && (!user.isAdmin || net.server())){
button.add().growY();
float bs = (h) / 2f;
button.table(t -> {
t.defaults().size(bs);
t.addImageButton(Icon.banSmall, Styles.clearPartiali,
() -> ui.showConfirm("$confirm", "$confirmban", () -> Call.onAdminRequest(user, AdminAction.ban)));
t.addImageButton(Icon.cancelSmall, Styles.clearPartiali,
() -> ui.showConfirm("$confirm", "$confirmkick", () -> Call.onAdminRequest(user, AdminAction.kick)));
t.row();
t.addImageButton(Icon.adminSmall, Styles.clearTogglePartiali, () -> {
if(net.client()) return;
String id = user.uuid;
if(netServer.admins.isAdmin(id, connection.address)){
ui.showConfirm("$confirm", "$confirmunadmin", () -> netServer.admins.unAdminPlayer(id));
}else{
ui.showConfirm("$confirm", "$confirmadmin", () -> netServer.admins.adminPlayer(id, user.usid));
}
})
.update(b -> b.setChecked(user.isAdmin))
.disabled(b -> net.client())
.touchable(() -> net.client() ? Touchable.disabled : Touchable.enabled)
.checked(user.isAdmin);
t.addImageButton(Icon.zoomSmall, Styles.clearPartiali, () -> Call.onAdminRequest(user, AdminAction.trace));
}).padRight(12).size(bs + 10f, bs);
}else if((!user.isLocal && !user.isAdmin) && net.client() && playerGroup.size() >= 3){ //votekick
button.add().growY();
button.addImageButton(Icon.banSmall, Styles.clearPartiali,
() -> ui.showConfirm("$confirm", "$confirmvotekick", () -> Call.sendChatMessage("/votekick " + user.name))).size(h);
}
content.add(button).padBottom(-6).width(350f).maxHeight(h + 14);
content.row();
content.addImage().height(4f).color(state.rules.pvp ? user.getTeam().color : Pal.gray).growX();
content.row();
});
content.marginBottom(5);
}
public void toggle(){
visible = !visible;
if(visible){
rebuild();
}
}
}

View File

@@ -0,0 +1,221 @@
package mindustry.ui.fragments;
import arc.*;
import arc.Input.*;
import arc.struct.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.ui.*;
import arc.scene.ui.Label.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.*;
import mindustry.input.*;
import mindustry.ui.*;
import static arc.Core.*;
import static mindustry.Vars.*;
public class ScriptConsoleFragment extends Table{
private final static int messagesShown = 30;
private Array<String> messages = new Array<>();
private boolean open = false, shown;
private TextField chatfield;
private Label fieldlabel = new Label(">");
private BitmapFont font;
private GlyphLayout layout = new GlyphLayout();
private float offsetx = Scl.scl(4), offsety = Scl.scl(4), fontoffsetx = Scl.scl(2), chatspace = Scl.scl(50);
private Color shadowColor = new Color(0, 0, 0, 0.4f);
private float textspacing = Scl.scl(10);
private Array<String> history = new Array<>();
private int historyPos = 0;
private int scrollPos = 0;
private Fragment container = new Fragment(){
@Override
public void build(Group parent){
scene.add(ScriptConsoleFragment.this);
}
};
public ScriptConsoleFragment(){
setFillParent(true);
font = Fonts.def;
visible(() -> {
if(input.keyTap(Binding.console) && !Vars.net.client() && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
shown = !shown;
if(shown && !open && enableConsole){
toggle();
}
clearChatInput();
}
return shown && !Vars.net.active();
});
update(() -> {
if(input.keyTap(Binding.chat) && enableConsole && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){
toggle();
}
if(open){
if(input.keyTap(Binding.chat_history_prev) && historyPos < history.size - 1){
if(historyPos == 0) history.set(0, chatfield.getText());
historyPos++;
updateChat();
}
if(input.keyTap(Binding.chat_history_next) && historyPos > 0){
historyPos--;
updateChat();
}
}
scrollPos = (int)Mathf.clamp(scrollPos + input.axis(Binding.chat_scroll), 0, Math.max(0, messages.size - messagesShown));
});
history.insert(0, "");
setup();
}
public Fragment container(){
return container;
}
public void clearMessages(){
messages.clear();
history.clear();
history.insert(0, "");
}
private void setup(){
fieldlabel.setStyle(new LabelStyle(fieldlabel.getStyle()));
fieldlabel.getStyle().font = font;
fieldlabel.setStyle(fieldlabel.getStyle());
chatfield = new TextField("", new TextField.TextFieldStyle(scene.getStyle(TextField.TextFieldStyle.class)));
chatfield.setMaxLength(Vars.maxTextLength);
chatfield.getStyle().background = null;
chatfield.getStyle().font = Fonts.chat;
chatfield.getStyle().fontColor = Color.white;
chatfield.setStyle(chatfield.getStyle());
bottom().left().marginBottom(offsety).marginLeft(offsetx * 2).add(fieldlabel).padBottom(6f);
add(chatfield).padBottom(offsety).padLeft(offsetx).growX().padRight(offsetx).height(28);
}
@Override
public void draw(){
float opacity = 1f;
float textWidth = graphics.getWidth() - offsetx*2f;
Draw.color(shadowColor);
if(open){
Fill.crect(offsetx, chatfield.getY(), chatfield.getWidth() + 15f, chatfield.getHeight() - 1);
}
super.draw();
float spacing = chatspace;
chatfield.visible(open);
fieldlabel.visible(open);
Draw.color(shadowColor);
Draw.alpha(shadowColor.a * opacity);
float theight = offsety + spacing + getMarginBottom();
for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos; i++){
layout.setText(font, messages.get(i), Color.white, textWidth, Align.bottomLeft, true);
theight += layout.height + textspacing;
if(i - scrollPos == 0) theight -= textspacing + 1;
font.getCache().clear();
font.getCache().addText(messages.get(i), fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true);
if(!open){
font.getCache().setAlphas(opacity);
Draw.color(0, 0, 0, shadowColor.a * opacity);
}else{
font.getCache().setAlphas(opacity);
}
Fill.crect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing);
Draw.color(shadowColor);
Draw.alpha(opacity * shadowColor.a);
font.getCache().draw();
}
Draw.color();
}
private void sendMessage(){
String message = chatfield.getText();
clearChatInput();
if(message.replaceAll(" ", "").isEmpty()) return;
history.insert(1, message);
addMessage("[lightgray]> " + message.replace("[", "[["));
addMessage(mods.getScripts().runConsole(message).replace("[", "[["));
}
public void toggle(){
if(!open){
scene.setKeyboardFocus(chatfield);
open = !open;
if(mobile){
TextInput input = new TextInput();
input.maxLength = maxTextLength;
input.accepted = text -> {
chatfield.setText(text);
sendMessage();
hide();
Core.input.setOnscreenKeyboardVisible(false);
};
input.canceled = this::hide;
Core.input.getTextInput(input);
}else{
chatfield.fireClick();
}
}else{
scene.setKeyboardFocus(null);
open = !open;
scrollPos = 0;
sendMessage();
}
}
public void hide(){
scene.setKeyboardFocus(null);
open = false;
clearChatInput();
}
public void updateChat(){
chatfield.setText(history.get(historyPos));
chatfield.setCursorPosition(chatfield.getText().length());
}
public void clearChatInput(){
historyPos = 0;
history.set(0, "");
chatfield.setText("");
}
public boolean open(){
return open;
}
public void addMessage(String message){
messages.insert(0, message);
}
}

View File

@@ -0,0 +1,292 @@
package mindustry.ui.layout;
import arc.struct.*;
import arc.math.geom.*;
/**
* Algorithm taken from <a href="https://github.com/abego/treelayout">TreeLayout</a>.
*/
public class BranchTreeLayout implements TreeLayout{
public TreeLocation rootLocation = TreeLocation.top;
public TreeAlignment alignment = TreeAlignment.awayFromRoot;
public float gapBetweenLevels = 10;
public float gapBetweenNodes = 10f;
private final FloatArray sizeOfLevel = new FloatArray();
private float boundsLeft = Float.MAX_VALUE;
private float boundsRight = Float.MIN_VALUE;
private float boundsTop = Float.MAX_VALUE;
private float boundsBottom = Float.MIN_VALUE;
@Override
public void layout(TreeNode root){
firstWalk(root, null);
calcSizeOfLevels(root, 0);
secondWalk(root, -root.prelim, 0, 0);
}
private float getWidthOrHeightOfNode(TreeNode treeNode, boolean returnWidth){
return returnWidth ? treeNode.width : treeNode.height;
}
private float getNodeThickness(TreeNode treeNode){
return getWidthOrHeightOfNode(treeNode, !isLevelChangeInYAxis());
}
private float getNodeSize(TreeNode treeNode){
return getWidthOrHeightOfNode(treeNode, isLevelChangeInYAxis());
}
private boolean isLevelChangeInYAxis(){
return rootLocation == TreeLocation.top || rootLocation == TreeLocation.bottom;
}
private int getLevelChangeSign(){
return rootLocation == TreeLocation.bottom || rootLocation == TreeLocation.right ? -1 : 1;
}
private void updateBounds(TreeNode node, float centerX, float centerY){
float width = node.width;
float height = node.height;
float left = centerX - width / 2;
float right = centerX + width / 2;
float top = centerY - height / 2;
float bottom = centerY + height / 2;
if(boundsLeft > left){
boundsLeft = left;
}
if(boundsRight < right){
boundsRight = right;
}
if(boundsTop > top){
boundsTop = top;
}
if(boundsBottom < bottom){
boundsBottom = bottom;
}
}
public Rectangle getBounds(){
return new Rectangle(boundsLeft, boundsBottom, boundsRight - boundsLeft, boundsTop - boundsBottom);
}
private void calcSizeOfLevels(TreeNode node, int level){
float oldSize;
if(sizeOfLevel.size <= level){
sizeOfLevel.add(0);
oldSize = 0;
}else{
oldSize = sizeOfLevel.get(level);
}
float size = getNodeThickness(node);
if(oldSize < size){
sizeOfLevel.set(level, size);
}
if(!node.isLeaf()){
for(TreeNode child : node.children){
calcSizeOfLevels(child, level + 1);
}
}
}
public int getLevelCount(){
return sizeOfLevel.size;
}
public float getGapBetweenNodes(TreeNode a, TreeNode b){
return gapBetweenNodes;
}
public float getSizeOfLevel(int level){
if(!(level >= 0)) throw new IllegalArgumentException("level must be >= 0");
if(!(level < getLevelCount())) throw new IllegalArgumentException("level must be < levelCount");
return sizeOfLevel.get(level);
}
private TreeNode getAncestor(TreeNode node){
return node.ancestor != null ? node.ancestor : node;
}
private float getDistance(TreeNode v, TreeNode w){
float sizeOfNodes = getNodeSize(v) + getNodeSize(w);
return sizeOfNodes / 2 + getGapBetweenNodes(v, w);
}
private TreeNode nextLeft(TreeNode v){
return v.isLeaf() ? v.thread : v.children[0];
}
private TreeNode nextRight(TreeNode v){
return v.isLeaf() ? v.thread : v.children[v.children.length - 1];
}
private int getNumber(TreeNode node, TreeNode parentNode){
if(node.number == -1){
int number = 1;
for(TreeNode child : parentNode.children){
child.number = number++;
}
}
return node.number;
}
private TreeNode ancestor(TreeNode vIMinus, TreeNode parentOfV, TreeNode defaultAncestor){
TreeNode ancestor = getAncestor(vIMinus);
return ancestor.parent == parentOfV ? ancestor : defaultAncestor;
}
private void moveSubtree(TreeNode wMinus, TreeNode wPlus, TreeNode parent, float shift){
int subtrees = getNumber(wPlus, parent) - getNumber(wMinus, parent);
wPlus.change = wPlus.change - shift / subtrees;
wPlus.shift = wPlus.shift + shift;
wMinus.change = wMinus.change + shift / subtrees;
wPlus.prelim = wPlus.prelim + shift;
wPlus.mode = wPlus.mode + shift;
}
private TreeNode apportion(TreeNode v, TreeNode defaultAncestor,
TreeNode leftSibling, TreeNode parentOfV){
if(leftSibling == null){
return defaultAncestor;
}
TreeNode vOPlus = v;
TreeNode vIPlus = v;
TreeNode vIMinus = leftSibling;
TreeNode vOMinus = parentOfV.children[0];
float sIPlus = (vIPlus).mode;
float sOPlus = (vOPlus).mode;
float sIMinus = (vIMinus).mode;
float sOMinus = (vOMinus).mode;
TreeNode nextRightVIMinus = nextRight(vIMinus);
TreeNode nextLeftVIPlus = nextLeft(vIPlus);
while(nextRightVIMinus != null && nextLeftVIPlus != null){
vIMinus = nextRightVIMinus;
vIPlus = nextLeftVIPlus;
vOMinus = nextLeft(vOMinus);
vOPlus = nextRight(vOPlus);
vOPlus.ancestor = v;
float shift = (vIMinus.prelim + sIMinus)
- (vIPlus.prelim + sIPlus)
+ getDistance(vIMinus, vIPlus);
if(shift > 0){
moveSubtree(ancestor(vIMinus, parentOfV, defaultAncestor),
v, parentOfV, shift);
sIPlus = sIPlus + shift;
sOPlus = sOPlus + shift;
}
sIMinus += vIMinus.mode;
sIPlus += vIPlus.mode;
sOMinus += vOMinus.mode;
sOPlus += vOPlus.mode;
nextRightVIMinus = nextRight(vIMinus);
nextLeftVIPlus = nextLeft(vIPlus);
}
if(nextRightVIMinus != null && nextRight(vOPlus) == null){
vOPlus.thread = nextRightVIMinus;
vOPlus.mode += sIMinus - sOPlus;
}
if(nextLeftVIPlus != null && nextLeft(vOMinus) == null){
vOMinus.thread = nextLeftVIPlus;
vOMinus.mode += sIPlus - sOMinus;
defaultAncestor = v;
}
return defaultAncestor;
}
private void executeShifts(TreeNode v){
float shift = 0;
float change = 0;
for(int i = v.children.length - 1; i >= 0; i--){
TreeNode w = v.children[i];
change = change + w.change;
w.prelim += shift;
w.mode += shift;
shift += w.shift + change;
}
}
private void firstWalk(TreeNode v, TreeNode leftSibling){
if(v.isLeaf()){
if(leftSibling != null){
v.prelim = leftSibling.prelim + getDistance(v, leftSibling);
}
}else{
TreeNode defaultAncestor = v.children[0];
TreeNode previousChild = null;
for(TreeNode w : v.children){
firstWalk(w, previousChild);
defaultAncestor = apportion(w, defaultAncestor, previousChild,
v);
previousChild = w;
}
executeShifts(v);
float midpoint = (v.children[0].prelim + v.children[v.children.length - 1].prelim) / 2f;
if(leftSibling != null){
v.prelim = leftSibling.prelim + getDistance(v, leftSibling);
v.mode = v.prelim - midpoint;
}else{
v.prelim = midpoint;
}
}
}
private void secondWalk(TreeNode v, float m, int level, float levelStart){
float levelChangeSign = getLevelChangeSign();
boolean levelChangeOnYAxis = isLevelChangeInYAxis();
float levelSize = getSizeOfLevel(level);
float x = v.prelim + m;
float y;
if(alignment == TreeAlignment.center){
y = levelStart + levelChangeSign * (levelSize / 2);
}else if(alignment == TreeAlignment.towardsRoot){
y = levelStart + levelChangeSign * (getNodeThickness(v) / 2);
}else{
y = levelStart + levelSize - levelChangeSign
* (getNodeThickness(v) / 2);
}
if(!levelChangeOnYAxis){
float t = x;
x = y;
y = t;
}
v.x = x;
v.y = y;
updateBounds(v, x, y);
if(!v.isLeaf()){
float nextLevelStart = levelStart
+ (levelSize + gapBetweenLevels)
* levelChangeSign;
for(TreeNode w : v.children){
secondWalk(w, m + v.mode, level + 1, nextLevelStart);
}
}
}
public enum TreeLocation{
top, left, bottom, right
}
public enum TreeAlignment{
center, towardsRoot, awayFromRoot
}
}

View File

@@ -0,0 +1,67 @@
package mindustry.ui.layout;
import arc.struct.*;
import arc.math.*;
public class RadialTreeLayout implements TreeLayout{
private static ObjectSet<TreeNode> visited = new ObjectSet<>();
private static Queue<TreeNode> queue = new Queue<>();
public float startRadius, delta;
@Override
public void layout(TreeNode root){
startRadius = root.height * 2.4f;
delta = root.height * 2.4f;
bfs(root, true);
ObjectSet<TreeNode> all = new ObjectSet<>(visited);
for(TreeNode node : all){
node.leaves = bfs(node, false);
}
radialize(root, startRadius, 0, 360);
}
void radialize(TreeNode root, float radius, float from, float to){
float angle = from;
for(TreeNode child : root.children){
float nextAngle = angle + ((float)child.leaves / root.leaves * (to - from));
float x = radius * Mathf.cos((angle + nextAngle) / 2f * Mathf.degRad);
float y = radius * Mathf.sin((angle + nextAngle) / 2f * Mathf.degRad);
child.x = x;
child.y = y;
if(child.children.length > 0) radialize(child, radius + delta, angle, nextAngle);
angle = nextAngle;
}
}
int bfs(TreeNode node, boolean assign){
visited.clear();
queue.clear();
if(assign) node.number = 0;
int leaves = 0;
visited.add(node);
queue.addFirst(node);
while(!queue.isEmpty()){
TreeNode current = queue.removeFirst();
if(current.children.length == 0) leaves++;
for(TreeNode child : current.children){
if(assign) child.number = current.number + 1;
if(visited.add(child)){
queue.addLast(child);
}
}
}
return leaves;
}
}

View File

@@ -0,0 +1,22 @@
package mindustry.ui.layout;
public interface TreeLayout{
void layout(TreeNode root);
class TreeNode<T extends TreeNode>{
public float width, height, x, y;
//should be initialized by user
public T[] children;
public T parent;
//internal stuff
public float mode, prelim, change, shift;
public int number = -1, leaves;
public TreeNode thread, ancestor;
public boolean isLeaf(){
return children == null || children.length == 0;
}
}
}